Click here to Skip to main content
Click here to Skip to main content

Silverlight Animations in a Practical Business Application

By , 29 Dec 2007
Rate this:
Please Sign up or sign in to vote.

Drink Mate Standard Imprint Image Viewer Screenshot

Contents

Introduction

This article shows the detailed steps required to build a practical business application using Silverlight animations. It illustrates how business related considerations and user interface design overlap with Silverlight coding decisions.

From a technical point of view, this article explains:

  • How to include Silverlight content in an HTML page
  • How to include multiple Silverlight controls in an HTML page
  • How to create custom buttons for your application
  • How to convert vector graphics to XAML
  • How to use the Silverlight Downloader
  • How to improve the performance of your application by using multiple Zip files with the Downloader
  • How to link event handlers to your Silverlight objects
  • How to use event handlers to move XAML graphics
  • How to use event handlers to colorize XAML graphics
  • How to use event handlers to display or hide HTML content
  • How to use event handlers to display or hide XAML content
  • How to create animations using Visual Studio 2008
  • How to create animations using Expression Blend
  • How to create left and right shift animations
  • How to create fade-out and fade-in animations
  • How to build an animated introduction to your application
  • How to allow a user to cancel an animation
  • How to use custom fonts in your Silverlight application
  • How to bind a slider to the FontSize property of a TextBlock
  • How to reposition TextBlocks by dragging
  • How to auto-center a TextBlock on another TextBlock
  • How to create tool tips that can be dynamically added and removed from your XAML content
  • How to make sense of Microsoft's ever changing default file names
  • How to manage interaction between HTML and your Silverlight objects
  • How to manage interaction between Silverlight objects in different Silverlight controls
  • How to prepare for your own first Silverlight project

Cautionary note: A friend of mine just pointed out to me that perhaps my title could be interpreted as suggesting that this article relates to Silverlight animations in Enterprise business applications (WCF, Web Services, etc.). Unfortunately, it does not. There are both large businesses and small businesses. Drink Mate is admittedly a small business (actually just tiny or any other equally diminutive adjective). It is, however, a real business and I am proud to report that we have sold over 3 million Drink Mates since 1990. As such, I think that the title is accurate provided that it is interpreted as I meant it. The list in this section includes pretty much everything which is covered in this article.

Background

As preparation for my upcoming Silverlight class at Foothill College (Los Altos Hills, CA), I decided to build a small demo application. Wishing to work on something practical as well as educational, I decided to choose an application which could benefit my ASP.NET 2.0 website for my Drink Mate business. My goal was to build some Silverlight animations which overlaid a collection of 2D graphics on a set of .jpg images of our Drink Mate product.

Drink Mates are a little plastic clip which you can use at a cocktail party when you have a plate full of food in one hand and a drink in the other. It is designed to solve the problem of trying to eat, drink, shake hands, sign an autograph, etc., if you would otherwise have both of your hands full.

At this point I strongly recommend that you pause briefly to try out the application itself. It starts out with a short introduction which should help you make much more sense out of this article. Also, if you actually see how the application works, my textual references will be much easier to associate with any particular feature or aspect of the application.

If you already have Silverlight installed, reviewing my application will take only a couple minutes to preview all of the functionality that will be discussed in this article. Since Silverlight is web based, if you already have the browser plug-in, there is nothing further to download, unzip, and install.

If you do not already have Silverlight installed, with all due respect, you are probably wasting your time reading further without trying out my application -- or some other equivalent sample application. Silverlight (and its cousin WPF) are not simple technologies, and it is very important to get some type of visual frame of reference with which to associate some of its esoteric terminology.

As is explained in the introduction to my application, most Drink Mates are sold with a custom advertising imprint. These are generally company logos or some personalized text.

However, for certain generic occasions (e.g., Christmas or New Years parties), we have some standard images (clip art) which we can use if the customer doesn't have any custom artwork but still would prefer a decorated Drink Mate to a plain one. The purpose of this Standard Imprint Viewer is to allow potential customers to preview our collection of standard imprints using any combination of product and imprint colors.

Of course, much of the preparation to build this Silverlight application had very little to do with programming. First, I had to fly around the world to get some decent photos to put into my header image. I even had to convince my nephew to get married so that he could pose for my header collage. Then I had to spend many hours on product photography to get a collection of images to use as the backdrop for my 2D graphic standard imprints. Fortunately, I have been working on this for many years and almost all of this preliminary effort had been completed long before I began on building my Silverlight application.

Despite having extensive experience with WPF, when I began this project, I was entirely new to Silverlight. I began by reading the entire Adam Nathan book, Silverlight 1.0 Unleashed.

I also read much of the Laurence Moroney book, Introducing Microsoft Silverlight 1.0, especially the parts on how to mechanically insert Silverlight content into an HTML page.

Honorable mention: Appendix E of the Chris Sells & Ian Griffiths Programming WPF also contains a nice summary of Silverlight that is worth reading.

Considering that my prior experience with JavaScript was virtually non-existent, almost immediately I discovered that it would be crucial for me to understand the relationship between the Silverlight control, XAML content, HTML, and JavaScript in order to make this project work. Nevertheless before addressing this area, I would first like to describe the relevant considerations which influenced my choices of user interface design.

User Interface Design Issues

Creating the Title and Header Image

Every application (or web page) should have a clearly stated title or heading. Fortunately, I had a header graphic available which I had created about a year ago for another project so all that I needed to do was to change the text to read "Drink Mate Standard Imprints".

Building Element Outlines

If Silverlight 1.0 had supported Borders (as does WPF) I would have used them to place outlines around my image controls. Since Borders were not an option, I used Lines instead. At one stage, I tried to use Rectangles on the assumption that it would be easier to draw four lines at once rather than one at a time. However, since I wanted the color of my outlines to be a dark gray and not pure black, I eventually switched to Lines because of some problems which I encountered when my Rectangles overlapped.

From a layout design point of view, I decided to place images of the different standard product colors of Drink Mates (White, Black, Clear, and Smoke) along the right side of the main image. To keep things symmetrical, I made each of the smaller images one-quarter the size of the main image (with adjustments for the thickness of the outlines).

Below the main image and the alternate product color images, I placed five outlines which represent the imprint area for Drink Mates. By making the size of each such outline exactly half of the size of the imprint area on the main image, I was able to greatly simplify the mathematics involved to determine the correct ScaleX and ScaleY values for the full size image since they were always exactly twice as large as the corresponding values for the thumbnail images contained in the outlines below (top row).

Designing the Color Bar

The color bar represents each of the non-custom inks which we use to imprint Drink Mates. As is standard in the promotional products industry, we can match almost any PMS color (PMS = Pantone Matching System) by mixing these standard inks, but there is a mixing charge for that service. Originally I laid out the colors to match the arrangement on the Drink Mate website which lists the available standard colors in a little chart. Later I concluded that it would be better to arrange these colors roughly from darkest to lightest. The advantage of this revised layout was that when mousing over the color bar to preview alternate imprint colors, the user can more easily restrict his stroke to light imprint colors in the case of a black or smoke Drink Mate, and to dark imprint colors in the case of a white or clear Drink Mate. This way a user can more easily avoid previewing the illogical combinations of a white imprint on a white Drink Mate, or a black imprint on a black Drink Mate.

The color bar itself consisted of simply a series of 30 x 30 Rectangles with their Fill property set to the appropriate color. Each Rectangle shared the same Canvas.Top value and had a Canvas.Left value which was 30 greater than its immediate neighbor to the left.

<TextBlock Text="Imprint Colors" Canvas.Top="920" 
    Canvas.Left="170"  FontSize="22" 
    FontFamily="Comic Sans MS"   />
<Rectangle Name="BlackRectangle"  Canvas.Top="920" Canvas.Left="330" 
    Height="30" Width="30" Fill="Black" 
    Stroke="#FFFFFF" StrokeThickness="0" 
    MouseLeftButtonUp="handleMouseUpImprintColors" 
    MouseEnter="handleMouseEnterImprintColors" 
    MouseLeave="handleMouseLeaveImprintColors" />
<Rectangle Name="ReflexBlueRectangle"  Canvas.Top="920" Canvas.Left="360" 
    Height="30" Width="30" Fill="#000099" 
    Stroke="#FFFFFF" StrokeThickness="0" 
    MouseLeftButtonUp="handleMouseUpImprintColors" 
    MouseEnter="handleMouseEnterImprintColors" 
    MouseLeave="handleMouseLeaveImprintColors"  />
    ...
<Rectangle Name="SilverRectangle"  Canvas.Top="920" Canvas.Left="630" 
    Height="30" Width="30" Fill="#CCCCCC" 
    Stroke="#000000" StrokeThickness="0" 
    MouseLeftButtonUp="handleMouseUpImprintColors" 
    MouseEnter="handleMouseEnterImprintColors" 
    MouseLeave="handleMouseLeaveImprintColors"  />
<Rectangle Name="WhiteRectangle"  Canvas.Top="920" Canvas.Left="660" 
    Height="30" Width="30" Fill="White" 
    Stroke="#000000" StrokeThickness="0" 
    MouseLeftButtonUp="handleMouseUpImprintColors" 
    MouseEnter="handleMouseEnterImprintColors" 
    MouseLeave="handleMouseLeaveImprintColors"  />

Displaying and Hiding the Text Related Controls

The image sets for Christmas, Celebrate, and New Years are true standard imprints while the image sets for Birthdays and Graduations are a combination of a standard imprint (clip art) and the name of the honored guest of the party. The HTML controls which allow the user to enter a name and to change its font family and font size are only relevant in the case of the Birthday and Graduation image sets. Accordingly, I set their Visibility property to "hidden" in the HTML where they are created. Technically, this Visibility property belongs to the Div or Span which contains the HTML control.

<span id="spnBirthdayName" style="margin-left:15px;  margin-top:5px; margin-right:250px; 
        visibility: hidden; font-family: 'Comic Sans MS'; font-weight: normal">
    Please Enter a Name: 
    <input type="text" id="txtBirthdayName" onkeyup="handleBirthdayNameChange(this);"
        onblur="CenterNameText();"  />
</span>

Then each time that there is a switch of image sets, if the new image set is either Birthdays or Graduations, this Visibility property is set to "visible".

switch(newImageSet)
{
    case "Christmas":
        HideUserTextControls(); 
        ... 
    case "Birthday Party":  
        //Display the controls to let users enter a Name
        //and change the font family and font size
        var spnBirthdayName = document.getElementById("spnBirthdayName");
        spnBirthdayName.style.visibility = "visible"; 
        var divCustomFonts = document.getElementById("divCustomFonts");
        divCustomFonts.style.visibility = "visible";  
        var divResizeFontText = document.getElementById("divResizeFontText");
        divResizeFontText.style.visibility = "visible";  
        var divSliderTrack = document.getElementById("divSliderTrack");
        divSliderTrack.style.visibility = "visible";  
        var divSliderDisplay = document.getElementById("divSliderDisplay");
        divSliderDisplay.style.visibility = "visible";

On the other hand, if the new image set is either Christmas, New Years, or Celebrate, I would hide these controls by setting the Visibility property of their host container to "hidden".

function HideUserTextControls()
{
    var spnBirthdayName = document.getElementById("spnBirthdayName");
    spnBirthdayName.style.visibility = "hidden"; 
    var divCustomFonts = document.getElementById("divCustomFonts");
    divCustomFonts.style.visibility = "hidden";  
    var divResizeFontText = document.getElementById("divResizeFontText");
    divResizeFontText.style.visibility = "hidden";  
    var divSliderTrack = document.getElementById("divSliderTrack");
    divSliderTrack.style.visibility = "hidden";  
    var divSliderDisplay = document.getElementById("divSliderDisplay");
    divSliderDisplay.style.visibility = "hidden";
}

Choosing Between Silverlight 1.0 and Silverlight 2.0

The principal differences between Silverlight 1.0 and Silverlight 2.0 (formerly known as Silverlight 1.1) are well known. Version 2.0 will support event handling using .NET languages (C# and VB.NET), while Version 1.0 supports event handling only in JavaScript. Advantage Version 2. Version 1.0 has virtually no support for user interface controls (no buttons, no list boxes or combo boxes, no textboxes, etc.). Microsoft has announced that most of these common user interface controls will be provided in Version 2.0. Again, advantage Version 2. Version 2.0 is also projected to include support for layout and data binding. One more time, advantage Version 2. Game, set, match.... Well, not so fast, Version 1.0 is a released product and Version 2.0 is currently in alpha. And while it is true that judging from the collection of Version 2.0 projects contained in the Silverlight Gallery it is possible to create some very impressive projects using Version 2.0, apart from the support for event handling in .NET languages, most of the advantages described above are still just future promises.

In the end, the decision for me personally on this project was very simple and was based on a number of non-generalizable factors. I wanted to launch my Foothill College Silverlight class in April 2008 well before the expected release of Version 2.0. It really is not practical to offer a class which is based on unreleased software. And while there currently are multiple books covering Silverlight 1.0, there are none yet covering Silverlight 2.0. Furthermore, as an instructor, I felt that it would be necessary for me to be familiar with Version 1.0 as well as Version 2.0 even though the latter is clearly the long term future of Silverlight.

One of my greatest regrets from choosing Version 1.0, however, came while watching a demo by Scott Stanfield of Vertigo of some Version 2.0 C# code. I was so jealous to see him using Regions to collapse his code for convenient navigation. I found the absence in my project of Regions in both the JavaScript and the XAML to be a major productivity impediment. The code for this project is over 3000 lines and even split into multiple files, locating particular pieces of code was often an exceedingly frustrating task.

Including Silverlight Content in an HTML Page

If you are new to Silverlight, unless you understand the things discussed in this section, you will not be able to make much progress in building a Silverlight project of your own. On the other hand, if you have already mastered these pre-requisites, you may as well jump to the next section (Creating Custom Buttons).

The HTML File

Silverlight is obviously a web based technology and therefore the starting point for every Silverlight application has to be some sort of web page. If you use Visual Studio 2008 to create a new Silverlight 1.0 project for you, it will create a web page entitled Default.html. Somewhere on that web page, you will need to place a Silverlight control. The following code shows the HTML content of the file Default.html which will be created for you by VS 2008 if you use the Silverlight 1.0 template to start a new project. The highlighted createSilverlight() method (explained later) will create the necessary Silverlight control.

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>CodeProjectIllustration</title>
    <script type="text/javascript" src="Silverlight.js"></script>
    <script type="text/javascript" src="Default.html.js"></script>
    <script type="text/javascript" src="Scene.xaml.js"></script>
</head>
<body>
    <div id="SilverlightPlugInHost">
        <script type="text/javascript">
            createSilverlight();
        </script>
    </div>
</body>
</html>

The XAML File

The content which this Silverlight control will display is normally contained in a separate XAML file (for good code organization purposes), in this case named by Visual Studio 2008 as Scene.xaml. In this respect, I find it useful to conceptualize this arrangement as roughly similar to an image control which will contain and display a bitmap image, typically a .jpg file. In this analogy, the Silverlight control is equivalent to an image control and the composite XAML content is equivalent to a .jpg image.

Assisting the User to Download and Install the Silverlight Plug-in (Silverlight.js)

The Silverlight case, however, is much more complex than the case of an image control. In order for the Silverlight control referred to in your web page to work, a browser plug-in must be present in the user's browser. And the chances are that on the first occasion that a user visits your web page, this browser plug-in will not be present. Your web page must therefore alert the user that he needs this plug-in and hopefully guide him through the install process. Fortunately, Microsoft has written a script, entitled Silverlight.js, which is designed to do exactly that. Unfortunately, this script could use a bit of an improvement as in my observation the user experience can tend to be rather confusing to average computer users (meaning anyone below the level of an intermediate to advanced .NET developer).

It would seem to me that the logical sequence of messages which the user should receive if he does not have Silverlight installed in his browser should be roughly as follows:

  1. The content which you have requested requires a browser plug-in called Silverlight.
  2. Silverlight is roughly comparable to Adobe Flash and is therefore both secure and easy to install.
  3. Click OK if you would like to install the Silverlight browser plug-in.
  4. License agreement, Silverlight is now installing, blah, blah, blah....
  5. Silverlight has finished installing. Click here to view the content which you previously requested.

I suggest that you try this process yourself to see how closely things follow my recommended outline. I think that you will find that the initial message "Get Microsoft Silverlight" hardly explains to the user why he would want to have Silverlight. Admittedly two more clicks will take a user to a page which attempts to address in great detail the question "Why Silverlight?", but for any user seeing the "Get Microsoft Silverlight" message when attempting to view your web page, a much simpler and more compelling answer would be "to be able to see the content which you had requested".

Moreover, requiring the user to save the install file to disk before running it has to be another psychological as well as physical impediment to this process.

Nevertheless, since it is hardly practical for you to build your own structure for this task, the best solution is to simply use the Microsoft provided Silverlight.js file. (Moreover, as pointed out by a good friend of mine, the use of Silverlight.js appears to be required by the Silverlight license agreement.) It is definitely advisable, however, to set the inplaceInstallPrompt property of the Silverlight control to true (not the default), in order to provide a shorter path to users who need to install the Silverlight plug-in. This setting links directly to the file to be downloaded rather than to the official download page in the Silverlight website.

Silverlight.createObjectEx({
    source: 'Scene.xaml',
    parentElement: document.getElementById('SilverlightPlugInHost'),
    id: 'SilverlightPlugIn',
    properties: {
        width: '1000',
        height: '780',
        background:'#7799aa',
        isWindowless: 'false',
        inplaceInstallPrompt: true, //must be added manually
            version: '1.0'
    },

In addition to assisting a user in downloading and installing the Silverlight browser plug-in, Silverlight.js has another important task. It contains the createObject() and createObjectEx() methods which are needed to build the actual Silverlight control which will host your XAML content. These two methods are essentially identical, varying only in the syntax which they accept for the parameters which are passed to them. CreateObjectEx uses the popular JSON syntax and is probably preferable for most developers.

Visual Studio 2008 places a call to Silverlight.createObjectEx in the createSilverlight() method which can be found in the Default.html.js file. The implementation for createObjectEx() in turn can be found in Silverlight.js. The structure of this arrangement is illustrated in the following diagram:

The names Default.html and Scene.xaml are just default names and can (and generally should) be changed to fit your project. While JavaScript can find a method regardless of the .js file it is located in, for good code organization, JavaScript event handlers for any XAML objects contained in Scene.xaml should be placed in a file called Scene.xaml.js. If you have more than one XAML file in your project (as does mine), the JavaScript event handlers for any XAML object contained in any additional XAML file should be placed in a separate file with a corresponding name and a .js extension. This will require additional references in the <head> section of your Default.html file, as is shown in my diagram below.

From these two diagrams, you can see that getting started on your project will begin with:

  • Adding any additional HTML content to Default.html.
  • Setting various properties of your Silverlight control (e.g., height, width, version) in the CreateSilverlight() method located in Default.html.js.
  • Creating your XAML content to be placed in Scene.xaml.

Default File Names

Finally, a note regarding default file names. Microsoft has not been consistent in its use of default file names over time or across its various tools for building Silverlight applications. The file names shown in my diagrams above (Default.html and Scene.xaml) are created by Visual Studio 2008 Beta 2 for a Silverlight 1.0 application. If you use the final release of Visual Studio 2008 for a Silverlight 1.0 application, you will still get these same default file names. On the other hand, if you create a Silverlight 1.0 application using Expression Blend, the HTML page is similarly named Default.html but the XAML file name is Page.xaml.

Also, if you read the Silverlight QuickStart entitled "How To Create a Silverlight Project", you will encounter the following quotation in a section entitled "What's in a Basic Silverlight Project?": "A root HTML file: typically, this will be named default.html or something similar. The Visual Studio template uses the file name TestPage.html". (Now if ever there were a default name which needed changing in a production application... tied, I guess, with the password "ChangeMe".) Reading further reveals the following quotation: "CreateSilverlight.js: In the Visual Studio templates, this file is named TestPage.html.js." That seems a little like "I'd like you to meet my son William. We call him Mike." Nathan too refers in his book to a file called CreateSilverlight.js which he suggests is used "by convention". Unfortunately, it would appear that over time convention has changed quite a bit and this can cause considerable confusion as you try to make sense out of different source materials written by different authors and at different times. The following table tries to clarify the use of these default files.

Although this table shows Silverlight.js as uniformly used across all cases, it is possible for you to include the content of this file in some other .js file of your own. Microsoft does, however, point out that their license agreement requires its unaltered content to be present in every Silverlight application.

Creating Custom Buttons

Since Silverlight 1.0 does not include Button as a user control, it is necessary to create your own using some type of graphics tool. Although there is no Button as such in Silverlight, you can use Rectangles, Ellipses, or Lines to build your own buttons. My experience with Expression Graphic being much too limited, I chose to use Expression Blend to create my buttons.

For this project, I needed a total of three buttons: one each of left and right triangular buttons plus a normal rectangular button whose function would be to display the usage instructions for the application.

One highly useful resource for this particular task is the training video on Expression Blend by Lee Brimelow entitled "Creating Animated Buttons" on Lynda.com. As of now this video can be viewed for free.

I began by trying to create my triangular shaped buttons and almost immediately encountered some difficulty. While XAML includes Rectangles, Ellipses, and Lines, there is no Triangle element per se and while there is a RadiusX and a RadiusY property on a Rectangle (or an Ellipse), there is no corresponding property on a Line or PolyLine. I was looking for symmetrically rounded corners on my triangular buttons and I couldn't find a simple way to start with a triangle and then merely pull on the adorners to shape my corners. Finally, I set upon the idea of creating two rounded corner rectangles and using one of them to "trim" the other one. This didn't turn out to be all that easy either since aligning the two buttons in a symmetric fashion wasn't very simple. Eventually I got them approximately where I wanted them and then selected Object->Combine->Intersect from the Blend menu. The left image below shows what the buttons looked like when superimposed on each other. Notice the faint blue outline which represents the external dimensions of the lower (blue) rectangle. The right image shows the result of the "trimming" operation.

Next I created a just slightly smaller version of my button and superimposed it on my initial button. One place where Blend really excels is in creating gradients. (Of course, Blend is no substitute for artistic talent as my buttons probably suggest.) It was also necessary to create additional versions of my buttons to represent the states of MouseOver and Disabled. My results are shown below:

Once I had the right triangular button, I simply used the Object->Flip->Horizontal menu entry to create an equivalent left button.

I used a similar process to create the rectangular button, omitting both the trimming and flipping steps.

Creating the Necessary 2D Graphics

Most of the 2D graphics for this project started out in CorelDraw format or at least somehow managed to find their way into that format. In order to use them in the way which I wanted in this application, I needed to convert them into XAML. (Displaying these graphics in XAML format would permit me to change their color simply by changing the Fill and Stroke properties of their Paths.) This was a relatively simple process consisting of first using CorelDraw to convert the files to Adobe Illustrator (.ai) format and from there using the Mike Swanson XAML exporter to convert the images into XAML. There is a video demonstration of this process on my blog which illustrates exactly how this is done.

Even though at the very beginning of the process in CorelDraw I used a template (a fixed size rectangle) to size each piece of artwork to approximately the same dimensions, I still faced some serious sizing related issues for a couple different reasons. In the first place, I needed two copies of each image, one for the thumbnail representation to fit into the imprint area outlines, and one for the full size version which would display superimposed on the Drink Mate. Secondly, given that these images came from a host of different sources and do not share any particular shape or even a common aspect ratio, the best which was possible was to create a rough approximation of the desired size. To fine tune the size of each specific image, after creating the XAML file using the Mike Swanson XAML exporter, I manually added a ScaleTransform element to each XAML file:

<Canvas.RenderTransform>
    <ScaleTransform x:Name="GraduationImageSetScaleTransform" 
                 ScaleX=".16" ScaleY=".16" />
</Canvas.RenderTransform>

This ScaleTransform would allow me to manually adjust the size of the images to correspond to the outlines into which they would be placed. Determining the correct final values for ScaleX and ScaleY was done on the basis of trial and error.

Using the Silverlight Downloader

The current version of my application contains five image sets, each consisting of five images. Each image has two versions, a thumbnail version and a full size one. Add an extra five images for the Available Image Sets collection and the application needs a total of 55 images. Collectively, these XAML files are approximately 4 MB in size.

To facilitate the transfer of files like this from the server to a client browser, Silverlight provides a Downloader object which can retrieve XAML files from the server and automatically convert them into Silverlight objects. Even better, the Downloader is capable of retrieving a Zip file which contains multiple XAML files and can seamlessly extract them to build the corresponding Silverlight objects which they contain.

So in my case, I put all of my XAML files into a Zip file and pointed the Downloader at that file.

var downloader = plugIn.CreateObject("downloader");
//Because the Downloader works asynchronously, we need to monitor its Completed Event
downloader.AddEventListener("Completed", handleCompletedImprintImages);
//Parameter 2: Use a path relative to the current HTML file
downloader.Open("GET", "ImprintImages/ImprintImages.zip"); 
downloader.Send();

As you can see from this code, the Downloader object is created by calling the CreateObject method of the Silverlight control. After attaching an event handler to respond to its Completed event, we call the Open() method to specify what file to retrieve and what protocol to use in this retrieval (HTTP GET, the only valid option). The Zip file is not actually retrieved until we call the Send() method.

For the appearance of better performance, I split my XAML files into two groups and placed only the Christmas image set in the first Zip file and all the remaining XAML files into a second Zip file. Since the Christmas images are the only images which are necessary in order to display the opening screen, by downloading only these images first, it is possible for the application to give the appearance of loading faster. Once the application is fully displayed to the user, all of the remaining XAML files can continue to be downloaded in the background and still be ready before they are actually needed by the application.

Instantiating any Silverlight object contained in any of the downloaded XAML files requires the use of either the CreateFromXaml() method or the CreateFromXamlDownloader() method, both of which belong to the Content property of the Silverlight control.

function handleCompletedImprintImages(sender, eventArgs)
{
    ///Sender = the Downloader object
    ...
    var controlContent = sender.GetHost().Content;
    var newThumbContent = controlContent.CreateFromXamlDownloader(
        sender, "PeaceOnEarthThumb.xaml");
    controlContent.Root.Children.Add(newThumbContent);
    var newThumbImage = controlContent.Root.FindName("PeaceOnEarthThumb");
    newThumbImage.SetValue("Canvas.Top", 766);
    newThumbImage.SetValue("Canvas.Left", 80);
    ...
}

From the sender object, we can get a reference to the Silverlight control by calling the GetHost() method. From there, we can get a reference to the Silverlight control's Content property by using property syntax. With this reference to the Silverlight control's Content property, we can now call the CreateFromXamlDownloader() method. The two parameters which we must pass to this method are the Downloader object itself and the name of the file which we want to extract.

Just creating an object from this XAML does not cause it to be displayed. Rather, it must be explicitly added somewhere into the logical tree. That is the purpose of the line: controlContent.Root.Children.Add(newThumbContent);. In this case, the PeaceOnEarthThumb image (2D graphic) is being made a direct child of the root Canvas object.

Positioning the 2D Graphics

By default, the position of any object is 0,0, a location not particularly suitable for most of my images. To move these images to the place at which I would like them to be displayed, it is necessary to first get a reference to each image. This can be quite easily accomplished by calling the FindName() method which belongs to every UI element. In other words, if you have a reference to any UI element anywhere in your application (with only a few minor exceptions), you can get a reference to any other UI element.

var newThumbImage = controlContent.Root.FindName("PeaceOnEarthThumb");

Using this reference, it is possible to set any of the writable properties of the image.

newThumbImage.SetValue("Canvas.Top", 766);
zewThumbImage.SetValue("Canvas.Left", 80);

These properties could also have been set using this alternate syntax:

newThumbImage["Canvas.Top"] = 766;
newThumbImage["Canvas.Left"] = 80;

The values assigned for these positions were determined based on trial and error.

Daisy-chaining the Downloader

The goal of placing the XAML images into two Zip files was to improve the perceived performance of the application by downloading only those images required to display the initial screen in the first Zip file. For the same reason, it would be preferable to defer the commencement of the download of the second Zip file until after the retrieval of the first Zip file had been completed so that the two downloads would not compete against each other for bandwidth. This was accomplished by placing the request for the second Zip file at the very end of the event handler for the download of the first Zip file.

sender.AddEventListener("Completed", handleCompletedImprintImages2);
sender.Open("GET", "ImprintImages/ImprintImages2.zip");  
sender.Send();

This code uses the same downloader object but points it to a different Zip file and uses a different event handler to process the contained XAML files. For reasons which are unclear to me, efforts to remove the first event handler at this point did not seem to work as desired. Accordingly, I used a simple flag to limit the first event handler to only run a single time. Immediately after requesting the second Zip file, my code sets this flag to prevent a second pass through the first event handler:

m_blnFirstPass = false;

Colorizing the 2D Graphics

All of the XAML graphics consist of some combination of Canvas and Path elements. While each such file in every case will have a Canvas as the root element, most also have one or more sub Canvases, occasionally extending down multiple layers. Of course, a Canvas is just like a pegboard on which the Paths are hung and it has no visible component of its own. As such, it is unnecessary (and not even possible) to colorize a Canvas. Colorizing the Paths is a simple matter of setting their Fill and Stroke properties to the currently selected color. However, since not all the Paths comprising a given image reside directly on the root Canvas, it was necessary to write a recursive method to navigate down to every sub Canvas in order to reach every Path in that image.

function applyNewImprintColor(elementReference, newImprintColor)
{
    for (var i = 0; i < elementReference.Children.Count; i++)
    {
        var element = elementReference.Children.GetItem(i);
        if (element.ToString() == "Canvas")
        {//Recursive call to this method
            applyNewImprintColor(element, newImprintColor); 
        }
        else if (element.ToString() == "Path")
        {
            element.Fill = newImprintColor;
            element.Stroke = newImprintColor;
        }
    }//Now color our three textblocks as well
    refTxbHappyBirthday = elementReference.FindName("txbHappyBirthday");
    refTxbHappyBirthday.Foreground = currentImprintColorPreference;        
    refTxbCongratulations = elementReference.FindName("txbCongratulations");
    refTxbCongratulations.Foreground = currentImprintColorPreference;         
    refTxbBirthdayBoy = elementReference.findName("txbBirthdayBoy");
    refTxbBirthdayBoy.Foreground = currentImprintColorPreference;
}

Working with Events

Linking Silverlight Objects to JavaScript Event Handlers

Linking your Silverlight objects to JavaScript event handlers can be accomplished in any of three different ways. By far the simplest is to assign the name of the event handler to the event property in your XAML code using attribute syntax.

<Path Name="Outline1"  Canvas.Top="754" Canvas.Left="4" Fill="#7799aa" 
    MouseLeftButtonUp="handleMouseUpImprintOutlines" 
    MouseEnter="handleMouseEnterImprints"  
    MouseLeave="handleMouseLeaveImprints" ...></Path>

However, while this method works fine for Silverlight objects defined in a .xaml file (i.e., at design time), it cannot be used for Silverlight objects contained in a downloaded XAML file (i.e., at run time). In that case, it is necessary to use the JavaScript addEventListener() method which can be used to wire up an event handler at run time which had been omitted at design time.

function handleCompletedImprintImages(sender, eventArgs)
{
    //Sender = the Downloader object
    if(m_blnFirstPass == false)
    {
        return;
    }
    //Retrieve First Imprint Image (Peace on Earth)
    var controlContent = sender.GetHost().Content;
    var newThumbContent = controlContent.CreateFromXamlDownloader(
        sender, "PeaceOnEarthThumb.xaml");
    controlContent.Root.Children.Add(newThumbContent);
    var newThumbImage = controlContent.Root.FindName("PeaceOnEarthThumb");
    newThumbImage.SetValue("Canvas.Top", 766);
    newThumbImage.SetValue("Canvas.Left", 80);
    newThumbImage.addEventListener("MouseLeftButtonUp", handleMouseUpImprints);
    newThumbImage.addEventListener("MouseEnter", handleMouseEnterImprints);
    newThumbImage.addEventListener("MouseLeave", handleMouseLeaveImprints);
    ...
}

Nathan, page 146, points out that this event handler can be specified as a string (e.g., "handleMouseUpImprints"), as a direct reference (e.g., handleMouseUpImprints), or even as an inline function.

The third method for attaching an event handler uses a method called Silverlight.createDelegate() which is automatically added by Visual Studio 2008 to the Default.html.js file.

onLoad: Silverlight.createDelegate(scene, scene.handleLoad)
...
Silverlight.createDelegate = function(instance, method) {
    return function() {
        return method.apply(instance, arguments);

If you are a little fuzzy on delegates, I highly recommend the book Illustrated C# 2005 (or the upcoming Illustrated C# 2008) by Dan Solis. Most likely, however, you will find it considerably easier to stick to the first two methods for specifying event handlers, described above.

Positioning the Full Size Images

Switching the full size images in my application is effected entirely through event handlers (i.e., without any animation). When a user clicks on either a thumbnail image or one of the outlines in the upper row, the handleMouseUpImprintOutlines event handler is triggered. The first responsibility of this event handler is to remove the existing full size image from view by moving it far off screen.

function hideCurrentlyDisplayedFullSizeImprint()
{
    var refSilverlightControl = document.getElementById("SilverlightPlugIn");            
    var refRoot = refSilverlightControl.Content.Root;
    var refFullSizeImprint = refRoot.findName(currentlySelectedImprint); 
    refFullSizeImprint.SetValue("Canvas.Top", 3340);
    refFullSizeImprint.SetValue("Canvas.Left", 110);        
}

Then the name of the full size image is derived by removing the last five characters ("thumb") from the currently selected thumbnail image:

var fullSizeImprint = 
  currentlySelectedThumbImage.substring(0, currentlySelectedThumbImage.length-5);

After that the new full size image can be moved into place by setting its Canvas.Left and Canvas.Top properties to correspond to the desired location (originally determined through trial and error).

function displayFullSizeImprint(newSelectedImprint)
{  
    currentlySelectedImprint = newSelectedImprint;
    var refSilverlightControl = document.getElementById("SilverlightPlugIn");            
    var refRoot = refSilverlightControl.Content.Root;
    refFullSizeImprint = refRoot.findName(newSelectedImprint);  
    //Position the imprint in the main imprint area
    switch (newSelectedImprint)
    {
        //Christmas Image Set - Display Full Size Images
        case "PeaceOnEarth":
        var imprintClickAnimation = refRoot.findName("PeaceOnEarthClickResponse");
        //This animation provides the expansion / contraction
        //        click effect of the thumbnail image
        imprintClickAnimation.begin(); 
        refFullSizeImprint.SetValue("Canvas.Top", 335);
        refFullSizeImprint.SetValue("Canvas.Left", 160);
        applyNewImprintColor(refFullSizeImprint, currentImprintColorPreference);
        break;
            ...
     }
     ...
}

Silverlight Animations

Choosing Which Tool to Use

The main reason for using Silverlight in this application is to take advantage of Silverlight's great built-in support for animations. In all, this project uses over 100 animations of different types.

One issue to address early on with respect to animations is which tool you should use to create them. If you are limited to using Visual Studio for creating animations, the process is essentially entirely manual. On the other hand, if you have Expression Blend available, you can let the tool write the animations for you.

If the property which you wish to animate is of data type double, I would say that provided that you have some prior experience, it is possible to write your animation code by hand. In fact, that is what I did for the majority of the animations in this project. Create a Storyboard object, assign it a name, and set whatever other properties are relevant. Then include one or more DoubleAnimation objects, specifying a duration, a target value, a target object, and a target property. Lather, rinse, repeat.

<Storyboard x:Name="PeaceOnEarthClickResponse"  AutoReverse="True">
    <DoubleAnimation Duration="00:00:00.20" To=".35" 
    Storyboard.TargetName="PeaceOnEarthThumbImprintScaleTransform" 
                    Storyboard.TargetProperty="ScaleX" />
    <DoubleAnimation Duration="00:00:00.20" To=".35" 
    Storyboard.TargetName="PeaceOnEarthThumbImprintScaleTransform" 
                    Storyboard.TargetProperty="ScaleY" />
</Storyboard>

I have close to 100 animations of this type which can be called simply by getting a reference to the appropriate Storyboard and then calling the Storyboard.Begin() method.

var imprintClickAnimation = refRoot.findName("PeaceOnEarthClickResponse");
imprintClickAnimation.begin();

However, not all animations are as simple as merely changing the value of ScaleX or Canvas.Left. Animations for changing the colors of a linear gradient fill can much more efficiently be created using Expression Blend. And even tweaking the value of DoubleAnimations can be more quickly and easily performed using Expression Blend by clicking the Play button in the Animation Workspace without the need to re-launch the entire application each time. Except for the simplest of animations, therefore, Expression Blend in my opinion is really an essential tool for building any meaningful Silverlight application.

Building a Fade-Out / Fade-In Animation

The first type of animation on which I worked involved switching the thumbnail images which display in the upper row of outlines. When the Downloader reads in the thumbnail images, it puts the first set (Christmas) into the upper row of outlines. All of the remaining thumbnail images are placed a long way off screen into "temporary storage".

Initially, I thought that it might be sufficient to simply place all of the thumbnail images in the appropriate outlines and just fade out one set and fade in a different set by changing their opacities. While this worked nicely from a visual standpoint, I soon discovered that an object whose opacity is set to 0 nevertheless responds to mouse events. This turned out to be quite disruptive when clicking near (but not exactly on) a visible image could result in displaying a different (hidden) image.

The image above is only a simulation and not an exact representation of this problem. In practice, the underlying image would be totally invisible but this simulation is intended to show how clicking near the visible image could result in triggering the event handler associated with the ghostly image whose Opacity property had been set to 0 but which was still physically present inside the imprint area outline.

While elements with an opacity of 0 still respond to mouse events, elements with a Visibility property of Collapsed do not. At first I thought that I would simply animate the Visibility property of each image, shifting it from Visible to Collapsed as necessary and vice versa. This approach turned out to be a dead end since Silverlight does not support DiscreteObjectKeyFrame animations which are required for animating the Visibility property.

Ultimately I concluded that the only practical approach was to move the images off screen while hidden and then move them back into place when needed. Since I did not want them to slide ghostly across the screen as they were fading in (or fading out), I first moved the new image set into place in .01 seconds and then began the fade in to run over a period of .8 seconds. For the departing set of images, I didn't begin the movement off screen until the fade out process had completed. This was accomplished by setting the BeginTime property on the departure movement to .9 seconds. The actual fade-ins and fade-outs were designed to happen simultaneously.

<Storyboard x:Name="PeaceOnEarthFadeOut"  AutoReverse="False">
    <DoubleAnimation Duration="00:00:00.80" To="0" 
         Storyboard.TargetName="PeaceOnEarthThumb" 
         Storyboard.TargetProperty="Opacity"  BeginTime="0:0:0.1" />
    <DoubleAnimation Duration="00:00:00.01" To="2000" 
         Storyboard.TargetName="PeaceOnEarthThumb" 
         Storyboard.TargetProperty="(Canvas.Left)" BeginTime="0:0:0.9"/>
</Storyboard>
...
<Storyboard x:Name="HappyBirthdayCakeFadeIn"  AutoReverse="False">
    <DoubleAnimation Duration="00:00:00.1" To="520" 
        Storyboard.TargetName="HappyBirthdayCakeThumb" 
        Storyboard.TargetProperty="(Canvas.Left)" />
    <DoubleAnimation Duration="00:00:00.80" To="1" 
        Storyboard.TargetName="HappyBirthdayCakeThumb" 
        Storyboard.TargetProperty="Opacity"  BeginTime="0:0:0.1" />
</Storyboard>

Building a Click Response Animation

To generate a visible response to a user click on one of the standard imprints in the upper row of outlines, I decided to create an animation which would grow and then re-shrink the size of these imprints by approximately 5% over a period of .2 seconds. Increasing the size of the image was accomplished by setting a new value for the ScaleX and ScaleY properties of each image. Returning the size of the image was accomplished by setting the AutoReverse property of the Storyboard to "True".

<Storyboard x:Name="PeaceOnEarthClickResponse"  AutoReverse="True">
    <DoubleAnimation Duration="00:00:00.20" To=".35" 
    Storyboard.TargetName="PeaceOnEarthThumbImprintScaleTransform" 
                    Storyboard.TargetProperty="ScaleX" />
    <DoubleAnimation Duration="00:00:00.20" To=".35" 
    Storyboard.TargetName="PeaceOnEarthThumbImprintScaleTransform" 
                    Storyboard.TargetProperty="ScaleY" />
</Storyboard>

Building Left Shift and Right Shift Animations

The animations which I wrote for the lower row of outlines were considerably more complicated than those for the upper row. The key effect which I wanted to achieve for these images and their associated outlines and captions was to shift all of the outlines either one position to the right or one position to the left when clicking on the corresponding (triangular) button.

My first step was to assign event handlers entitled "LeftShiftImageSets" and "RightShiftImageSets" to the left and right buttons, respectively. Even though my application currently has only five image sets with four displayed and only one hidden at any given point in time, I wanted to make the application as flexible as possible to accommodate future additional image sets. This is achieved via a multistep process. When the LeftShiftImageSets event handler is triggered, it first calls another method called AssignTargetPositionValues which then adds 98 to a set of module level variables which are used by the animation to set the Canvas.Left property of each Image Set image and outline. Naturally, when the RightShiftImageSets event handler calls this same AssignTargetPositionValues method instead of adding 98, it subtracts 98 from this set of module level variables. Each outline and each image starts out at some pre-assigned position. Since the width of each outline is exactly 98 pixels, shifting one position to the left can be accomplished by reducing the Canvas.Left property by 98, and shifting one position to the right can be accomplished by adding 98 to the Canvas.Left property.

function LeftShiftImageSets()
{//Event handler for the Left Button
    ...
    AssignTargetPositionValues("Left");
    ...
}
...
function AssignTargetPositionValues(strDirection)
{
    switch(strDirection)
    {
        case "Left":
            m_intTargetPositionOutline1 -= 98;
            m_intTargetPositionOutline2 -= 98;
            m_intTargetPositionOutline3 -= 98;
            m_intTargetPositionOutline4 -= 98;
            ...
        case "Right":
            m_intTargetPositionOutline1 += 98;
            m_intTargetPositionOutline2 += 98;
            m_intTargetPositionOutline3 += 98;
            m_intTargetPositionOutline4 += 98;
            ...
     }
     ...
}

Once these target values have been assigned, we can use these new values to set the To property of each horizontal shift animation.

var refDoubleAnimationOutline1 = refRoot.findName("HorizontalShiftOutline1");
refDoubleAnimationOutline1.To = m_intTargetPositionOutline1;  
var refDoubleAnimationOutline2 = refRoot.findName("HorizontalShiftOutline2");
refDoubleAnimationOutline2.To = m_intTargetPositionOutline2;

Once all of these new target values have been assigned, we can call the animation which smoothly shifts all of the images along with their outlines and captions either one position to the left or one position to the right.

refHorizontalShiftImageSetAnimation.begin();
...
<Storyboard x:Name="HorizontalShiftImageSetAnimation" 
          AutoReverse="False" FillBehavior="HoldEnd" >
    <DoubleAnimation x:Name="HorizontalShiftOutline1" 
      Duration="00:00:01" To="0" 
      Storyboard.TargetName="ImageSetOutline1" 
      Storyboard.TargetProperty="(Canvas.Left)" />
    <DoubleAnimation x:Name="HorizontalShiftOutline2" 
      Duration="00:00:01" To="0" 
      Storyboard.TargetName="ImageSetOutline2" 
      Storyboard.TargetProperty="(Canvas.Left)" />
</Storyboard>

Animating a MouseOver Effect

Moving on, animating the MouseOver of the Image Set outlines also presented a number of challenging issues. The response which I wanted to give to this MouseOver was to expand the outline and the image by about 10% and to darken the background color slightly.

The first problem which I encountered was that if the outline was expanded by simply increasing the ScaleX and ScaleY values, it would not expand uniformly from the center point but only down and to the right. My solution for this was to simultaneously shift its Canvas.Left and Canvas.Top properties. Note that the FillBehavior property of the expansion animation is set to HoldEnd so that the outline and the associated image will stay in their expansion state until the MouseLeave event is triggered.

<Storyboard x:Name="BirthdayImageSetExpansion" 
         AutoReverse="False" FillBehavior="HoldEnd" >
    <DoubleAnimation Duration="00:00:00.20" To=".30" 
      Storyboard.TargetName="BirthdayImageSetScaleTransform" 
      Storyboard.TargetProperty="ScaleX" />
    <DoubleAnimation Duration="00:00:00.20" To=".30" 
      Storyboard.TargetName="BirthdayImageSetScaleTransform" 
      Storyboard.TargetProperty="ScaleY" />
    <DoubleAnimation Duration="00:00:00.20" To=".45" 
       Storyboard.TargetName="ImageSetOutline1ScaleTransform" 
       Storyboard.TargetProperty="ScaleX" />
    <DoubleAnimation Duration="00:00:00.20" To=".45" 
      Storyboard.TargetName="ImageSetOutline1ScaleTransform" 
      Storyboard.TargetProperty="ScaleY" />
    <DoubleAnimation Duration="00:00:00.20" To="23" 
      Storyboard.TargetName="ImageSetOutline1" 
      Storyboard.TargetProperty="(Canvas.Top)" />
    <DoubleAnimation x:Name="ExpansionCanvasLeftOutline1" 
      Duration="00:00:00.20" To="31" 
      Storyboard.TargetName="ImageSetOutline1" 
      Storyboard.TargetProperty="(Canvas.Left)" />
    <DoubleAnimation Duration="00:00:00.20" To="30" 
      Storyboard.TargetName="BirthdayImageSet" 
      Storyboard.TargetProperty="(Canvas.Top)" />
    <DoubleAnimation x:Name="ExpansionCanvasLeftBirthdayImageSet" 
      Duration="00:00:00.20" To="40" 
      Storyboard.TargetName="BirthdayImageSet" 
      Storyboard.TargetProperty="(Canvas.Left)" />
</Storyboard>
...
<Storyboard x:Name="BirthdayImageSetContraction" 
        AutoReverse="False" FillBehavior="HoldEnd" >
    <DoubleAnimation Duration="00:00:00.20" To=".28" 
       Storyboard.TargetName="BirthdayImageSetScaleTransform" 
       Storyboard.TargetProperty="ScaleX" />
    <DoubleAnimation Duration="00:00:00.20" To=".28" 
      Storyboard.TargetName="BirthdayImageSetScaleTransform" 
      Storyboard.TargetProperty="ScaleY" />
    <DoubleAnimation Duration="00:00:00.20" To=".42" 
      Storyboard.TargetName="ImageSetOutline1ScaleTransform" 
      Storyboard.TargetProperty="ScaleX" />
    <DoubleAnimation Duration="00:00:00.20" To=".42" 
      Storyboard.TargetName="ImageSetOutline1ScaleTransform" 
      Storyboard.TargetProperty="ScaleY" />
    <DoubleAnimation Duration="00:00:00.20" To="27" 
      Storyboard.TargetName="ImageSetOutline1" 
      Storyboard.TargetProperty="(Canvas.Top)" />
    <DoubleAnimation x:Name="InitialCanvasLeftOutline1" 
      Duration="00:00:00.20" To="34" 
      Storyboard.TargetName="ImageSetOutline1" 
      Storyboard.TargetProperty="(Canvas.Left)" />
    <DoubleAnimation Duration="00:00:00.20" To="35" 
      Storyboard.TargetName="BirthdayImageSet" 
      Storyboard.TargetProperty="(Canvas.Top)" />
    <DoubleAnimation x:Name="InitialCanvasLeftBirthdayImageSet" 
       Duration="00:00:00.20" To="42" 
       Storyboard.TargetName="BirthdayImageSet" 
       Storyboard.TargetProperty="(Canvas.Left)" />
</Storyboard>

Unfortunately, the target values for Canvas.Left of 34 and 42 shown above for the outline and BirthdayImageSet in the BirthdayImageSetContraction Storyboard only work correctly when the image sets are in their original location. Once the images have been shifted either to the right or to the left, the correct new values are some multiple of 98 more or less than this original value. This was solved by adding or subtracting 98 from the original value in the AssignTargetPositionValues() method and then resetting the To property of each animation. This is the reason why the DoubleAnimations for these Canvas.Left properties have a Name property (e.g., InitialCanvasLeftOutline1 and InitialCanvasLeftBirthdayImageSet, respectively). It is through this name that we are able to modify the To property in our event handler code.

function AssignTargetPositionValues(strDirection)
{
    switch(strDirection)
    {
        case "Left":
            ...
            m_intInitialCanvasLeftOutline1 -= 98;
            m_intExpansionCanvasLeftOutline1 -= 98;
            ...
            m_intInitialCanvasLeftBirthdayImageSet -= 98;
            m_intExpansionCanvasLeftBirthdayImageSet -= 98;
            ...
     }
     ...
}

...
function HorizontalShiftImageSets()
{
    ....
    var refExpansionCanvasLeftOutline1 = 
        refRoot.findName("ExpansionCanvasLeftOutline1");
    refExpansionCanvasLeftOutline1.To = m_intExpansionCanvasLeftOutline1;   
    var refInitialCanvasLeftOutline1 = refRoot.findName("InitialCanvasLeftOutline1");
    refInitialCanvasLeftOutline1.To = m_intInitialCanvasLeftOutline1;   
    var refExpansionCanvasLeftBirthdayImageSet = 
        refRoot.findName("ExpansionCanvasLeftBirthdayImageSet");
    refExpansionCanvasLeftBirthdayImageSet.To = m_intExpansionCanvasLeftBirthdayImageSet;    
    var refInitialCanvasLeftBirthdayImageSet = 
        refRoot.findName("InitialCanvasLeftBirthdayImageSet");
    refInitialCanvasLeftBirthdayImageSet.To = m_intInitialCanvasLeftBirthdayImageSet; 
    ...
}

My next problem involved Z order. Simply increasing the ScaleX and ScaleY values would not give the desired result if a portion of the expansion is obscured by one of the other outlines positioned on top of the expanded one. The solution for this was to increase the ZIndex properties of both the outline and its associated image in the MouseEnter event handler and then later to reduce these ZIndex properties back to their initial values in the MouseLeave event handler. These event handlers are also the place where the background color change is effected.

function handleMouseEnterImageSets(sender, eventArgs)
{//Changes the background color, expands the
// outline and image to give the expansion effect
    var refSilverlightControl = document.getElementById("SilverlightPlugIn2");            
    var refRoot = refSilverlightControl.Content.Root; 
    switch(sender.Name)
    {
        case "ImageSetOutline1":
        case "BirthdayImageSet":
            var refImageSetOutline1 = refRoot.findName("ImageSetOutline1");
            refImageSetOutline1.SetValue("Canvas.ZIndex", 10);
            var refBirthdayImageSet = refRoot.findName("BirthdayImageSet");
            refBirthdayImageSet.SetValue("Canvas.ZIndex", 11);
            var imprintClickAnimation = refRoot.findName("BirthdayImageSetExpansion");
        //Call the animation which increases the ScaleX and ScaleY values (shown above)
            imprintClickAnimation.begin(); 
            refImageSetOutline1.Fill = "#7780aa";  //Darker color 
            break;
            ...
     }
     ...
}
...
function handleMouseLeaveImageSets(sender, eventArgs)
{
    //Returns to original values for mouseovers
    var refSilverlightControl = document.getElementById("SilverlightPlugIn2");            
    var refRoot = refSilverlightControl.Content.Root; 
    switch(sender.Name)
    {
        case "ImageSetOutline1":
        case "BirthdayImageSet":
            var refImageSetOutline1 = refRoot.findName("ImageSetOutline1");
            refImageSetOutline1.SetValue("Canvas.ZIndex", 0);
            var refBirthdayImageSet = refRoot.findName("BirthdayImageSet");
            refBirthdayImageSet.SetValue("Canvas.ZIndex", 1);
            var imprintClickAnimation = refRoot.findName("BirthdayImageSetContraction");
            imprintClickAnimation.begin(); 
            refImageSetOutline1.Fill = "#7799aa";    //Original color
            break;
            ...
     }
     ...
}

Animating Linear Gradient Changes

Finally, the animations of the MouseOver effect on my buttons required a change in the colors of my linear gradient fills. This was the one set of animations where Expression Blend for me was totally indispensible.

There were two advantages which Blend provided for these animations. In the first place, as I created the animation using the Blend UI, it wrote the XAML for the animation for me. And secondly, it allowed me to choose the target colors from a color chart rather than merely through trial and error by guessing at RGB values.

The ultimate result of these animations created a ColorAnimationUsingKeyFrames which changed the color of each gradient stop from an initial color to a new target color over a period of .4 seconds. Although I used Blend to create the MouseEnter animations, I wrote the MouseLeave animation manually by simply switching the starting and ending color values. If you watch the Lee Brimelow video, you can see how this switching can also be accomplished using Blend. I found both techniques to be about the same amount of effort.

//Animation generated by Expression Blend
<Storyboard x:Name="RightButtonMouseover"
         AutoReverse="False" FillBehavior="HoldEnd" >
    <ColorAnimationUsingKeyFrames BeginTime="00:00:00" 
     Storyboard.TargetName="RightInnerButton" 
     Storyboard.TargetProperty=
      "(Shape.Fill).(GradientBrush.GradientStops)[0].(GradientStop.Color)">
        <SplineColorKeyFrame KeyTime="00:00:00" Value="#FF185C84"/>
        <SplineColorKeyFrame KeyTime="00:00:00.4000000" Value="#FF3C8CBB"/>
    </ColorAnimationUsingKeyFrames>
    <ColorAnimationUsingKeyFrames BeginTime="00:00:00" 
      Storyboard.TargetName="RightInnerButton" 
      Storyboard.TargetProperty=
       "(Shape.Fill).(GradientBrush.GradientStops)[1].(GradientStop.Color)">
        <SplineColorKeyFrame KeyTime="00:00:00" Value="#FF99CBE8"/>
        <SplineColorKeyFrame KeyTime="00:00:00.4000000" Value="#FFC4DBE8"/>
    </ColorAnimationUsingKeyFrames>
    <ColorAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="RightOuterButton" 
    Storyboard.TargetProperty="(Shape.Fill).(GradientBrush.GradientStops)[0].(GradientStop.Color)">
        <SplineColorKeyFrame KeyTime="00:00:00" Value="#FF79B7DA"/>
        <SplineColorKeyFrame KeyTime="00:00:00.4000000" Value="#FFF5F9FB"/>
    </ColorAnimationUsingKeyFrames>
    <ColorAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="RightOuterButton" 
    Storyboard.TargetProperty="(Shape.Fill).(GradientBrush.GradientStops)[1].(GradientStop.Color)">
        <SplineColorKeyFrame KeyTime="00:00:00" Value="#FF276D95"/>
        <SplineColorKeyFrame KeyTime="00:00:00.4000000" Value="#FF4993BD"/>
    </ColorAnimationUsingKeyFrames>
</Storyboard>
...
//Symmetrical animation manually created by reversing the starting and ending color values
<Storyboard x:Name="RightButtonMouseLeave" 
        AutoReverse="False" FillBehavior="HoldEnd" >
    <ColorAnimationUsingKeyFrames BeginTime="00:00:00" 
              Storyboard.TargetName="RightInnerButton" 
              Storyboard.TargetProperty=
                "(Shape.Fill).(GradientBrush.GradientStops)[0].(GradientStop.Color)">
        <SplineColorKeyFrame KeyTime="00:00:00" Value="#FF3C8CBB"/>
        <SplineColorKeyFrame KeyTime="00:00:00.4000000" Value="#FF185C84"/>
    </ColorAnimationUsingKeyFrames>
    <ColorAnimationUsingKeyFrames BeginTime="00:00:00" 
            Storyboard.TargetName="RightInnerButton" 
            Storyboard.TargetProperty=
              "(Shape.Fill).(GradientBrush.GradientStops)[1].(GradientStop.Color)">
        <SplineColorKeyFrame KeyTime="00:00:00" Value="#FFC4DBE8"/>
        <SplineColorKeyFrame KeyTime="00:00:00.4000000" Value="#FF99CBE8"/>
    </ColorAnimationUsingKeyFrames>
    <ColorAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="RightOuterButton" 
    Storyboard.TargetProperty="(Shape.Fill).(GradientBrush.GradientStops)[0].(GradientStop.Color)">
        <SplineColorKeyFrame KeyTime="00:00:00" Value="#FFF5F9FB"/>
        <SplineColorKeyFrame KeyTime="00:00:00.4000000" Value="#FF79B7DA"/>
    </ColorAnimationUsingKeyFrames>
    <ColorAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="RightOuterButton" 
    Storyboard.TargetProperty="(Shape.Fill).(GradientBrush.GradientStops)[1].(GradientStop.Color)">
        <SplineColorKeyFrame KeyTime="00:00:00" Value="#FF4993BD"/>
        <SplineColorKeyFrame KeyTime="00:00:00.4000000" Value="#FF276D95"/>
    </ColorAnimationUsingKeyFrames>
</Storyboard>

When there are no more image sets off screen in either direction, it is appropriate to disable the left or right button to indicate that clicking on it will not bring out any new image sets from that direction. The animation to disable (and to re-enable) these buttons is functionally identical to the MouseEnter (and MouseLeave) animations, described above.

<Storyboard x:Name="DisableRightButton" 
         AutoReverse="False" FillBehavior="HoldEnd" >
    <ColorAnimationUsingKeyFrames BeginTime="00:00:00" 
             Storyboard.TargetName="RightInnerButton" 
             Storyboard.TargetProperty=
               "(Shape.Fill).(GradientBrush.GradientStops)[0].(GradientStop.Color)">
        <SplineColorKeyFrame KeyTime="00:00:00" Value="#FF185C84"/>
        <SplineColorKeyFrame KeyTime="00:00:00.4000000" Value="#FF616668"/>
    </ColorAnimationUsingKeyFrames>
    <ColorAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="RightInnerButton" 
    Storyboard.TargetProperty="(Shape.Fill).(GradientBrush.GradientStops)[1].(GradientStop.Color)">
        <SplineColorKeyFrame KeyTime="00:00:00" Value="#FF99CBE8"/>
        <SplineColorKeyFrame KeyTime="00:00:00.4000000" Value="#FFD5E2EA"/>
    </ColorAnimationUsingKeyFrames>
    <ColorAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="RightOuterButton" 
    Storyboard.TargetProperty="(Shape.Fill).(GradientBrush.GradientStops)[0].(GradientStop.Color)">
        <SplineColorKeyFrame KeyTime="00:00:00" Value="#FF276D95"/>
        <SplineColorKeyFrame KeyTime="00:00:00.4000000" Value="#FF939595"/>
    </ColorAnimationUsingKeyFrames>
    <ColorAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="RightOuterButton" 
    Storyboard.TargetProperty="(Shape.Fill).(GradientBrush.GradientStops)[1].(GradientStop.Color)">
        <SplineColorKeyFrame KeyTime="00:00:00" Value="#FF276D95"/>
        <SplineColorKeyFrame KeyTime="00:00:00.4000000" Value="#FF171D20"/>
    </ColorAnimationUsingKeyFrames>
</Storyboard>

Applying Custom Fonts

Silverlight 1.0 natively supports only nine fonts: Arial, Arial Black, Comic Sans MS, Courier New, Georgia, Lucinda Sans Unicode, Times New Roman, Trebuchet MS, and Verdana. If you want to use any other fonts, you must provide them with your application. (Nathan p.85 points out that even if other fonts are installed on the target computer, they will not be used.)

In addition to having the actual font file, you need to know the exact Family Name of the font. For example, the family name of the Comic Sans font is "Comic Sans MS", not just "Comic Sans". This information is not available from the font file's properties but is available from a font management program such as Bitstream Font Navigator.

To transport the font files to the client browser, include them in one of the Zip files which are retrieved by the Downloader. Unlike the XAML image files, there is no need for the Silverlight control to create any font objects. Instead, each Silverlight object which you want to be able to use the fonts must call the SetFontSource() method once the fonts have been retrieved. In my case, the three TextBlocks above the main image needed to use these fonts, so in the event handler for the second Zip file, I called the SetFontSource() method for each one. Notice how this method is only called once for each object to use the downloaded fonts, regardless of how many fonts are included in the Zip file. The parameter for the SetFontSource() method is the Downloader object.

refTxbHappyBirthday = controlContent.Root.FindName("txbHappyBirthday");
refTxbHappyBirthday.SetFontSource(sender);
refTxbCongratulations = controlContent.Root.FindName("txbCongratulations");
refTxbCongratulations.SetFontSource(sender);
refTxbBirthdayBoy = controlContent.Root.FindName("txbBirthdayBoy");
refTxbBirthdayBoy.SetFontSource(sender);

Choosing the font takes place in the combo box. Because I wanted to learn about how to interact between regular HTML elements and Silverlight elements, I decided to use an HTML combo box rather than trying a .NET version. (Since there are no Microsoft combo boxes provided with Version 1.0, the only Silverlight comboboxes come from third parties.) I set the event handler of the HTML select control to a method in my JavaScript file and passed in the select control as a parameter to the method.

<select id="cboCustomFonts" onchange="setCustomFont(this)"  >
...
</select>

Setting the font on each of the three TextBlocks requires three steps:

  • Get a reference to the TextBlock
  • Determine which font has been selected by the user
  • Set the FontFamily property of the TextBlock to the selected font family

It doesn't really matter if the TextBlock is currently displayed or not. The easiest and most efficient thing is to simply set the FontFamily property of all three TextBlocks rather than use some conditional logic to determine whether or not a given TextBlock is currently visible to the user.

function setCustomFont(selectBox)
{//Event handler for Font ComboBox    
    var silverlightControl = document.getElementById("SilverlightPlugIn");            
    var refRoot = silverlightControl.Content.Root;  
    refTxbBirthdayBoy = refRoot.findName("txbBirthdayBoy");
    var customFontPreference = selectBox.options[selectBox.selectedIndex].value;
    refTxbBirthdayBoy.FontFamily = customFontPreference;  
    refTxbCongratulations = refRoot.FindName("txbCongratulations");
    refTxbCongratulations.FontFamily = customFontPreference;
    refTxbHappyBirthday = refRoot.FindName("txbHappyBirthday");
    refTxbHappyBirthday.FontFamily = customFontPreference;
}

Resizing the Fonts

Every TextBlock has a FontSize property which, not surprisingly, determines how large or small the font is. However, the actual physical dimensions of characters of two different fonts may vary significantly even when their FontSize property is identical, as is illustrated in the following two screenshots.

Comic Sans - FontSize 36

Linus - FontSize 36

To compensate for these differences in physical font sizes, I decided to add a slider and link that slider to the FontSize property of my three TextBlocks. Having already made the decision to use an HTML textbox and an HTML combo box, I decided to use an HTML slider as well. As a practical matter, this means a slider created with JavaScript. So I searched on Google for a freeware slider which I could use. Without any extensive research, I chose a slider from the first or second link and inserted it into my project at the appropriate location.

The slider itself required almost no modifications. I simply followed the instructions provided by its developer and included a reference to its JavaScript file to the list in my Default.html file. I set the range of values for the slider to correspond to the range of font sizes which I wanted to permit in my application (36 - 50).

To change the FontSize property of my TextBlocks, I created a method entitled SetFontSize() which took a single parameter representing a double value for the target FontSize. Inside that method, I simply got a reference to each TextBlock and set its FontSize property to the value represented by the incoming parameter.

function SetFontSize(sliderValue)
{
    //Event handler for HTML Slider            
    var silverlightControl = document.getElementById("SilverlightPlugIn");            
    var refRoot = silverlightControl.Content.Root;
    refTxbHappyBirthday = refRoot.FindName("txbHappyBirthday");
    refTxbHappyBirthday.FontSize = sliderValue; 
    refTxbCongratulations = refRoot.FindName("txbCongratulations");
    refTxbCongratulations.FontSize = sliderValue;    
    refTxbBirthdayBoy = refRoot.findName("txbBirthdayBoy");
    refTxbBirthdayBoy.FontSize = sliderValue;
}

Of course, this also required adding a call to my method inside the event handler for the slider which dealt with its move event.

SetFontSize(v); //Where v represents the current value of the slider

Repositioning the Text by Dragging

Both the Birthdays image set and the Graduations image set are customizable by allowing the user to add a name to the standard imprint image. To achieve this, I shrank the clip art images slightly, relegating them roughly to the upper half of the imprint area and added a TextBlock reading Happy Birthday in the case of Birthdays and Congratulations in the case of Graduations.

If the "Name" TextBlock is stationary and is positioned to handle names of an "average" length, the results can be rather lopsided for both shorter and longer names.

Fortunately, my preparations for my Silverlight class included watching the various How-To Videos on the Silverlight website. I highly recommend these videos by the way, but in general, only after you have familiarized yourself with the basics of Silverlight. As you can tell from the titles of these videos, many of them deal with very specific topics. As such they constitute a very disjointed method for learning unless you already have a bit of a structure on which to pin this additional knowledge. Some of the topics are rather advanced and others are rather esoteric. As such they aren't that useful for a beginner. (Imagine watching a video on delegates or attributes when you were just starting to learn C#.)

There are, however, several significant exceptions to this recommendation, principally the video entitled "Getting Started with Silverlight". This video is well worth watching (possibly several times) if you are "getting started".

Several of the presenters are excellent, including particularly Jesse Liberty and Shawn Wildemuth.

Having previously watched these videos when I encountered this alignment problem, I already knew that there was a simple solution. So I put Visual Studio on my left monitor and the Silverlight video in the right monitor. Each time the presenter (Jesse Liberty) would type a few additional lines of code, I would pause the video and type the equivalent into my application. When the video was complete, I built and ran my code. It ran like a charm. Thanks Jesse. Much appreciated.

var beginX;
var beginY;
var blnTrackingMouseMove = false;

function handleMouseDownTextBlocks(sender, eventArgs)
{
    beginX = eventArgs.getPosition(null).x;
    beginY = eventArgs.getPosition(null).y;
    blnTrackingMouseMove = true;
    sender.captureMouse();
    //Lock the appropriate TextBlock to later retain the user selected position
    //when the auto-centering function would otherwise apply
    if (sender.Name == "txbHappyBirthday")
    {
        m_blnLockTxbHappyBirthday = true;
    }
    else if (sender.Name == "txbBirthdayBoy")
    {
        m_blnLockTxbBirthdayBoy = true;
    }
    else if (sender.Name == "txbCongratulations")
    {
        m_blnLockTxbCongratulations = true;
    }
}        
 
function handleMouseUpTextBlocks(sender, eventArgs)
{
    //Continue to drag until the user releases the mouse
    sender.releaseMouseCapture();
    blnTrackingMouseMove = false;
}        
 
function handleMouseMoveTextBlocks(sender, eventArgs)
{
    if (blnTrackingMouseMove == true)
    {
        var currentX = eventArgs.getPosition(null).x;
        var currentY = eventArgs.getPosition(null).y;
        sender["Canvas.Left"] += currentX - beginX;
        sender["Canvas.Top"] += currentY - beginY;
        beginX = currentX;
        beginY = currentY;
   }
}

What this feature allowed a user to do was to use his mouse to grab either of the TextBlocks and drag them to a new location of his preference. However, since the availability of this feature would not be obvious to users, particularly less experienced ones, I set the Cursor property of each of these TextBlocks to "Hand". (Thanks for this suggestion Robert.)

<TextBlock Name="txbHappyBirthday" Text="Happy Birthday"  ... Cursor="Hand" />

You will also notice that I set a flag inside one of these event handlers to respect this user selected location when my auto-centering code runs (described immediately below). When this flag is set to true, the auto-centering code will leave the user's preferred location untouched.

I considered placing some constraints on the range of locations to which a user could drag this text, but ultimately I decided that these constraints were both unnecessary and too complex to be easily implemented. Accordingly, the user can move any of the three TextBlocks to any location inside the Silverlight control which hosts them. This does have the advantage of permitting the user to try out other layout options.

Auto-Centering the Text

By specifying the Hand cursor for each of my three TextBlocks, if a user moves his mouse over the TextBlock, the cursor will change from its normal pointer shape into a hand, thereby suggesting drag capability. Nevertheless, an even better solution would be to simply center the text for the user automatically. To achieve this, I linked an event handler to the lost focus event (onblur) of the Name textbox.

<input type="text" id="txtBirthdayName" ... onblur="CenterNameText();"  />

In this event handler, I put code which centered the name TextBlock on the upper TextBlock (either Happy Birthday or Congratulations). The required steps for this process consist of:

  • Get a reference to the upper TextBlock
  • Determine the center point for the upper TextBlock
  • Get a reference to the name TextBlock
  • Determine the actual width of the name TextBlock
  • Set the Canvas.Left property of the name TextBlock equal to the center point of the upper TextBlock minus 1/2 of its ActualWidth
function CenterNameText()
{
    //Triggered by LostFocus (onblur) of txtBirthdayBoy
    var intCenterPointX;
    var silverlightControl = document.getElementById("SilverlightPlugIn");            
    var refRoot = silverlightControl.Content.Root;  
    //Determine which of Birthday or Graduation is the currently selected Image Set
    switch(currentlySelectedImprintImageSet)
    {//Get the CenterPointX of "Happy Birthday" or "Congratulations"
        case "Birthday Party":
            refTxbHappyBirthday = refRoot.findName("txbHappyBirthday");
            intCenterPointX = refTxbHappyBirthday["Canvas.Left"] 
            + (refTxbHappyBirthday.ActualWidth/2);
            break;
        case "Graduation":
            refTxbCongratulations = refRoot.findName("txbCongratulations");
            intCenterPointX = refTxbCongratulations["Canvas.Left"] 
            + (refTxbCongratulations.ActualWidth/2);
            break;
    }            
    //Get the ActualWidth of "BirthdayBoy" (doubles as the graduate)
    refTxbBirthdayBoy = refRoot.findName("txbBirthdayBoy");
    var dblActualWidth = refTxbBirthdayBoy.ActualWidth;
    //Set the Canvas.Left of BirthdayBoy to CenterPointX - 1/2 ActualWidth
    refTxbBirthdayBoy["Canvas.Left"] =  intCenterPointX - (dblActualWidth/2);
    m_blnLockTxbBirthdayBoy = true;
}

With this event handler in place, whenever a user tabs or clicks away from the name TextBox, the name TextBlock will be auto-centered on the upper TextBlock (Happy Birthday or Congratulations).

Another text alignment issue involved switching from one Birthday or Graduation image to another. If the image presented is the first such image, it is necessary to move the appropriate TextBlock out of "storage" and into position in front of the main image. However, if the user has already entered a name and has manually repositioned it or auto-centered it, it would not be appropriate to reposition the name back to its default location. To prevent this undesirable result, I added a module level variable for each of the three TextBlocks (e.g., m_blnLockTxbBirthdayBoy). These variables start out set to false but are set to true if for any reason the TextBlock is moved away from its default position. Conversely, these variables are reset to false whenever the user switches image sets.

Before repositioning one of the TextBlocks to its default location, this variable is checked to determine whether repositioning is appropriate.

if(m_blnLockTxbHappyBirthday == false)
{
    var refTxbHappyBirthday = refRoot.findName("txbHappyBirthday"); 
    refTxbHappyBirthday.SetValue("Canvas.Top", 440);
    refTxbHappyBirthday.SetValue("Canvas.Left", 100);
}
if(m_blnLockTxbBirthdayBoy == false)
{
    var refTxbBirthdayBoy = refRoot.findName("txbBirthdayBoy"); 
    refTxbBirthdayBoy.SetValue("Canvas.Top", 490);
    refTxbBirthdayBoy.SetValue("Canvas.Left", 130);
}

Creating Tool Tips and Instructions

When it came time to creating my tool tips, I found a very useful article on the Microsoft website entitled "Constructing Objects at Runtime". This article explains how you can place some pre-built XAML inside a JavaScript event handler. Then when this event handler runs, the XAML fragment is instantiated by the Silverlight control and added to the pre-existing XAML content at whatever location is specified.

At first I followed the example in the Microsoft article and placed my tool tips on the screen in relation to where the user positioned his mouse (hover location). The effect of this, however, was that the tool tip could pop up at any place within a specified range and when the mouse was successively moved over an image, the outline containing that image, the next outline, the next image, etc. This gave a rather ragged appearance to the positioning of the tool tips as they jumped around on the screen following the mouse movement.

Later I decided that considering that all of the messages provided by my tooltips were ones which most users should either already know or could probably guess or at a minimum quickly learn, it was better to make the location of the tooltips less obtrusive. Ultimately, therefore, I chose to locate all of the tooltip messages at the same location as shown in the following illustration.

function  handleMouseEnterImprintColors(sender, EventArgs)
{
    //Creates a tooltip for instructions
    if (toolTipImprintColors == null)
    {
        var xamlFragment = '<canvas width=""235"" height=""25"" />';
        xamlFragment += '<rectangle width=""235"" height=""25"" ' + 
          'fill=""#FFFFE1"" stroke=""Black"" ' + 
          'radiusx=""5"" radiusy=""5"" />';
        xamlFragment += '<textblock text=""Click" canvas.left=""10"" ' + 
          'canvas.top=""3"" fontsize=""13"" />';
        xamlFragment += '<</textblock /></rectangle /></canvas />';            
        toolTipImprintColors = sender.GetHost().content.CreateFromXaml(xamlFragment, false);
        //var cursorPosition = EventArgs.getPosition(sender);
        toolTipImprintColors["Canvas.Left"] = 750;
        toolTipImprintColors["Canvas.Top"] = 920;
        toolTipImprintColors["Canvas.ZIndex"] = 10; //unnecessary
        var mainCanvas = sender.FindName("mainCanvas");
        mainCanvas.children.add(toolTipImprintColors);
    } 
    ...
}

Creating the Animated Introduction

Building the Introduction

It is a sad but simple fact of life that 99% of people outside my close circle of friends and relatives have no familiarity with Drink Mate. I can't tell you how frustrated my mother always was that she never met anyone who had ever seen or heard of Drink Mate -- even after we passed the point where we had sold over 3 million of them.

It is mandatory, therefore, to assume that a very high percentage of users looking at this application will start out wondering: "what the heck is it that I'm looking at". To compensate for this lack of experience with Drink Mates, I decided to create an animated introduction to convey the following items of information:

  • Drink Mates are a little plastic clip that lets you hold your plate and your glass with just one hand
  • Most Drink Mates are sold with a custom advertising message
  • In addition to custom imprints, we can also provide certain standard imprints for most of the basic holidays

For the first of these messages, I selected a photo which I had of a friend using a Drink Mate to hold a plate and glass.

For the second message, I selected a representative photo of a Drink Mate with a custom imprint (the Phoenician Hotel in Phoenix, Arizona in this case).

And for the last message, I chose a close up of a Drink Mate with a standard imprint (Joy).

First, I created three additional Image controls to hold these three extra images. Next I positioned them with the first one to be displayed on the top and the others sequentially, with the last one to be displayed on the bottom. In XAML, each element is displayed on top of all elements previously created which occupy the same location. I considered using only two Image controls and resetting the Source property after each appearance to display the other image, but was unable to make that approach work satisfactorily.

The transition between images was accomplished simply by changing the Opacity property from 1 to 0 over a period of half a second. At the same time, the Opacity of the next image is shifted from 0 to 1 during the same transition period. While this works reasonably well, in practice, this Opacity shift results in a bit of a flicker. Since the same type of animation does not flicker in WPF, my suspicion is that this is a bug in the Silverlight plug-in. It is one of the few things about this application with which I am not really satisfied.

By comparison, the fade-in or fade-out of any 2D graphic is completely flicker free. As a last step, here I bring in a message explaining that this application can be used to preview the collection of Drink Mate standard imprints. Then to illustrate that point, I position one of the full size imprints (Joy) on the Drink Mate background by shifting its Canvas.Left and Canvas.Top properties.

<Storyboard x:Name="DrinkMateIntro1">
    <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" 
        Storyboard.TargetName="Alternate1MainImageViewer" 
        Storyboard.TargetProperty="(UIElement.Opacity)">
        <SplineDoubleKeyFrame KeyTime="00:00:00" Value="1"/><!--  Cecilia -->
        <SplineDoubleKeyFrame KeyTime="00:00:08" Value="1"/>
        <SplineDoubleKeyFrame KeyTime="00:00:08.5" Value="0"/>
    </DoubleAnimationUsingKeyFrames>
    <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" 
      Storyboard.TargetName="Alternate2MainImageViewer" 
        Storyboard.TargetProperty="(UIElement.Opacity)">
        <SplineDoubleKeyFrame KeyTime="00:00:00" Value="0"/><!--  Phoenician -->
        <SplineDoubleKeyFrame KeyTime="00:00:08" Value="0"/>
        <SplineDoubleKeyFrame KeyTime="00:00:08.5" Value="1"/>
        <SplineDoubleKeyFrame KeyTime="00:00:14" Value="1"/>
        <SplineDoubleKeyFrame KeyTime="00:00:14.5" Value="0"/>
    </DoubleAnimationUsingKeyFrames>
    <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" 
        Storyboard.TargetName="Alternate3MainImageViewer" 
        Storyboard.TargetProperty="(UIElement.Opacity)">
        <SplineDoubleKeyFrame KeyTime="00:00:00" Value="0"/><!--  Joy -->
        <SplineDoubleKeyFrame KeyTime="00:00:14" Value="0"/>
        <SplineDoubleKeyFrame KeyTime="00:00:14.5" Value="1"/>
        <SplineDoubleKeyFrame KeyTime="00:00:20" Value="1"/>
        <SplineDoubleKeyFrame KeyTime="00:00:20.5" Value="0"/>
    </DoubleAnimationUsingKeyFrames>
    <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" 
        Storyboard.TargetName="MainImageViewer" 
        Storyboard.TargetProperty="(UIElement.Opacity)">
        <SplineDoubleKeyFrame KeyTime="00:00:00" 
           Value="0"/><!--  White Background 800 -->
        <SplineDoubleKeyFrame KeyTime="00:00:20" Value="0"/>
        <SplineDoubleKeyFrame KeyTime="00:00:20.5" Value="1"/>
    </DoubleAnimationUsingKeyFrames>
    <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" 
        Storyboard.TargetName="txbStandardImprints" 
        Storyboard.TargetProperty="(UIElement.Opacity)">
        <SplineDoubleKeyFrame KeyTime="00:00:00" Value="0"/>
        <SplineDoubleKeyFrame KeyTime="00:00:20" Value="0"/>
        <SplineDoubleKeyFrame KeyTime="00:00:20.5" Value="1"/>
    </DoubleAnimationUsingKeyFrames>
    <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" 
        Storyboard.TargetName="txbApplicationInstructions" 
        Storyboard.TargetProperty="(UIElement.Opacity)">
        <SplineDoubleKeyFrame KeyTime="00:00:00" Value="0"/>
        <SplineDoubleKeyFrame KeyTime="00:00:20" Value="0"/>
        <SplineDoubleKeyFrame KeyTime="00:00:20.5" Value="1"/>
        <SplineDoubleKeyFrame KeyTime="00:00:25" Value="1"/>
        <SplineDoubleKeyFrame KeyTime="00:00:26" Value="0"/>
    </DoubleAnimationUsingKeyFrames>
    <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" 
           Storyboard.TargetName="Joy" 
           Storyboard.TargetProperty="(Canvas.Left)">
        <SplineDoubleKeyFrame KeyTime="00:00:24" Value="3000"/>
        <SplineDoubleKeyFrame KeyTime="00:00:25" Value="135"/>
    </DoubleAnimationUsingKeyFrames>
    <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" 
           Storyboard.TargetName="Joy" 
           Storyboard.TargetProperty="(Canvas.Top)">
        <SplineDoubleKeyFrame KeyTime="00:00:24" Value="3000"/>
        <SplineDoubleKeyFrame KeyTime="00:00:25" Value="340"/>
    </DoubleAnimationUsingKeyFrames>
    <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" 
      Storyboard.TargetName="Joy" 
      Storyboard.TargetProperty="(UIElement.Opacity)">
        <SplineDoubleKeyFrame KeyTime="00:00:23" Value="0"/>
        <SplineDoubleKeyFrame KeyTime="00:00:26" Value="0"/>
        <SplineDoubleKeyFrame KeyTime="00:00:29.5" Value="1"/>
    </DoubleAnimationUsingKeyFrames>
</Storyboard>

After I finished these animated instructions, I noticed upon testing my code that if a user clicked on one of the thumbnail imprints, the full size imprint would be displayed even though the introduction had not yet finished. This was potentially a serious problem as when a new full size imprint was displayed, it would become the currentlySelectedImprint. (Remember that when each new thumbnail image is clicked, the first step is to move the "currentlySelectedImprint" out of the way to make room for the next full size imprint.) In this case, when the introduction finished by placing the Joy imprint on the Drink Mate background, it would not be the "currentlySelectedImprint" and, therefore, would remain in place until a user subsequently selected it from the list of thumbnails. In the meantime, two separate images would be displayed -- rendering the application obviously broken.

My solution for this problem was to create a module level variable entitled m_blnLockImprintImages. This variable begins as true and is only set to false once the introduction ends. However, setting this variable to true right after calling Storyboard.Begin() wasn't effective because the introduction runs asynchronously and my lock would be released long before the animation completed (thereby accomplishing nothing). The proper procedure was to add an event handler to the Storyboard for the Completed event of the Storyboard and then set the lock to false in this event handler.

refDrinkMateIntro1.addEventListener("Completed", onCompletedDrinkMateIntro1);

... 

function onCompletedDrinkMateIntro1()
{
    m_blnLockImprintImages = false;
}

Allowing the User to Skip the Introduction

For returning users or simply the impatient, I added a feature that permits a user to skip the introduction and immediately begin previewing Drink Mate Standard Imprints. This was accomplished by adding a TextBlock which reads "Skip Introduction" and linking it to an event handler which:

  • Stops the animation by calling [Storyboard].Stop()
  • Sets the Opacity property of each of the AlternateMainImageViewers to 0
  • Sets the Opacity of the "Standard Imprints" TextBlock to 1
  • Moves the Joy full size imprint into place on the white Drink Mate background image
  • Removes the lock on the thumbnail imprints so that they can respond to mouse clicks
function handleMouseUpSkipIntroduction(sender, eventArgs)
{
    //Used to terminate the introduction if a user chooses
    if(sender.Opacity == 1)
    {
        var refDrinkMateIntro1 = sender.FindName("DrinkMateIntro1");
        refDrinkMateIntro1.Stop();     
        var refAlternate1MainImageViewer = 
            sender.FindName("Alternate1MainImageViewer");
        refAlternate1MainImageViewer.Opacity = "0";     
        var refAlternate2MainImageViewer = 
            sender.FindName("Alternate2MainImageViewer");
        refAlternate2MainImageViewer.Opacity = "0";     
        var refAlternate3MainImageViewer = 
            sender.FindName("Alternate3MainImageViewer");
        refAlternate3MainImageViewer.Opacity = "0"; 
        var refMainImageViewer = sender.FindName("MainImageViewer");
        refMainImageViewer.Opacity = "1"; 
        var reftxbStandardImprints = 
            sender.FindName("txbStandardImprints");
        reftxbStandardImprints.Opacity = "1";      
        var reftxbApplicationInstructions = 
            sender.FindName("txbApplicationInstructions");
        reftxbApplicationInstructions.Opacity = "0";  
        var refJoy = sender.FindName("Joy");
        refJoy["Canvas.Left"] = "135";
        refJoy["Canvas.Top"]  = "340";
        refJoy.Opacity = "1";
        sender.Opacity = "0"
        m_blnLockImprintImages = false;
    }
}

Cleaning Up the Code

Maintaining good code organization was severely hampered by the absence of Regions in either the XAML or JavaScript code. (Please contact your local Microsoft rep and beg him / her to urge the WPF and Silverlight teams to add Regions.)

When Visual Studio creates the Scene.xaml file, it places a single button in the file to provide users with some sample code which illustrates how to create your own custom button. It also places some sample event handling code in Scene.xaml.js. Initially I left this code in place (although I set the Opacity of the button to 0 so that it wouldn't get in my way) thinking that at some stage I might use it. However, at the end of the project, this code was still unused so I simply deleted it.

And of course no code cleanup could ever be complete without adding comments to help you remember what decisions you made in your code and what different elements are intended to be used.

Recommendations

Probably my most significant observation was that for almost every task which I needed to perform, I was able to locate some resource which explained exactly how to perform that task. In many cases, the sample code presented could be used with only minimal modifications to achieve the desired results. Examples:

  • How to create the drag functionality to permit a user to reposition the TextBlocks: The Jesse Liberty video on the Silverlight website.
  • How to create a button from a rectangle: The Lee Brimelow training video on Lynda.com.
  • How to include Silverlight content in an HTML page: The Nathan and Moroney books, the Wildemuth video on the Silverlight website.
  • How to use the Silverlight downloader: The Nathan book.
  • How to convert 2D graphics files to XAML: My blog (www.WPFLearningExperience.com).
  • How to create tool tips: The article on the Microsoft website.

Based on my experience in this project, here are my recommendations for you:

  • Prepare as much as possible before commencing your project. If at all possible, read the entire Adam Nathan book.
  • Learn how to use Expression Blend. Watch the entire Lee Brimelow series on Lynda.com.
  • Be sure to thoroughly understand the relationship between the various files in your project and the pre-supplied code from Microsoft.
  • Try some hands on exercises or step by step lessons before embarking on your own project.

VS 2008 Contest Submission

This article has been submitted for the Visual Studio 2008 contest. If you believe that this article is worthy of consideration for that contest, please take just a moment to rate it. If you have read this far, I think that you can appreciate that this project and article took several weeks of full time effort. Thank you for your consideration.

I very much welcome your comments although, as you can see from my bio below and some of my photographs above, my ability to respond is often impeded by foreign travel.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Cal Schrotenboer

United States United States
No Biography provided

Comments and Discussions

 
GeneralGreate job Cal PinmemberMohsen Afshin5-Jan-08 5:54 
GeneralRe: Greate job Cal Pinmemberqingshanyin6-Jan-08 20:22 

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

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

| Advertise | Privacy | Mobile
Web02 | 2.8.140415.2 | Last Updated 29 Dec 2007
Article Copyright 2007 by Cal Schrotenboer
Everything else Copyright © CodeProject, 1999-2014
Terms of Use
Layout: fixed | fluid