Archive for the Tutorial Category

Despite what you say, Dave, I refuse to believe I'm the *only* one! =)

The project described in my last post described how to set up a basic object-oriented approach to Silverlight in JavaScript. Now we need a way to access properties on the object, so we're going to add "getters" and "setters".

If you don't have the last project, it's available here.

Getters and setters are just functions (or methods) added to the prototype object, which is located in the objSquare.js file. Getters "return" values to the calling code, while setters set some value on the specified object.

Let's add a getter that will return the canvas top of the specified object.

In the objSquare.js file, add a comma after the closing curly brace of the "initialize" function, then add the following code:

get_top : function()
{
return this._square["Canvas.Top"];
}

This getter is called by specifying the object name, a dot (.), then the method - get_top(). Because it specifies a return value, you can assign it to a variable or use it for calculations.

Let's test this out. Open the Page.xaml.js file, and after the lines of code that instantiate the two square objects, add a line to open a message box with the value of the canvas top for "newSquare".

alert(newSquare.get_top());

Run the app and you should get a message box with "100" in it. Change "newSquare" to "newSquare1" and run it again. You should see a message box with "500" in it.

Setters work the same way, but they do not return a value. Instead, they perform an action on the specified object.

Add a comma after the closing curly brace for the get_top method, and add a method called "set_top" that looks like this:

set_top : function(newTop)
{
this._square["Canvas.Top"] = newTop;
}

"newTop" is a value we will pass into the setter, which will then be applied to the specified object.

Save the objSquare.js file and go back to Page.xaml.js. Add the following two lines of code after the alert:

newSquare.set_top(250);
newSquare1.set_top(50);

This calls the setter for both of the square objects in the app. The first one will be moved so that the canvas top is at 250, and the second will be moved so the canvas top is 50. Run the program - you should see the message box open, then both squares draw at the specified location.

If you've made a habit of using a traditional JavaScript coding style, you can probably see where this is a useful, powerful way of creating/manipulating objects for Silverlight. It offers another benefit too, which you may have already guessed: "custom" properties.

I've seen people ask on the forums about accessing properties that don't exist by default - for example, canvas right.

Let's add a getter that will return a right canvas value on a specified object.

In the objSquare.js file, add a comma after the closing curly brace for the set_top method, then add the following getter:

get_right : function()
{
return this._square["Canvas.Left"] + this._square.width;
}

As you can probably figure out from reading the code, this will return the specificed objects canvas left value added to the specified object's width, which will give us the object's right bound. It's not really adding a property to the object per se, but it is cutting down on potential code clutter by setting up the calculation in a single place.

Save the objSquare.js file, and open Page.xaml.js. Add a message box that calls the get_right method on the newSquare object:

alert(newSquare.get_right());

When the program is run, you should see a message box open that displays the top value for "newSquare1" (500), then the squares will draw on the canvas, and a second message box will open that displays "125", which is the right canvas value for the "newSquare" object.

The final files for this entry can be found here.

NOTE: The project files included for this entry use the Silverlight 2 runtime, so you will need to have that installed on your system. The example code itself is still in JavaScript.

I've wanted to move towards programming in C# for a while, and it seems like Silverlight 2 is giving me a great reason to make the time to learn it. In the meantime, in preparation for working with objected-oriented programming (it's been a while since I did C++), I've started trying to shift my JavaScript habits towards an OO format in order to ease my transition.

The problem? The lack of a simple tutorial to bridge between "ad hoc" functions and a more object oriented approach. Chris Klug was a big help in shedding some light on the subject for me, so I owe him a big "thank you".

So here it is, a simple example.

Start Blend and create a new Silerlight 1 project called squareObject. Change the name of the default canvas to "rootCanvas" and set the dimensions to 800x600. Add another canvas called "content". Don't worry about the width/height of the content canvas - we'll be taking care of that programmatically. Save the project.

First, we need to create a "constructor" for the square object class that we're going to write. Create a new JS file in the project folder called objSquare.js, and type in the code for the constructor. The constructor function looks like this (don't worry if this part doesn't make sense yet - when you see the whole context, it will become clearer):

square = function(name, Parent, height, width, left, top)
{
    this.initialize(name, Parent, height, width, left, top);
}

This is a constructor that will call the initialize function in the object class we're about to code. We're going to pass in a name for the object instance, the object parent, a height, width, left, and top position, so we are creating buckets to hold all of those values.

We want to continue on by creating the object class, which looks like this:

square.prototype =
{
    initialize: function(name, Parent, height, width, left, top)
    {
 var xaml =  '<Rectangle Name="' + name + '" Canvas.Left="' + left + '" Canvas.Top="' + top + '" Width="' + width + '" Height="' + height + '" Fill="#FFCC0000"/>';

        this._parent = Parent;       
        this._host = this._parent.getHost();
        this._square = this._host.content.createFromXaml(xaml);
        this._parent.children.add(this._square);
    }
 }

The class defines a function called initialize, which is called by the constructor that was created in the prior step. The initialize function creates a new rectangle object using the name, height, width, left, and top properties that were passed in, and then adds it to the "content" canvas, which is located using the "Parent" value that was passed.

Save the objSquare.js file.

This would be a good time to add a reference to this script file in the default.html page, so open that file up, and add the following script reference to the header section of the page:

<script type="text/javascript" src="objSquare.js" mce_src="objSquare.js"></script>

While you're in there, change the width and height styles to 800x600 as well so that they match the size of our root canvas.

Save the default.html file.

Now we want to make some edits to the Page.xaml.js file in order to make use of our square class when the application is loaded. Open the Page.xaml.js file. There's some sample code in there that can be removed. The skeleton of the Page.xaml.js file should look like this:

if (!window.squareObject)
 squareObject = {};

squareObject.Page = function()
{
}

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

 }
}

The handleLoad function is called when Silverlight creates an instance of the "squareObject" app. Notice that control, userContext, and rootElement are all passed into the handleLoad function. Under the line that says "this.control = control;", we're going to add a few lines of code in order to set up references to the rootCanvas and content canvas objects. We also want to set the content cavas width and height to match the root canvas. To do that, this is the code that is used:

this.rootElement = rootElement;
this.content = rootElement.findName("content");
      
this.content.width = this.rootElement.width;
this.content.height = this.rootElement.height;

At this point, we're ready to add code to call the constructor and have it create an object on the canvas for us, so let's quickly review what we've done. We just finished modifying the Page.xaml.js file to create some references to the rootElement and content canvas, and made the content canvas resize to the size of the rootElement.

Before that, we created a square class in objSquare.js. In our square class, we added a constructor function which when called, will create an instance of the "square" object that is defined in the square.prototype class. We will now add some code to the Page.xaml.js file to create an object on the canvas.

Beneath the code you just added to the file, add the following code:

var newSquare = new square("square1", rootElement, 25, 25, 100, 100);

This code creates a new object called "newSquare" which is of the object type square. We passed the name "square1" to be used in the XAML as a unique identifier for the XAML object, the rootElement, which is used to locate the parent object and insert the XAML into the content canvas, and then width, height, left, and right values.

Save the file and run.

You will see a small red square appear on the canvas. By manipulating these numbers, we can change the size or location of the object being created. We can also easily create multiple instances of an object. Add a line of code beneath the one you just added, with the following:

var newSquare1 = new square("square2", rootElement, 125, 125, 500, 500);

Run the project again, and you should see two squares.

If you're looking to start moving towards object oriented programming, take some time and play with this example. I will be revisiting it in additional entries soon to add functionality and custom properties to the square class.

There's also a good article on OO JavaScript here, though it's not in the context of Silverlight, it's still good stuff.

The project files are here if you want to take the easy way out.

One of the things I've seen asked a couple of times on the Silverlight.net forums is how to call two functions with one event handler. I'm not sure if this has been answered elsewhere, but I want to capture it here for my own reference.

By moving the event handlers into the code behind instead of placing them in the XAML, it's fairly easy to set up multiple event handlers for the same event.

Here's some sample XAML code for a simple button:

<Canvas
 xmlns="http://schemas.microsoft.com/client/2007"
 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 Width="640" Height="480"
 Background="White"
 x:Name="Page"
 >
 <Rectangle Width="153" Height="37" Stroke="#FF000000" Canvas.Top="164" Canvas.Left="205" RadiusY="9.5" RadiusX="9.5" x:Name="button">
  <Rectangle.Fill>
   <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
    <GradientStop Color="#FF1016B7" Offset="0"/>
    <GradientStop Color="#FFFFFFFF" Offset="1"/>
   </LinearGradientBrush>
  </Rectangle.Fill>
 </Rectangle>
 <TextBlock Width="69" Height="24" Text="Click Me!" TextWrapping="Wrap" Canvas.Top="170.5" Canvas.Left="247" Foreground="#FFFFFFFF" IsHitTestVisible="False"/>
</Canvas>

Adding a few lines to the page.xaml.js file will attach two event handlers for the single "MouseLeftButtonDown" event:

if (!window.twoFunctions)
 window.twoFunctions = {};

twoFunctions.Page = function()
{
}

twoFunctions.Page.prototype =
{
 handleLoad: function(control, userContext, rootElement)
 {
  this.control = control;
  myButton = this.control.content.findName("button");
  
  // Event hookups: 
  myButton.addEventListener("MouseLeftButtonDown", Silverlight.createDelegate(this, this.handleMouseDown));
  myButton.addEventListener("MouseLeftButtonDown", Silverlight.createDelegate(this, this.handleMouseDown2)); 
 
 },
 
 // Event handlers
 handleMouseDown: function(sender, eventArgs)
 {
  alert("Function 1.");
 },

 handleMouseDown2: function(sender, eventArgs)
 {
  alert("Function 2.");
 }

 
 
}

Alternatively, the functions can be split out:

if (!window.twoFunctions)
 window.twoFunctions = {};

twoFunctions.Page = function()
{
}

twoFunctions.Page.prototype =
{
 handleLoad: function(control, userContext, rootElement)
 {
  this.control = control;
  myButton = this.control.content.findName("button");
  
  // Event hookups: 
  myButton.addEventListener("MouseLeftButtonDown", function1);
  myButton.addEventListener("MouseLeftButtonDown", function2); 
 
 }
 
}

// Event handlers
function function1(sender, eventArgs) {
 alert("Function 1.");
}

function function2(sender, eventArgs) {
 alert("Function 2.");
}
 

Here is the example.

Hi Dave. =)

Expanding a bit upon my previous entry on animated clipping paths, here is an example that may have a more practical application - using an animated clipping path to create glowing edge effects. Mind you, it's not the prettiest example in the world, but it illustrates my point.

For this project, I used two images. One is the outline of the state of Oregon, and the second is the same outline with a different color and a 10px outer glow filter applied in Photoshop (it's a little hard to see the glow on the second image here on a white background).

base image

glow image

As you might expect, the base layer is brought into Blend as an image object, as is the glow layer. Both were set to the same dimensions (590x481), and the same position so that they overlay. I then created a canvas object the same size as the images named "clip".

For the glow effect, I want it to go from the bottom left of the image to the top right, so I added a rectangle that was 10px wide, rotated it around 45 degrees or so, and made sure it was tall enough to span the entire outline of Oregon at the widest point. With the rectangle positioned at the lower left of my outline image and selected on the Objects palette, I Ctrl+clicked the "clip" canvas, right-clicked, and selected Path/Make Clipping Path.

The result is shown in the image below. The glow layer is placed inside the clipping canvas, and will look like it has disappeared since it falls outside of the clipping area.

screen 1

Like the prior example, animation on the clipping path is used to finish the effect. I created a new timeline called "glow", moved the timeline marker to 2, then used the direct selection tool to pick all four control points that make up the rectangle. I then used the arrow keys (+ Shift) to move the rectangle to the top right of the outline.

The last thing to do for this example was to play the timeline, so I added an event handler to the root canvas:

<Canvas
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="800" Height="600"
x:Name="rootCanvas" Background="#FF9B9B9B"
Loaded="canvasLoaded">

And then added the following function to one of the JavaScript files in the project:

function canvasLoaded(sender){
sender.findName("glow").Begin();
}

You can view the example project, or download a zip.

The ability to perform vertex animation on clipping paths opens up some interesting possibilities effects-wise. I spent some time playing around with animated clipping paths, and came up with an example project to illustrate a technique that mimics an EKG readout.

Start by creating a new project in Blend (December preview). Since an EKG is wide, but not very tall, set the canvas width to 800, and height to 285.

In the sample project, I changed the name of the canvas to "rootCanvas" because I use that as my standard naming convention.

Next, add a rectangle that is the same size as the root canvas, select No Fill on the brushes palette, and add a black 1px stroke to the rectangle. This creates a border for the root canvas.

At this point, my project looked like this:

<Canvas
 xmlns="http://schemas.microsoft.com/client/2007"
 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 Width="800" Height="285"
 x:Name="rootCanvas">
 <Rectangle Width="800" Height="285" Stroke="#FF000000" x:Name="canvasStroke"/>
</Canvas>

For the EKG pattern, you can Google "EKG" and find a suitable image over which you will trace. Once you have one, bring it into Blend on it's own layer, and lock the layer to avoid inadvertantly moving it. Create a path object and trace over the EKG shape in the reference graphic. The path used in this example had no fill, and a 1.5px thick stroke that was colored #FF357414. Once you're done tracing, you can remove the reference image and use the direct select tool to tweak the points in your path if necessary. In the example project, the path is named "heartbeatPath". Your project should look something like this:

screen 1

To add a clipping mask, add a new canvas to the project named "clip", and make it the same size as the root canvas. Add a rectangle object that is 800x10 to the clip canvas and position it at -10 left. This will position the rectangle just outside of the clipping canvas. The intention here is to make a very small, off-canvas clipping region that will clip the EKG path object once it is animated.

screen 2

To create the clipping area, click the rectangle, and then while holding the Ctrl key, click the "clip" canvas object. With both selected, right-click the selection on the Objects and Timeline palette and select Path/Make Clipping Path. 

In order for the clipping region to affect the EKG path, the heartbeatPath object needs to be in the clipping canvas. This can be done by dragging and dropping the heartbeatPath object onto the clip object. Once this is done, the heartbeat will disappear because it is being clipped.

The next step is to create the animation to simulate the heartbeat. In Blend, select the New Storyboard button, then click OK on the Create Storyboard dialog that opens (the default values are fine).

The animation for the clipping region is a very simple, linear animation. In the timeline area, move the yellow current frame marker to 2. On the toolbar, click on the direct selection tool, and then click the "clip" object on the objects palette.

Select the leftmost two points on the rectangle, by first clicking the top one, and then Ctrl-clicking the bottom one. Both should be selected. Use either the right arrow key on the keyboard, or the Shift + right arrow combination to move the points to the right until they align with the right edge of the root canvas.

screen 3

The animation is done, so close the storyboard.

This particular animation, should repeat, so edit the storyboard XAML by adding the "RepeatBehavior" property.

<Storyboard x:Name="Storyboard1"> becomes <Storyboard x:Name="Storyboard1" RepeatBehavior="Forever">

At this point, opening the project results in... nothing.

The storyboard doesn't automatically play when the page is opened. To do this, you can use triggers in the XAML, but I have become accustomed to doing it via JavaScript - if there are a lot of storyboards running, I find it easier to catch problems when I am controlling them from script. If your preference is to use triggers, that's fine too.

Open the default.html file in your favorite editor, and add the following code beneath the other JavaScript references:

 <script type="text/javascript" src="Silverlight.js" mce_src="Silverlight.js"></script>
 <script type="text/javascript" src="Default_html.js" mce_src="Default_html.js"></script>
 <script type="text/javascript" src="Page.xaml.js" mce_src="Page.xaml.js"></script>
 <script type="text/javascript" language="javascript">
 <!--//
  function canvasLoaded(sender) {
   sender.findName("Storyboard1").begin();
  }
 //-->
 </script>

Also, be sure to update the silverlightControlHost height and width style to match the dimensions of the root canvas.

This script locates the storyboard named "Storyboard1", and tells it to begin. The function is called "canvasLoaded", but it has not yet been linked to an event, meaning nothing will call the script and cause it to do its thing. That is the last thing you will need to do to the XAML file, so head back over to Blend and edit the rootCanvas XAML to include the "Loaded" event:

<Canvas
 xmlns="http://schemas.microsoft.com/client/2007"
 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 Width="800" Height="285"
 x:Name="rootCanvas"
 Loaded="canvasLoaded">

Opening the default.html file in a browser at this point should produce something similar to this.

From a high level, there's a lot of flexibility in this example. The path color can easily be changed by changing the color of the stroke. The path can have an image inserted behind it to act as a backdrop, or a fill color can be added to the rectangle to put some color behind the EKG pulse. With some simple modifications to the timeline, it's easy to access the keyframes by script, and by manipulating those values over time, cause the pulse to speed up.

Download the example.

As I spend increasing amounts of time writing increasingly complex Silverlight apps, I find that using the full references for objects can quickly become tedious to type in - sender.findName("productPanel")["Canvas.Left"], etc. I started using global variables that are assigned on canvas load to make the references less of a hassle. Given the example above, the code might look something like this:

var main;
var panel;

function canvasLoaded(sender){
main = sender.findName("rootCanvas");
panel = sender.findName("productPanel");
}

Now when I need references elsewhere in the code, something like sender.findName("productPanel")["Canvas.Left"] simply becomes panel["Canvas.Left"]. It also means that I don't need to pass the sender into every function that I write (not that it takes up THAT much space, but it's less clutter in the code). So a function to display the width of the root canvas might look like this:

function displayWidth(){
alert(main.width);
}

I feel like it makes the code a little easier for me to read - you get the object name and the property in one short description.

Objects in Silverlight give you a handful of choices for the cursor - none, arrow, ibeam, wait, and hand. It may seem a little limited, but it's fairly straightforward to make your own custom cursors.

Here's a bit of XAML that will create a canvas with a gradient fill, and a red ellipse.

<Canvas
 xmlns="http://schemas.microsoft.com/client/2007"
 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 Width="800" Height="600"
 x:Name="rootCanvas">
 <Canvas.Background>
  <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
   <GradientStop Color="#FF000000" Offset="0"/>
   <GradientStop Color="#FFFFFFFF" Offset="1"/>
  </LinearGradientBrush>
 </Canvas.Background>
 <Ellipse Width="25" Height="25" Fill="#FFFF0000" Stroke="#FF000000" x:Name="myCursor"/>
 <Rectangle Width="800" Height="600" Stroke="#FF000000" Canvas.Left="0" Canvas.Top="0"/>
</Canvas>

This will give you a scene that looks like this: 

We want to make the red ellipse element, named "myCursor", follow the mouse, so add a mouse move event to the main canvas:

<Canvas
 xmlns="http://schemas.microsoft.com/client/2007"
 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 Width="800" Height="600"
 x:Name="rootCanvas"
 MouseMove="mouseMove">

Now that the event is hooked up, we will need to create the function being called. Edit the <head> portion of the Default.html file to include a JavaScript file that will contain the mouseMove function:

 <script type="text/javascript" src="Silverlight.js" mce_src="Silverlight.js"></script>
 <script type="text/javascript" src="Default_html.js" mce_src="Default_html.js"></script>
 <script type="text/javascript" src="Page.xaml.js" mce_src="Page.xaml.js"></script>
 <script type="text/javascript" src="cursor.js" mce_src="cursor.js"></script>

Since you're in the Default.html file, you may also want to modify the styles so that the height and width styles reflect the size of the root canvas (800x600).

Next, create a new file called cursor.js, and save it in the same directory as the Default.html file. This is where the mouseMove function will live.

The mouseMove function will get the current coordinates of the mouse as it moves, and reposition the red ellipse to those coordinates. The function looks like this:

function mouseMove(sender, mouseEventArgs){
 var cursor = sender.findName("myCursor");

  var mouseX = mouseEventArgs.getPosition(null).x;
  var mouseY = mouseEventArgs.getPosition(null).y;
 
  cursor["Canvas.Left"] = mouseX;
  cursor["Canvas.Top"] = mouseY;

}

Notice that we've set up a variable called "cursor" to give us shorthand access to the "myCursor" element in the XAML file. Instead of having to type out sender.findName("myCursor")... each time we want to access that element, it's simply referenced as cursor.

At this point, if you run the file, you'll see that as you move the mouse, the red ellipse will follow.

You may notice that there are two problems. First, the red ellipse is positioned such that the mouse pointer is at the top left of the element. This is expected given that we are assigning the top left of the element's bounding box to the current cursor position. In this case, it really doesn't matter, but if you were trying to use a crosshair or needed some pinpoint accuracy, it would be a problem. To fix it, you'll need to move the red ellipse so that the cursor aligns with the middle of the element. To do this, make a simple change to the cursor positioning code:

  cursor["Canvas.Left"] = mouseX - cursor.width/2;
  cursor["Canvas.Top"] = mouseY - cursor.height/2;

This moves the cursor so that the center point of the ellipse is now positioned over the current mouse coordinates.

The second problem is that the arrow mouse cursor is still showing. Again, this is not a problem here, but if you were using a custom crosshair cursor or something similar, you may not want the default cursor to show. To make this change, the cursor property for the root canvas is set to "none":

<Canvas
 xmlns="http://schemas.microsoft.com/client/2007"
 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 Width="800" Height="600"
 x:Name="rootCanvas"
 MouseMove="mouseMove"
 Cursor="None">

Now run the project and you will see that the red dot moves and has become your custom cursor. Download (6.5K ZIP)

I updated the gallery wall app this morning. I felt like the reflections were a little over the top, and it looks like the version I put out there had the gradient masks a little misplaced. I updated the main zip file download. You can see the updated app here: View

If you want just the file to update your own installation, only the Page.xaml file was updated, and can be downloaded by right-clicking and picking "Save target as" here: Download xaml In the root folder of the application, rename your existing one to page.old, and then drop this one in. Keep in mind if you've made any customizations to the XAML, this change will overwrite them, so you will need to migrate the changes from page.old into this file.

Photo Gallery Wall

An attractive image gallery containing 16 images. Images and their captions (if configured) can be viewed by clicking. If no images are selected, the gallery will automatically switch to slideshow mode after 10 seconds. Includes a how-to document that provides an overview of the creation process using Blend and detailed description of functionality! View     Download (706K ZIP)

Silverlight Tooltips

A 4K script that allows you to add popup tooltip functionality to objects in your Javascript Silverlight applications.

View    Download (99K ZIP)