# Thursday, June 14, 2007

Quick Tip - Input Controls in Silverlight

UPDATE 10/25/2007: Updated this sample to function correctly with the Silverlight 1.0 release. The main changes necessary were the typical 0.9 to 1.0 changes (remove "Sys.", isWindowless now takes a string and not a boolean, event handlers no longer string based), and I also had to explicitly set the z-index of the INPUT element. Thank you Erik for bringing it to my attention.

 

I have seen a lot of questions and confusion regarding capturing input when using Silverlight. There are no Button, TextBox, or other similar controls to work with. I see many folks asking for help with building their own directly within Silverlight - and bless their hearts, that is a daunting task indeed! The UI model currently does not offer basic input capture features such as "focus" or "tabbing", or even control-level keystroke capture. So folks tend to start building those basic services first, before they ever even get to writing code and xaml to support that simple textbox they need.

If you read that first paragraph and thought "thats crazy, it shouldn't be that hard!", then I would agree with you. Fortunately, there is a FAR easier and more robust way to achieve the same thing. In fact, its something that is not new to Silverlight at all, it's been with us for years. I am of course talking about the tried-and-true html <input> tag.

One of the most overlooked aspects of Silverlight is that it is a component, not a platform. Your browser is the platform. It can do a lot of stuff, if you just ask it to. Nobody wants an entire site as a single Silverlight canvas, just like nobody wants an entire site as a Flash canvas (unless possibly it is a mini-game or rich media application)... Flash designers realized this fact years ago. And as a component, a part of the solution if you will, Silverlight can play nicely with it's neighbors. With just a little bit of effort and sprinkling a very minimal amount of javascript pixie dust, we can get a Silverlight applet talking to the rest of our html DOM. And that's exactly what I am going to show in this topic...

You can download the code demonstrated in this article here (QuickTip-TextboxesInSilverlight.zip).

First of all, I am doing this with Silverlight 1.0 beta (the javascript one), as I think the 1.1 alpha is far too likely to change, and this technique should work with either. That, and I am lazy and don't want to come back and revisit this post later to correct the code...

Secondly, I am using the current CTP builds of Visual Studio Orcas and Blend 2 (the May 2007 bits), both with the Silverlight extensions. If you are using something else, then your mileage may vary.

UPDATE 10/25/2007: Code updated for VS2008 beta2 and the RTM version of Silverlight 1.0.

Now on to the code... to be sure we are on the same page, I am creating a new project from scratch...

First, Create a new project in Blend. Select the Silverlight 1.0 (JavaScript) project type. It does not matter what you name the project, but for this example I went with "TextboxesInSilverlight".

Switch to XAML view and replace the default canvas with this markup:

<Canvas
 xmlns="
http://schemas.microsoft.com/client/2007"
 xmlns:x="
http://schemas.microsoft.com/winfx/2006/xaml"
 Width="252" Height="272"
 Background="#FFFF2121"
 >
  <TextBlock Width="64" Height="24" Canvas.Left="8" Canvas.Top="8"
             Text="Opacity" TextWrapping="Wrap"/>
  <Ellipse Opacity="1" Fill="#FF0406FF" Stroke="#FF000000"
           x:Name="TheCircle" Width="180" Height="180" 
           Canvas.Left="36" Canvas.Top="64"/>
</Canvas>

This will create a simple red canvas, with a blue circle.

At this point, I generally switch over to Visual Studio Orcas since Blend does not have Intellisense nor does it really know how to deal with JavaScript. You can do this easily by right-clicking a project item (such as the xaml file) and selecting "Edit in Visual Studio".

Next, we need to do one small housekeeping chore to make sure that our Silverlight canvas plays nicely - specifically, we need to ask it to operate "windowless", which will allow other dhtml elements to overlay it, Open the Default.html.js file, and modify the call to Silverlight.createObjectEx(). We want to add the parameter for isWindowless...

 Silverlight.createObjectEx({
  source: "Scene.xaml",
  parentElement: document.getElementById("SilverlightControlHost"),
  id: "SilverlightControl",
  properties: {
   width: "100%",
   height: "100%",
   version: "0.9",
   isWindowless: "true"
  },
  events: {
   onLoad: sceneLoaded
  }
 });

To make things a bit more "clean", we will create the input element directly from code, however it's always good to control visual styling with CSS. Therefore, open up the Default.html, and alter the <style> tag to match the following:

 <style type="text/css">
  div, body, input
  {
   margin: 0;
   padding: 0;
  }
  #opacity
  {
   margin: -272px 0 0 75px;
   z-index: 100;
  }
  .silverlightHost
  {
   margin: 40px auto auto auto;
   height: 272px;
   width: 252px;
  }
 </style>

This will handle the placement and sizing of the silverlight container <div> as well as the input control itself. The negative margin is not a typo - this is used to pull the input control "on top" of the silverlight canvas. We could have also used absolute positioning, but that is much more brittle, relative positioning FTW. Also, notice the use of "auto" margins for the main Silverlight Host <div>. This is how you can center content without resorting to using <center> or <table>... if you take nothing away from this post, at least remember that one trick.

The last thing we must do is wire the whole thing up. This can be done in many places, in many ways. I consider this particular TextBox to be an extension of the Xaml "scene", so I will add my code to the TextboxesInSilverlight.Scene class which was created for us by the Blend project template. This is not the only place you could do this kind of code, but I found that in this particular example it made the most sense. Had I been building a dialog for a game engine, I might have this code in a seperate script file that manages my game mechanics (but thats another article...).

First, we need to capture a global reference to the Scene object that is created (this object is instantiated by the createSilverlight() function of the Default.html.js script file we edited in a previous step). The purpose of capturing this reference is that we will need it later in an event handler. This will allow our html <input> control to communicate back with the silverlight content. This is easier done than said. Open the Scene.xaml.js file. Just before the definition of the TextboxesInSilverlight.Scene.prototype (look up javascript prototype for what this is if you are interested, but that discussion is out of scope for this article), add a line of code to declare the global reference:

var globalScene = null;

Now we will create a callback function that will be used to create our JavaScript object and initialize it:

function sceneLoaded(control, userContext, rootElement)
{
    globalScene = new TextboxesInSilverlight.Scene();
    globalScene.handleLoad(control, userContext, rootElement);
};

Now, the idea for this example is that the value of the text box will determine the "Opacity" Xaml property of the Ellipse shape in our markup. In order for the event handler we are about to add to be able to do this, we will capture a reference to the circle object (technically we can wait and use findName() later during the event handler, but I prefer to capture it only once - its just my style of coding). Add this line to the handleLoad function:

this.circle = control.content.findName("TheCircle");

Next, we will create the input control and add it to the DHTML document. We will add it directly to the same <div> that Silverlight has injected itself into, and therefore any layout or positioning that affects the Silverlight canvas will also affect our <input> box. Add this code to the end of the handleLoad function:

var opacityEdit = window.document.createElement("input");
opacityEdit.type = "text";
opacityEdit.id = "opacity";
opacityEdit.name = "opacity";
opacityEdit.value = "1.0"
;

We are almost done - only two more steps and then we can fire this thing up! First, we need to add an event handler to react to changes in the value property of the input control. Add this code to the very end of the handleLoad function:

opacityEdit.onpropertychange = function()
    {
        if (event.propertyName == "value")
        {
            globalScene.circle.Opacity = event.srcElement.value;
        }
    }

This effectively creates an anonymous function to handle property change events on the <input> control, which in turn updates the Opacity property of the circle shape. Cool, huh?

The last thing to do is finally add the new <input> element to the page, otherwise all the work until this point will have had no discernable impact at all... add this one last line to the handleLoad function:

this.control.parentElement.appendChild(opacityEdit);

At this point, your Scene.xaml.js file should look like this:

if (!window.TextboxesInSilverlight)
 window.TextboxesInSilverlight = {};

TextboxesInSilverlight.Scene = function()
{
}

var globalScene = null;

function sceneLoaded(control, userContext, rootElement)
{
    globalScene = new TextboxesInSilverlight.Scene();
    globalScene.handleLoad(control, userContext, rootElement);
}

TextboxesInSilverlight.Scene.prototype =
{
 handleLoad: function(control, userContext, rootElement)
 {
  this.control = control;
  
  this.circle = control.content.findName("TheCircle");
  
  var opacityEdit = window.document.createElement("input");
  opacityEdit.type = "text";
  opacityEdit.id = "opacity";
  opacityEdit.name = "opacity";
  opacityEdit.value = "1.0";
  opacityEdit.onpropertychange = function()
    {
      if (event.propertyName == "value")
      {
        globalScene.circle.Opacity = event.srcElement.value;
      }
    }
  
this.control.parentElement.appendChild(opacityEdit);
 } 
}

If the typo gods favor you, then you should be able to use F5 to run the page and see it all working together:

Notice the positioning of the input textboxes, and the interaction of them with the underlying Silverlight canvas. This is just a simple example, but can be a very powerful way to "plug in" Silverlight into the surrounding DHTML.

View Keith Rome's profile on LinkedIn

On this page....

Archives

Navigation

Categories

About

Disclaimer
The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.

Sign In

Certification Logo Certification Logo Certification Logo Certification Logo Certification Logo

Powered by: newtelligence dasBlog 2.3.9074.18820