In this article I will show what I've learned while building a Silverlight Javascript driven custom control. The technique I will be discussing is simple and provides good end results. If you'd like to write a control of your own you will need a copy of Expressions Blend 2 Beta - May preview, ASP .Net AJAX with the latest drop of Futures and of course Visual Studio.
To begin, I will show how I've developed a custom Silverlight button that looks cool and acts like a button. The basic ideea is that I create a Javascript object prototype and the XAML is contained within as a property. So in essence it's just a Javascript object with a graphical representation that happens to be XAML in a Silverlight host. You can see this in action in Joe Stegman's MIX '07 video here.
You can start by creating a new Silverlight (Javascript) project in Expressions Blend. This will create the basic project for you with little fuss. What you end up with can be seen in the screen shot at the right. Scene.xaml.js contains the prototype for your scene. Default.html.js contains code to initialize the scene and wireup the onLoad event. I will be using this event to instantiate the button and wire up its events to do some useful work.
The XAML to define the button is pretty simple. It is a Canvas with three children, the button background, the button text and the button higlight that displays on mouse overs. The end result is displayed on the right. The javascript code for the full button can be found below.
Type.registerNamespace("ControlsDemo");ControlsDemo.Button = function(parent, text, name, left, top) { this.$parent = parent; this.$buttontext = text; this.$name = name; this.create(name, text, left, top); ControlsDemo.Button.initializeBase(this);}// This function initializes the button into the given canvas.ControlsDemo.Button.prototype.create = function(name, text, left, top) {this.$xaml = String.format(this.$xaml, name); this.$root = this.$cfx(this.$xaml); this.$parent.children.add(this.$root); this.$root["Canvas.Left"] = left; this.$root["Canvas.Top"] = top; this.$main = this.$f("btnFace"); this.$text = this.$f("btnText"); this.$text.Text = this.$buttontext; this.$highlight = this.$f("btnHighlight"); // Set event handlers for the button clicks and mouse overs this.$main.addEventListener("MouseEnter", this.onMouseEnter); this.$main.addEventListener("MouseLeave", this.onMouseLeave); this.$main.addEventListener("MouseLeftButtonUp", Sys.Silverlight.createDelegate(this, this.click));}ControlsDemo.Button.prototype.$xaml = '<Canvas xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"\ x:Name="{0}" Width="60" Height="20" Cursor="Hand" >\ <Rectangle Fill="#FF595959" Stroke="#FF323232" StrokeThickness="1"\ RadiusX="2" RadiusY="2" Cursor="Hand" Width="60" Height="20" />\ <Rectangle x:Name="btnHighlight" Fill="#FF6B6B6B" Stroke="#FF303030"\ StrokeThickness="0" RadiusX="2" RadiusY="2" Cursor="Hand" Width="56"\ Height="8" Canvas.Top="2" Canvas.Left="2" Visibility="hidden"/>\ <TextBlock x:Name="btnText" Foreground="#FFFFFFFF" Cursor="Hand"\ Width="29" Height="17" Canvas.Left="10" Canvas.Top="2" FontSize="12" Text="OK" />\ </Canvas>';//Creates the visual representation of the control on the canvasControlsDemo.Button.prototype.$cfx = function(xaml) { return this.$parent.getHost().content.createFromXaml(xaml, true);}//Removes the control from the XAML treeControlsDemo.Button.prototype.remove = function() { this.$parent.children.remove(this.$root);}//Finds subordinate controlsControlsDemo.Button.prototype.$f = function(name) { return this.$root.findName(name);}//Add a listener to the click eventControlsDemo.Button.prototype.add_click = function(handler) { this.get_events().addHandler('click', handler);}//Add a listener to the click eventControlsDemo.Button.prototype.remove_click = function(handler) { this.get_events().removeHandler('click', handler);}//Notify listeners of the click eventControlsDemo.Button.prototype.click = function() { var handler = this.get_events().getHandler("click"); if(handler) handler(this, Sys.EventArgs.Empty);}//Handle the MouseEnter eventControlsDemo.Button.prototype.onMouseEnter = function(sender) { var highlight = sender.findName("btnHighlight"); highlight.Visibility = "visible";}//Handle the MouseLeave eventControlsDemo.Button.prototype.onMouseLeave = function(sender) { var highlight = sender.findName("btnHighlight"); highlight.Visibility = "hidden";}ControlsDemo.Button.registerClass('ControlsDemo.Button', Sys.Component);// Notify ScriptManager that this is the end of the script.if (typeof(Sys) !== 'undefined') Sys.Application.notifyScriptLoaded();
To create an instance of the button simply pass the following parameters to the constructor: parent, text, name, left, top where parent is the canvas you want to create the button on, text is the text that will appear on the button, name is the name you can refer to the main button canvas in the XAML tree and left,top are the coordinates the button will be created at on the parent canvas. Notice the name gets replaced with the name you supply to the constructor. The reason for this is that once the name of an element is set is read-only from there on. The other thig to keep in mind is that the name of the child elments of the button canvas are scoped to the canvas to avoid name collision with other buttons' children.
Once you have all this set up you can create an .aspx (or simply rename Default.html to Default.aspx) page and take your new control for a test drive. Include a ScriptManager on the page and add the Button.js file as a script reference. In the onLoad event handler you can create the button and hook the event listeners. I've added two button for the purpose of the demo, btnOk and btnCancel. Here is the code:
handleLoad: function(control, userContext, rootElement) { this.control = control; //Instantiate a new button control and wire up the click event var btnOk = new ControlsDemo.Button(rootElement, "Ok", "btnOk", 20, 20); btnOk.add_click(button_Click); //Instantiate another new button control and wire up the click event var btnCancel = new ControlsDemo.Button(rootElement, "Cancel", "btnCancel", 100, 20); btnCancel.add_click(button_Click); }
When the button is clicked the button_Click event handler will be notified and the name of the button iwll be displayed.
//Handle the click event and display the sender.$namefunction button_Click(sender, args){ alert(String.format("You clicked {0}!", sender.$name));}
And that's all there is to it. Of course the button can be improved by adding animations, better look and feel, centering the text and so on. Maybe I will polish this up a little and talk about those things in another post. You can find the code for this post here.