Introduction
Recently, I got interested in a very amazing technology called Silverlight, and I'm trying to learn it because it's very powerful.
Actually, Silverlight has two versions:
- Silverlight 1.0 release
- Silverlight 2.0 beta 1
The difference between 1.0 and 2.0b1 is quite big:
- Supports .NET languages on the client-side (C#, VB.NET,..) instead of JavaScript
- A lot of controls from WPF
- Subset of framework 3.5 (LINQ, ...)
For a complete list of the differences, see here.
Unfortunately, to easily use Silverlight 2.0 beta1, Framework 3.5 and Visual Studio 2008 personal or above are required.
My idea is to integrate a Silverlight control into an ASP.NET AJAX control, but it's seem quite difficult without Visual Studio 2008. For this reason, I integrated a Silverlight 1.0 control into an ASP.NET AJAX control using Visual Studio 2005.
Background
Normally, when I start to learn a new technology, I always try to implement a very simple example, but to make it all the more interesting, I implement a complete application or game.
When I was a child and there were no computers at home, I spent a bit of my free time playing with a game called Fifteen Puzzle. It's a very simple game, but not absolutely easy to solve at the beginning. More details about it at this link. I implemented it here in Silverlight 1.0, integrated into a ASP.NET AJAX control.
Using the Code
The solution is divided into the following projects:
- Web Application: Containing the ASP.NET page that hosts the control
- Class Library: Containing the control
In the web application, we have an ASP.NET web form that contains the control and a ScriptManager
(the core of the ASP.NET AJAX Framework).
In the control, we have the following files:
- FifteenPuzzle.cs: Server side control that extends
ScriptControl
;
- FifteenPuzzle.js: JavaScript that contains the JavaScript class that implements the Silverlight control into an ASP.NET AJAX control;
- FifteenPuzzle.xaml: XAML that describes the fixed part of the control (the dynamic part is generated within the JavaScript class);
- Helper.js: JavaScript that contains the same useful functions;
- Silverlight.js: Standard JavaScript used to instantiate the Silverlight control into an HTML div;
The class FifteenPuzzle
extends ScriptControl
(the base class for all ASP.NET AJAX controls on the server side) and overrides two methods:
GetScriptDescriptors
: method used to pass parameters to the JavaScript class using JSON format.
xamlUrl
: The URL from the Resource used to generate the static part of the XAML in Silverlight.
width
, height
: The size of the control.
imageUrl
: Used when the game is in the puzzleRenderMode.Image
mode.
puzzleRenderMode
: enum used to define the two different modes to render the game (Number
or Image
).
GetScriptReferences
: method used to pass the script URL (I used GetWebResourceUrl
because I embedded all the scripts in the assembly)
- Controls.Resources.FifteenPuzzle.js: Contains the JavaScript class of the AJAX control, and extends
Sys.UI.Control
.
- Controls.Resources.Helper.js: Contains some utility functions.
- Controls.Resources.Silverlight.js: Default Silverlight JavaScript to instantiate the plug-in into a div.
protected override IEnumerable<ScriptDescriptor> GetScriptDescriptors()
{
ScriptControlDescriptor descriptor =
new ScriptControlDescriptor("Controls.FifteenPuzzle", this.ClientID);
descriptor.AddProperty("xamlUrl", this.Page.ClientScript.GetWebResourceUrl(
this.GetType(), "Controls.Resources.FifteenPuzzle.xaml"));
descriptor.AddProperty("width", this.Width.ToString());
descriptor.AddProperty("height", this.Height.ToString());
descriptor.AddProperty("imageUrl", this.ImageUrl);
descriptor.AddProperty("puzzleRenderMode", this.PuzzleRenderMode);
yield return descriptor;
}
protected override IEnumerable<ScriptReference> GetScriptReferences()
{
List<ScriptReference> scripts = new List<ScriptReference>();
ScriptReference scriptReference1 = new ScriptReference();
scriptReference1.Path = this.Page.ClientScript.GetWebResourceUrl(this.GetType(),
"Controls.Resources.FifteenPuzzle.js");
scripts.Add(scriptReference1);
ScriptReference scriptReference2 = new ScriptReference();
scriptReference2.Path = this.Page.ClientScript.GetWebResourceUrl(this.GetType(),
"Controls.Resources.Helper.js");
scripts.Add(scriptReference2);
ScriptReference scriptReference3 = new ScriptReference();
scriptReference3.Path = this.Page.ClientScript.GetWebResourceUrl(this.GetType(),
"Controls.Resources.Silverlight.js");
scripts.Add(scriptReference3);
return scripts;
}
The file Controls.Resources.FifteenPuzzle.js contains the JavaScript class Controls.FifteenPuzzle
the encapsulates most of the logic.
The position of each piece of puzzle is contained in a 4x4 array. At the beginning, the array is initialized to the normal order, and using JavaScript, I render the pieces dynamically.
When puzzleRenderMode
is set to Number
, the following is the XAML generated for each piece:
<Canvas Name="FifteenPuzzle1_Cell_15" Canvas.Left="219"
Canvas.Top="370" Canvas.ZIndex="3" Width="100"
Height="100" Background="White" Cursor="Hand">
<Rectangle Fill="#80000000" Width="98" Height="98"
Canvas.Top="2" Canvas.Left="2"
RadiusX="15" RadiusY="15">
</Rectangle>
<Rectangle Width="100" Height="100"
Canvas.Top="0" Canvas.Left="0"
RadiusX="15" RadiusY="15">
<Rectangle.Fill><LinearGradientBrush
StartPoint="0,0" EndPoint="0,1">
<GradientStop Offset="0" Color="Green"/>
<GradientStop Offset="0.6" Color="Lime"/>
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle><Rectangle Width="98" Height="98"
Canvas.Top="1" Canvas.Left="1"
RadiusX="14" RadiusY="14">
<Rectangle.Fill>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<GradientStop Offset="0" Color="#FFFFFFFF"/>
<GradientStop Offset="1" Color="#00000000"/>
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
<TextBlock Name="FifteenPuzzle1_TextBlock_3_2"
Canvas.Left="0" Canvas.Top="5"
Canvas.ZIndex="1" FontFamily="Verdana"
FontSize="70" Text="15"
FontWeight="Bold" Cursor="Hand" />
</Canvas>
When puzzleRenderMode
is set to Image
, the following is the XAML generated for each piece:
<Image Name="FifteenPuzzle1_Cell_15" Canvas.Left="19"
Canvas.Top="70" Canvas.ZIndex="3" Width="400"
Height="400" Cursor="Hand" Source="Resources/Photo.jpg">
<Image.Clip>
<RectangleGeometry Rect="200,300,100,100"
RadiusX="15" RadiusY="15">
</RectangleGeometry>
</Image.Clip>
<Image.RenderTransform>
<TranslateTransform Name="FifteenPuzzle1_Cell_15_Transform"
X="0" Y="0" />
</Image.RenderTransform>
</Image>
The algorithm used to shuffle the pieces is very easy, and probably not really optimized, but seems to work fine. I just create a random number between 1 and 15 and try if the move is available. The problem with this algorithm is that, in theory, it could take a long time, but for the "Law of large numbers", it works fine. :)
The most important part of the JavaScript class is the integration with Silverlight.
In the _renderControl
function is called Silverlight.createObject
that instantiates the plug-in attached to the HTML DIV. When the plug-in is instantiated, the event handler _onXamlLoaded
is called and the dynamic part of the XAML is rendered.
_renderControl : function()
{
this.get_element().innerHTML = String.format("<div id='{0}_Content' " +
"style='width:{1};height:{2}'></div>",
this.get_id(), this.get_width(), this.get_height());
var hostId = String.format("{0}_Host", this.get_id());
var bounds = Sys.UI.DomElement.getBounds(this._getContentElement());
Silverlight.createObject(this.get_xamlUrl(), this._getContentElement(), hostId,
{ width:bounds.width.toString(), height:bounds.height.toString(), version:'1.0' },
{ onError:null, onLoad:Function.createDelegate(this, this._onXamlLoaded) },
null);
},
_onXamlLoaded : function(plugIn, userContext, rootElement)
{
this._plugIn = plugIn;
this._rootElement = rootElement;
this._renderNumbers();
this._drawShuffleButton();
},
I stored the references to the plug-in and the rootElement
in the local JavaScript variable because I use it to dynamically generate the XAML using the createFromXaml
method. In fact, when I generate the pieces, I used the following code to attach it to the static part of the XAML:
var numberElement = this._plugIn.content.createFromXaml(sb.toString(), false);
this._rootElement.children.add(numberElement);
and the following code to get the element using the name and to attach the event handler on mouse click:
var elementNumber = this._rootElement.findName(String.format("{0}_Cell_{1}",
this.get_id(), n));
elementNumber.AddEventListener("MouseLeftButtonDown", this._onNumberClick)
Points of Interest
I know that this is a very poor example considering the possibilities available when using this framework, but it can be useful to start to understand the technology and to understand the possibility to integrate it into an existing ASP.NET application.
History
- 26 May 2008 - First release.
- 30 May 2008 - Added possibility to switch between
Number
and Image
, and improved article details.