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.