Welcome to MSDN Blogs Sign in | Join | Help

Adam Kinney posted all of the Fire Starter videos and the site has a cool Silverlight UI.

firestarter_500

 

I will be speaking at the Silverlight 1.0 Fire Starter event on November 29th along with a bunch of my colleagues. My two sessions are "Media, Markers and More" and "What will Silverlight look like in future versions? WPF".  The first session goes into the gory details of working with Media in Silverlight 1.0. How you can use dynamic and static markers to bring a media experience some spice. And some of the pitfalls to avoid.  The second session is an introduction to programming with WPF and how what we see in WPF today will be reflected in Silverlight tomorrow.   If your interested in attending the check out the invite below!

image

On November 29, 2007 Microsoft will be hosting Silverlight 1.0 Fire Starter on the Redmond, Washington campus. This daylong event is free to anyone who wants to learn about designing and developing with Microsoft Silverlight 1.0.

Microsoft Silverlight 1.0 is a cross platform browser plug-in that enables for easy development of media rich web sites. For more information, visit http://silverlight.net.

November 29, 2007
Microsoft Redmond Campus
1 Microsoft Way
Redmond, WA
Building 33, Kodiak Room
** Please have a photo ID with you to register onsite and park

Check-in:
8:00 am
Event: 8:30 am – 5:00 pm
Register: http://msevents.microsoft.com/CUI/EventDetail.aspx?EventID=1032359153&Culture=en-US
or by calling 1-877-673-8368 and referencing Event ID 1032359153

AGENDA

8:00 am – 8:30 am Breakfast  
8:30 am – 9:00 am Introduction to Silverlight Mithun Dhar
9:00 am – 10:00 am Getting Started with Silverlight Laurence Moroney
10:00 am – 10:15 am Break  
10:15 am – 11:15 am Microsoft Expression Design Tools Arturo Toledo
11:15 am – 12:15 pm XAML Essentials for Silverlight Laurence Moroney
12:15 pm – 1:00 pm Lunch  
1:00 pm – 2:00 pm Developer Tools for Silverlight Adam Kinney
2:00 pm – 3:00 pm Media, Markers and More Ernie Booth
3:00 pm – 3:15 pm Break  
3:15 pm – 4:15 pm Popfly and Silverlight – Writing a Silverlight ‘Social’ app Popfly Team
4:15 pm – 5:00 pm What will Silverlight look like in future versions? WPF Ernie Booth
Post Event Evening event with XBOX, networking and refreshments  
My officemate and friend Laurence Moroney's Silverlight 1.0 book is now available.  Check out his detailed post here: http://blogs.msdn.com/webnext/archive/2007/10/27/introducing-silverlight-1-0-book-is-now-available.aspx

Jamie Cansdale of Test Driven.Net has improved the Reflector Silverlight Add-in and started a community project here.  If you have improvements, bugs, or features request please posted them to the community site.

Jamie changed it so that the C# or VB doesn't get displayed in tabs by defaut as this doesn't scale well for larger projects. You can still digg through the code as the assembly is added to Reflector.  He tested against all the samples on the Silverlight.net Site and found that the add-in didn't scale well for the airplane sample.

Jaime also has done some great work on integrating unit testing with Silverlight .NET that I would suggest checking out.

One of the nice advantages to Silverlight is that you can view the source of a site to see how things work, but with Silverlight 1.1 Alpha that process became more complex.  While you can still fish through the JavaScript and Xaml to find the .NET assembly(dll) that is doing all the logic it takes a lot more time.

Thus I decided to write a plugin for Lutz Roeder's Reflector that takes a URL to a Silverlight page and finds the assembly for that page.  It also loads up the JavaScript and root Xaml for the page.

Download

Here is all the source and assembly for the 0.2 Alpha of the Reflector Silverlight Browser.

Install

To install the plugin:

  1. Put the "Reflector.SilverlightBrowser.dll" file in the same directory as you have Reflector installed. 
  2. Launch Reflector
  3. Choose "View->Add-ins"
  4. Click Add
  5. Choose the Reflector.SilverlightBrowser.dll file.
  6. To test the plugin try the chess game sample from Silverlight.net

Using the Plugin

Once it is installed hit "CRTL+U" or "File->Open Silverlight URL". 

Paste a link to your favorite Silverlight site into the textbox and hit go.

 

Protecting Your Code

So the feedback I get when I tell people about this plugin is how can protect the IP in my code from being seen by other people. I have two pieces of advice on that subject.  First option is to keep the code you want to keep protected on your server and then make webservice calls from your client to that code.

The second option is to use Obfuscation.  What is Obfuscation you say? "In the context of software, obfuscation is the process of scrambling the symbols, code, and data of a program to prevent reverse engineering." - CLR and .NET Security blog.

If you would like to learn more about Obfuscation check out this great post.

Bugs and Features

First off I wrote this in a matter of a day or two with some great guidance from Lutz Roeder so a big thank you to Lutz. Thus there are inevitably going to be a number of bugs in the code.  You have code so feel free to improve it and use it as you like.

The major issue I am going to hear about is that some particular site doesn't work correctly, if you find a site that doesn't work that you would like to have work please send me the link.

Second what features are missing that you would like to see?

Third I would love to hear any other feedback you might have to offer.

Enjoy!

The .NET framework version Silverlight targets is the same that C# 3.0 targets and so we are able to take advantage of the new language features in C# 3.0. The LINQ (Language INtegrated Query) features of C# 3.0 has gotten the most attention of any of the new features, but some of the other features are my favorite additions to the language. I will a link to learning more about LINQ at the end of this post.

Let's start looking at the feature in C# 3.0 by looking at the code listing from my previous post.  Here it is as I wrote it:

C# without 3.0 Features

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

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

            parentCanvas = (Canvas)FindName("parentCanvas");

            this.ParentCanvas.Background = new SolidColorBrush(Color.FromRgb(255, 0, 0));

            Ellipse childShape = new Ellipse();

            childShape.Width = 100;
            childShape.Height = 100;

            LinearGradientBrush lgb = new LinearGradientBrush();

            GradientStop gs1 = new GradientStop();
            gs1.Color = Color.FromRgb(0, 0, 255);
            lgb.GradientStops.Add(gs1);

            GradientStop gs2 = new GradientStop();
            gs2.Color = Color.FromRgb(0, 255, 0);
            gs2.Offset = 1;
            lgb.GradientStops.Add(gs2);

            childShape.Fill = lgb;

            this.parentCanvas.Children.Add(childShape);
        }

        private Canvas parentCanvas;

        public Canvas ParentCanvas
        {
            get { return parentCanvas; }
            set { parentCanvas = value; }
        }
    }
}

There is nothing particularly special about the previous code listing, but a few item's to notice. First in the Page_Loaded method it is difficult to descern what the intent of the programmer was.  Unlike Xaml where everything is declarative the code above is very procedural. All that is happening of course is that the program is displaying a red rectangle containing a small circle with a blue to green gradient color.  Something like this:

Result

This was so non obvious from the code above I original thought we were trying to display a red rectangle containing a smaller rectangle with a blue to green gradient color.  My eyes scanned the program above and failed to notice that the code was creating and Ellipse instead of a rectangle.

If had tried to achieve this same result with xaml the code would look this this:

<Canvas x:Name="parentCanvas"
        xmlns="http://schemas.microsoft.com/client/2007" 
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
        Loaded="Page_Loaded" 
        x:Class="ManagedSilverlight101.Page;assembly=ClientBin/ManagedSilverlight101.dll"
        Width="640"
        Height="480"
        Background="Red"
        >
  <Ellipse Width="100" Height="100">
    <Ellipse.Fill>
      <LinearGradientBrush>
        <GradientStop Color="Blue"/>
        <GradientStop Color="Green" Offset="1"/>
      </LinearGradientBrush>
    </Ellipse.Fill>
  </Ellipse>
</Canvas>

This is much easier to quickly read and understand what the program will do. The problem of course is that often times we don't have the ability to statically define our display because of the dynamic nature of a given program. This is where the features in C# 3.0 come into play.  Below is a code listing that I have transformed from our original code to instead use the new features of the language.

C# 3.0 Style

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Collections.Generic;

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

            this.ParentCanvas = (Canvas)FindName("parentCanvas");

            this.ParentCanvas.Background = new SolidColorBrush(Color.FromRgb(255, 0, 0));

            var stops = new GradientStopCollection { 
                            new GradientStop { Color = Color.FromRgb(0, 0, 255) },
                            new GradientStop { Color = Color.FromRgb(0, 255, 0), Offset = 1 } };

            var lgb = new LinearGradientBrush() { GradientStops = stops };

            var childShape = new Ellipse { Width = 100, Height = 100, Fill = lgb };

            this.ParentCanvas.Children.Add(childShape);                
        }
        public Canvas ParentCanvas { get; set; }   
    }
}

We have reduced the length of our program and increased it's readability. It in fact is very declarative like the Xaml. Let's examine the new features of C# 3.0 I am using in this example.  First after the call to the method InitializeComponent we are assigning the property ParentCanvas from the result of the FindName.  This code on it's own isn't any different, but if our eyes scan to the bottom of the Page class we have the line:

public Canvas ParentCanvas { get; set; }

This line is defining a new property named Canvas. Notice that we didn't have to create a separate field to hold the data that is being set by the ParentCanvas property.  This is a great shortcut to creating properties which is a good programming practice since in the feature we can change how the data is stored or retrieved without breaking the Page interface.

The next change to our program is the statement:

var stops = new GradientStopCollection { new GradientStop { Color = Color.FromRgb(0, 0, 255) },
                                                                   new GradientStop { Color = Color.FromRgb(0, 255, 0), Offset = 1 } };

Implicitly Typed Local Variables

This is using a few new features of C#. The first is this part of the statement:

var stops = new GradientStopCollection

We are creating a variable named stops which is of type GradientStopCollection.  It is often cumbersome to write the name of the type on both sides of an assignment statement. By placing the var keyword before the name of our variable we are telling the compiler to infer the type information for stops from the type on the right hand side of the assignment operation.  In this case a GradientStopCollection. 

Unlike JavaScript or other loosely typed languages the local variable stops is strongly typed.  Indeed if we were to say something like this after the creating of the stops variable:

 stops = "Hello";

The compiler will throw the error: Cannot implicitly convert type 'string' to 'System.Windows.Media.GradientStopCollection'.  This is because stops is in fact an instance of a GradientStopCollection. The var keyword is simply a shorthand convenience.

Collection Initializers

The next C# feature is in this part of that statement:

new GradientStopCollection { ... };

It is creating a new GradientStopCollection and then calling Add() passing each element in between the curly braces.  A simpler example looks like this:

List<int> digits = new List<int> { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

and is semantically the same as this code:

 List<int> digits = new List<int>();
            
 digits.Add(0);
 digits.Add(1);
 digits.Add(2);
 digits.Add(3);
 digits.Add(4);
 digits.Add(5);
 digits.Add(6);
 digits.Add(7);
 digits.Add(8);
 digits.Add(9);

And if fact this is what the compiler is generating for us when we use this syntax.  It is called a collection initializer is a natural extension to how we were already able to assign items to an array:

int [] digits = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

Object Initializers

The last new feature in that same statement applies to the elements we are adding to the collection:

new GradientStop { Color = Color.FromRgb(0, 0, 255) }, 
new GradientStop { Color = Color.FromRgb(0, 255, 0), Offset = 1 }

Here we are using a feature called Object Initializers. Just like a Collection Initializers we are using a curly brace instead of a parentheses after:

new GradientStop {

Then we are have some code that looks like this:

Color = Color.FromRgb(0, 255, 0), Offset = 1

If this looks like property assignment that's because it is.  We are simply setting the Color and Offset properties of the GradientStop objects we are creating. That produces much cleaner code then the alternative syntax:

GradientStop gs2 = new GradientStop(); gs2.Color = Color.FromRgb(0, 255, 0); gs2.Offset = 1;

In fact when we combine all three of those features into one statement we are left much easier to read code:

var stops = new GradientStopCollection { new GradientStop { Color = Color.FromRgb(0, 0, 255) },
                                                                   new GradientStop { Color = Color.FromRgb(0, 255, 0), Offset = 1 } };

Compared to:

LinearGradientBrush lgb = new LinearGradientBrush();

GradientStop gs1 = new GradientStop();
gs1.Color = Color.FromRgb(0, 0, 255);
lgb.GradientStops.Add(gs1);

GradientStop gs2 = new GradientStop();
gs2.Color = Color.FromRgb(0, 255, 0);
gs2.Offset = 1;
lgb.GradientStops.Add(gs2);

Also notice that we didn't have come up with local variable names for our GradientStops.  Which also makes for easier to maintain code.

The last two changes to our program are just making use of the same new C# features described above:

var lgb = new LinearGradientBrush() { GradientStops = stops };

var childShape = new Ellipse { Width = 100, Height = 100, Fill = lgb };

 

Conclusion

As you have seen some of the new features is C# 3.0 make code much more readable and hopefully maintainable. There are a lot of other exciting features in both C# and VB thanks to LINQ. If you are interested in learning about all other great new features check out this great MSDN article on LINQ by Anders Hejlsberg and Don Box.

To begin our .NET Silverlight learning we will start off with an overview of .NET. For those are used to developing Silverlight applications with JavaScript, we will be concentrating on the differences between using JavaScript and .NET.  If you haven't written a Silverlight application before this will also be a good introduction to using .NET.  If .NET is old hat for you by now then most of this will be review, but there are still some important differences with regard to how Silverlight works compared to WPF (Windows Presentation Foundation).

Why .NET?

A question that often comes up when we talk about .NET and Silverlight is why do we need .NET when JavaScript has already proven it's self as a great way to develop web applications.  Four reasons spring to mind right way. 

Consistency is the first reason compared to JavaScript, not in the core EcmaScript it's self, but more on the DOM side or how we access the page elements.  For each browser the DOM is different and only an experienced web developer knows how to write code that works well in all browsers.  With Silverlight we bypass most of this problem as we are targeting XAML and the Silverlight object model instead of the browsers object model. The other part of this problem is that when you are using JavaScript you are relying on each browser's implementation of the JavaScript execution engine.  This is where .NET differs.  When we write a .NET application it is the Silverlight plugin which handles the execution of our code so there are no differences from one browser to the next. It is the same on Mac, Windows, Firefox, Safari and IE!

Performance is a great reason to use .NET over JavaScript.  Scott Guthrie had a great demonstration in his MIX keynote which showed off a chess game which implemented chess algorithms.  Once in JavaScript and once with .NET.  The performance comparison between the two was staggering.

Type Safety is one that rings home with me.  I love the fact that when I compile I get feedback that my code is wrong and I have a chance to fix my mistakes before running the application. While Type Safety is great there are a lot of features in JavaScript which I have grown to enjoy. C# 3.0 has some new language features such as local type inferencing, anonymous types, extension methods and type intializers which allow for quick development similar to JavaScript while maintaining Type Safety.  We will get to some of those features a bit later.

.NET Framework is the most compelling reason to use .NET.  The framework is a collection of libraries for a variety of tasks, from UI development to security and networking. While the Silverlight version of the .NET framework is much smaller then the full .NET Framework it can still be a lot to learn. I remember when I first started with .NET back in 2000 I would write some code to do x and then later I would discover that there was already a class which did x.  Since then a lot has been written so we can search to see what libraries we can use, but there is still no replacement for discovering all that the .NET Framework has to offer.

There are a bunch of other reasons to use .NET like the support for multiple language 37 at last count including, IronPython, IronRuby, JavaScript, Cobal, PHP, C#, VB, C++\CLI, Ada, Boo, Chrome, Eiffel, Forth, FORTRAN, Haskell and many more. Here is a a more complete list.

Desktop .NET vs. Silveright .NET

There are a few differences between the full .NET Framework and the version that ships with Silverlight 1.1 Alpha.

Cross Platform, Cross Browser is very important to Silverlight and so just like the rest of Silverlight the .NET components will work on Mac and Windows.  They will also run in FireFox, IE and Safari.  There is no difference between the framework when running on different platforms.

Size of the Silverlight .NET version is about 4 MBs in the 1.1 Alpha.  This is significantly smaller then the full framework which is around 50 MBs.  In order to maximize user experience the size and install time were greatly reduced so that users with out Silverlight can still be targeted by Silverlight web sites.

Sandbox model is a way to provide full .NET applications in Silverlight without having to prompt users for install.  It provides a safe way to run applications, but has a few restrictions.  There is limited file access, so that you can't just start enumerating the files on a users machine. Networking support, no socket support. No elevation prompts and no ability to escape the sandbox.

 

Getting Started

The first place to start is to define what is required to create a .NET Silverlight application.  There are four basic ingredients in any Silverlight 1.1 program:

  1. An html file to hose our Silverlight plugin with.
  2. JavaScript files which load the plugin with the appropriate settings such as Height, Width and Windowless.
  3. A XAML file which defines the look of our root page and more importantly points at our .NET code to run.
  4. .NET Assembly (dll) which houses all of the logic from our .NET code.

 

What you need to Install

  1. Silverlight 1.1 Alpha
  2. Visual Studio "Orcas" Beta 1
  3. Silverlight Tools for Visual Studio "Orcas" Beta 1

 

Creating a New Silverlight .NET project in Visual Studio

Now that you have everything installed lets start by having visual studio create a project for us.

Open Visual Studio "Orcas". 

NOTE: If this is the first time you have opened Visual Studio you will be prompted to choose which setup you want, C#, VB, C++, etc.  This simple configures menus, and toolbar layout, it doesn't restrict you from creating projects with the other languages.

  1. From the menu choose File->New->Project (CRTL+SHIFT+N)
  2. In the Dialog that pops up expand the Visual C# node and choose the Silverlight node.
  3. Make sure the Silverlight Project is highlighted.
  4. In the name field at the bottom of the dialog write "ManagedSilverlight101"
  5. Click OK

Visual Studio will create a project containing a number of files for us. On the right in Visual Studio our Solution Explorer should look like the image below:

We will start exploring each of the files to see what our Silverlight project is made of.  We will begin with TestPage.htm. Double click it in the solution explorer and it will open in Visual Studio.

TestPage.htm

Here are the contents of our html file:

<html xmlns="http://www.w3.org/1999/xhtml">
<!-- saved from url=(0014)about:internet -->
<head>
<title>Silverlight Project Test Page </title>
<script type="text/javascript" src="Silverlight.js" mce_src="Silverlight.js"></script>
<script type="text/javascript" src="TestPage.html.js" mce_src="TestPage.html.js"></script>

</head>

<!-- Give the keyboard focus to the Silverlight control by default -->
<body onload="document.getElementById('SilverlightControl').focus()">
<div id="SilverlightControlHost" >
<script type="text/javascript">
createSilverlight();
</script>

</div>
</body>
</html>

This is a very simple test page and there are only a few tags which we care about on this page.  The first is the two script tags which point to Silverlight.js and TestPage.html.js.  The first is a file we will include in every Silverlight application we write.  It contains functions to detect if Silverlight is installed and give us information about version.  It also contains two functions createObject() and createObjectEx() which we will call create the Silverlight plugin on our page.

The other part of this page which is important to note is the call createSilverlight(); which is calling a method in TestPage.html.js.

Silverlight.js and TestPage.html.js

Now open TestPage.html.js from the Solution Explorer.  You might have to expand the TestPage.html file by clicking on the plus symbol. Below is the code listing from TestPage.html.js:

function createSilverlight()
{
    Sys.Silverlight.createObjectEx({
    source: "Page.xaml",
    parentElement: document.getElementById("SilverlightControlHost"),
    id: "SilverlightControl",
   

    properties: {
        width: "100%",
        height: "100%",
        version: "0.95",
        enableHtmlAccess: true
        },
        events: {}
    });
}

As you can see the only function in the JavaScript file is createSilverlight().  This function is just calling a function Sys.Silverlight.createobjectEx() which is found in the Silverlight.js file.  The two important items to note for now are the source paramter which is set to Page.xaml and the parentElement parameter which is being set to the result of document.getElementById("SilverlightControlHost").  It is important to make sure that the name passed into getElementById here matches the id of our div in our html file.  Both are highlighted in green above. 

The Page.xaml file is the name of the file where all our XAML exists. This is the first page which the Silverlight plugin will load after the call to createObjectEx.

XAML

Xaml (extensible Application Markup Language) is an XML language which describes our user interface. Open the Page.xaml file by double clicking on it in the Solution Explorer.  Below is the Xaml in Page.xaml:

<Canvas x:Name="parentCanvas"
xmlns="
http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Loaded="Page_Loaded"
x:Class="ManagedSilverlight101.Page;assembly=ClientBin/ManagedSilverlight101.dll"
Width="640"
Height="480"
Background="White"
>

</Canvas>

We have a single element a Canvas which has a width of 640 and a height of 480. There are two important attributes to observer in the Xaml.  The first is the Loaded attribute which simply sets the Loaded event to a method called Page_Loaded. 

The second attribute and the one that says we are using .NET vs. JavaScript is the x:Class attribute.  Let's break apart the the statement that is being assigned to x:Class. 

ManagedSilverlight101.Page specifies that the class we want to load is called Page which is in the ManagedSilverlight101 namespace. We will look at the code in a minute. 

assembly=ClientBin/ManagedSilverlight101.dll says that the Page class in the namespace ManagedSilverlight101 is stored in the ManagedSilverlight101.dll which is located in a relative directory called ClientBin.

As a review a dll is just a file which stores code.  However instead of storing it as text the compiler has transformed the code into an intermediate language called MSIL (Microsoft Intermediate Language).  The reason we compile the code is so that we can guarantee the programmer didn't make any typing mistakes while writing the program.  In .NET we call the dll files assemblies.

For those familiar with WPF a big difference between Silverlight and WPF is that Xaml is never compiled, only our .NET code gets compiled.  However we may still store the Xaml as an embedded resource in the dll.

.NET Code

Now open the Page.xaml.cs file from the solution explorer.  You might have to expand Page.xaml by clicking on the plus sign to find the Page.xaml.cs file below it. Here is the code from the Page.xaml.cs file:

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

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

All of the code above is written in the language C# which has very similar syntax to JavaScript. I am not going to go into the details of C# here, but I will cover enough to explain what is happening in our small program. I have intentionally left out details to make understanding simpler. I would suggest picking up a good C# book if you are interested in learning more about the language or pickup a book on your favorite .NET language.  There is a new JavaScript implementation that the DLR (Dynamic Language Runtime) team is working on which can also be used to write .NET programs.

At the top of the code we have nine lines which all start with the word "using" followed by something like System.Windows.Ink.  These statements are just saying that we want to use the classes in those namespaces. Namespaces allow us to scope classes, methods and variables. It is very similar to using a script tag to import other JavaScript files that you are using, except that we don't have to import everything defined in the file, only the parts we care about.

Then we have lines of code like this:

namespace ManagedSilverlight101

{

This simple says that we want to create all the code contained in the curly braces in the ManagedSilverlight101 namespace.

Next we have the lines:

public partial class Page : Canvas
{

Here we are creating a new class named Page which derives from the class Canvas.  This means our Page class will take on all the properties, methods, fields and events of the Canvas class and then we can add new behavior and data to the Page class to make it do what we want. Classes are similar to JavaScript prototypes.

Now we have the lines:

public void Page_Loaded(object o, EventArgs e)
{

With this line we are defining a function called Page_Loaded that takes two parameters.  C# requires parameters to be typed. The first parameter is called o and is of Type object.  object is the base type of every other type in .NET so any type could be passed into the Page_loaded function.  The second parameter is called e and is of type EventArgs.

The Page_Loaded function is actually an event handler.  If you remember that in our Xaml we defined an event handler for the Loaded event to be "Page_Loaded".

The final part of our small program is:

InitializeComponent();

This is just a call to the function InitalizeComponents(). Currently this method doesn't do anything, but if you put the cursor on the method and right click you will get a context menu.  Then choose "Go To Definition" and Visual Studio will open a new file.

 

The file is called Page.g.cs and is generated by Visual Studio on our behalf.  Here are the contents of the file:

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

namespace ManagedSilverlight101
{
    public partial class Page
    {
        // variable declarations
        private void InitializeComponent()
        {
        }
    }
}

As you can see this looks very similar to our class defined in Page.xaml.cs. In fact it has the same namespace and class name.  This is because it is actually the same class we were defining in the Page.xaml.cs file, except that there is an additional function declared called InitializeComponent(). The partial keyword before the class keyword tells the compiler that we want to extend the other class we are defining in another file, not create a new class. Partial classes are owned by the designer (which we don't yet have with Silverlight) and by the compiler when we update our xaml file. It is a place where generated code can be put without messing up code written by programmers.  Most of the time we aren't going to need to look at this file and we will almost never have to edit it, so close that tab by clicking on the X at the top, right of the tab page.

 

Creating Visual Elements with Code

Now that we have a grasp of what Visual Studio created for us we can start writing code.  So switch or open the file Page.xaml.cs.

Xaml to .NET

After the Page_Loaded function definition we are going to add a property to hold the root element of our Xaml file. Start by typing in lowercase: 

prop

And then hit TAB twice.  The first time closes the intellesense that popped up when we typed prop and the second time will create a code snippet for us. Notice that the work int is highlighted with a blue background:

Now type the word Canvas with the uppercase C.  C# like JavaScript is a type sensitive language. 

Press the TAB key twice again and myVar will become highlighted in blue. Notice that when you hit TAB that Canvas changed not only where you typed it but also before the work MyProperty. Now we will finish off the property by naming it:

Type: parentCanvas

Press TAB once:

Type: ParentCanvas

Press: Enter

When you hit Enter your cursor will be placed after the property we just created.  It should be defined like this:

 

private Canvas parentCanvas;

public Canvas ParentCanvas
{
    get { return parentCanvas; }
    set { parentCanvas = value; }
}

Now that we have a property to hold our root Xaml element we need to pull the element out of the Xaml and assign it to our new property.

NOTE: In WPF the compiler generates properties for any element you name in Xaml.  Currently you must create and wire up the properties yourself.

In the Page_Loaded function after the InitializeComponent function call add the line:

parentCanvas = (Canvas)FindName("parentCanvas");

This line of code calls the FindName method passing the name of the root element in the xaml file.  In this case we named our property the same as what we named the root element.  This is not required but is a good practice. Then it assigns the result to the parentCanvas field we created when we defined the ParentCanvas property.

Because .NET is typesafe we need to add (Canvas) just before the FindName function.  This bit of code casts what is returned from FindName to the type Canvas. FindName is defined in the class DependencyObject to return the type DependencyObject. DependencyObject is the base type for most everything in Silverlight including the Canvas we declared in our Xaml file, so we are able to cast back to Canvas.

This

Now that we know how to use FindName I am going to introduce a great shortcut.  We don't actually need to call FindName most of the time. The compiler and framework will take care of creating the properties for us.  Remove the lines:

private Canvas parentCanvas;

public Canvas ParentCanvas
{
    get { return parentCanvas; }
    set { parentCanvas = value; }
}

and

parentCanvas = (Canvas)FindName("parentCanvas");

We instead are going to access the parentCanvas through the keywork this.  By placing the x:Class tag in our xaml page we told the compiler that we wanted it to link that xaml snippet to our class.  "this" meaning the current object we are dealing with.  In this case the Canvas that is our root element on our xaml file. So this is evaluates to true:

this == (Canvas)FindName("parentCanvas");

Now we can start manipulating the parentCanvas by changing it's properties and adding children.

Manipulating Properties

The first place to start exploring is to play with the properties of our parentCanvas. We will start by changing the background brush. Where we had:

parentCanvas = (Canvas)FindName("parentCanvas");

Put this line:

this.Background = new SolidColorBrush(Color.FromRgb(255, 0, 0));

The line of code above sets the Background of our Canvas to the SolidColor Red. We start by creating a new SolidColorBrush and pass the color we want to assign in the constructor.  To pass the color we are using the Color classes static method FromRgb. Which takes three arguments of red, green and blue values which range from 0 to 255. In this case we are getting the red color.

We also can access the colors through named colors like this:

this.Background = new SolidColorBrush(Colors.Red));

Now that we have added that we should run our project to see our result. 

Press: CRTL+F5

The project will build and then open the default browser to display our Silverlight application. If something was mistyped you will get build errors. While errors can be frustrating to fix they are a lot more convenient then discovering the issues at runtime.

Adding Children

For the finaly we will add children to our parentCanvas programmatically. We are going to add an Ellipse that has a gradient brush that fades from Blue to Green.  We are going to put this on the line after:

this.Background = new SolidColorBrush(Color.FromRgb(255, 0, 0));

Here is the code listing:

Ellipse childShape = new Ellipse();

childShape.Width = 100;
childShape.Height = 100;

LinearGradientBrush lgb = new LinearGradientBrush();

GradientStop gs1 = new GradientStop();
gs1.Color = Color.FromRgb(0, 0, 255);
lgb.GradientStops.Add(gs1);

GradientStop gs2 = new GradientStop();
gs2.Color = Color.FromRgb(0, 255, 0);
gs2.Offset = 1;
lgb.GradientStops.Add(gs2);

childShape.Fill = lgb;

this.Children.Add(childShape);

Let's breakdown what is happening here as it is a lot of code. To start we are creating an Ellipse called childShape.  Then we are setting withe Width and Height to 100.  Next we create the LinearGradientBrush called lgb that we are going to paint our Ellipse with.  We great two GradientStop objects and set there Color and Offset properties. This should look very similar to what we did above except that we aren't using a SolidColorBrush. 

We have a line after each of our GradientStop declarations that looks like this:

lgb.GradientStops.Add(gs1);

Here we are adding the gradient stop to the linear gradient brush we defined as lgb.

Then we assign the ChildShape's Fill property our linear gradient brush we just created.

Lastly we call:

this.parentCanvas.Children.Add(childShape);

Which adds our ellipse to the Children collection of the parentCanvas.

Conclusions

We have covered a lot of information in this post, but it is a great foundation for writing future applications.  In my next post I will show how the new features of C# 3.0 can make this code a lot simpler.

This week we launched Silverlight at MIX07 and now all the sessions are online at sessions.visitmix.com.  I am watching a great session on LINQ and Silverlight presented by Anders Hejlsberg. LINQ is only one part of the announcements we made this week at MIX.  We also announced that we have support for .NET with Silverlight. I have been waiting for this announcement for some time as I am much more of a .NET programmer then JavaScript.  Over the next few months I will transform my blog to concentrate on Silverlight with .NET.   At the end of the May I will be presenting in Italy and Sweden on Silverlight and .NET, but more about that later.

It is important to understand the different versions of Silverlight currently available:

Silverlight 1.0 Beta: At MIX the Silverlight Beta 1.0 was released.  It contains support for JavaScript based programming.  While I will not be spending a lot of time writing about that here I will be covering a lot of the same APIs.

Silverlight 1.1 Alpha: At MIX we also released the Silverlight Alpha 1.1.  This version supports both JavaScript based programming and .NET.  Additionally it supports the DLR (Dynamic Language Runtime).  Being a language geek I will be spending some time talking about Iron Ruby and Iron Python. In order to follow the samples on my blog for Silverlight you will need the 1.1 Alpha plugin.

 All the bits, samples, video tutorials, and more can be found on Silverlight.net

A few weeks ago I headed over to Gamefest when XNA Express was announced to shoot a Channel9 video with the team.  Check it out.

For those of you not familiar with XNA Express it allows developers to write games using C# and play them on Xbox 360 and Windows.  XNA is built on top of DirectX 9 and makes writing games a lot easier.

Today we are going to look at a few different methods.  The first set of methods allows us to register a PNRP name and the next set of methods allows us to resolve a PNRP name.  A few important things to keep in mind are that the Windows Firewall Client will block traffic so this code will not work between machines unless we add and exception to the Windows Firewall client.  For now you can manually add the exception or work between two instances running on the same machine. PNRP will not resolve a name registered in the same process, so you need to run two separate instances to make PNRP work.  We will come back to the Windows Firewall problem in a feature post when we look at those APIs.

In my last post I talked about the libs and header files required to compile so reference that post if you are having problems.

 

Most of the peer to peer APIs are exposed as native Win32 libraries.  This means we either have to use PINVOKE or write a C++\CLI 2005 wrapper.  I have chosen to write a wrapper, because the peer to peer APIs require special methods to release the memory from the structures they create.  If you are new to writing interop code you should get a copy of “.Net and Com The Complete Interoperability Guide” by Adam Nathan.

 

For a deep understanding of how PNRP works under the covers check out this article.  Here is the code for the register method. We begin by looking a method that allows us to register a PNRP name.  PNRP is a “DNS-like” protocol accept that it is server-less.  Something to keep in mind is I have removed all error checking from this sample to make it easier to understand.

 

HRESULT P2PCom::Peer::PnrpRegister(PWSTR pwzName)

{

      ULONG                       cAddresses = 0;

 

      HRESULT                     hr = S_OK;

     

      // Address locals

      ULONG pcAddresses =     PEER_PNRP_AUTO_ADDRESSES;

      PWSTR pwzPeerName =     NULL;

      PEER_PNRP_REGISTRATION_INFO info;

      HANDLE registration;

     

ZeroMemory(&info, sizeof(PEER_PNRP_REGISTRATION_INFO));

     

      // Create the peer name.

      hr = PeerCreatePeerName(NULL, pwzName, &pwzPeerName);

     

// We need the cloudName and address to register the PNRP name.

      info.pwzCloudName = L"Global_";

      info.cAddresses = pcAddresses;     

           

      // Register the PNRP name in the cloud.

      hr = PeerPnrpRegister(pwzPeerName, &info, &registration);

     

// Clean up

      PeerFreeData(pwzPeerName);

     

      return hr;

}

Breaking down the PnrpRegister method we first call the PeerCreatePeerName function which creates a PNRP name from the string that is passed into the method.  We passed NULL to the first method because we want to create an unsecured PNRP name.  If we wanted to have a secured name then we would have to pass the results from a call to PeerIdentityCreate fuction.  The article I pointed to earlier will explain the difference between secured and unsecured PNRP names in detail, but basically an unsecure name is not unique a secured name is unique. We passed Ernie into this method we would get 0.Ernie as a result of the call to PeerCreatePeerName. The zero dot in front of Ernie means this is an unsecured peername a secured PNRP name would have a long hash of a public key infront of it.  

Next we assign the name of the cloud we want to register the PNRP name in to a PEER_PNRP_REGISTRATION_INFO structure called info.  In this case we are registering in the global cloud which means the internet.  There are other options for clouds shown in the table below from the Windows SDK:

 

Value

Meaning

PNRP_SCOPE_ANY
0

All IP addresses are allowed to register with the PNRP cloud.

PNRP_GLOBAL_SCOPE
1

The scope is global; all valid IP addresses are allowed to register with the PNRP cloud.

PNRP_SITE_LOCAL_SCOPE
2

The scope is site-local; only IP addresses defined for the site are allowed to register with the PNRP cloud.

PNRP_LINK_LOCAL_SCOPE
3

The scope is link-local; only IP addresses defined for the local area network are allowed to register with the PNRP cloud.

 

The other field of the structure we specifiy is cAddresses which we set to PEER_PNRP_AUTO_ADDRESSES. This tells  PeerPnrpRegister how to bind our PNRP name to our IP address.  Last we call PeerPnrpRegister which actually does the PNRP name registration.

The method below is the .NET wrapper method to expose the native method we just looked at.  It simply marshals the name that is passed in and calls the native PnrpRegister method.

void P2PCom::Peer::RegisterName(System::String ^name)

{

      System::IntPtr ^piName = Marshal::StringToBSTR(name);

 

      PWSTR pName = (PWSTR)piName->ToPointer();

       

      PnrpRegister(pName);

 

      Marshal::FreeBSTR(*piName);

}

Now that we have registered a name we need to resolve the name.  IMPORTANT: you cannot resolve a PNRP name register in the same process you are doing the resolve in, this is a big gotchya when first developing with this API.  Take a look at the source code listing then we discuss what is happening here.

IPAddress ^P2PCom::Peer::ResolvePeerNameCommand(PCWSTR pwzName)

{

      HRESULT                 hr = S_OK;

      PPEER_PNRP_ENDPOINT_INFO pEndpointInfo = NULL;

      ULONG                   cEndpoints = MAX_ENDPOINTS_TO_RESOLVE;

 

      // Perform a synchronous resolve

      hr = PeerPnrpResolve(pwzName, L"Global_", &cEndpoints, &pEndpointInfo);

     

if(pEndpointInfo == NULL)

      {

            return IPAddress::None;

      }

     

      WCHAR   wzAddr[MAX_PEERNAME_LENGTH];     

 

      // Display associated addresses

      for (int i = 0; i < pEndpointInfo->cAddresses; i++)

      {

            DWORD dwLen = (sizeof(wzAddr) / sizeof(wzAddr[0]));

           

            // Make sure the address is IPv6

            if(pEndpointInfo->ppAddresses[i]->sa_family == AF_INET6)

            {

 

                  hr = WSAAddressToString(

                (LPSOCKADDR) pEndpointInfo->ppAddresses[i],

                sizeof(SOCKADDR_IN6), NULL, wzAddr, &dwLen);

            }

      }

 

// Release resources.

PeerFreeData(pEndpointInfo);

 

      IPAddress ^ip = IPAddress::Parse(gcnew System::String(wzAddr));

 

    return ip;

}

We only make one P2P PNRP API call in this method to PeerPnrpResolve which attempts to do a sycronous resolve of the method.  There are also async versions of this method which make sense to use in GUI applications as this method can takes a few seconds to return.  It will return all the pnrp end points it found in the pEndpointInfo argument. That is end points plural because I could register the same end point at work, home and on my laptop, also with unsecure names there will likely be other people with the same end point name as mine.

Next check to see if we received any end points and then loop through all the end points we received.  We only care about those addresses that are IPv6 for this sample.  Change the last endpoint into a string and save it in the wzAddr variable using a WinSock helper method. Then we create a managed IPAddress by calling it’s Parse method on the address string. 

Last thing is to write a wrapper method so that .NET applications can call this method.

System::Net::IPAddress ^P2PCom::Peer::ResolveName(System::String ^name)

{

      System::IntPtr ^piName = Marshal::StringToBSTR(name);

 

      PWSTR pName = (PWSTR)piName->ToPointer();

     

      IPAddress ^ip = ResolvePeerNameCommand((PWSTR)pName);

 

      Marshal::FreeBSTR(*piName);

     

      return ip;

}

In my full class I move all the start up an shutdown code to my constructor and destructor as shown here:

P2PCom::Peer::Peer()

{

      PeerPnrpStartup(PNRP_VERSION);

}

 

P2PCom::Peer::~Peer()

{

      PeerPnrpShutdown();

}

There is a lot more to PNRP and there is a great sample in the SDK located here:

“C:\Program Files\Microsoft SDKs\Windows\v6.0\Samples\NetDs\PeerToPeer\PNRP”

 

This week we are going to start exploring the peer to peer (p2p) in Windows Vista.  Before we get into any code an overview of the p2p APIs available in Windows Vista  is in order.

PNRP (Peer Name Resolution Protocol) is like server-less DNS.  It allows clients to register secured and unsecured peer names that can be resolved over the internet.  Currently large data centers are required to host services that PNRP has the potential to replace.  PNRP gives people the ability to discover a friend and see their presence online and then play a game or work on a project together.

PNM (People Near Me) allows for dynamic discovery and invitation of computers running Windows Vista on the same subnet or Ad-hoc wireless.  This means you can play games with people in the airport, swap photos with your friends or collaborate with colleagues. For example Windows Meeting Space uses PNM.

Peer Naming API allows email providers to tie PNRP names to email addresses.  Then any program that uses the WinSock APIs can resolve registered email addressesThis means you can literally ping a friend or put their email address where you would have to put an IP address in a game.

Graphing is a mesh networking API.  It allows groups of people to share information and collaborateA game could create a mesh of online players so that it could do server-less match making. Or an evangelism team could collaborate on a document without having to upload the latest copy to a server.

Grouping is the security component of Graphing.  It allows for a group to be administrated, so that membership and permissions can be restricted.  Think of Grouping and Graphing as a database in the cloud.

Peer Channel is the only managed API in the P2P API suite.  It allows for mesh replication instead of mesh collaboration.

Application Invite (AppInvite) allows a user to invite another user to use a collaboration application.  Any application can register with the invite API and when a user sends an invite to use that application the application will be launched by Windows Vista.  Windows Meeting Space is an example of an application that uses AppInvite.

Contacts API allows the creation and administration of p2p contacts.

Serverless Presence:  Gets contact presence information.

Now that we know what peer to peer offers let’s look at an example of how to use one of these APIs. A few weeks ago I did a video to explain why developers and end users should care about peer to peer.  As a part of that video there was an explanation of how PNRP works. Luckily calling the PNRP API doesn’t require understanding how the resolution works.

We will start our p2p adventure off with the People Near Me API.  Most of the p2p API is Win32 and doesn’t lend its self well to being called through interop from managed code.  So I will be writing all the examples in C++\CLI 2005 which will allow me to expose the APIs through a nice managed wrapper, which will make calling the API possible from .NET.

Our PNM sample application will just request enumerate the people on our local subnets and print their nick name, IP address and endpoint name to the console.  After creating a new C++ CLR console application in Visual Studio we need to add the Windows SDK path to our additional includes and our additional dependencies fields in our project. 

On my machine header files are here "C:\Program Files\Microsoft SDKs\Windows\v6.0\lib" and libs are here "C:\Program Files\Microsoft SDKs\Windows\v6.0\include". NOTE: these paths will be deferent on builds other then Windows Vista RC1 and are subject to where you installed the Windows SDK.

We also need to add three libraries: p2p.lib, p2pgraph.lib, Ws2_32.lib

The first two libraries contain all the peer to peer definitions and the last is WinSock2 and is needed when we convert an IP address to a string later in the program. The last thing we need to do is a #include for “p2p.h”

Here is the whole program listing. 

// PNMSample.cpp : main project file.

 

#include "stdafx.h"

#include "p2p.h"

 

#define MAX_PEERNAME_LENGTH 256

 

using namespace System;

using namespace System::Runtime::InteropServices;

 

int main(array<System::String ^> ^args)

{

      HPEERENUM hPeerEnum;

      HRESULT hr;

     

      // Startup Collaboration.

      hr = PeerCollabStartup(PEER_COLLAB_VERSION);

     

      // Find out who is in our local subnet.

      hr = PeerCollabEnumPeopleNearMe(&hPeerEnum);

 

      ULONG Count = 0;

 

      // Get the number of people in our subnet returned to hPeerEnum; an enumeration.

      hr = PeerGetItemCount(hPeerEnum, &Count);

 

      // Create a place to hold the array of items.

      PVOID *items = NULL;

     

      // Fetch the items from the enumeration.

      hr = PeerGetNextItem(hPeerEnum, &Count, &items);     

 

      // Loop through the people near us.

      for(int i = 0; i < Count; i++)

      {

            // Cast each item to the PEER_PEOPLE_NEAR_ME structure.

            PEER_PEOPLE_NEAR_ME *item = (PEER_PEOPLE_NEAR_ME*)(items[i]);          

 

            // Get the people near me nick name for this item.

            PWSTR szNickName = item->pwzNickName;

 

            // Marshal our native char array to a managed string and write it to the console.

            System::String ^nickName = gcnew System::String(szNickName);

            Console::WriteLine(L"Nick Name:{0}", nickName);

 

            // Get the peer end point information out of our item.

            PEER_ENDPOINT endpoint = item->endpoint;

 

            System::String ^endpointName = gcnew System::String(endpoint.pwzEndpointName);

            Console::WriteLine(L"End Point Name:{0}", endpointName);

     

            // Get the peer address structure from the end point.

            PEER_ADDRESS pa =  endpoint.address;

 

            // Get the IPv6 address.

            if(endpoint.address.sin6.sin6_family == AF_INET6)

            {

                  WCHAR  wzAddr[MAX_PEERNAME_LENGTH];

 

                  DWORD dwLen = (sizeof(wzAddr) / sizeof(wzAddr[0]));

 

                  // Convert a SocAddrIn6 address to a string. This method is defined in Ws2_32.lib

                  hr = WSAAddressToString(

                        (LPSOCKADDR) &pa.sin6,

                        sizeof(SOCKADDR_IN6), NULL, wzAddr, &dwLen);

 

                  // Marshal our native char array to a managed string and write it to the console.

                  System::String ^address = gcnew System::String(wzAddr);

                  Console::WriteLine(L"IPv6 Address:{0}", address);

            }                      

           

 

            // Put a space between entries.

            Console::WriteLine();        

           

      }    

 

      // Cleanup memory.

      PeerFreeData(items);   

      PeerEndEnumeration(hPeerEnum);

 

      hr = PeerCollabShutdown();

    return 0;

}

 

When I run the program I get this as my output:

Nick Name:Erik Porter

End Point Name:ERIKPOVISTA

IPv6 Address:[fe80::19f:84f3:bc7a:bb15%10]:52157

 

Nick Name:Karsten Januszewski

End Point Name:KARSTENJ-U5U7P

IPv6 Address:[fe80::d400:972a:1e29:a8a2%10]:51268

 

Press any key to continue . . .

 

When you run the program you might not get any output as you might not have anyone on your subnet who has a pnrp name register.  If there are other people on your subnet running Windows Vista then they can run “Windows Meeting Space” and tell it to start pnm when they start and you will get a listing for their machine since “Windows Meeting Space” uses PNM and publishes PNRP names. 

Now we will take a look in detail and the important parts of this application. We start by calling PeerCollabStartup(PEER_COLLAB_VERSION) which is responsible for starting up the p2p stack and must be called before any other p2p functions.

 Next we call PeerCollabEnumPeopleNearMe(&hPeerEnum). This function is the heart of our console application as it returns the enumeration of PNRP names on our subnet via the hPeerEnum handle.  At this point in time we just need to walk through the enumeration and print out the information we want.

The PeerGetItemCount(hPeerEnum, &Count) function takes the handle to the peer enumeration returns us the count of items in that enumeration via the Count variable.  To get the items out of the enumeration we call the PeerGetNextItem(hPeerEnum, &Count, &items) which takes the same handle and a number of items we want and returns an array of structures of whatever peer to peer type the enum contains.  This method is interesting as we can request more items then are in the enum and it will return us only the remaining amount.  If we had a large number of items in the enum and only wanted to process a few items at a time we could pass in the n number that we wanted and it would return us the next n number of items in the enumeration until it was empty.

The  PeerCollabEnumPeopleNearMe  function documentation states that  all the items in the enumeration it returns will be of type PEER_PEOPLE_NEAR_ME. Now that we have the pointer to our array of peer nodes we can loop through the items and cast each item to the PEER_PEOPLE_NEAR_ME structure. 

Now we start writing the desired information to the console with the help of some marshaling to System.String types.   The only other complex part of this application is getting the address information. First we check if address type is IPv6, this isn’t really necessary here, but will be when we look at other areas of p2p.

if(endpoint.address.sin6.sin6_family == AF_INET6)

{

WCHAR  wzAddr[MAX_PEERNAME_LENGTH];

 

      DWORD dwLen = (sizeof(wzAddr) / sizeof(wzAddr[0]));

 

      // Convert a SocAddrIn6 address to a string. This method is defined in Ws2_32.lib

      hr = WSAAddressToString(

                        (LPSOCKADDR) &pa.sin6,

                        sizeof(SOCKADDR_IN6), NULL, wzAddr, &dwLen);

 

                  // Marshal our native char array to a managed string and write it to the console.

      System::String ^address = gcnew System::String(wzAddr);

      Console::WriteLine(L"IPv6 Address:{0}", address);

}          

Then we declare a place to store the IPv6 address string and call the WSAAddressToString function which is a WinSock function that will convert our SocAddr6 address to a PWSTR for us.  Now we just have to marshal that and write it to the console.

Lastly we release the objects we have created as described by the p2p API documentation and call  PeerCollabShutdown() to shut down the p2p infrastructure for our application.  That is all there is to this application.  Next time we will look at PNRP and how we can register and resolve addresses over the internet and then use WCF to communicate between the nodes.

My friend ran into a problem recently where they wanted to use commands such as Cut, Copy, Paste in buttons, they couldn’t figure out how to bind the buttons CommandTarget property to multiple targets.  For those of you not familiar with WPF Commands check out this MSDN article.

 

When a command is applied to a control such as a button the focus scope of the button or it’s parent container determine if the command will apply to other controls in the Window or Page.  By default Menu or ToolBar elements have their own FocusScope so controls with commands on them will work on controls outside that FocusScope. 

 

For example the xaml snippet below shows a DockPanel with a Menu which has a MenuItem that whose Command property is assigned the Copy command.  Additionally the StackPanel below the menu has a button whose Command property is also set to Copy. 

 

<DockPanel>

    <Menu DockPanel.Dock="Top">

      <MenuItem Command="Copy"/>     

    </Menu>

    <StackPanel DockPanel.Dock="Top">

      <Button Command="Copy" >Copy</Button>

    </StackPanel>

    <StackPanel Name="Group" DockPanel.Dock="Bottom">

      <TextBox Name="MyTextBox1"/>

      <TextBox Name="MyTextBox2"/>

    </StackPanel>

</DockPanel>

 

When this code is run typing some text and then selecting that text in either TextBox in the last StackPanel will cause the MenuItem to become in enabled because it is bound to the Copy command, but the Button in the StackPanel will remain disabled.  The reason for this is because the Button is in the same FocusScope as the TextBox and in WPF this will keep the Command from working with the TextBox.

 

To solve this problem we can add a CommandTarget property to the Button and bind to the TextBox by name. This is shown in the xaml snippet below.  We add CommandTarget ="{Binding ElementName=MyTextBox1}" To the Button which causes the Button to enable when we select text in the first TextBox, but we still have a problem.  We want the Copy button to work for all Text elements in our application, not just the single TextBox we have set our CommandTarget property to.

 

<DockPanel>

    <Menu DockPanel.Dock="Top">

      <MenuItem Command="Copy"/>

    </Menu>

    <StackPanel DockPanel.Dock="Top">

      <Button Command="Copy" CommandTarget="{Binding ElementName=MyTextBox1}">Copy</Button>

    </StackPanel>

    <StackPanel Name="Group" DockPanel.Dock="Bottom">

      <TextBox Name="MyTextBox1"/>

      <TextBox Name="MyTextBox2"/>

    </StackPanel>

</DockPanel>

 

To solve this problem we are going to use an attached property on a class called FocusManager.  The property is called IsFocusScope and is a bool.  This property allows us to set the FocusScope for a control.  The xaml snippet below uses the attached property on the StackPanel to set the StackPanel to its own FocusScope.  This makes it so that we don’t need to set a CommandTarget, because the StackPanel with the button and the TextBoxes are now in different FocusScopes. 

 

<DockPanel>

    <Menu DockPanel.Dock="Top">

      <MenuItem Command="Copy"/>

    </Menu>

    <StackPanel FocusManager.IsFocusScope="True" DockPanel.Dock="Top">

      <Button Command="Copy" >Copy</Button>

    </StackPanel>

    <StackPanel Name="Group" DockPanel.Dock="Bottom">

      <TextBox Name="MyTextBox1"/>

      <TextBox Name="MyTextBox2"/>

    </StackPanel>

 </DockPanel>

 

Menu and ToolBar both use the FocusManager.IsFocusScope property to set their own FocusScope and if desired you can set that property to false on a Menu or ToolBar to get the reverse behavior. The xaml snippet below does this by setting  FocusManager.IsFocusScope="False" on the Menu.

 

<DockPanel>

    <Menu FocusManager.IsFocusScope="False" DockPanel.Dock="Top">

      <MenuItem Command="Copy"/>

    </Menu>

    <StackPanel FocusManager.IsFocusScope="True" DockPanel.Dock="Top">

      <Button Command="Copy" >Copy</Button>

    </StackPanel>

    <StackPanel Name="Group" DockPanel.Dock="Bottom">

      <TextBox Name="MyTextBox1"/>

      <TextBox Name="MyTextBox2"/>

    </StackPanel>

</DockPanel>

This is the third and final post about my WPF 2D Cell Animation program; Ink-A-Mator. Once you can get the source on the .NET 3.0 Community site. This week we are going to look at how we can create a color palette, since WPF doesn’t have one.

The first place to start with creating a color picker is to figure out what color is at a given x, y coordinate. If you are familiar with GDI or GDI+, then you might think there is a GetPixel method available on some object similar to a Graphics object. Unfortunately there is no such method or property. Instead we will have to create a bitmap from the UIElement we want to get the color from, create a copy of its pixels and then index into that copy to find the pixel we want.

// We are using 32 bit color.

int bytesPerPixel = 4;

 

// Where we are going to store our pixel information.

byte[] Pixels = new byte[(int) uiElement.Height * (int) uiElement.Width * bytesPerPixel];

 

// Create a render target which allows us to get a copy of any UI element as a bitmap

RenderTargetBitmap rt = new RenderTargetBitmap((int)uiElement.Width, (int) uiElement.Height, 96, 96, PixelFormats.Pbgra32);

 

// Render the image of the uiElement to a bitmap.

rt.Render(uiElement);

 

// Calculate the stride of the bitmap

int stride = (int) uiElement.Width * bytesPerPixel;

 

// Copy the pixels from our render target to the array of pixels.

rt.CopyPixels(Pixels, stride, 0);

// p is a Point so use p with formula (Y * Width * BPP + X * BPP) to figure out index in array.

int pixelIndex = (int)((int)(p.Y) * uiElement.Width * bytesPerPixel + (int)(p.X) * bytesPerPixel);

 

// extract the rgb components from the array. We use Pbgra32 here so our order is b g r.

byte b = Pixels[pixelIndex];

byte g = Pixels[pixelIndex + 1];

byte r = Pixels[pixelIndex + 2];

 

// Create a color from the rgb components.

Color color = Color.FromRgb(r, g, b);

Each pixel in our Pixel array is made up of four components; blue, green, red and alpha all of which are one byte, which is 32 bits or 4 bytes, this is known as the bit depth. Each entry in our Pixels array is going to hold one of these components. To get the total size of the array to hold the pixels we need to multiply the width, height and bytes per pixel.

Bitmap

Next we create our RenderTargetBitmap passing it the desired width, height, DPIX, DPIY and the desired pixel format. I have hard coded the DPI to be 96, which I thought might cause a problem when I changed the DPI, but WPF does a nice job of abstracting so that it performs and looks correct at different DPI settings. RenderTargetBitmap derives from BitmapSource which is the same type as the Source property on an Image control, so we can display a RenderTargetBitmap using an Image control.

Now we call Render on our RenderTargetBitmap instance passing in the uiELement. RenderTargetBitmap allows us to get a bitmap version of a WPF Visual. Once we compute the stride we call CopyPixels on our RenderTargetBitmap instance which as the name implies copies the pixels in the bitmap to our Pixels byte array.

All we have to do now is compute the index for the pixel and point x, y. Since our Pixels byte array represents a pixel component per index we need to multiply our y coordinate by the width and bit depth then add that to our x coordinate times the bit depth. This will put our index and the first component of the pixel. Since we are using BGR color ordering defined by the PixelFormat.Pbgra32, pixel components are in the order blue, green, red, alpha instead of red, green, blue, alpha. We don’t care about alpha so we are just going to ignore that component.

In the source code I have two different palettes that I create. The simpler one called Palette2 uses a LinearGradientBrush to create the look of the Palette. The other palette called Palette in source code takes four colors and blends them from the four corners of the palette.

Palette

To create Palette2 we need to create a Visual that will define look of our palette. To do this we are going to create a LinearGradientBrush and add some gradient stops.

LinearGradientBrush brush = new LinearGradientBrush();

 

brush.StartPoint = new Point(0.5, 0);

brush.EndPoint = new Point(0.5, 1);

 

brush.GradientStops.Add(new GradientStop(Colors.Orange, 0));

brush.GradientStops.Add(new GradientStop(Colors.Yellow, 0.15));

brush.GradientStops.Add(new GradientStop(Colors.Green, 0.25));

brush.GradientStops.Add(new GradientStop(Colors.Blue, 0.5));

brush.GradientStops.Add(new GradientStop(Colors.Red, 0.75));

brush.GradientStops.Add(new GradientStop(Colors.Black, 0.9));

brush.GradientStops.Add(new GradientStop(Colors.White, 1));

 

We then create a Rectangle and set its Fill property to the brush. We then use RenderTargetBitmap to make a bitmap copy of the Rectangle the first time the user clicks on it to generate the Pixels array. We then cache the Pixels array and index into when a user clicks somewhere else in the Rectangle.

The more complex Palette uses a bit of math to create the blending of the colors in the four corners. Palette.cs has the code for this palette. Unlike Palette2 which took a snapshot of a Visual and turned it into an image, Palette will create an image from scratch.

The image below shows that we blend from left to right and then from top to bottom. We actually do the gradient math for the first line of the image and the last line of the image and then blend between the top and bottom lines.

Blend

First we calculate the amount each color component changes as pixels go from left to right. The method CalculatePixelSlope shows how we calculate this change. The first two parameters define the color components of the pixels we want to interpolate between and distance specifics the distance between these pixels. Notice that the return type is PixelAsDoubles which is important because the change in color will most likely be small and possibly negative.

PixelAsDoubles CalculatePixelSlope(Pixel p1, Pixel p2, int distance)

{

return new PixelAsDoubles((double)(p2.r - p1.r) / distance, (double)(p2.g - p1.g) / distance, (double)(p2.b - p1.b) / distance);

}

struct Pixel

{

public byte r;

public byte g;

public byte b;

}

struct PixelAsDoubles

{

public double r;

public double g;

public double b;

}

Next we use the pixel slope to fill in the pixels in our image. The CalculatePixelGradient method below takes the starting pixel color components and pixel color slope and figure out for a given distance what the color should be.

private static Pixel CalculatePixelGradient(Pixel startingPixel, int distance, PixelAsDoubles slope)

{

Pixel p = new Pixel();

 

// We don't want to start at zero so shift.

distance++;

 

p.r = (byte)(startingPixel.r + slope.r * distance);

p.g = (byte)(startingPixel.g + slope.g * distance);

p.b = (byte)(startingPixel.b + slope.b * distance);

 

return p;

}

Now let’s look at how we create an image from scratch and call these methods to create our four color palette. Again we need to create a byte array which will hold our pixel components. Then we create the four corner pixels and pass them to CalculatePixelSlope to figure out the gradient for the top and bottom lines of the image. We begin looping through the pixels calling CalculatePixelGradient which will give us the final pixel at the top and bottom. Once we have these pixels we calculate the pixel slope for the top to bottom gradient and call CalculatePixelGradient which returns the final pixel. Notice that our pixel order is now RGB instead of BGR, this is because we are going to use the PixelFormat.Rgb24 instead of the PixelFormat.Pbgra32 format as we did with Palette2. After the look we calculate our stride and then call the static method Bitmap.Create which creates a BitmapSource from an array of bytes which represent the pixels.

BitmapSource CreateFourColorPalette()

{

// Create a place to hold our pixel data.

pixels = new Byte[(int)(FourColorPaletteSize.Width * FourColorPaletteSize.Height * bytesPerPixel)];

 

int i = 0;

 

// Find Color Slopes

Pixel upperLeft = new Pixel(upperLeftColor.Color.R, upperLeftColor.Color.G, upperLeftColor.Color.B);

Pixel upperRight = new Pixel(upperRightColor.Color.R, upperRightColor.Color.G, upperRightColor.Color.B);

Pixel lowerLeft = new Pixel(lowerLeftColor.Color.R, lowerLeftColor.Color.G, lowerLeftColor.Color.B);

Pixel lowerRight = new Pixel(lowerRightColor.Color.R, lowerRightColor.Color.G, lowerRightColor.Color.B);

 

// Get the change in color for the distance the color is traveling

PixelAsDoubles LeftToRightTop = CalculatePixelSlope(upperLeft, upperRight, (int)FourColorPaletteSize.Width);

PixelAsDoubles LeftToRightBottom = CalculatePixelSlope(lowerLeft, lowerRight, (int)FourColorPaletteSize.Width);

 

// We interpolate from left to right and then top to bottom.

for (int y = 0; y < FourColorPaletteSize.Height; y++)

{

    for (int x = 0; x < FourColorPaletteSize.Width; x++)

{

    Pixel p1, p2;

 

p1 = CalculatePixelGradient(upperLeft, x, LeftToRightTop);

p2 = CalculatePixelGradient(lowerLeft, x, LeftToRightBottom);

 

PixelAsDoubles topToBottom = CalculatePixelSlope(p1, p2, (int)FourColorPaletteSize.Height);

 

Pixel p = CalculatePixelGradient(p1, y, topToBottom);

 

pixels[i++] = p.r;

pixels[i++] = p.g;

pixels[i++] = p.b;

}

}

 

// Figure out the stride.

int stride = (int)FourColorPaletteSize.Width * bytesPerPixel;

 

return BitmapImage.Create((int)FourColorPaletteSize.Width, (int)FourColorPaletteSize.Height, 96, 96, PixelFormats.Rgb24, null, pixels, stride);

}

 

The two main advantages of creating a palette this way is that we don’t have to wait for WPF to render the brush like we did with Palette2 and we have more control over the colors in the palette. When looking at the source code you will notice that Palette.cs actually creates two palettes. The master palette uses more advanced blending to create a palette that allows us to select the colors of the FourCornerPalette.

masterPalette

Tomorrow I will start a new series about an application I created using WPF, WCF and peer to peer which allows for content distribution without servers.

Last week we looked at how we could use the Ink APIs in WPF to create 2D animation authoring application. This week we are going to look at how we can use the Open Packaging Conventions APIs in WPF to save and load our animation. Once you can get the source on the .NET 3.0 Community site.

The Open Packaging Conventions (OPC) APIs can be found in the System.IO.Packaging namespace. Before we get into the code let’s look at the ideas behind the OPC. The Office 2007 file formats use OPC along with the XPS file type. One of the reasons to use OPC is that we only have to learn one API to be able to read and write many formats. An OPC based file consists of three parts; the Package which holds the other parts, the PackageParts which holds the data and the PackageRelationships which describe what the PackageParts are and how they are related to other parts.

We are going to use a ZipPackage which is provided as a default implementation of the Package abstract class. The ZipPackage stores the package as a zip file and the parts as xml files. This is very helpful when developing a new file format because we can just change our file extension to .zip and explore how the file is laid out.

Now we’ll take a look at how we save a file using OPC. Here is the save code from Ink-A-Mator:

private const string ResourceRelationshipType =

@"http://schemas.microsoft.com/samples/Ink-A-Mator/Required-Resource";

// Create a new ZipPackage and save the animation in it.

using (Package p = ZipPackage.Open(fileName, FileMode.Create))

{

// Save out each of our frames.

for (int i = 0; i < this.frames.Count; i++)

{

    // We need to create a part to save to.

PackagePart part = p.CreatePart(PackUriHelper.CreatePartUri(new Uri(string.Format("Frame{0}{1}", i, ".frm"), UriKind.Relative)), "Ink/WPF");

 

// Create a relationship for our part so that we can walk through relationships on open.

p.CreateRelationship(part.Uri, TargetMode.Internal, ResourceRelationshipType);

 

// Get a memory stream to save our part.

using (Stream s = part.GetStream())

{

     // save each frame into the stream

this.frames[i].Save(s);

 

// don't forget to close the stream.

s.Close();

}

}

}

 

Let’s look at each line in detail. The first line declares a const string ResourceRelationshipType which is just a URI for our part type. The URI has no significance it is just what I have chosen to represent my frame parts. Next we create our ZipPackage using the static Open method. Notice that we have wrapped this line in a using statement because the Package class implements IDisposable. Then we loop through our collection of animation frames and create a PackagePart and PackageRelationship for each frame. To create the part we call the CreatePart method on our package instance. This method takes two arguments the URI of this part and the type of this part. We use the PackUriHelper.CreatePartUri static method to help us create the part. We just want to store the name of each frame in the form “Frame0.frm”. The StrokeCollection stores the .frm files in Ink Serialized Format (ISF).

 

Next we are going to use our package to create a PackageRelationship. We use relationships like a table of contents for the OPC file, it allows us to ask for parts by relationship when we read files back. Then we’re going to use the part instance we created to write our frames content to using the GetStream method. This method returns a stream that we can use to write our content too. Remember that our frames are just StrokeCollections which have a Save method to write to a stream, so we just have to pass the part stream to the Save method. Last we call close on the part stream.

 

The image below shows the layout of our package if we unzip our saved file. Now we will examine the parts in the package.

 

 

The _rels folder contains a single file .rels which represent the part relationships we created:

 

Package/_rels/.rels:

 

<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">

<Relationship Type="http://schemas.microsoft.com/samples/Ink-A-Mator/Required-Resource" Target="/Frame0.frm" Id="R1856367c1f81411f" />

<Relationship Type="http://schemas.microsoft.com/samples/Ink-A-Mator/Required-Resource" Target="/Frame1.frm" Id="R77e932075fd84ad2" />

<Relationship Type="http://schemas.microsoft.com/samples/Ink-A-Mator/Required-Resource" Target="/Frame2.frm" Id="R2485e76da2c64a2d" />

All the other 48 frames

</Relationships>

 

Here we can see that the .rels file contains a collection of relationships. The Type attribute of a Relationship is useful when we load this file as we will see shortly. The Target attribute points to another file in our Package which is a PackagePart, which holds the contents of a single frame. As seen in the image above. Lastly the [Content_Types].xml file contains the types we have defined for this Package.

[Content_Types].xml:

<?xml version="1.0" encoding="utf-8"?>

<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">

<Default Extension="frm" ContentType="Ink/WPF" />

<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml" />

</Types>

We have two types in our Package. Our Frame type with the extension .frm and the relationships with the extension .rels.

That’s all there is to saving a file using the OPC file format. Now we will take a look at our Load method and see how easy it is to open a file using OPC.

 

// Open our file using the Open Packaging APIs

using (Package p = ZipPackage.Open(fileName, FileMode.Open))

{

// We pull frames by part so we need the package relationship.

PackageRelationshipCollection rels = p.GetRelationshipsByType(ResourceRelationshipType);

 

// Walk through our frames in part form.

foreach (PackageRelationship rel in rels)

{

    // Get the address of this frame part.

Uri uri = PackUriHelper.ResolvePartUri( new Uri("/", UriKind.Relative), rel.TargetUri);

 

// Get the frame part with the address.

PackagePart part = p.GetPart(uri);

 

// Put the frame to memory

using (Stream s = part.GetStream())

{

// A frame just consists of a stroke collection so load it.

this.Paper.Strokes = new StrokeCollection(s);

 

// Close our stream.

s.Close();

 

// Move to the next frame.

OnNextFrame(this, new RoutedEventArgs());

    }

    }

 

// Move back to the last frame.

OnPreviousFrame(this, new RoutedEventArgs());

}

 

Most of this code should be familiar to you now because it is very similar to the Save method. Once again we use the ZipPackage.Open method, this time we are using the Open FileMode. Next we are going to call GetRelationshipsByType and pass our frame relationship type. This method is going to return us a collection of relationships. This is basically the object form of the .rels file we looked at. Since we have one relationship for each frame we are going to loop through the relationships to create each file. Using the ResolvePartUri static helper method we pass in target uri from the relationship. Once we have this URI we can call GetPart passing the URI which will return us an instance of frame PackagePart. Now we just have to call the GetStream method on the part and create a new instance of a StrokeCollection passing the stream of our ISF file. The other calls here were explained last week and deal with the Ink API.

The OPC APIs have some other interesting functionality that we didn’t make use of here such as digital signatures, but you should have enough of an understanding to read and write your own files. For more information on OpenXML and working with the Office 2007 formats check out the articles on the Open XML Developer website.

2D animation is an area that I have always had a lot of interest in. I thought I would try my hand at creating a simple 2D animation program with WPF and ran into a few surprises.

Ink-A-Mator 

The sample and source is posted on the .NET 3.0 Framework site.

The first feature I wanted was the ability to create a cell of animation store that cell in memory and then advance to another cell. To do this I choose to use an InkCanvas, because it has built in drawing features.

<InkCanvas Name="Paper" />

The InkCanvas captures drawings as Strokes these strokes are stored in a StrokeCollection.

// Our Frames

List<StrokeCollection> frames = new List<StrokeCollection>();

The frames are stored in a generic list of StrokeCollections. To advance to the next frame we first save the current frame. When we save we want to make sure we are saving to the correct place in our frame collection. If we have fewer frames then the frame count we add our cell to the frames collection otherwise we update the current frame.

Next we create a new stroke collection to store the next frame and then advance our frame counter. If the next frame has already been created we want to assign its stroke collection to our InkCanvas’s Strokes property. We have similar code for viewing the previous frame, an arbitrary frame and inserting a frame between two frames.

 

private void SaveFrame()

{

if (frames.Count <= CurrentFrame)

{

    frames.Add(this.Paper.Strokes);

}

else

{

    frames[CurrentFrame] = this.Paper.Strokes;

    }

}

void OnNextFrame(object sender, RoutedEventArgs e)

{

SaveFrame();

 

this.Paper.Strokes = new StrokeCollection();

 

CurrentFrame++;

 

if (frames.Count > CurrentFrame)

{

    this.Paper.Strokes = frames[CurrentFrame];

}

 

RenderOnionLayer();

}

 

The InkCanvas class along with the Stroke and StrokeCollection class make it easy to add other features such as changing the color, size and shape of the brush. The last thing we have to do in our OnNextFrame method is to render the onion layer.

For those not familar with creating animation the ability to see what has just happened is important. We achive this by drawing the previous frames in lighter and lighter shades of gray. Here is an example of an animation that shows a bouncing red ball. The circles drawn in gray are the onion layer. They will not appear in the final animation when we play it back, they are just a tool to help the animator.

Onion

To render the onion layer we are going to use another control called an InkPresenter.

<InkPresenter Name="OnionLayer"/>

An InkPresenter is similar to an InkCanvas except that it is used for displaying ink instead of manipulating it. It too has a Strokes collection. Since we want to draw a number of different cells we are going to combine copies of their collections together in this sincle OnionLayer InkPresenter.

We can see the whole process by reading through the comments in the RenderOnionLayer method. From a high level perspective we want to change the color attribute on each of the strokes by in each of our cells. The Stroke class DrawingAttributes property exposes a large number of attributes we can change.

void RenderOnionLayer(int currentFrame)

{

// Get starting frame for the onioning

int i = CurrentFrame - onionFrameCount;

 

int count = currentFrame;

 

// Make sure the starting onion frame isn't less then zero.

if (i < 0)

{

    // update the count if it is less then zero. We only want to show up to this frame.

    count = i + onionFrameCount;

 

    i = 0;

}

 

// Create a new stroke collection for the onion layer.

this.OnionLayer.Strokes = new StrokeCollection();

 

int c = 0;

 

// iterate through the onion layers.

for (int j = i; j < count; j++)

{

    // Make a copy of the real frame.

this.OnionLayer.Strokes.Add(this.frames[j].Clone());

 

// Figure out how many stokes we have, since we are going to add all the onion strokes to the same frame.

c = this.OnionLayer.Strokes.Count - this.frames[j].Count;

 

// Make sure we have strokes to color

if (this.OnionLayer.Strokes.Count != 0)

{

    // Go through the strokes in this onion frame and change their color.

for (int k = c; k < this.OnionLayer.Strokes.Count; k++)

{

    Color color = Colors.Gray;

 

// Calculate the shade of gray dynamically so we can change based on the onion layer count.

color.A = (byte)(color.A / (count - j + 1));

 

// Apply the color.

this.OnionLayer.Strokes[k].DrawingAttributes.Color = color;

    }

    }

}

}

 

The last feature I am going to cover in this post is how we playback our animation. WPF has a great built in animation system, but it is made to interpolate between multiple values over a set period of time. For our animation we don’t want to do interpolation we want to simply display each frame for 1/24th of a second (Standard Animation Frames Per Second). To do this we are going to use an event that is fired everytime WPF renders a frame.

 

CompositionTarget.Rendering

 

If we just displayed a frame each time this event fired we wouldn’t see any change, instead we want to display frames for 1/24th of a second then show the next frame. We do this with the help of the Stopwatch class. Stopwatch is a high percision timer.

We start a new stop watch instance and then check the ElapsedTicks agaist Frequency / 24 to see if we have reached the next frame. We then restart the Stopwatch. In the middle of our loop we check to see if we have reached the end our our animation. It is important to notice that we have a bool playing that we check to make sure the animiator is playing this animation otherwise we don’t want to render the animation. This is because we have to signup for CompositionTarget.Rendering before WPF renders the first frame.

 

// The playing timer.

Stopwatch watch;

watch = Stopwatch.StartNew();

void CompositionTarget_Rendering(object sender, EventArgs e)

{

    // Only render if we are playing.

    if (playing)

    {

        long l = watch.ElapsedTicks;

      

    // Play at 24 frames a second.

if (l > Stopwatch.Frequency / 24)

{

    // We are done playing through the frames

if (playingFrame >= this.frames.Count)

{

    DonePlaying();

return;

}

 

// Advance the frame.

this.Player.Strokes = this.frames[playingFrame++];

 

// restart the stopwatch

watch.Reset();

watch.Start();

    }

}

}

In my next post I will discuss how animations are saved and loaded using the Open Packaging Format APIs that are a part of WPF and then we will look at how I created the palette controls.

More Posts Next page »
 
Page view tracker