DCSIMG
Silverlight Controls - The path to reusable XAML - Justin myJustin = new Justin( Expriences.Current );

Silverlight Controls - The path to reusable XAML

Silverlight is like the wild wild west.
Everyone code however they want, 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 shiny snazzy samples that we forgot 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.

http://en.wikipedia.org/wiki/Don%27t_repeat_yourself

 

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).

http://en.wikipedia.org/wiki/God_object

 

image
(http://www.antipatterns.com/briefing/sld024.htm)

 

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 there're 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 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.
http://joestegman.members.winisp.net/Jelly/Bar.htm

image

This is a very stylish graph built in Silverlight.
You can see my mouse pointer hovering 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 that Richard knows how to make awesome graphs), this is not a static image!
This actually loads data and rebuild the Xaml code!

Let's see how it's done:

    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 the 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, 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 testsing support. This is literally a sure fired way of getting into apostrophe maintenence 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 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 a Silverlight 1.1 as it's running

Let's see some code:

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>";

                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 so hard-coded Xaml with Tokens $toekn$ in it, and for each RSS post it process the template once, then it load 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 are template client centric their Pros & Cons are the same.

 

Third way of creating reusable Silverlight Object:
        Setting the Source Property of our Silverlight Control to a server side .Net Page / HttpHandler that renders a 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

//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 or software evolution.  

   - Can't load the Xaml code into Designer Blend.

 

Fourth way of creating reusable Silverlight Object:
      Render XAML code on the 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 us 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:Delcare> 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's "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 throught 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 truely ingenious people who had to do SOMETHING and it works.  

 

 

fifth way of creating 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 & 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 to download from - http://www.justinangel.net/files/SilverlightObjectOriented.zip.

image

Here's what i see - there's one object that repeat itself in the sample (use View source in 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

The main Silverlight canvas is left. It's got a snazzy headline, refresh button and a special canvas in the middle we can initialize our Bar objects into it.

 

Step #1 - XAML : Creating the XAML file

Let's refactor the Bar object to a 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:

    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 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 pixel.

 

Let's Refactor this Javascript into normal XAML:

            // 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:

    <!--  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.

                '<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 al 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 is 150 pixel height so "Canvas.Top" is 154 pixel (304 - 150 = 154).
Same goes for Height that will become 155 pixel (150 + 5).

              + '   <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:

        <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:

            ////////////////

            // 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:

    <!-- 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're four elements on the page (barB, bar, bubble & 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 them, let's try and run one of our timeline and see what we get...
image

Now we can 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 Siverlight 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 look at bar.xaml.cs:

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's no ID collisions.

        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:

        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.

    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 hierachy like ASP.Net does, but this is outside the scope of this already full article.

        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. And this is the end result:

    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 a XAML file in a certain name hierarchy. 

 

Step #2 BLOCKED SCRIPT 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 project.

image

By now you've noticed that we're developing both Silverlight 1.0 Javascript controls in a parallel mannger to Silverlight 1.1 C# User controls.

It's impotent 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.

Bar = function()

{

}

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

Bar.prototype =

{

}

Inside these three rows is where we'll write our Class Members (properties, methods & events).

We allready know that the Bar object gets two parameters in it's constructor.

Bar = function(ID, BarHeight)

{

}

 

Bar.prototype =

{

}

Problem with Javascript is that'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.

Bar = function(ID, BarHeight)

{

    /// <param name="ID" type="String" />

    /// <param name="BarHeight" type="Number" intger="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.

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.

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.

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 this 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.

/// <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 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.

/// <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.

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.

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.

    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

    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.

    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 things 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 variables types to get Intellisense for them.

    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 existing Bar objects.

    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 initilize a Canvas control from the XAML we just got back from the server and add it to the parent XAML control.

    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

 

    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 belongs to our Silverlight User Control.

        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 corrspending C# field name to XAML x:Name)

        private Rectangle _bar;

        private ScaleTransform _barscale;

        private Rectangle _barB;

        private ScaleTransform _barscaleB;

        private Path _bubble;

        private TextBlock _bubbleText;

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

        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.

    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.

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 necessary findNameByXamlID & getIdFor functions.

    _findNameByXamlID : function(nameInXamlFile)

    {

        /// <param name="nameInXamlFile" type="String" />

        /// <returns type="DependencyObject" />

        return this._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.

    _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.

        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.

            // 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.

        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)

Let's place all XAML GUI logic in our method.

        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 & MouseLeave events in the Xaml "Bar" object.

Let's see how this looked like the original BLOCKED SCRIPT

            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:

        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.

        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 BLOCKED SCRIPT 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.

    _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 the MouseEnter & MouseOver events that cause the Bar to change it's color and show/hide the bubble Text.

image

image

image

image

image

image

    _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 BLOCKED SCRIPT 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 the Canvas.Left on the Page Canvas in the internal control.

/// 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:

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.

/// 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 we got when we created the project.

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 is 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.

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.

    handleLoad: function(control, userContext, rootElement)

    {

        this.control = control;

 

        for(i = 0; i <= 20; i++)

        {

            var curBarHeight = Math.round(270*Math.random());

 

        }

    }

Let's initlize the actual Bar javascript object.

image

image

 

    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 impotently, We create 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 refernces 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 were 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 the 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. But doesn't know what's the final height of the Bar 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.XAML GUI 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.

Similarly 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.

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. Similarly to the Javascript code, we'll create 20 Bars with Random height.

    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.

        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.

        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 a 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.

<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.

    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.
Addtionally, even if we add constructor redirect in our class, we still won't have the right ID parameter and that's cruciel 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's who ever 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.

    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.

        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 when ever 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:

        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's 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 on - http://www.justinangel.net/files/SilverlightObjectOriented.zip.

 

 

Justin-Josef Angel

Senior .Net Consultant, Microsoft C# MVP

Published Tuesday, August 14, 2007 7:51 PM by Justin-Josef Angel [MVP]

Comments

No Comments