MULTIMEDIA

Dynamically Loading XAML on Silverlight 4

9/14/2010 11:44:34 AM
1. Problem

You need to load XAML dynamically at runtime from JavaScript or from Managed Code.

2. Solution

Use the CreateFromXaml method in JavaScript to dynamically load XAML markup at runtime into a Silverlight 4 application from JavaScript. Use FindName to locate the parent control where the XAML will be attached in the visual object tree. (We covered FindName in Recipe 1.) Use the XamlReader object to dynamically load XAML markup at runtime from managed code.

3. How It Works

Silverlight 4 runs as an Internet browser plug-in that is created from within an HTML <object> tag in the browser. Even though Silverlight 4 has a managed code execution model, the Silverlight 4 plug-in is still accessible from and can interoperate with HTML using JavaScript, as in Silverlight 1.0. 

To load XAML using managed code, use the XamlReader object. The XamlReader object sits in the System.Windows.Markup namespace. The static Load method takes a string of XAML and converts the string to an object or object tree, depending on what is contained within the XAML string. The static Load method then returns a reference to root element created of type UIElement, which can be added to the UI visual tree. Since all XAML elements inherit from UIElement, it makes sense that the return type from Load would also be UIElement. The string must consist of valid markup with the addition of two namespaces on the top-level element in the XAML contained in the string:

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation
xmlns:x="xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml"

Once you have a valid string of XAML, pass it to the XamlReader.Load method, and a UIElement object reference is returned. The final step is to convert the object to a UIElement or descendant class and add it as a child to the desired parent element in the visual tree.

4. The Code

There are two ways to approach this recipe: using an HTML page or using an ASP.NET page. The plain-old HTML page can be used as a guide for configuring Silverlight for non-Microsoft platforms.

For the ASPX page, you'll use the default test page created by Visual Studio 2010. You'll also add a button to the ASPX test page and a script reference to a JavaScript file that will load the XAML (in this case, one named Recipe2.4.js in the /js folder under the TestWeb project).

NOTE

Silverlight 2 developer tools included an ASP.NET Silverlight control to configure a Silverlight application. In Silverlight 3 or later, the control has been removed. The techniques for hosting the Silverlight control covered here will work with any web technology because it is simply HTML with the <object> tag.

For the ASPX page, the Silverlight 4 plug-in is configured directly in an <object> tag located inside a <div> tag. The default ASPX test page created by Visual Studio 2010 does not include an id value in the <object> declaration, so set the value for id to SilverlightPlugInID so that it can be accessed in JavaScript using the document.getElementById method:

var slControl = document.getElementById("SilverlightPlugInID");

After obtaining a reference to the Silverlight plug-in, you can create a new Silverlight control, using the CreateFromXaml method on the Content property of the plug-in, and hold a JavaScript reference to it:

slControl.Content.CreateFromXaml(
'<Ellipse Height="200" Width="200" Fill="Navy" />');

Next, call the FindName method on the Silverlight plug-in to access the XAML control tree in MainPage.xaml to obtain a reference to the default root <Grid> control with an x:Name of "LayoutRoot":

var layoutRoot = slControl.content.FindName("LayoutRoot");

Once you have a reference to the <Grid> control, you can add the control you created with the CreateFromXaml method to the Grid's Children collection using the Add method:

layoutRoot.Children.Add(e);

When you attach XAML to a visual tree, the added tree creates a new namescope for that XAML within the existing scope of the Page UserControl class. Calling FindName to locate a control within the newly added XAML from the Page level will not succeed, because the method will search inside the newly created namescope. The best way to manage this is to retain a reference to the newly added XAML and call FindName from the reference.

The only additional code you need to add to the ASPX test page is logic to enable the button after the Silverlight control is fully loaded. If you do not take this into account and call FindName before the Silverlight application is fully loaded, FindName will return null. The way to manage this is to put logic inside the OnLoad method for the Silverlight plug-in object that does not access or allow access to the control tree until the event fires:

function onSilverlightLoad(sender, args)
{
var btn = document.getElementById("testButton");
btn.disabled = false;
}

In the ASPX test page, the code in onSilverlightLoad simply enables the button. Otherwise, if the button was not disabled and the button is clicked before the control is fully loaded, a null reference exception occurs. To assign the OnLoad event to the Silverlight plug-in, set a <param> tag on the <object> tag:

<param name="onload" value="onSilverlightLoad" />

The MainPage.xaml is not modified for this recipe, so it is not listed here. All of the action occurs in the custom script file in Listing 2-4 and the HTML and ASP.NET test pages shown in Listings 2 and 3.

In Listing 1, you use the HTML getElementById to obtain a reference to the Silverlight control. Next, you create an ellipse, and add it to the existing XAML content. The JavaScript file in Listing 1 is referenced by the HTML and ASP.NET page.

Listing 1. The Recipe 2.js JavaScript File
function createEllipse()
{
var slControl = document.getElementById("SilverlightPlugInID");
var e =
slControl.Content.CreateFromXaml(
'<Ellipse Height="200" Width="200" Fill="Navy" />');
var layoutRoot = slControl.content.FindName("LayoutRoot");
layoutRoot.Children.Add(e);
}

function onSilverlightLoad(sender, args)
{
var btn = document.getElementById("testButton");
btn.disabled = false;
}

Listing 2 includes the script file in Listing 1. Also in Listing 2, the onSilverlightLoad event from Listing 1 is assigned to the Silverlight control's onload event, dynamically adding the XAML to the LayoutRoot element in the XAML shown in Figure 1.

Listing 2. Recipe 2's TestPage.aspx File :

<%@ Page Language="C#" AutoEventWireup="true" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Test Page for Recipe 2.2</title>
<script runat="server">

protected void Page_Load(object sender, EventArgs e)
{
testButton.Attributes.Add("onclick", "createEllipse();");
}
</script>
<style type="text/css">
html, body

{
height: 100%;
overflow: auto;
}
body
{
padding: 0;
margin: 0;
}
#silverlightControlHost
{
height: 100%;
text-align: center;
}
</style>
<script src="js/Recipe2_2.js" type="text/javascript"></script>
<script type="text/javascript" src="Silverlight.js"></script>
<script type="text/javascript">
function onSilverlightError(sender, args) {
var appSource = "";
if (sender != null && sender != 0) {
appSource = sender.getHost().Source;
}

var errorType = args.ErrorType;
var iErrorCode = args.ErrorCode;

if (errorType == "ImageError" || errorType == "MediaError")
{
return;
}

var errMsg = "Unhandled Error in Silverlight Application "
+ appSource + "\n";

errMsg += "Code: " + iErrorCode + " \n";
errMsg += "Category: " + errorType + " \n";
errMsg += "Message: " + args.ErrorMessage + " \n";

if (errorType == "ParserError") {
errMsg += "File: " + args.xamlFile + " \n";
errMsg += "Line: " + args.lineNumber + " \n";
errMsg += "Position: " + args.charPosition + " \n";
}
else if (errorType == "RuntimeError") {

if (args.lineNumber != 0) {
errMsg += "Line: " + args.lineNumber + " \n";
errMsg += "Position: " + args.charPosition + " \n";
}
errMsg += "MethodName: " + args.methodName + " \n";
}

throw new Error(errMsg);
}
</script>
</head>
<body>
<form id="form1" runat="server" style="height: 100%">
<asp:Button ID="testButton" runat="server" Enabled="false"
Text="Click Me!" UseSubmitBehavior="false" /><br />
<div id="silverlightControlHost">
<object id="SilverlightPlugInID"
data="data:application/x-silverlight-2,"
type="application/x-silverlight-2"
width="100%" height="100%">
<param name="source" value="ClientBin/Ch02_ProgrammingModel.Recipe2_2.xap" />
<param name="onError" value="onSilverlightError" />
<param name="onload" value="onSilverlightLoad" />
<param name="background" value="white" />
<param name="minRuntimeVersion" value="4.0.50401.0" />
<param name="autoUpgrade" value="true" />
<a href="http://go.microsoft.com/fwlink/?LinkID=149156&v=4.0.50401.0" style="text-
decoration: none">
<img src="http://go.microsoft.com/fwlink/?LinkId=161376" alt="Get Microsoft
Silverlight"
style="border-style: none" />
</a>
</object>
<iframe id="_sl_historyFrame" style="visibility: hidden; height: 0px; width: 0px;
border: 0px"></iframe>
</div>
</form>
</body>
</html>


Listing 3. Recipe 2's MainPage.xaml File
<UserControl x:Class=" Ch02_ProgrammingModel.Recipe2_2.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">

<Grid x:Name="LayoutRoot" Background="White">
<Grid.RowDefinitions>
<RowDefinition Height="138*" />
<RowDefinition Height="162*" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="1" >
<Button Click="Button_Click" Margin="4" Content="Click To Load XAML from Managed Code"
/>
<Grid x:Name="GridforManagedCode" Margin="4"></Grid>
</StackPanel>
</Grid>
</UserControl>
Now that you know how to load XAML using JavaScript, let's move on to showing you how to load XAML using managed code. In Listing 3, you can see a nested Grid control named GridforManagedCode. To load XAML using managed code, you can use the XamlReader.Load(xamlString) method. It takes a valid XAML fragment as a string. The string must contain the Silverlight namespace in the root of the XAML fragment. Here is the code from MainPage.xaml.cs with the namespace as part of the root Ellipse element:
string xamlString = "<Ellipse
xmlns=\http://schemas.microsoft.com/winfx/2006/xaml/presentation\
Height=\"200\" Width=\"200\" Fill=\"Navy\" Grid.Column=\"1\"
Grid.Row=\"1\" />";
UIElement element = (UIElement)XamlReader.Load(xamlString);
GridforManagedCode.Children.Add(element);

Figure 1 shows the output from Recipe 2.

Figure 1. Recipe 2 Final Output

Other  
 
Most View
Group Test: Web Browsers (Part 1) : Firefox 17, Opera 12.10
Gammatech Durabook T70Q : The $2,000 Windows-Based Tablet With High Durability
Use Preview to Edit On Your Mac (Part 2)
ECS Z77H2-A2X v1.0 - Golden LGA 1155 Mainboard From The Black Series (Part 5)
Tech Spelunk - Google Nexus 7
All You Need To Know About iOS 6 (Part 2)
Fractal Design Node 605 – Elegant HTPC Case
Lenovo Netbook Thinkpad X131e
SQL Server 2005 : Advanced OLAP - Advanced Dimensions and Measures (part 2) - Parent-Child Dimensions
Building LOB Applications : Databinding in XAML
Top 10
Kingston Wi - Drive 128GB: Simple To Get Started
Seagate Wireless Plus 1 TB - Streaming Videos To Various Devices
Seagate Wireless Plus 1TB - Seagate's Second Wireless External Hard Drive
Western Digital My Passport 2TB - The Ideal Companion For Anyone
Lenovo IdeaTab A2109 - A Typical Midrange Android Tablet
Secret Tips For Your Kindle Fire
The Best Experience With Windows 8 Tablets And Hybrids (Part 2)
The Best Experience With Windows 8 Tablets And Hybrids (Part 1)
Give Your Browser A Health Check
New Ways To Block Irritating Ads…