|  Login
 SilverlightObject
 Creating a Silverlight Custom Control

Various methods exists for creating Silverlight XAML objects. Many of these methods dynamically create XAML. The problem that arises is that this XAML cannot be edited with a designer such as Expression Blend. Justin-Josef Angel in "Silverlight Controls - The path to reusable XAML" describes a method to create XAML objects in a designer editable .xaml file that is paired with a JavaScript file that contains the methods for XAML object.

His article also implements other object oriented techniques that introduce a level of abstraction that may be difficult for those new to Silverlight development to follow. This tutorial contains a simplified implementation.

Challenges with DotNetNuke Silverlight modules

Silverlight 1.0 applications are primarily comprised of XAML files and JavaScript. DotNetNuke allows for a single module to be placed on a page multiple times. This can cause undesired behavior as Silverlight requires a uniquely named DIV for each Silverlight control on the page that must be initialized by JavaScript. The JavaScript would not know the name of this DIV tag because it is created at run time. The solution to this is to dynamically create the code that injects the Silverlight module on the page.

Creating a Silverlight module

To best explain how to create the DotNetNuke Silverlight module, it is helpful to first create it as a normal Silverlight application. To start, we will open Microsoft Expression Blend 2 (or higher). From the File menu, select New Project...

Create a JavaScript project.

In the Project Explorer, right-click on the project and select "Add New Item...".

Select the Scene template and name it "box.xaml".

When the "box.xaml" file displays, switch to XAML view replace all the code with the following:

<Canvas
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="box"
Width="100" Height="50">
<Canvas.Background>
<LinearGradientBrush EndPoint="0.49,-0.342" StartPoint="0.49,1.662">
<GradientStop Color="#FF000000" Offset="0"/>
<GradientStop Color="#FF1C408C" Offset="0.99"/>
<GradientStop Color="#FF334160" Offset="0.375"/>
<GradientStop Color="#FFC1CADE" Offset="0.625"/>
</LinearGradientBrush>
</Canvas.Background>
<TextBlock x:Name="BoxTitle" Width="50" Height="23" Text="test" TextWrapping="Wrap" Canvas.Left="33" Canvas.Top="15"/>
</Canvas>

Switch to Design view to view the "box object".

Create The JavaScript

In the Project Explorer, click the right arrow next to "Page.xaml" to display the "Page.xaml.js" file. Double-click on the file to open it up in the configured editor. Replace all the code with the following code and then close the file (select save when prompted):

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

SilverlightObject.Page = function()
{
}

SilverlightObject.Page.prototype =
{
handleLoad: function(control, userContext, rootElement)
{
// A loop that creates 5 instances of the "box" object
// Each instance is passed the "rootElement", an ID that appends a number "box"
// and a position that is the multiplication of 40 times the current iteration

for(i = 0; i <= 4; i++)
{
new box("box" + i, rootElement, i * 40);
}
}
}

Next, double-click on the file to open it again. This time replace all the code with the following code:

// Michael Washington
// Silverlight Tutorials
// http://www.adefwebserver.com/DotNetNukeHELP/Misc/Silverlight/

// Adapted from Justin-Josef Angel's
// "Silverlight Controls - The path to reusable XAML"
// http://blogs.microsoft.co.il/blogs/justinangel/archive/2007/08/14/Silverlight-Controls-_2D00_-The-path-to-reusable-XAML.aspx


// This JavaScript file defines the object "box"
box = function(ID, Parent, XLocation)
{

this._ID = ID + "_";
this._parent = Parent;
this._XLocation = XLocation;
this._host = this._parent.getHost();

// The first step is to retrive the XAML content for the "box"
this.StartXamlDownload();
}

box.prototype =
{
_findNameByXamlID : function(nameInXamlFile)
{
return this._parent.findName(this._getIdFor(nameInXamlFile));
},

_getIdFor : function(nameInXamlFile)
{
return this._ID + nameInXamlFile;
},

StartXamlDownload : function()
{
// A Silverlight "downloader" object is used to retrieve the "box.xaml" file that contains
// the XAML for the "box"
// A delegate is created that will call the "XamlDownloadCompleted" method when the
// download is completed

var xamlDownloader = this._host.createObject("downloader");
xamlDownloader.open("GET", "box.xaml");
xamlDownloader.addEventListener("completed", Silverlight.createDelegate(this, this.XamlDownloadCompleted));
xamlDownloader.send();

},

XamlDownloadCompleted : function(sender, eventArgs)
{
// The download of "box.xaml" has been completed
// "sender.ResponseText" contains the contents of "box.xaml"

var originalXaml = sender.ResponseText;

// In order to avoid name collisions, the name of each "box" object will be replaced
// with a name that begins with the ID that was passed in the object constructor

originalXaml = originalXaml.replace(/Name="/g, "Name=\"" + this._ID);

// The "box" will now be added to the main Canvas
// "plugin" is a reference to the Silverlight control on the html page

var plugin = sender.getHost();

// The altered "box.xaml" is used to create a XAML object
var newElement = plugin.content.createFromXaml(originalXaml)

// "rootCanvas" is a reference to the main Silverlight Canvas
var rootCanvas = plugin.content.findName("Page");

// The XML object is added to the root Canvas
rootCanvas.children.add(newElement);

// Now that the "box" has been added to the main Canvas
// the "BoxTitle" will be altered and the "box" position will be set

this._setControlReferences();
},

_setControlReferences : function()
{
// This method sets the "BoxTitle" and the "box" position
this._BoxTitle = this._findNameByXamlID("BoxTitle");
this._BoxTitle.text = this._XLocation.toString();

this._box = this._findNameByXamlID("box");
this._box["Canvas.Left"] = this._XLocation;
this._box["Canvas.Top"] = this._XLocation + 10;
}
}

This time, select File, then "Save As...".

Save the file as "box.xaml.js".

In the Project Explorer, right-click on the project and select "Add Existing Item...".

Select the "box.xaml.js" file and click Open.

In the Project Explorer, double-click on Default.html to open it in the configured editor.

In the source of the Default.html file add the following line under "<script type="text/javascript" src="Page.xaml.js"></script>":

<script type="text/javascript" src="box.xaml.js"></script>

Save the file. In Expression Blend, press the F5 button to run the project.

The results will now appear.

Download the complete source code here: SilverlightObject.zip

Create the DotNetNuke module

First, install the latest version of DotNetNuke from DotNetNuke.com (see this page for assistance).

From the menu bar select File then Open Web Site... to open the site in Visual Studio (or Visual Web Developer Express). In the Solution Explorer, right-click on the DesktopModules folder and select New Folder.

Name the folder "SilverlightObject"

Right-click on the folder and select Add Existing Item...

Navigate to the Expression Blend project created earlier in the tutorial.

Hold down the Ctrl key as you select box.xaml, box.xaml.js, Page.xaml, Page.xaml.js, and Silverlight.js. Click the Add button. The files will then be imported.

Next, right-click on the SilverlightObject folder and select Add New Item...

Select Web User Control, enter View.ascx for the name, select Visual C# for the language, ensure that Place code in a separate file is checked, and click the Add button.

In the Solution Explorer, double-click on the View.ascx file so that it opens in the editing screen.

Switch to Source view (if it is not already in source view) and replace all the code with the following code and save the page:

<%@ Control Language="C#" AutoEventWireup="true" CodeFile="View.ascx.cs" Inherits="DotNetNuke.Modules.SilverlightObject.View" %>
<asp:Panel ID="SilverlightControlHost" runat="server" Height="240px" Width="280px">

<script type="text/javascript">
<asp:Literal ID="lrlSilverlight" runat="server"></asp:Literal>
</script>

</asp:Panel>

Now, right-click on View.ascx in the Solution Explorer and select View Code. The View.ascx.cs file will open in the Editing window. Replace all the code with the following code:

using System;
using System.Collections.Generic;
using System.Collections;
using System.Text;
using System.Web.UI.WebControls;
using DotNetNuke;
using DotNetNuke.Common;
using DotNetNuke.Security;
using DotNetNuke.Security.Roles;
using DotNetNuke.Entities.Modules;
using DotNetNuke.Services.Exceptions;

namespace DotNetNuke.Modules.SilverlightObject
{
public partial class View : PortalModuleBase
{

protected void Page_Load(object sender, EventArgs e)
{
try
{
// Silverlight scripts
Page.ClientScript.RegisterClientScriptInclude(this.GetType(), "Silverlight", (this.TemplateSourceDirectory + "/Silverlight.js"));
Page.ClientScript.RegisterClientScriptInclude(this.GetType(), "SilverlightObject_main", (this.TemplateSourceDirectory + "/Page.xaml.js"));
Page.ClientScript.RegisterClientScriptInclude(this.GetType(), "SilverlightObject_box", (this.TemplateSourceDirectory + "/box.xaml.js"));

Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "DNN_Silverlight_Object", DNN_Silverlight_Object());
Page.ClientScript.RegisterClientScriptBlock(this.GetType(), string.Format("createSilverlight{0}", Convert.ToString(ModuleId)), CreateSilverlight());

lrlSilverlight.Text = string.Format("createSilverlight{0}()", Convert.ToString(ModuleId));
}

catch (Exception exc)
{
// Module failed to load

Exceptions.ProcessModuleLoadException(this, exc);
}

}

#region DNN_Silverlight_Object
private string DNN_Silverlight_Object()
{
// This method creates a JavaScript object that exposes the current TemplateSourceDirectory
// for use by JavaScript methods


System.Text.StringBuilder sb = new System.Text.StringBuilder();

sb.Append("<script type='text/javascript'> ");
sb.Append("if (!window.DNN_Silverlight_Object) ");
sb.Append("window.DNN_Silverlight_Object = {}; ");
sb.Append("DNN_Silverlight_Object.Module = function() ");
sb.Append("{ ");
sb.Append(string.Format("this.TemplateSourceDirectory = '{0}'; ", this.TemplateSourceDirectory));
sb.Append("} ");
sb.Append("</script> ");

return sb.ToString();
}
#endregion

#region CreateSilverlight

private string CreateSilverlight()
{
System.Text.StringBuilder sb = new System.Text.StringBuilder();

sb.Append("<script type='text/javascript'> ");
sb.Append(string.Format("function createSilverlight{0}() ", Convert.ToString(ModuleId)));
sb.Append("{ ");
sb.Append("var scene = new SilverlightObject.Page(); ");
sb.Append("Silverlight.createObjectEx(");
sb.Append("{ ");
sb.Append(string.Format("source: '{0}', ", (this.TemplateSourceDirectory + "/Page.xaml")));
sb.Append(string.Format("parentElement: document.getElementById('dnn_ctr{0}_View_SilverlightControlHost'), ", Convert.ToString(ModuleId)));
sb.Append(string.Format("id: '{0}', ", Convert.ToString(ModuleId)));
sb.Append("properties: { ");
sb.Append("width:'100%', ");
sb.Append("height:'100%', ");
sb.Append("version: '1.0' ");
sb.Append("}, ");
sb.Append("events: { ");
sb.Append("onLoad: Silverlight.createDelegate(scene, scene.handleLoad) ");
sb.Append("} ");
sb.Append("});} ");

sb.Append("if (!window.Silverlight) ");
sb.Append("window.Silverlight = {}; ");
sb.Append("Silverlight.createDelegate = function(instance, method) { ");
sb.Append("return function() { ");
sb.Append("return method.apply(instance, arguments); ");
sb.Append("} ");
sb.Append("} ");

sb.Append("</script> ");
return sb.ToString();

}

#endregion

}
}

Save the page and select Build from the menu bar and then Build Page. The page should build without errors.

In the Solution Explorer, double-click on the box.xaml.js file to open it.

Replace:

StartXamlDownload : function()
{
// A Silverlight "downloader" object is used to retrieve the "box.xaml" file that contains
// the XAML for the "box"
// A delegate is created that will call the "XamlDownloadCompleted" method when the
// download is completed

var xamlDownloader = this._host.createObject("downloader");
xamlDownloader.open("GET", "box.xaml");
xamlDownloader.addEventListener("completed", Silverlight.createDelegate(this, this.XamlDownloadCompleted));
xamlDownloader.send();

},

with:

StartXamlDownload : function()
{
// A Silverlight "downloader" object is used to retrieve the "box.xaml" file that contains
// the XAML for the "box"
// A delegate is created that will call the "XamlDownloadCompleted" method when the
// download is completed


var xamlDownloader = this._host.createObject("downloader");

// Get an instance of the DNN_Silverlight object that contains the current
// information containing the location of the .xaml file

var Module = new DNN_Silverlight_Object.Module();

xamlDownloader.open("GET", Module.TemplateSourceDirectory + "/box.xaml");
xamlDownloader.addEventListener("completed", Silverlight.createDelegate(this, this.XamlDownloadCompleted));
xamlDownloader.send();

},

Save the file and close it.

Create the module definition

Navigate to the DotNetNuke site using the web browser and log in as the Host account. From the Host menu, select Module Definitions.

From the Module Definitions menu select Create Module Definition.

Enter "SilverlightObject" for Module Name, Folder Name, Friendly Name, and Description. Then click the Create link.

Next, enter "SilverlightObject" for New Definition and click the Add Definition link.

 Click the Add Control link.

On the Edit Module Control page enter "SilverlightObject" for Title, select the DesktopModules/SilverlightObject/View.ascx from the Source drop-down, ensure that Type is set to View and click the Update link.

Navigate to a page in the DotNetNuke site, and from the administration menu, select SilverlightObject from the Module drop-down and click the Add link.

The module will now appear.

 

The DotNetNuke module explained

The DotNetNuke module required you to replace the Default.html, and the Default_html.js files with the View.ascx and View.ascx.cs files. The files provide the same functionality except that the DotNetNuke files provide this functionality dynamically.

The article: Creating a DotNetNuke Silverlight Module For Absolute Beginners covers the changes required to run a Silverlight application in the DotNetNuke framework. The only difference between this example and that one is the requirement to pass the location of the XAML file to a method that is inside a JavaScript file.

The following method inside the View.ascx.cs dynamically creates a JavaScript class that contains the location of the module:

#region DNN_Silverlight_Object
private string DNN_Silverlight_Object()
{
// This method creates a JavaScript object that exposes the current TemplateSourceDirectory
// for use by JavaScript methods


System.Text.StringBuilder sb = new System.Text.StringBuilder();

sb.Append("<script type='text/javascript'> ");
sb.Append("if (!window.DNN_Silverlight_Object) ");
sb.Append("window.DNN_Silverlight_Object = {}; ");
sb.Append("DNN_Silverlight_Object.Module = function() ");
sb.Append("{ ");
sb.Append(string.Format("this.TemplateSourceDirectory = '{0}'; ", this.TemplateSourceDirectory));
sb.Append("} ");
sb.Append("</script> ");

return sb.ToString();
}
#endregion

The JavaScript method inside box.xaml.js is then able to instantiate the DNN_Silverlight_Object class and retrieve the location of the XAML file.

// Get an instance of the DNN_Silverlight object that contains the current
// information containing the location of the .xaml file

var Module = new DNN_Silverlight_Object.Module();

xamlDownloader.open("GET", Module.TemplateSourceDirectory + "/box.xaml");

 

Download the complete DotNetNuke module here: SilverlightObject_01.00.00_Install.zip