Contents
In this article, I'll show you how to create a custom ASP.NET control with new generation UI provided by Microsoft Silverlight. So our objective is to create a custom control similar to standard ASP.NET controls such as Button in functionality, but the presentation will be customized not with a CSS class but with Silverlight. I'll call such controls Silverlight-enabled. The basis for this fantastic concept of ASP.NET control development is the Silverlight control (don't mix it up with the name of the technology) provided by the ASP.NET 3.5 Extension CTP, which was released a few days ago. Also, the Silverlight control may be regarded as an updated version of the Xaml control provided by ASP.NET Futures, but there are some key differences between them which make my samples hard to get to work with ASP.NET Futures, so I strongly recommend you to use ASP.NET 3.5 Extensions.
A Silverlight-enabled control is a custom AJAX-enabled control with integrated ASP.NET 3.5 Extensions Silverlight control within. (If you aren't familiar with the ASP.NET AJAX-enabled control development process, you'd better read about it in the wonderful book "ASP.NET AJAX in Action" before you start working with this article.)
Like an AJAX-enabled control, a Silverlight-enabled control consists of server and client parts which interact with each other through ScriptDesriptors. On the server side, we inherit from the WebControl
class, implement the interface IScriptControl
to connect our control with its client side part, register our control in ScriptManager
, and finally add the Silverlight control to its control collection. But on the client side, unlike an AJAX-enabled control, we inherit from Sys.UI.Silverlight.Control
instead of Sys.UI.Control
, and the elementID
passed to the constructor of the ScriptDescriptor
class should be the ClientID of the integrated Silverlight control instead of this.Client.ID
.
So as a result, in our ASP.NET page, we will have the following tag:
<MyNamespace:MySilverlightEnabledControl ID="Control1"
runat=""server"" SkinUrl="MySkin.xaml" Width="200"
Height="100" Click="Control1_Clicked" />
As you can see from this sample, the Silverlight-enabled control tag looks like a standard ASP.NET control, but instead of the CssClass
property, we have the SkinUrl
.
To show this ASP.NET control development concept in practice, I've chosen the Button
control. Its functionality and properties (such as width, height, text, and click event, which can be handled by the ASP.NET engine) will be similar to the standard ASP.NET Button
control, but its UI will be customized by the Silverlight skin.
Note: In this article, I won't show you how to create XAML skin (you will find two in the demo project). All you need to know is that it has a pulsation animation (PulseStoryBoard
), a root element (RootCanvas
), and a text field (TextBlock
). So your custom skins must include these elements with such names. Their names will be used during the initialization process.
First of all, we should create an ASP.NET server control project and add a Web.Extensions 3.6.0.0 reference.
As I said earlier, there is a client and server part of our control. Let's start creating the server part step by step:
- Create a .cs file with an empty class
Button
with the following namespaces:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.SilverlightControls;
namespace SilverlightUIControls {
public class Button: WebControl, IPostBackEventHandler, IScriptControl
{
public Button() : base(HtmlTextWriterTag.Div)
{
}
}
- Now we should add the necessary properties to our control to let the user customize the design for his needs.
public string SkinUrl
{
get { return (string)ViewState["SkinUrl"]; }
set { ViewState["SkinUrl"] = value; }
}
public new int Width
{
get { return (int)ViewState["Width"]; }
set { ViewState["Width"] = value; }
}
public new int Height
{
get { return (int)ViewState["Height"]; }
set { ViewState["Height"] = value; }
}
public string Text
{
get {
if (ViewState["Text"] != null)
{
return (string)ViewState["Text"];
}
else return "Button";
}
set { ViewState["Text"] = value; }
}
public string FontFamily
{
get
{
if (ViewState["FontFamily"] != null)
{
return (string)ViewState["FontFamily"];
}
else return "Comic Sans MS";
}
set { ViewState["FontFamily"] = value; }
}
public int FontSize
{
get
{
if (ViewState["FontSize"] != null)
{
return (int)ViewState["FontSize"];
}
else return 20;
}
set { ViewState["FontSize"] = value; }
}
- Create a private method where you will initialize ASP.NET 3.5 Extensions Silverlight control (it is just an initialization of the Silverlight plug-in; resizing the setting text, its font family, and size will be done on the client side), and add it to the
Controls
collection of your custom control. This method should be called during the pre-render stage of the ASP.NET control life cycle.
private void CreateSilverlightControl()
{
_silverlightControl = new Silverlight();
_silverlightControl.Source = SkinUrl;
_silverlightControl.Width = Width;
_silverlightControl.Height = Height;
_silverlightControl.Windowless = true;
_silverlightControl.ID = this.ID + "Silverlight";
_silverlightControl.ClientType = "SilverlightUIControls.Button";
_silverlightControl.PluginBackColor = System.Drawing.Color.Transparent;
Controls.Add(_silverlightControl);
}
- To connect the server and client parts of our control, we should do several things. First of all, we should register our script control at the script manager during the prerender stage, and its script descriptors during the render stage of the control lifecycle. After that, we should implement the
IScriptConrol
interface to specify a reference to the .js file with the client side part of our control and properties, which need to be sent to the client side for initializing of our Silverlight UI.
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
if (_silverlightControl == null)
{
CreateSilverlightControl();
}
ScriptManager manager;
manager = ScriptManager.GetCurrent(this.Page);
if (manager == null)
throw new InvalidOperationException(
"A ScriptManager is required on the page.");
manager.RegisterScriptControl(this);
}
protected override void Render(HtmlTextWriter writer)
{
base.Render(writer);
Page.ClientScript.GetPostBackEventReference(this, "");
ScriptManager.GetCurrent(this.Page).RegisterScriptDescriptors(this);
}
public IEnumerable<scriptdescriptor> GetScriptDescriptors()
{
ScriptControlDescriptor descriptor =
new ScriptControlDescriptor("SilverlightUIControls.Button",
_silverlightControl.ClientID);
descriptor.AddProperty("text", this.Text);
descriptor.AddProperty("width", this.Width);
descriptor.AddProperty("height", this.Height);
descriptor.AddProperty("fontSize", this.FontSize);
descriptor.AddProperty("fontFamily", this.FontFamily);
descriptor.AddProperty("buttonId", this.ID);
yield return descriptor;
}
public IEnumerable<scriptreference> GetScriptReferences()
{
yield return new ScriptReference(
Page.ClientScript.GetWebResourceUrl(typeof(Button),
"SilverlightUIControls.Button.js"));
}
- And the last thing we should do on the server side is to implement the
IPostBackEventHandler
interface to make our button do postback after clicking.
public event EventHandler Click;
public void RaisePostBackEvent(string eventArgument)
{
OnClick(new EventArgs());
}
protected virtual void OnClick(EventArgs e)
{
if (Click != null)
Click(this, e);
}
So now we are ready to move on to the development of the client side part:
- Add a JavaScript file to your project and mark it as an embedded resource.
- Describe this JavaScript file as a
WebResource
in the AssemblyInfo.cs of your project.
[assembly: WebResource("SilverlightUIControls.Button.js", "text/javascript")]
- In the .js file, create a JavaScript class inherited from
Sys.UI.Silverlight.Control
which will initialize our Silverlight button skin (set the text and its font size and family, resizing, text centering), and handle its events.
Note: There are no classes in JavaScript, so they are simulated by prototypes and functions. The Microsoft AJAX library standardized the process of class simulation in JavaScript. You can read more about this process in the book I've mentioned at the introduction to my article.
- Initializing: all you need to know about this step you will find in the code comments.
_initializeFields: function(slhost)
{
var host = slhost.content;
this._rootCanvas = host.findName('RootCanvas');
this._pulseStoryBoard = host.findName('PulseStoryBoard');
this._textBlock = host.findName('TextBlock');
this._textBlock.text = this._text;
this._textBlock.fontFamily = this._fontFamily;
this._textBlock.fontSize = this._fontSize;
var horizontalResizePercent = parseFloat(this._width) / this._rootCanvas.width;
var verticalResizePercent = parseFloat(this._height) / this._rootCanvas.height;
this._rootCanvas.width = this._rootCanvas.width * horizontalResizePercent;
this._rootCanvas.height = this._rootCanvas.height * verticalResizePercent;
this._rootCanvas.setValue("Canvas.Left",
this._rootCanvas.getValue("Canvas.Left") * horizontalResizePercent);
this._rootCanvas.setValue("Canvas.Top",
this._rootCanvas.getValue("Canvas.Top") * verticalResizePercent);
this._Resize(this._rootCanvas, horizontalResizePercent, verticalResizePercent);
this._centerText(this._rootCanvas, this._textBlock);
},
_centerText: function (canvas, textBlock)
{
var left = (canvas.Width - textBlock.ActualWidth) / 2;
var top = (canvas.Height - textBlock.ActualHeight) / 2;
textBlock.SetValue("Canvas.Left", left);
textBlock.SetValue("Canvas.Top", top);
},
_Resize: function (root, horizontalResizePercent, verticalResizePercent)
{
for (var i = 0; i < root.children.count - 1; i++)
{
var child = root.children.getItem(i);
if (child.toString() != 'TextBox')
{
child.width = child.width * horizontalResizePercent;
child.height = child.height * verticalResizePercent;
child.setValue("Canvas.Left",
child.getValue("Canvas.Left") * horizontalResizePercent);
child.setValue("Canvas.Top",
child.getValue("Canvas.Top") * verticalResizePercent);
if (child.toString() == 'Rectangle')
{
child.RadiusX = child.RadiusX * horizontalResizePercent;
child.RadiusY = child.RadiusY * verticalResizePercent;
if (horizontalResizePercent > verticalResizePercent)
{
child.strokeThickness =
child.strokeThickness * horizontalResizePercent;
}
else
{
child.strokeThickness =
child.strokeThickness * verticalResizePercent;
}
}
if (child.toString() == 'Canvas')
{
this._Resize(child,horizontalResizePercent,
verticalResizePercent);
}
}
}
},
- The last thing we should do is handle the events. All events are invoked by the root canvas of our skin. Here are its handlers:
_onButtonMouseEnter
and _onButtonMouseLeave
handlers are used to start and stop button pulsation animation.
_onButtonClicked
calls the __doPostBack
function to tell the ASP.NET engine to invoke the button click event. __doPostBack
needs the control ID, that's why we send it to the client side through the script descriptor.
_onButtonMouseEnter: function(sender, e)
{
this._pulseStoryBoard.begin();
},
_onButtonMouseLeave: function(sender, e)
{
this._pulseStoryBoard.stop();
},
_onButtonClick: function(sender, e)
{
__doPostBack(this._buttonId,'');
},
- Visual Studio 2008
- ASP.NET 3.5 Extensions CTP
I hope that you enjoyed reading my article and now you are ready to create your own controls. Using this concept of control development, you can create amazing grids, menus, and other complex controls too. There is also a way to use managed code instead of JavaScript, and I'll certainly write about it in my future articles.
If you haven't understood something, write about it in the comments section and I'll be glad to answer your questions. And don't forget to vote for the article if you find it useful. ;-)
- Nikhil Kothari "Developing ASP.NET AJAX Controls with Silverlight", MIX07 video.
- Manning, Allesandro Gallo, ASP.NET AJAX in Action.