Click here to Skip to main content
15,867,330 members
Articles / Desktop Programming / XAML
Article

Silverlight Controls - The Path to Reusable XAML

Rate me:
Please Sign up or sign in to vote.
4.72/5 (31 votes)
23 Dec 2007Ms-PL25 min read 103.9K   730   61   13
An article about Silverlight Controls - The path to reusable XAML

Introduction

Silverlight is like the wild wild west.
Everyone codes however he or she wants. Every new discovery is like virgin territory and everything has that new car smell.

image

Most samples of Silverlight out there today are procedural based and not Object-Oriented.
We're all so wrapped up in making shinysnazzysamples that we have forgotten those weird alien like concepts "Don't repeat yourself" and "Blob"/"Big Hairy Object"/"God Object".

Don't Repeat Yourself (DRY, also known as Once and Only Once) is a process philosophy aimed at reducing duplication, particularly in computing. The philosophy emphasizes that information should not be duplicated, because duplication increases the difficulty of change, may decrease clarity, and leads to opportunities for inconsistency. [Wikipedia]

The basic idea behind structured programming is that a big problem is broken down into many smaller problems (divide and conquer) and solutions are created for each of them. God object based code does not follow this approach. Instead, much of a program's overall functionality is coded into a single object. Because this object holds so much data and has so many methods, its role in the program becomes God-like (all-encompassing). [Wikipedia]

image

We, as developers, have not been given proper tools and instructions on how to develop reusable Silverlight objects.

Let's see if we can start right now.

We're going to see that there are four ways right now out there trying to do reusable Silverlight objects.
And after that I'm going to offer the fifth one which is more object oriented.
In future articles, I'll show how to use this technique in Data Driven Silverlight applications.

First Way of Creating a Reusable Silverlight Object:
Using "content.createFromXaml(Xaml)" JavaScript on the Client Based on Some XAML Script

Let's have a look at Richard Z's famous Silverlight chart samples.

image

This is a very stylish graph built in Silverlight.
You can see that hovering my mouse pointer makes the column color itself green.
Let's refresh the page (either by pressing F5 or clicking the "New Graph" button).

image

Something is becoming increasingly clear over here (besides the fact that Richard knows how to make awesome graphs) - this is not a static image!
This actually loads data and rebuilds the XAML code!

Let's see how it's done:

XML
for (i = 1; i < 21; ++i)
{
    values[i] = Math.min(260,        //10 + 280 * (group + 1) / colours.length,
                        Math.max(10, // + 280 * group / colours.length,
                                  values[i-1] - 70 + Math.random()*141));
    var x = i * 40;

    // Draw the bar
    var bar = control.content.createFromXaml(
        '<Rectangle Name="' + id('barB',i) + '" Canvas.Left="' + (x-35) + '" Width="30"'
      + '           Canvas.Top="' + (304 - values[i-1]) + '" Height="'
                    + (values[i-1] + 5) + '"'
      + '           RadiusX="2" RadiusY="2">'
      + '   <Rectangle.Fill>'
      + '       <SolidColorBrush Name="' + id('brushB',i) + '" Color="#7F004296" />'
      + '   </Rectangle.Fill>'

      + '   <Rectangle.RenderTransform>'
      + '       <ScaleTransform Name="' + id('barscaleB',i) + '" CenterY="'
                + values[i-1] + '" ScaleY="0.0" />'
      + '   </Rectangle.RenderTransform>'
      + '</Rectangle>'
    );
    _sl.children.add(bar);
}

This code takes hard-coded XAML, changes several attributes (like the Height of the bar and Canvas.Left) based on data available to JavaScript and uses the createFromXaml to create the XAML Controls based on this dynamic XAML markup code.

Pros

  • It's dynamic meaning that relevant data sets various display related properties.

Cons

  • XAML is hard-coded. Expression Blend 2 can't just pick up this XAML code and start editing it.
  • It's a good time to mention that JavaScript has no support for compilation or descent unit testing support. This is literally a sure fired way of getting into apostrophe maintenance hell (that's the hell we had before DLL hell).
During this article (after the review) we will refactor the Jelly Graph code to more maintainable code.

Second Way of Creating a Reusable Silverlight Object:
Using "XmlReader.Read(Xaml)" .NET Code on the Client Based on Some XAML Script

A famous example using this technique is the Streamed Template Processing for Data Binding in Silverlight(code). It's basically the same thing as the previous sample, only now it has to be Silverlight 1.1 as it's running. Let's look at some code:

XML
string templateXml =
    @"<Canvas
        xmlns=""http://schemas.microsoft.com/client/2007""
        xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml""
            Width=""960"" Height=""150"" x:Name=""$(cnvItem)$"" Opacity=""1""
            MouseLeftButtonDown=""DoClick"">

<TextBlock x:Name=""$(hdln)$"" Width=""576"" Height=""40""
           Canvas.Left=""376"" Canvas.Top=""8""
           FontFamily=""Tahoma"" FontSize=""24""
           FontWeight=""Normal"" Foreground=""#FFFFFFFF""
           Text=""$/title$"" TextWrapping=""Wrap""/>

<TextBlock x:Name=""$(detl)$"" Width=""576""
           Height=""96"" Canvas.Left=""376""
           Canvas.Top=""48"" FontFamily=""Tahoma""
           FontSize=""14"" FontWeight=""Normal""
           Foreground=""#FFFFFFFF""
           Text=""$/description$""
           TextWrapping=""Wrap""/>
</Canvas>";

C#
XmlReader template = XmlReader.Create(new StringReader(templateXml));
string xamlResult = rssItem.ProcessTemplate(template, AllRssItemTemplates);

Visual v = (Visual)XamlReader.Load(xamlResult);
this.Children.Add(v);

There's hard-coded XAML with Tokens $token$ in it, and for each RSS post it processes the template once, then it loads the final XAML with XmlReader.Load(xaml). The tokens inside the XAML are later processed in a semi-xpath like mechanism which replace their content with ascending ID number and content from an RSS post. Since they are both this way and the first way is template client centric, their pros and cons are the same.

Third Way of Creating a Reusable Silverlight Object:
Setting the Source Property of our Silverlight Control to a Server Side .NET Page / HttpHandler that Renders an existing XAML File.

A good example of this technique can be found on Rob Conery's blog at Silverlight Day 2: Creating A Data-driven Control.

image

image

JavaScript
//contains calls to silverlight.js, example below loads Page.xaml
function createSilverlight(source, elementID)
{
    Sys.Silverlight.createObjectEx({
        source: source,
        parentElement: document.getElementById(elementID),
        id: "SilverlightControl"+elementID,
        properties: {
            width: "100%",
            height: "100%",
            version: "0.95",
            enableHtmlAccess: true
        },
        events: {}
    });
}

function loadAG(){
    createSilverlight("Menu.xaml","Menu");
    createSilverlight("XAMLWelcome.aspx","Welcome");
}

This code has a server side ASPX page that contains hard-coded XAML that runs like a normal ASP script and replaces values. In this example, we could have actually used ASP 3 script to render out the XAML code. But in more complex and real world examples, we would've needed the power of .NET behind us.

Pros

  • Dynamic
  • Has access to the full strength of the .NET Framework (or whatever server side technology we'll use) which is considerably better then the Silverlight 1.1 CLR or JavaScript.

Cons

  • This is literally XAML code hard-wired into ASP.NET. This just can't be the future of software evolution.
  • Can't load the XAML code into Designer Blend.

Fourth Way of Creating a Reusable Silverlight Object:
Render XAML Code with Some Sort of Generation Tool that Uses XML Comments for Instructions

This is an interesting one. It uses XML comments embedded inside XAML to run certain generator-oriented commands that help replace XML attributes or duplicate existing XML elements. Here's a good example of such an implementation: Auto-code generation for Silverlight Controls

image

This goes into a custom generator application which churns out the final XAML. In the example above, we can see three types of statements:

  1. <cc:Repeat>XXX<cc:RepeatEnd> which duplicates XML content inside of it.
  2. <cc:Replace Attribute="XXX">NewValue</cc:Replace> which changes previous XML node's XML Attributes based on local script variables.
  3. <cc:Evaluate> and <cc:Declare> which lets you declare a local script variable and change it's value.

Pros

  • This can actually be loaded by Expression Blend.
  • It's very clear as to what we're trying to accomplish and this isn't just any "place something here" code. It is "replace attribute for XML node" which is somewhat better.

Cons

  • It's an entire programming language written inside XML comments. No intellisense, No compilation, no nothing.

Summing Up

Cons

  • Most development options we've seen now don't support load XAML into Expression Blend.
    This is pretty much a death sentence for these options, as no sane person would do so much reparative work as to extract XAML back & forth into Expression Blend. You'll have to be twice as mad to try and edit this brittle XAML-JavaScript/C#/ASP.NET code. This type of code is just why I escaped ASP 3 to the promised land of ASP.NET.

    image

    Copied from learnasp.com tutorial on how to update databases. Place one apostrophe wrong and you'll debug it for the rest of your life.

  • We still can't use the same XAML code in two different files. Let's say we have a standard XAML button, we won't go around duplicating it through our system, we'll have to use some myButton.Xaml file which isn't supported in any format here.

  • These are tricks of getting some sort of extensible markup in an extremely non extensible format.

  • Back to procedural coding. We're back to creating massive chunks of code that produce markup that will only get bigger and messier until it can't be maintained.

Pros

  • It works. You can't scuff your nose on that one, these are invented by truly ingenious people who had to do SOMETHING and it works.

Fifth Way of Creating a Reusable Silverlight Object:
Using a "one .NET/JavaScript Class - One XAML File" Project Methodology

image

This is exactly what we've got today in ASP.NET. We've got one Markup file and on Code Behind file.
I like this modal, I do my best work with this modal, it's extensible, it's maintainable, it's object oriented.

Like any Object oriented programming, this modal could easily be abused. This is the only modal that offers us "KISS" (Keep it straight and simple) coding, that offers "YAGNI" (You Aren't Going to Need It), that we expect "DRY" (Don't Repeat yourself).

Let's take the Jelly Bar Chart sample and rewrite it. All the code we're writing in this article is available for download here.

image

Here's what I see - there's one object that repeats itself in the sample (use View source on the Sample page to examine it for yourself).
That object is the Blue/Green Bar we're seeing with all it's GUI logic.

There's quite a bit to draw. There's the blue graph, on mouse hover, we need to change the color & show the tool tip, on mouse leave, we need to repaint the Bar Blue & Hide the tooltip. But each Set of Silverlight XAML Controls (Rectangles & TextBlocks) and XAML Animations (Show/Hide tooltip, color the Bar) are all just one "Bar object".

image

So let's say we've refactored the bar object out, what's left?

image

If we take out the Chart Bars, what's left is the main Silverlight canvas. It's got 1) a snazzy headline, 2) a refresh button, and 3) a special canvas in the middle. The special canvas in the middle is used to initialize our Bar objects into it.

Step #1 - XAML : Creating the XAML File

Let's refactor the Bar object to it's own XAML file. So the first thing we should do is create a new empty XAML file in Expression Blend:

image

Here's the first piece of JavaScript we need to convert to XAML:

JavaScript
for (i = 1; i < 21; ++i)
{
    values[i] = Math.min(260,        //10 + 280 * (group + 1) / colours.length,
                        Math.max(10, // + 280 * group / colours.length,
                                  values[i-1] - 70 + Math.random()*141));

    var x = i * 40;
        // Draw the bar
        var bar = control.content.createFromXaml(
            '<Rectangle Name="' + id('barB',i) + '" Canvas.Left="' + (x-35)
            + '" Width="30"'
          + '           Canvas.Top="' + (304 - values[i-1]) + '" Height="'
            + (values[i-1] + 5) + '"'
          + '           RadiusX="2" RadiusY="2">'
          + '   <Rectangle.Fill>'
          + '       <SolidColorBrush Name="' + id('brushB',i)
            + '" Color="#7F004296" />'
          + '   </Rectangle.Fill>'
          + '   <Rectangle.RenderTransform>'
          + '       <ScaleTransform Name="' + id('barscaleB',i) + '" CenterY="'
            + values[i-1] + '" ScaleY="0.0" />'
          + '   </Rectangle.RenderTransform>'
          + '</Rectangle>'
        );

        _sl.children.add(bar);

So first let's understand what value[i] and x stand for.

  • values[i] is the height of the current bar and the max height is 300 pixel
  • x is how far the current bar is from the left of the canvas (i.e. Canvas.Left)

Now, we'd like to remove all GUI logic from the XAML to a new class. We will do just that and we can place in stand of any calculation the values of "0.0". However we do want some Expression Blend support so we can see something, so let's say our "template" bar is about 150 pixels.

Let's refactor this JavaScript into normal XAML:

JavaScript
// Draw the bar

var bar = control.content.createFromXaml(

    '<Rectangle Name="' + id('barB',i) + '" Canvas.Left="' + (x-35) + '" Width="30"'
  + '           Canvas.Top="' + (304 - values[i-1]) + '" Height="'
        + (values[i-1] + 5) + '"'
  + '           RadiusX="2" RadiusY="2">'
  + '   <Rectangle.Fill>'
  + '       <SolidColorBrush Name="' + id('brushB',i) + '" Color="#7F004296" />'
  + '   </Rectangle.Fill>'
  + '   <Rectangle.RenderTransform>'
  + '       <ScaleTransform Name="' + id('barscaleB',i) + '" CenterY="'
        + values[i-1] + '" ScaleY="0.0" />'
  + '   </Rectangle.RenderTransform>'
  + '</Rectangle>'
);

_sl.children.add(bar);

becomes:

XML
 <!--  Draw the bar -->
 <Rectangle x:Name="barB" Canvas.Left="5" Width="30"
Canvas.Top="154" Height="150"
RadiusX="2" RadiusY="2">
     <Rectangle.Fill>
         <SolidColorBrush x:Name="brushB" Color="#7F004296" />
     </Rectangle.Fill>

     <Rectangle.RenderTransform>
         <ScaleTransform x:Name="barscaleB" CenterY="150.0" ScaleY="0.0" />
     </Rectangle.RenderTransform>
 </Rectangle>

We'll go line by line and see what we changed.

XML
  '<Rectangle Name="' + id('barB',i) + '" Canvas.Left="' + (x-35) + '" Width="30"'
+ '           Canvas.Top="' + (304 - values[i-1]) + '" Height="' + (values[i-1] + 5) + '"'
+ '           RadiusX="2" RadiusY="2">'

We'll remove all ID building functions and just place a normal x:Name there.
We said we'll replace all the x values with 40 so Canvas.Left is 5 (40 - 35 = 5).
We said our current bar has a 150 pixel height so Canvas.Top is 154 pixels (304 - 150 = 154). Same goes for Height that will become 155 pixels (150 + 5).

XML
+ '   <Rectangle.Fill>'
+ '       <SolidColorBrush Name="' + id('brushB',i) + '" Color="#7F004296" />'
+ '   </Rectangle.Fill>'
+ '   <Rectangle.RenderTransform>'
+ '       <ScaleTransform Name="' + id('barscaleB',i) + '" CenterY="'
    + values[i-1] + '" ScaleY="0.0" />'
+ '   </Rectangle.RenderTransform>'
+ '</Rectangle>'

becomes:

XML
    <Rectangle.Fill>
        <SolidColorBrush x:Name="brushB" Color="#7F004296" />
    </Rectangle.Fill>

    <Rectangle.RenderTransform>
        <ScaleTransform x:Name="barscaleB" CenterY="150.0" ScaleY="0.0" />
    </Rectangle.RenderTransform>
</Rectangle>

Same deal here, we changed all IDs to static X:Name and just wrote mock values based on "x = 40, values[i-1] = 150".

We'll do all remaining conversions in the same way. Let's see just one more:

XML
////////////////
// This is the text block that shows up inside the bubble
//
var yInt = parseInt(values[i-1]);
var textBlock = control.content.createFromXaml(
    '<TextBlock Name="' + id('bubbleText',i) + '" FontSize="11" Text="' + yInt + '"'
    + '        Canvas.Left="' + (x - 32) + '" Canvas.Top="'
            + (273 - values[i-1] + 4) + '"'
    + '        Canvas.ZIndex="3" Opacity="0" Foreground="#FFFFFF">'
    + ' <TextBlock.RenderTransform>'
    + '    <ScaleTransform CenterX="12" CenterY="24" ScaleX="1.5" ScaleY="1.5" />'
    + ' </TextBlock.RenderTransform>'
    + '</TextBlock>'
    );
_sl.children.add(textBlock);

becomes:

XML
   <!-- This is the text block that shows up inside the bubble -->
   <TextBlock x:Name="bubbleText" FontSize="11" Text="150"
Canvas.Left="18" Canvas.Top="127"
Canvas.ZIndex="3" Opacity="0" Foreground="#FFFFFF">
       <TextBlock.RenderTransform>
           <ScaleTransform CenterX="12" CenterY="24" ScaleX="1.5" ScaleY="1.5" />
       </TextBlock.RenderTransform>
   </TextBlock>
  • id becomes as static x:Name
  • yint is replaced with 150
  • Canvas.Left is 8 (40 - 32 = 8)
  • Canvas.Top is 127 (273 - 150 - 4 = 127)

So let's say we converted our entire JavaScript Bar Dynamic XAML to a Bar.Xaml file.

If we open the XAML file in Blend Expression, we'll see this:

image

All we get is a small white canvas.

Let's see if expression has anything interesting to tell us on the left side of the screen...

image

Hmm.. this is interesting, there are four elements on the page (barB, bar, bubble and bubbleText) but we can't see any of them!

Let's select them and see if Expression Blend can show us where they are.

image

Ok, so each blue line on the screen is where one element is. But we still can't actually see it, let's try and run one of our timelines and see what we get...

image

Now we can see the Bar! It probably only shows up after loading... but where's our TextBubble?

image

Depending on which animation Storyboard we start, we'll get different items of our Bar Object to show up.

Step #2 - C#: Setting up the XAML Code-behind for Both Silverlight 1.1 (Silverlight 1.0 is Next)

Let's create a new Silverlight Project.

image

We'll also create a Silverlight class library.

image

And this is how our solution explorer looks like:

image

We'll create a new Silverlight User Control Named Bar in our Silverlight 1.1 project.

image

And we get an empty XAML file with a C# Code-behind:

image

image

We'll copy our Bar.Xaml file into this new empty XAML file.

image

We'll also add a reference from our Silverlight Project to our Silverlight Control Library.

image

image

Let's have a look at bar.xaml.cs:

C#
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

namespace SilverlightOOControlsLibrary
{
    public class Bar : Control
    {
        public Bar()
        {
            System.IO.Stream s = this.GetType().Assembly.GetManifestResourceStream
                ("SilverlightOOControlsLibrary.Bar.xaml");
            this.InitializeFromXaml(new System.IO.StreamReader(s).ReadToEnd());
        }
    }
} 

It's pretty basic stuff, there's two lines of code over there linking between the XAML file and the CS file. We'll have to change the internal ID of the elements in Bar.Xaml file so there are no ID collisions.

C#
public Bar()
{
    System.IO.Stream s = this.GetType().Assembly.GetManifestResourceStream
        ("SilverlightOOControlsLibrary.Bar.xaml");
    string originalXaml = new System.IO.StreamReader(s).ReadToEnd();
    this.InitializeFromXaml(originalXaml);
}

becomes:

C#
public Bar() : this("Bar")
{

}

public Bar(string ID)
{
    System.IO.Stream s = this.GetType().Assembly.GetManifestResourceStream
        ("SilverlightOOControlsLibrary.Bar.xaml");
    string originalXaml = new System.IO.StreamReader(s).ReadToEnd();
    this.InitializeFromXaml(originalXaml);
} 

And now we'll change the internal x:Name properties of all XAML elements based on the ID string we just got.

C#
public class Bar : Control
{
    public Bar() : this("Bar")
    {

    }

    public Bar(string ID)
    {
        System.IO.Stream s = this.GetType().Assembly.GetManifestResourceStream
            ("SilverlightOOControlsLibrary.Bar.xaml");
        string originalXaml = new System.IO.StreamReader(s).ReadToEnd();
        originalXaml = originalXaml.Replace("x:Name=\"",
            string.Format("x:Name=\"{0}_", ID));
        this.InitializeFromXaml(originalXaml);
    }
} 

The ID could also be determined by a control hierarchy like ASP.NET does, but this is outside the scope of this already full article.

C#
public Bar(string ID)
{
    System.IO.Stream s = this.GetType().Assembly.GetManifestResourceStream
        ("SilverlightOOControlsLibrary.Bar.xaml");
    string originalXaml = new System.IO.StreamReader(s).ReadToEnd();
    originalXaml = originalXaml.Replace("x:Name=\"",
        string.Format("x:Name=\"{0}_", ID));
    this.InitializeFromXaml(originalXaml);

    this.ID = ID;
}

private string _ID;

public string ID
{
    get { return _ID;}
    private set { _ID = value; }
} 

We've added an ID property to keep our control ID. Initializing the bar, we should also receive the height of the Bar. This is the end result:

C#
public class Bar : Control
{
    public Bar() : this("Bar", 150)
    {

    }

    public Bar(string ID, int BarHeight)
    {
        System.IO.Stream s = this.GetType().Assembly.GetManifestResourceStream
            ("SilverlightOOControlsLibrary.Bar.xaml");
        string originalXaml = new System.IO.StreamReader(s).ReadToEnd();
        originalXaml = originalXaml.Replace("x:Name=\"",
            string.Format("x:Name=\"{0}_", ID));
        this.InitializeFromXaml(originalXaml);

        this.ID = ID + "_";
        this.BarHeight = BarHeight;
    }

    private int _BarHeight;

    public int BarHeight
    {
        get { return _BarHeight; }
        private set { _BarHeight = value; }
    }

    private string _ID;
    public string ID

    get { return _ID;}
    private set { _ID = value; }
    }
} 

So we have a Silverlight 1.1 User control that gets some constructor data and can initialize an XAML file in a certain name hierarchy.

Step #2 JavaScript - Setting up the XAML Code-behind for Both Silverlight 1.0

We'll create a new Silverlight 1.0 JavaScript project from inside Expression Blend:

image

We'll copy our new Bar.Xaml file into two JavaScript Silverlight projects.

image

By now, you've noticed that we're developing both Silverlight 1.0 JavaScript controls in a parallel manner to Silverlight 1.1 C# User controls. It's important to see how easily we can write OO aware code in JavaScript as we do in C#. So the first step is to create a new JavaScript file called bar.xaml.js.

image

Now we need to create a new class named Bar.

image

Let's review this syntax before we go any further.

JavaScript
Bar = function()
{
}

The first three rows are equivalent to the class constructor in C#.

JavaScript
Bar.prototype =
{
} 

Inside these three rows is where we'll write our class members (properties, methods & events). We already know that the Bar object gets two parameters in it's constructor.

JavaScript
Bar = function(ID, BarHeight)
{
}

Bar.prototype =
{
}

The problem with JavaScript is that it's not a strongly-typed language and developers using this class could have no way of knowing what's the type of ID & Parent. So we'll use the VS2008 JavaScript comments syntax to add some Intellisense for this class.

JavaScript
Bar = function(ID, BarHeight)
{
/// <param name="ID" type="String" />
/// <param name="BarHeight" type="Number" integer="true" />
}

Bar.prototype =
{
} 

What we're basically saying here, "anybody that uses this constructor please know that - ID is of type String & BarHeight is an integer". Here's an example of the Intellisense we get: for this class constructor in VS2008:

image

Now let's store the parameters we got in the class constructor as private variables of the class instance.

JavaScript
Bar = function(ID, BarHeight)
{
    /// <param name="ID" type="String" />
    /// <param name="BarHeight" type="Number" intger="true" />
    this._ID = ID;
    this._barHeight = BarHeight;
}

Bar.prototype =
{
} 

Next we'll make sure that the internal variables have some getter properties. We're doing this for two reasons: First, we want to make sure developers using our class don't use our "internal" variables (because they can), Second and more important we don't have Intellisense for "this._XXX" variables as of VS2008 Beta2.

JavaScript
Bar = function(ID, BarHeight)
{
    /// <param name="ID" type="String" />
    /// <param name="BarHeight" type="Number" intger="true" />
    this._ID = ID;
    this._barHeight = BarHeight;
}

Bar.prototype =
{
    get_ID : function()
    {
        return this._ID;
    },

    get_barHeight : function()
    {
        return this._barHeight;
    }
} 

Additionally, we'll add <returns /> XML JavaScript comments to the getter properties.

JavaScript
Bar = function(ID, BarHeight)
{
    /// <param name="ID" type="String" />
    /// <param name="BarHeight" type="Number" intger="true" />
    this._ID = ID;
    this._barHeight = BarHeight;
}

Bar.prototype =
{
    get_ID : function()
    {
        /// <returns type="String" />
        return this._ID;
    },

    get_barHeight : function()
    {
        /// <returns type="Number" intger="true" />
        return this._barHeight;
    }
} 

We've added these comments so people using our class (as will we) know what's the return type for this functions.

image

image

At this point, I'd like to add Silverlight JavaScript Intellisense to our project. I'll do the five steps listed in the Silverlight 1.0 Full JavaScript Intellisense article.

JavaScript
/// <reference path="intellisense.js" />
Bar = function(ID, BarHeight)
{
    /// <param name="ID" type="String" />
    /// <param name="BarHeight" type="Number" intger="true" />
    this._ID = ID;
    this._barHeight = BarHeight;
}

Bar.prototype =
{
    get_ID : function()
    {
        /// <returns type="String" />
        return this._ID;
    },

    get_barHeight : function()
    {
        /// <returns type="Number" intger="true" />
        return this._barHeight;
    }
} 

I'll save us some time and tell us up front that we need to get two additional parameters in our constructor - Xlocation which will be the location of the Bar on the X axis (Canvas.Left) and Parent which is the parent Canvas for the Bar. We'll add those, with JavaScript XML comments, internal variables and getter properties.

JavaScript
/// <reference path="intellisense.js" />
Bar = function(ID, Parent, BarHeight, XLocation)
{
    /// <param name="ID" type="String" />
    /// <param name="Parent" type="Canvas"/>
    /// <param name="BarHeight" type="Number" integer="true" />
    /// <param name="XLocation" type="Number">Canvas.Left</param>
    this._ID = ID + "_";
    this._parent = Convert.ToCanvas(Parent);
    this._barHeight = BarHeight;
    this._XLocation = XLocation;
}

Bar.prototype =
{
    get_ID : function()
    {
        /// <returns type="String" />
        return this._ID;
    },

    get_parent : function()
    {
        /// <returns type="Canvas" />
        return this._parent;
    },

    get_barHeight : function()
    {
        /// <returns type="Number" integer="true" />
        return this._barHeight;
    },

    get_XLocation: function()
    {
        /// <returns type="Number" />
        return this._XLocation;
    }
} 

Note that Parent is of type Canvas. The definition for Canvas is part of the Silverlight JavaScript Intellisense. Now, if we need too, we'll get Intellisense for Canvas. (Currently we don't need it, but shortly we will)

image I'll also add one additional internal variable so we can use it as a "shortcut" later.

JavaScript
Bar = function(ID, Parent, BarHeight, XLocation)
{
    /// <param name="ID" type="String" />
    /// <param name="Parent" type="Canvas"/>
    /// <param name="BarHeight" type="Number" integer="true" />
    /// <param name="XLocation" type="Number">Canvas.Left</param>
    this._ID = ID + "_";
    this._parent = Convert.ToCanvas(Parent);
    this._barHeight = BarHeight;
    this._XLocation = XLocation;

    this._host = this._parent.element.getHost();
} 

Now after we've taken care of the constructor, the constructor parameters, the constructor parameters comments, the internal variables, the getter properties for the internal variables and the JavaScript XML comments for the getter properties, CAN WE PLEASE LOAD SOME XAML? We're going to use the Silverlight downloader object which only works in Async mode. That means that one function will start the XAML download, and one will have to get the result of the XAML download.

JavaScript
Bar = function(ID, Parent, BarHeight, XLocation)
{
    ...
    this.StartXamlDownload();
}

Bar.prototype =
{
    ...
    StartXamlDownload : function()
    {

    },

    XamlDownloadCompleted : function(sender, eventArgs)
    {

    }
} 

Let's create a new Downloader object.

image

image

image We'll send as Host the internal host variable we initialized earlier.

JavaScript
StartXamlDownload : function()
{
    var xamlDownloader = Downloader.createFromXaml(this._host);
}, 

Let's start a download request to download Bar.Xaml.

image

image

image Now we should also make sure that when the downloader Silverlight object is done downloading it'll call XamlDownloadCompleted function.

image

image

JavaScript
StartXamlDownload : function()
{
    var xamlDownloader = Downloader.createFromXaml(this._host);
    xamlDownloader.open("GET", "Bar.Xaml");
    xamlDownloader.add_Completed(this.XamlDownloadCompleted);
}, 

While this is the appropriate C# like syntax, it will have unexpected results in JavaScript. When the this.XamlDownloadCompleted function is called, it will initialize a new Bar object and use it's XamlDownloadCompleted method. That's why we should use the following syntax to assure that the current instance's XamlDownloadCompleted gets called.

JavaScript
StartXamlDownload : function()
{
    var xamlDownloader = Downloader.createFromXaml(this._host);
    xamlDownloader.open("GET", "Bar.Xaml");
    xamlDownloader.add_Completed(Silverlight.createDelegate
        (this, this.XamlDownloadCompleted));
}, 

Let's call the send method to make sure our download starts.

image

Ok, the XAML download process has started and let's say it's complete - let's write the XamlDonwloadCompleted function.

image

image

image

So the first thing we did is get type-specific and type-safe instances of our downloader. Additionally, as of VS2008 Beta2, we'll have to "re-type" the internal variable types to get Intellisense for them.

JavaScript
XamlDownloadCompleted : function(sender, eventArgs)
{
    var xamlDownloader = Convert.ToDownloader(sender);
    var _parent = Convert.ToCanvas(this._parent);
} 

Let's get the XAML text we just finished downloading.

image We'll make sure to change the XAML x:Name Control ID's so there won't be any conflicts with the existing Bar objects.

JavaScript
XamlDownloadCompleted : function(sender, eventArgs)
{
    var xamlDownloader = Convert.ToDownloader(sender);
    var _parent = Convert.ToCanvas(this._parent);
    var originalXaml = xamlDownloader.get_responseText();
    originalXaml = originalXaml.replace("Name=\"", "Name=\"" + this._ID);
} 

Let's initialize a Canvas control from the XAML we just got back from the server and add it to the parent XAML control.

JavaScript
XamlDownloadCompleted : function(sender, eventArgs)
{
    var xamlDownloader = Convert.ToDownloader(sender);
    var _parent = Convert.ToCanvas(this._parent);
    var originalXaml = xamlDownloader.get_responseText();
    originalXaml = originalXaml.replace("Name=\"", "Name=\"" + this._ID);
    var newElement = Convert.ToCanvas(this._host.content.createFromXaml(originalXaml));
} 

image

image

JavaScript
XamlDownloadCompleted : function(sender, eventArgs)
{
    var xamlDownloader = Convert.ToDownloader(sender);
    var _parent = Convert.ToCanvas(this._parent);
    var originalXaml = xamlDownloader.get_responseText();
    originalXaml = originalXaml.replace("Name=\"", "Name=\"" + this._ID);
    var newElement = Convert.ToCanvas(this._host.content.createFromXaml(originalXaml));
    _parent.get_children().add(newElement);
} 

So we have a Silverlight 1.0 JavaScript object that gets some constructor data and can initialize a XAML file in a certain name hierarchy.

Step #3 C#: Setting up Specific XAML Control References in Silverlight 1.1

Inside our Silverlight 1.1 user control we don't automatically get access to the XAML elements with x:Name as we do in certain other places. We need to create our own references to the XAML objects. We'll create those references only for those elements that we will need to set their properties based on the BarHeight property that belong to our Silverlight User Control.

C#
public Bar(string ID, int BarHeight)
{
    System.IO.Stream s = this.GetType().Assembly.GetManifestResourceStream
        ("SilverlightOOControlsLibrary.Bar.xaml");
    string originalXaml = new System.IO.StreamReader(s).ReadToEnd();
    originalXaml = originalXaml.Replace("Name=\"", string.Format("Name=\"{0}_", ID));
    FrameworkElement newElement = this.InitializeFromXaml(originalXaml);
    this.ID = ID this.BarHeight = BarHeight;

    SetControlReferences();
} 

These are the XAML elements we'll need with their types: (with corresponding C# fieldname to XAML x:Name)

C#
private Rectangle _bar;
private ScaleTransform _barscale;
private Rectangle _barB;
private ScaleTransform _barscaleB;
private Path _bubble;
private TextBlock _bubbleText; 

So let's get a reference to these objects after the Canvas has loaded.

C#
public Bar(string ID, int BarHeight)
{
    System.IO.Stream s = this.GetType().Assembly.GetManifestResourceStream
        ("SilverlightOOControlsLibrary.Bar.xaml");
    string originalXaml = new System.IO.StreamReader(s).ReadToEnd();
    originalXaml = originalXaml.Replace("Name=\"", string.Format("Name=\"{0}_", ID));
    FrameworkElement newElement = this.InitializeFromXaml(originalXaml);

    this.ID = ID;
    this.BarHeight = BarHeight;

    SetControlReferences();
}

private Rectangle _bar;
private ScaleTransform _barscale;
private Rectangle _barB;
private ScaleTransform _barscaleB;
private Path _bubble;
private TextBlock _bubbleText;
private Storyboard _showBubble;
private Storyboard _hideBubble;

private void SetControlReferences()
{
    _bar = FindNameByXamlID("bar") as Rectangle;
    _barscale = FindNameByXamlID("barscale") as ScaleTransform;
    _barB = FindNameByXamlID("barB") as Rectangle;
    _barscaleB = FindNameByXamlID("barscaleB") as ScaleTransform;
    _bubble = FindNameByXamlID("bubble") as Path;
    _bubbleText = FindNameByXamlID("bubbleText") as TextBlock;
    _showBubble = FindNameByXamlID("showBubble") as Storyboard;
    _hideBubble = FindNameByXamlID("hideBubble") as Storyboard;
}

private DependencyObject FindNameByXamlID(string nameInXamlFile)
{
    return this.FindName(GetIdFor(nameInXamlFile));
}

private string GetIdFor(string nameInXamlFile)
{
    return String.Format("{0}_{1}", this.ID, nameInXamlFile);
} 

There're two helper methods in this class. FindNameByXamlID is used for well... finding an element by XAML ID. GetIdFor returns the ID for an element inside the current control. Later on we'll use these references to change properties & events belonging to these XAML objects.

Step #3 BLOCKED SCRIPT - Setting up Specific XAML Control References in Silverlight 1.0 JavaScript

Same things we did for C# in the last paragraph, we're going to have to do for JavaScript.

JavaScript
XamlDownloadCompleted : function(sender, eventArgs)
{
    var xamlDownloader = Convert.ToDownloader(sender);
    var _parent = Convert.ToCanvas(this._parent);

    var originalXaml = xamlDownloader.get_responseText();
    originalXaml = originalXaml.replace("Name=\"", "Name=\"" + this._ID);
    var newElement = Convert.ToCanvas(this._host.content.createFromXaml(originalXaml));
    _parent.get_children().add(newElement);
    this._setControlReferences();
} 

We need to add the declaration for internal variables in the Bar class constructor.

JavaScript
Bar = function(ID, Parent, BarHeight, XLocation)
{
    ...
    this._bar = Convert.ToRectangle(null);
    this._barscale = Convert.ToScaleTransform(null);
    this._barB = Convert.ToRectangle(null);
    this._barscaleB = Convert.ToScaleTransform(null);
    this._bubble = Convert.ToPath(null);
    this._bubbleText = Convert.ToTextBlock(null);

    this._showBubble = Convert.ToStoryboard(null);
    this._hideBubble = Convert.ToStoryboard(null);

    ...
}

And let's add the necessary findNameByXamlID and getIdFor functions.

JavaScript
_findNameByXamlID : function(nameInXamlFile)
{
    /// <param name="nameInXamlFile" type="String" />
    /// <returns type="DependencyObject" />
    returnthis._parent.findName(this._getIdFor(nameInXamlFile));
},

_getIdFor : function(nameInXamlFile)
{
    /// <param name="nameInXamlFile" type="String" />
    return this._ID + nameInXamlFile;
} 

And finally we'll add write the code for setControlReferences.

JavaScript
_setControlReferences : function()
{
    this._bar = Convert.ToRectangle(this._findNameByXamlID("bar"));
    this._barscale = Convert.ToScaleTransform(this._findNameByXamlID("barscale"));
    this._barB = Convert.ToRectangle(this._findNameByXamlID("barB"));
    this._barscaleB = Convert.ToScaleTransform(this._findNameByXamlID("barscaleB"));
    this._bubble = Convert.ToPath(this._findNameByXamlID("bubble"));
    this._bubbleText = Convert.ToTextBlock(this._findNameByXamlID("bubbleText"));
    this._showBubble = Convert.ToStoryboard(this._findNameByXamlID("showBubble"));
    this._hideBubble = Convert.ToStoryboard(this._findNameByXamlID("hideBubble"));
}, 

Step #4 C#: Using Specific Class XAML Controls in Silverlight 1.1

Now, after we've got references to the XAML elements that will be changed based on the Bar object BarHeight we'll change the properties according to the calculations previously written in the original JavaScript file.

C#
public Bar(string ID, int BarHeight)
{
    ...
    this.ID = ID + "_";
    this.BarHeight = BarHeight;
    SetControlReferences();
    SetXamlControlsPropertiesBasedOnClassProperties();
}

private void SetXamlControlsPropertiesBasedOnClassProperties()
{

} 

This was the original dynamic XAML in JavaScript for the XAML bar element.

C#
// Draw the bar
var bar = control.content.createFromXaml(
    '<Rectangle Name="' + id('barB',i) + '" Canvas.Left="' + (x-35) + '" Width="30"'
    + ' Canvas.Top="' + (304 - values[i-1]) + '" Height="' + (values[i-1] + 5) + '"'
    + ' RadiusX="2" RadiusY="2">'
    + ' <Rectangle.Fill>'
    + ' <SolidColorBrush Name="' + id('brushB',i) + '" Color="#7F004296" />'
    + ' </Rectangle.Fill>'
    + ' <Rectangle.RenderTransform>'
    + ' <ScaleTransform Name="' + id('barscaleB',i) + '" CenterY="' + values[i-1] +
        '" ScaleY="0.0" />'
    + ' </Rectangle.RenderTransform>'
    + '</Rectangle>'
    );
_sl.children.add(bar); 

We'll take this same logic and write it in C# code.

C#
private void SetXamlControlsPropertiesBasedOnClassProperties()
{
    _barB.SetValue(Canvas.TopProperty, 304 - this.BarHeight);
    _barB.Height = this.BarHeight + 5;
    _barscaleB.CenterY = this.BarHeight;
}

Please note that any calculations with x variable aren't needed because we'll just move the whole Bar Canvas once(you'll see this). Let's place all XAML GUI logic in our method.

C#
private void SetXamlControlsPropertiesBasedOnClassProperties()
{
    _barB.SetValue(Canvas.TopProperty, 304 - this.BarHeight);
    _barB.Height = this.BarHeight + 5;
    _barscaleB.CenterY = this.BarHeight;
    _bar.SetValue(Canvas.TopProperty, 300 - this.BarHeight);
    _bar.Height = this.BarHeight + 5;
    _barscale.CenterY = this.BarHeight;
    _bubble.SetValue(Canvas.TopProperty, 260 - this.BarHeight);
    _bubbleText.Text = this.BarHeight.ToString();
    _bubbleText.SetValue(Canvas.TopProperty, 273 - this.BarHeight + 4);
} 

But we're not done here, by just setting instance properties. We also need to handle the MouseOver and MouseLeaveevents in the XAML Bar object. Let's see how this looked like the original JavaScript:

JavaScript
var bar = control.content.createFromXaml(
    '<Rectangle Name="' + id('bar',i) + '" Canvas.Left="' + (x-36) + '" Width="30"'
    + ' Canvas.Top="' + (300 - values[i-1]) + '" Height="' + (values[i-1] + 5) + '"'
    + ' StrokeThickness="1" RadiusX="2" RadiusY="2"'
    + ' MouseEnter="mouseenter" MouseLeave="mouseleave" Loaded="loadbar">' 

So we'll create our own C# methods that will register as EventHandlers to these events. Here's how the event registration looks like:

C#
private void SetXamlControlsPropertiesBasedOnClassProperties()
{
    ...
    _bar.MouseEnter += new MouseEventHandler(_bar_MouseEnter);
}

void _bar_MouseEnter(object sender, MouseEventArgs e)
{
    // do something
} 

Inside our EventHandler we'll make sure to run the appropriate Storyboard.

C#
private void SetXamlControlsPropertiesBasedOnClassProperties()
{
    _barB.SetValue(Canvas.TopProperty, 304 - this.BarHeight);
    _barB.Height = this.BarHeight + 5;
    _barscaleB.CenterY = this.BarHeight;
    _bar.SetValue(Canvas.TopProperty, 300 - this.BarHeight);
    _bar.Height = this.BarHeight + 5;
    _barscale.CenterY = this.BarHeight;
    _bubble.SetValue(Canvas.TopProperty, 260 - this.BarHeight);
    _bubbleText.Text = this.BarHeight.ToString();
    _bubbleText.SetValue(Canvas.TopProperty, 273 - this.BarHeight + 4);
    _bar.MouseEnter += new MouseEventHandler(_bar_MouseEnter);
    _bar.MouseLeave += new EventHandler(_bar_MouseLeave);
}

private void _bar_MouseEnter(object sender, MouseEventArgs e)
{
    _showBubble.Begin();
}

private void _bar_MouseLeave(object sender, EventArgs e)
{
    _hideBubble.Begin();
} 

Step #4 JavaScript- Using Specific Class XAML Controls in Silverlight 1.0

Well, it's pretty much the same thing as we did in Silverlight 1.1. But there're couple of differences. First, If we try to change internal JavaScript variables defined in the JavaScript class constructor as of VS2008 Beta2 we still don't have any Intellisense for them in the class body. Inside the function that uses the Convert Method we still have Intellisense.

image

image

But outside of it...

image Hopefully, We'll get Intellisense support for the this keyword in VS2008 RTM, but right now I prefer having good Intellisense over software engineering best practices. So I'll write all the property assignments in the same function, please feel free to NOT follow my bad example.

JavaScript
_setControlReferences : function()
{
    ...

    var CanvasTop = new DependencyProperty("Canvas.Top");
    this._barB.setValue(CanvasTop, 304 - this.get_barHeight());
    this._barB.set_height(this.get_barHeight() + 5);
    this._barscaleB.set_centerY(this.get_barHeight());
    this._bar.setValue(CanvasTop, 300 - this.get_barHeight());
    this._bar.set_height(this.get_barHeight() + 5);
    this._barscale.set_centerY(this.get_barHeight());
    this._bubble.setValue(CanvasTop, 260 - this.get_barHeight());
    this._bubbleText.set_text(this.get_barHeight().toString());
    this._bubbleText.setValue(CanvasTop, 273 - this.get_barHeight() + 4);
}, 

Here's an example of where we need Intellisense and we'll use it:

image

image

Just imagine writing this scary piece of code without any Intellisense... Now we should also handle theMouseEnter and MouseOver events that cause the Bar to change it's color and show/hide the bubble Text.

image

image

image

image

image

image

JavaScript
_setControlReferences : function()
{
    ...
    this._bar.add_MouseEnter(Silverlight.createDelegate(this
    ,this._bar_MouseEnter));
    this._bar.add_MouseLeave(Silverlight.createDelegate(this
    ,this._bar_MouseLeave));
},

_bar_MouseEnter : function(sender, eventArgs)
{
    this._showBubble.begin();
},

_bar_MouseLeave : function(sender, eventArgs)
{
    this._hideBubble.begin();
}, 

Step #5 JavaScript- Using our JavaScript Control & Deployment in Silverlight 1.0

All through this article we've used a "C# - JavaScript" ping-pong format, I'd like to apologize for not giving C# a change to serve the next round first. C# takes a bit more refactoring work and has a more extensive deployment process so we'll first do the JavaScript and then C#. One thing we didn't do up until now was to refer how the Bars are placed on the form.

image

The question we're asking is - How and who places each separate Bar control in the proper X axis location? In the JavaScript case, only the JavaScript control should be able to change the location of the actual Canvas.Left on the internal Canvas XAML element. So inside the _setControlReferences function we'll change Canvas.Left on the Page Canvas in the internal control.

XML
/// Bar.Xaml
<Canvas
    xmlns="http://schemas.microsoft.com/client/2007"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    x:Name="Page"
    Background="Transparent"
    Width="48" Height="300">
    ....
</Canvas> 

And in our Bar.Xaml.js we'll change the Page Canvas.Top.

image

image

image

image

image

Finally we get:

JavaScript
Bar = function(ID, Parent, BarHeight, XLocation)
{
    /// <param name="XLocation" type="Number">Canvas.Left</param>
    this._XLocation = XLocation;
    ...
    this._page = Convert.ToCanvas(null);
    ...
}

Bar.prototype = {
    _setControlReferences : function()
    {
        ...

        var CanvasLeft = Convert.ToDependencyProperty("Canvas.Left");
        this._page = Convert.ToCanvas(this._findNameByXamlID("Page"));
        this._page.setValue(CanvasLeft, this._XLocation);
    },
} 

Now let's take care of who initializes our JavaScript control and how it's done. This is how our normal non-Silverlight HTML page looks like:

image

Our canvas is the blank area in the middle.

XML
/// Page.Xaml
<Canvas
    xmlns="http://schemas.microsoft.com/client/2007"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    x:Name="Scene"
    Width="800" Height="300">
</Canvas> 

This is also the same XAML file initialized by the CreateSilverlight function call.

image

So let's have a look at the default code behind for Page.Xaml.js that we got when we created the project.

JavaScript
if (!window.SilverlightOOControlsJavascript)
window.SilverlightOOControlsJavascript = {};

SilverlightOOControlsJavascript.Page = function()
{
}

SilverlightOOControlsJavascript.Page.prototype =
{
    handleLoad: function(control, userContext, rootElement)
    {
        this.control = control;

        // Sample event hookup:
        rootElement.addEventListener("MouseLeftButtonDown",
            Silverlight.createDelegate(this, this.handleMouseDown));
    },

    // Sample event handler
    handleMouseDown: function(sender, eventArgs)
    {
        // The following line of code shows how to find an element by name
        // and call a method on it.
        // this.control.content.findName("Timeline1").Begin();
    }
} 

As I said, this is the default code we get for Page.Xaml.js. What it does is create a class which is initialized by the CreateSilverlight function and registers to it's own MouseLeftButtonDown event. Let's delete all the code we don't need from the example code.

JavaScript
if (!window.SilverlightOOControlsJavascript)
window.SilverlightOOControlsJavascript = {};

SilverlightOOControlsJavascript.Page = function()
{
}

SilverlightOOControlsJavascript.Page.prototype =
{
    handleLoad: function(control, userContext, rootElement)
    {
        this.control = control;
    }
} 

Let's add a JavaScript reference to our Bar.Xaml.js file so we get Intellisense for it.

image

Now we need to initialize 20 Bars with various heights. We'll use random heights of anywhere between 0 and 270.

JavaScript
handleLoad: function(control, userContext, rootElement)
{
    this.control = control;

    for(i = 0; i <= 20; i++)
    {
        var curBarHeight = Math.round(270*Math.random());
    }
} 

Let's initialize the actual Bar JavaScript object.

image

image

JavaScript
handleLoad: function(control, userContext, rootElement)
{
    this.control = control;

    for(i = 0; i <= 20; i++)
    {
        var curBarHeight = Math.round(270*Math.random());
        var newBar = new Bar("bar" + i, rootElement, curBarHeight, 40 * i);
    }
} 

One last thing we have to do is add a <script> tag reference to our Bar.Xaml.js file to our HTML page.

image

image

image

Let's run this in our browser and see the final result of all our JavaScript efforts:

image

And we're done with JavaScript. Everything works! Let's review our architecture, file structure and what we actually did over here.

Step 1) Wrote the basic no-data-no-substance XAML file. All the XAML file does is give us Design time support.

image

Step 2) Created a new JavaScript Silverlight 1.0 project in Expression Blend (this should have actually happened in step 1). More importantly, we created a myXamlFileName.Xaml.js file for each XAML file we have with a basic constructor, internal fields, public properties and a method or two.

image

Step 3) We added references to internal Silverlight XAML objects inside our myXamlFileName.Xaml file.

image

Step 4) We used the references from the previous step to change various GUI attributes of our GUI elements to suit our business logic.

image

Step 5) Created the code that initializes our myXamlFileName JavaScript control.

image

So we got an Object-oriented modal where no XAML file is being manipulated by more then one JavaScript file which encapsulates all of it's behavior and business logic. Let's review our file structure:

image

  • Default.html - Default start page for the project. Created when opening a new project. We added a reference to Bar.Xaml.js to it.
  • Default_html.js - Contains the CreateSilverlight function which initialized a new Page JavaScript object. Created with Default.html when we opened the new project.
  • Page.xaml - Default XAML canvas has only a black background. Also created when we opened a new project.
  • Page.XAML.js - JavaScript object that uses the Page.XAML file. Also created when we opened a new project. This is the file that initializes a new Bar JavaScript object.
  • Bar.Xaml - XAML containing our Bar object and all it's animations. However, it doesn't know what's the final height of the Bar is or where it'll be placed.
  • Bar.Xaml.js -JavaScript object for the Bar.Xaml file.This is our "heavy-lifter" that does all the hard business logic work.This class and only this class changes the Bar.XAMLGUI display properties.

Step #5 C#: Using our JavaScript Control & Deployment in Silverlight 1.1

Let's have a look at the current project file structure:

image

We've got two projects: Silverlight Project and Silverlight Class Library. Up until now we've only dealt with the Silverlight Class Library which contains Bar.Xaml and Bar.Xaml.js.

Similar to the JavaScript project Page.Xaml is a blank file that only has a black background. So let's have a look at Page.Xaml.cs.

C#
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using SilverlightOOControlsLibrary;

namespace SilverlightOOControlsCSharp
{
    public partial class Page : Canvas
    {
        public void Page_Loaded(object o, EventArgs e)
        {
            // Required to initialize variables
            InitializeComponent();
        }
    }
} 

The Page class is the one that's going to initialize our Bar Objects. Similar to the JavaScript code, we'll create 20 Bars with Random height.

C#
public partial class Page : Canvas
{
    public void Page_Loaded(object o, EventArgs e)
    {
        // Required to initialize variables
        InitializeComponent();

        LoadBars();
    }

    private void LoadBars()
    {
        Random rnd = new Random();
        for (int i = 0; i < 21; i++)
        {
            int curBarHeight = rnd.Next(0, 270);
            // Create Bar
        }
    }
} 

So, how do we initialize a Bar object? Let's try this syntax:

C#
private void LoadBars()
{
    Random rnd = new Random();
    for (int i = 0; i < 21; i++)
    {
        int curBarHeight = rnd.Next(0, 270);
        Control newBar = new Bar("bar" + i, curBarHeight, this);
        newBar.SetValue(Canvas.LeftProperty, i*40);
    }
} 

Let's have a look inside the Bar constructor:

C#
public Bar(string ID, int BarHeight, Canvas parent)
{
    System.IO.Stream s = this.GetType().Assembly.GetManifestResourceStream
        ("SilverlightOOControlsLibrary.Bar.xaml");
    string originalXaml = new System.IO.StreamReader(s).ReadToEnd();
    originalXaml = originalXaml.Replace("x:Name=\"", 
        string.Format("x:Name=\"{0}_", ID));
    newElement = this.InitializeFromXaml(originalXaml);
    this.ID = ID ;
    this.BarHeight = BarHeight;
    SetControlReferences();
    SetXamlControlsPropertiesBasedOnClassProperties();
} 

Inside the constructor we initialize the current Bar XAML code into a FrameworkElement class.

So, if we'll do the same in JavaScript in C# we'll add the Bar control to it's parent in the constructor. Let's try this and run the sample.

image

We got a catastrophic failure from inside our constructor that points to the line which adds the Bar to the Page. A first chance exception of type System.Exception occurred in agclr.dll. Additional information: Catastrophic failure (Exception from HRESULT: 0x8000FFFF (E_UNEXPECTED))

Silverlight 1.1 throwing catastrophic failure is almost always a problem with our Animation. Maybe we wrote to a TargetName that doesn't exist, or we left the TargetName empty, or we did something else that's slightly wrong and caused this error.

In our case it's caused because Adding we're not allowed to start animations from inside the constructor. So we have to move it somewhere else. Let's see another problem with this constructor, this time with Expression Blend. We do have support for designing our Bar.Xaml file.

image

Let's say we'd like our control to be added to our Page.Xaml from inside Expression Blend.

image

We'll click the arrow on the left bottom area of our screen and we get this screen.

image

Let's choose custom controls.

image

And finally let's add a Bar object to our Page just to see what we get.

XML
<Canvas
    xmlns="http://schemas.microsoft.com/client/2007"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:SilverlightOOControlsLibrary="clr-namespace:SilverlightOOControlsLibrary;
            assembly=ClientBin/SilverlightOOControlsLibrary.dll"
    x:Name="parentCanvas"
    Loaded="Page_Loaded"
    x:Class="SilverlightOOControlsCSharp.Page;
            assembly=ClientBin/SilverlightOOControlsCSharp.dll"
    Width="800"
    Height="300"
>
<SilverlightOOControlsLibrary:Bar Width="24" Height="24" Canvas.Left="48"
        Canvas.Top="88"/>
</Canvas> 

This syntax of initializing a Silverlight User control from pure XAML code uses the normal empty default constructor.

C#
public class Bar : Control
{
    public Bar()
    {
    }

    private FrameworkElement newElement;
    public Bar(string ID, int BarHeight, Canvas parent)
    {
        System.IO.Stream s = this.GetType().Assembly.GetManifestResourceStream
            ("SilverlightOOControlsLibrary.Bar.xaml");
        string originalXaml = new System.IO.StreamReader(s).ReadToEnd();
        originalXaml = originalXaml.Replace("x:Name=\"",
            string.Format("x:Name=\"{0}_", ID));
        newElement = this.InitializeFromXaml(originalXaml);

        this.ID = ID;
        this.BarHeight = BarHeight;

        SetControlReferences();
        SetXamlControlsPropertiesBasedOnClassProperties();
    } 

So Silverlight by-passes our non-default constructor and never initializes the XAML code. Additionally, even if we add constructor redirect in our class, we still won't have the right ID parameter and that's crucial to initializing the XAML. So we won't use the non-default constructor at all.

image

Let's use a different initializing modal. We'll use the default empty constructor and assume that whoever is using our control (either the Silverlight engine parsing XAML or the developer of another class) will use the properties to give us our data. We'll use the Loaded event to get notified when the control has been filled with data and has been added to the visual control tree on the Silverlight Page.

C#
public class Bar : Control
{
    public Bar()
    {
        this.Loaded += new EventHandler(Bar_Loaded);
    }

    private FrameworkElement newElement;
    void Bar_Loaded(object sender, EventArgs e)
    {
        System.IO.Stream s = this.GetType().Assembly.GetManifestResourceStream
            ("SilverlightOOControlsLibrary.Bar.xaml");
        string originalXaml = new System.IO.StreamReader(s).ReadToEnd();
        originalXaml = originalXaml.Replace("Name=\"", string.Format("Name=\"{0}_", ID));
        newElement = this.InitializeFromXaml(originalXaml);

        SetControlReferences();
        SetXamlControlsPropertiesBasedOnClassProperties();
} 

At this point, we're all set. Let's initialize our control from the Page class.

C#
private void LoadBars()
{
    Random rnd = new Random();
    for (int i = 0; i < 21; i++)
    {
        int curBarHeight = rnd.Next(0, 270);

        Bar newBar = (Bar)XamlReader.Load("<SilverlightOOControlsLibrary:Bar xmlns:
        SilverlightOOControlsLibrary=\"clr-namespace:SilverlightOOControlsLibrary;
        assembly=ClientBin/SilverlightOOControlsLibrary.dll\" ID=\"bar\"
        BarHeight=\"150\" />", true);

        newBar.SetValue(Canvas.LeftProperty, i*40);
        this.Children.Add(newBar);
    }
} 

Our first option is to write XAML code, load it using the XamlReader.Load method and we get back a new Bar control. Please note that whenever using the XamlReader.Load statement, we have to declare any namespace used in it (even the "x" namespace). Another option we've got is just initializing the class:

C#
private void LoadBars()
{
    Random rnd = new Random();
    for (int i = 0; i < 21; i++)
    {
        int curBarHeight = rnd.Next(0, 270);

        Bar newBar = new Bar();
        newBar.ID = "bar" + i;
        newBar.BarHeight = curBarHeight;
        newBar.SetValue(Canvas.LeftProperty, i*40);
        this.Children.Add(newBar);
    }
} 

Personally, I prefer the second option as it's strongly typed and much more flexible to changes, but it's up to you. Both will have similar results. Now it's time to deploy our Silverlight App. We'll create a new Website to deploy the web project in.

image

We'll use the same Default.html and Default_html.js we've used in the JavaScript project to initialize our Page.Xaml file. Now it's time to add our Silverlight project into our Website project (which BTW only has HTML files). We'll right-click on the project node and choose "Add Silverlight Link".

image

We'll tell it to use the Silverlight project.

image

Visual Studio as part of it's build process will make sure to copy all the right files into our web project and order the build process. Now, Let's run our application.

image

And everything runs as expected. The architecture of the C# solution is very similar to the JavaScript solution. Only difference is we've got a third project that's used primarily to run the Silverlight application in the context of a website.

Summary

I hope you've enjoyed this article and maybe learned something new. The point I was trying to get across all through this article isn't technique, it's a state of mind. Let's not repeat code, let's not dynamically create XAML, let's use the best of what Object Oriented has to offer us. All code we've developed here today is available for download here.

History

  • December 23, 2007: Published on The Code Project
  • August 14, 2007: Published on CodePlex

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)


Written By
JustinAngel.Net, Senior .Net consultant
Israel Israel
Justin-Josef Angel is a C# Microsoft Most Valuable professional, a Senior .Net consultant in Israel with 4 years of .Net experience and has 8 years of Web experience.

Justin's been working this past year on two Enterprise sized Silverlight projects with his customers. During that time he's gained a real-insight into Silverlight's inner workings and how to integrate Silverlight into the real world of software development. Additionally, During that time he's developed a few well-known projects like the "Silverlight 1.0 Javascript Intellisense", "Silverlight 1.1 Hebrew & Arabic Languages support" and a few others you might know.

Justin is also a seasoned presenter with an impressive track-record of talking in front of thousands of people in Israel.

Justin owns the first .Net blog written in Hebrew - http://www.JustinAngel.Net .
And he also owns an additional blog with mixed Hebrew & English content - http://blogs.Microsoft.co.il/blogs/JustinAngel.

A full list of his articles (all 100+ of them) can be found at: http://www.JustinAngel.Net/#index




Comments and Discussions

 
GeneralVery good Pin
Portatofe2-Oct-08 8:40
Portatofe2-Oct-08 8:40 
GeneralFree Silverlight Chart Control Pin
beowulf14-May-08 8:22
beowulf14-May-08 8:22 
GeneralRe: Free Silverlight Chart Control Pin
Dewey19-Jan-09 22:01
Dewey19-Jan-09 22:01 
JokeRe: Free Silverlight Chart Control Pin
Guidii11-Aug-09 4:46
Guidii11-Aug-09 4:46 
GeneralRe: Free Silverlight Chart Control Pin
Bill SerGio, The Infomercial King14-May-10 15:20
Bill SerGio, The Infomercial King14-May-10 15:20 
Generalnice article Pin
MikeBeard14-Mar-08 9:24
MikeBeard14-Mar-08 9:24 
Generalexcellent article - one q though Pin
ashhorner15-Jan-08 8:27
ashhorner15-Jan-08 8:27 
GeneralRe: excellent article - one q though Pin
Dewey19-Jan-09 22:03
Dewey19-Jan-09 22:03 
GeneralGreat Article Pin
defwebserver7-Jan-08 5:03
defwebserver7-Jan-08 5:03 
I have examples using this technique at: http://www.adefwebserver.com/DotNetNukeHELP/Misc/Silverlight/[^]
GeneralExcellent Pin
Paul Conrad29-Dec-07 10:08
professionalPaul Conrad29-Dec-07 10:08 
GeneralExcellent ! Pin
roximoc24-Dec-07 2:30
roximoc24-Dec-07 2:30 
GeneralVery nice article Pin
Sacha Barber23-Dec-07 22:49
Sacha Barber23-Dec-07 22:49 
General:) Pin
Michael Sync23-Dec-07 16:04
Michael Sync23-Dec-07 16:04 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.