Wintellect  

Last week I found myself needing a Silverlight layout control that arranges its items radially so I could use it to template a ListBox. A quick Web search revealed a number of RadialPanel implementations, but none that offered the features I needed. For example, I wanted to be able to rotate the items in the panel to create a spoked arrangement. But I also wanted to have the option of NOT rotating the items to achieve a more traditional layout.

So I built a RadialPanel that supports both options. It has three key properties (all dependency properties) that you can use to configure it:

  • Radius, which specifies the radius of the radial layout
  • ItemAlignment, which specifies whether items are left, center, or right-aligned 
  • ItemOrientation, which specifies whether items are upright or rotated

To demonstrate, I declared a ListBox containing eight colored rectangles and assigned a RadialPanel to the ListBox's ItemsPanel property:

<ListBox Width="600" Height="600">

  <ListBox.ItemsPanel>

    <ItemsPanelTemplate>

      <custom:RadialPanel Radius="200" ItemAlignment="Center"

        ItemOrientation="Upright" />

    </ItemsPanelTemplate>

  </ListBox.ItemsPanel>

  <Rectangle Width="75" Height="75" Fill="Red" />

  <Rectangle Width="75" Height="75" Fill="Orange" />

  <Rectangle Width="75" Height="75" Fill="Yellow" />

  <Rectangle Width="75" Height="75" Fill="Green" />

  <Rectangle Width="75" Height="75" Fill="Blue" />

  <Rectangle Width="75" Height="75" Fill="Darkblue" />

  <Rectangle Width="75" Height="75" Fill="Purple" />

  <Rectangle Width="75" Height="75" Fill="#ff80c0" />

</ListBox>

Here's what the resulting ListBox looked like:

RadialPanel with Upright Items 

Next, I created another RadialPanel ListBox, but this time I set ItemOrientation to "Rotated" and used TextBlocks as ListBox items. I also set ItemAlignment to "Left" to align the TextBlocks along the inside radius:

<ListBox Width="600" Height="600" FontSize="20">

  <ListBox.ItemsPanel>

    <ItemsPanelTemplate>

      <custom:RadialPanel Radius="150" ItemAlignment="Left"

        ItemOrientation="Rotated" />

    </ItemsPanelTemplate>

  </ListBox.ItemsPanel>

  <TextBlock Text="Jeff Prosise" Foreground="Red" />

  <TextBlock Text="Jeffrey Richter" Foreground="Orange" />

  <TextBlock Text="John Robbins" Foreground="Green" />

  <TextBlock Text="Steve Porter" Foreground="Blue" />

  <TextBlock Text="Keith Rome" Foreground="Blue" />

  <TextBlock Text="Rik Robinson" Foreground="Darkblue" />

  <TextBlock Text="Andy Hopper" Foreground="Purple" />

  <TextBlock Text="Sergio Lascio" Foreground="#ff80c0" />

    ...

</ListBox>

And here was the result:

RadialPanel with Rotated Items 

I posted the source code in case you need a RadialPanel, too. It works in Silverlight 2 and in Silverlight 3 Beta 1. Feel free to use my RadialPanel in projects of your own, with one condition: let me know if you find bugs. I think I accounted for everything, but custom layout controls are tricky to write, and many of the custom layout controls that are bandied about the Web aren't very robust.

Incidentally, if you're coming to TechEd next week and want to see lots of cool Silverlight samples, drop by my Silverlight precon on Sunday. The last couple of hours of the day will be devoted to Silverlight 3, so it's sure to be a blast!

A few weeks ago, I blogged about a sample application I wrote that uses Silverlight 3's WriteableBitmap class to draw views of the Mandelbrot set. I hadn't used it long before I realized that it really needed Back-button support. You could drill down into the Mandelbrot set, but you couldn't drill back out. Enabling the browser's Back and Forward buttons seemed a natural way to remedy the situation, but Silverlight and the Back button have never gotten along very well.

Silverlight 3's navigation framework enables the Back button by allowing developers to organize content into navigable "pages" and adding an entry to the browser's history journal each time the user navigates from one page to another. It also adds deep linking support by making each page (and the state that defines it) bookmarkable. Having deep linking support would be a huge plus in my Mandelbrot viewer because users would be able to share fractal scenes by passing around URLs.

So I built a new version of the app that combines Silverlight 3's navigation framework with self-referential pages to add deep linking and Back-button support. "Self-referential" means the application consists of a single page that navigates to itself to draw new views of the Mandelbrot set. Each navigation event is accompanied by a RESTful URL that defines precisely which region of the set to render. And each navigation event generates a history node that enables the user to back out of the current location by clicking the Back button.

Navigable Mandelbrot Demo 

You can try the app online, or you can download the source code and run it locally. Of course, you'll need to have the Silverlight 3 Beta 1 Tools for Visual Studio installed to do the latter. As you peruse the source code, here are a few highlights to serve as guide posts:

  • MainPage.xaml contains little more than a Frame control. The only "page" in the app is Mandelbrot.xaml.
  • The logic for drawing a new view of the Mandelbrot set lives in the page's OnNavigatedTo method and in the helper method named DrawMandelbrotSet. OnNavigatedTo looks for a query string containing four coefficients that define a rectangle in the complex plane; this rectangle identifies a region of the Mandelbrot set. When you let go of the mouse button after dragging a selection rectangle around the region you want to zoom into, the page navigates to itself and includes a new query string identifying that region.
  • An instance of UriMapper declared in App.xaml converts raw URLs into RESTful ones and cleans up the stuff in the browser's address bar a bit.

Check it out and if you find a particularly beautiful scene in the Mandelbrot set, post a link in the comments section so the rest of us can see it, too. And for kicks, here's a scene to get you started.

If you've worked with Silverlight 3, you may have noticed that Visual Studio's Add New Item dialog includes an option for adding a "Silverlight Child Window" to your Silverlight project:

Silverlight Child Window

The new child window feature makes it easy to add modal dialogs to Silverlight applications. A child window derives from the new ChildWindow class, and its content is defined in XAML (of course!). So you can now embellish an application with modal popups containing rich content.

To see this feature in action, create a new Silverlight project and add a Silverlight child window to it. Replace "ChildWindow1.xaml" in the Name box with "ErrorDialog.xaml" and click OK. Visual Studio responds by adding two new files to your project: ErrorDialog.xaml, which contains the child window's UI, and ErrorDialog.xaml.cs, which contains the child window's code-behind class. You can display your new child window with two simple lines of code:

ErrorDialog dlg = new ErrorDialog();

dlg.Show();

When you do, an empty dialog appears with OK and Cancel buttons at the bottom. The content behind the dialog grays out and the user has to dismiss the dialog before doing anything else in the application.

You can add content to the dialog by declaring the content in ErrorDialog.xaml. You can also remove the OK and Cancel buttons by deleting the Button declarations. Here's some content you can plug into ErrorDialog.xaml to customize the dialog's appearance:

<StackPanel Orientation="Vertical">

  <Image Source="Images/UnhappyFace.png" Stretch="None" Margin="8" />

  <TextBlock Text="An unforeseen error has occurred. Please contact a system administrator or, better yet, use our competitor's Web site until we get the problem resolved." TextWrapping="Wrap" />

</StackPanel>

<Button x:Name="CloseButton" Content="Close" Click="CloseButton_Click"

  Width="75" Height="23" HorizontalAlignment="Center" Margin="16"

  Grid.Row="1" />

Add the following line of code to ErrorDialog's constructor to customize the title bar:

this.Title = "Error";

And add the following method to the ErrorDialog class to handle clicks of the Close button:

private void CloseButton_Click(object sender, RoutedEventArgs e)

{

    this.DialogResult = false;

}

When you display the dialog, here's what you get:

Child Window 

It's not earth-shaking, but it's certainly nice to have the ability to dress up your apps with genuine modal dialogs.

This is all rather straightforward, but certain aspects of a child window's operation merit further explanation. For example, the ChildWindow class has a Close() method that you can call to close a dialog, but my CloseButton_Click handler, which is patterned after the click handlers provided by Visual Studio, doesn't call it. DialogResult's setter calls Close for you, so simply assigning a value to the DialogResult property is sufficient to close the dialog.

Another aspect of child windows that may not be obvious is that Show() is an asynchronous call. That is, it doesn't wait for the dialog to be dismissed; it returns immediately. That's why Show() returns void rather than a DialogResult. If you want to know how the dialog was dismissed (that is, what the DialogResult value is once the dialog is closed), you handle the dialog's Closed event and check DialogResult there:

ErrorDialog dlg = new ErrorDialog ();

dlg.Closed += new EventHandler(OnErrorDialogClosed);

dlg.Show();

  ...

private void OnErrorDialogClosed(object sender, EventArgs e)

{

    ErrorDialog dlg = (ErrorDialog)sender;

    bool? result = dlg.DialogResult;

}

Note that DialogResult is a nullable bool, so it isn't necessarily set to true or false.

One last item to consider is how to retrieve input from a child window that allows the user to input data. Generally you build public properties into the dialog class and transfer values from the dialog's input controls into these properties in the OK button's click handler. After the dialog is dismissed, you then retrieve the user input from the dialog properties. For example, suppose a child-window class named InputDialog contains a TextBox named "InputBox" so the user can enter text. You could do this in the dialog class:

public string Input { get; set; }

  ...

private void OKButton_Click(object sender, RoutedEventArgs e)

{

    this.Input = InputBox.Text;

    this.DialogResult = true;

}

And after the dialog is dismissed, you could do this to find out what the user typed into the TextBox:

private void OnInputDialogClosed(object sender, EventArgs e)

{

    InputDialog dlg = (InputDialog)sender;

    bool? result = dlg.DialogResult;

 

    if (result.HasValue && result.Value)

    {

        string input = dlg.Input;

    }

}

This is how we used to retrieve input from dialog boxes in MFC. And now, 15 years later, it comes in handy again. I guess useful patterns never die!

If I had a dollar for every time a developer has said to me "You mean a style can only be applied to an object once in Silverlight? How brain dead!" or "Why doesn't Silverlight support BasedOn styles like WPF?", I'd be retired on a tropical island with my own private runway and a fleet of radio-controlled jets. So maybe I exaggerate. Nevertheless, developers will love the style-related enhancements coming in Silverlight 3, which include:

  • Support for BasedOn styles (style inheritance)
  • Support for dynamic styling and skinning (styles can be applied more than once) 
  • Support for merged resource dictionaries

The sample application pictured below demonstrates all three facets of Silverlight 3's new and improved style story:

Stylin'!!! 

Here's how you can build the application and experience these enhancements yourself. Begin by creating a new Silverlight 3 project and adding the following declarations to MainPage.xaml:

<StackPanel Orientation="Vertical" VerticalAlignment="Center">

  <Button x:Name="SampleButton" Content="Sample Button"

    Width="300" Height="200" FontSize="24" />

  <RadioButton x:Name="UnstyledOption" Content="Unstyled" FontSize="20"

    Width="260" Margin="0,36,0,0" IsChecked="True" Click="RadioButton_Click" />

  <RadioButton x:Name="BlurredOption" Content="Blurred" FontSize="20"

    Width="260" Click="RadioButton_Click" />

  <RadioButton x:Name="RotatedOption" Content="Rotated" FontSize="20"

    Width="260" Click="RadioButton_Click" />

  <RadioButton x:Name="BlurredRotatedOption" Content="Blurred and Rotated"

    FontSize="20" Width="260" Click="RadioButton_Click" />

</StackPanel>

Now add the following event handler to the MainPage class in MainPage.xaml.cs:

private void RadioButton_Click(object sender, RoutedEventArgs e)

{

    string name = ((FrameworkElement)sender).Name;

 

    switch (name)

    {

        case "UnstyledOption":

            SampleButton.Style = null;

            break;

 

        case "BlurredOption":

            SampleButton.Style = (Style)Application.Current.Resources["Blurred"];

            break;

 

        case "RotatedOption":

            SampleButton.Style = (Style)Application.Current.Resources["Rotated"];

            break;

 

        case "BlurredRotatedOption":

            SampleButton.Style = (Style)Application.Current.Resources["BlurredAndRotated"];

            break;

    }

}

Observe that this event handler programmatically applies global styles to a Button control. We need to define those styles, so go to App.xaml and add the following XAML to <Application.Resources>:

<ResourceDictionary>

  <ResourceDictionary.MergedDictionaries>

    <ResourceDictionary Source="Styles.xaml" />

  </ResourceDictionary.MergedDictionaries>

  <Style x:Key="Blurred" TargetType="Button">

    <Setter Property="Effect">

      <Setter.Value>

        <BlurEffect Radius="8" />

      </Setter.Value>

    </Setter>

  </Style>

  <Style x:Key="BlurredAndRotated" TargetType="Button"

    BasedOn="{StaticResource Blurred}">

    <Setter Property="RenderTransform">

      <Setter.Value>

        <RotateTransform Angle="10" />

      </Setter.Value>

    </Setter>

  </Style>

</ResourceDictionary>

One of the styles used in the application is defined external to App.xaml in Styles.xaml. (Note the Source attribute attached to the ResourceDictionary element.) Add a new file named Styles.xaml to your Silverlight project. Set its build action to "Resource" and add the following XAML to it:

<ResourceDictionary

  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

  <Style x:Key="Rotated" TargetType="Button">

    <Setter Property="RenderTransform">

      <Setter.Value>

        <RotateTransform Angle="10" />

      </Setter.Value>

    </Setter>

  </Style>

</ResourceDictionary>

Now build the project and you're ready to go. Each time you click a radio button, a style is programmatically applied to the Button control—something that you couldn't do more than once in Silverlight 2. One of the styles—the one named "BlurredAndRotated"—is a BasedOn style that adds a RenderTransform setter to the Effect setter defined in the "Blurred" style. Finally, the "Rotated" style is defined externally in Styles.xaml and imported into App.xaml. Stylin'!

To be sure, there is still a gap between the style support in Silverlight and the style support in WPF. Silverlight still does not, for example, support implicit styles or style triggers, but hey! The team has to save something for Silverlight 4. :-)

One of Silverlight 3's prominent new features is its built-in navigation framework. This framework allows you to break your content into navigable chunks, or "pages," that derive from the new System.Windows.Controls.Page class. Pages are addressable by URI, just like pages in a conventional Web application, and the new System.Windows.Controls.Frame class provides an API for navigating among pages and integrating with the browser's history mechanism. One of the benefits of using the navigation framework is that it (gasp!) enables the browser's Back and Forward buttons. It also lends a degree of SEO friendliness to Silverlight since individual pages can be identified by URL.

I wrote a simple application to demonstrate the basics of the navigation framework. You can download the app and check it out for yourself. MainPage.xaml hosts a Frame control that provides visibility into two pages: Index.xaml, which displays a collection of aircraft, and Detail.xaml, which displays a detailed view of a particular aircraft. Index.xaml is displayed when the application first comes up; here's what it looks like:

Navigation Demo Home Page

And here's what you see when you click one of the aircraft images (in this case, the one in the middle):

Navigation Demo Detail Page 

Index.xaml uses Silverlight 3's new WrapPanel control with some manual data binding to display all the aircraft. The aircraft are represented by instances of a simple custom class named Aircraft, and a separate class named Hangar has methods for retrieving collections of Aircraft objects as well as individual Aircraft objects.

A good starting point for exploring the sample application is MainPage.xaml, which declares a Frame control and sets the default page to "Aircraft:"

<nav:Frame x:Name="Main" Source="Aircraft" ... /> 

An instance of UriMapper declared in App.xaml maps requests for "Aircraft" to Index.xaml. Note that in Silverlight 3 Beta 1, the UriMapper must be named "uriMapper," or else it will not work:

<nav:UriMapper x:Key="uriMapper">

  <nav:UriMapping Uri="Aircraft" MappedUri="/Index.xaml" />

  <nav:UriMapping Uri="Detail/{id}" MappedUri="/Detail.xaml?id={id}" />

</nav:UriMapper>

When you click one of the images in Index.xaml, the following event handler retrieves the corresponding aircraft ID from the image's Tag property and navigates to Detail.xaml, passing the aircraft ID in the URI:

void OnImageClicked(object sender, MouseButtonEventArgs e)

{

    string id = ((FrameworkElement)sender).Tag.ToString();

    NavigationService.Navigate(new Uri(String.Format

        ("Detail/{0}", id), UriKind.Relative));

}

Thanks to the second URI mapping in the UriMapper above, "Detail/1000" becomes "/Detail.xaml?id=1000," and the following statement in Detail.xaml.cs extracts the aircraft ID from the query string:

string sid = NavigationContext.QueryString["id"];

Once the aircraft ID is known, a few additional statements in Detail.xaml.cs retrieve the corresponding Aircraft object and use properties of that object to initialize the Image and TextBlock declared in the page.

When you run the application, notice that after you've clicked a few times, you can use the browser's Back and Forward buttons to navigate around. Also notice that you can bookmark a page that shows a particular aircraft (for example, "NavigationDemoTestPage.html#Detail/1001") and go back to that page simply by pasting the URL into the browser's address bar.

Using UriMapper isn't strictly necessary, but the beauty of URI mapping is that it allows you to control your URIs. I would rather a user see "NavigationDemoTestPage.html#Aircraft" in the browser's address bar than "NavigationDemoTestPage.html#/Index.xaml." The more complex the URI, the more you'll appreciate URI mapping.

There is much, much more to the navigation framework than I have presented here, but this sample covers the essentials. Tim Heuer and others have blogged about the navigation framework more extensively and have even posted some helpful videos, including this one. If your chief concern about previous versions of Silverlight is lack of navigability and incompatibility with search engines, the navigation framework is something you'll find worth digging into.

One of the overarching goals when designing a Silverlight application is to minimize the size of the XAP file. The smaller the XAP file, the faster the application loads.

In Silverlight 2, one way to prevent external BCL assemblies such as System.Xml.Linq.dll from swelling the XAP file was to delay-load them, as described in this blog post. Silverlight 3 offers a simpler and more elegant option called assembly caching. To demonstrate, here's a XAP file from a project that includes references to System.Xml.Linq.dll and other assemblies that aren't part of the core run-time. The total size of the XAP file was 225K:

XAP file without assembly caching 

To enable assembly caching, I opened the project properties in Visual Studio and checked the "Reduce Xap size" box:

Reduce XAP size option 

Here's the same XAP file after I rebuilt the project. The new size was only 12K:

XAP file with assembly caching

How can Silverlight use the external assemblies if they're not packaged in the XAP file? It downloads them from Microsoft's Web site the first time they're needed and caches them locally so they don't have to be downloaded again. Here's the application manifest before the rebuild:

<Deployment xmlns=http://schemas.microsoft.com/client/2007/deployment

  xmlns:x=http://schemas.microsoft.com/winfx/2006/xaml

  EntryPointAssembly="RestDemo" EntryPointType="RestDemo.App"

  RuntimeVersion="3.0.40307.0">

  <Deployment.Parts>

    <AssemblyPart x:Name="RestDemo" Source="RestDemo.dll" />

    <AssemblyPart x:Name="System.Windows.Controls.Data"

      Source="System.Windows.Controls.Data.dll" />

    <AssemblyPart x:Name="System.Xml.Linq"

      Source="System.Xml.Linq.dll" />

    <AssemblyPart x:Name="System.ComponentModel.DataAnnotations"

      Source="System.ComponentModel.DataAnnotations.dll" />

    <AssemblyPart x:Name="System.ComponentModel"

      Source="System.ComponentModel.dll" />

  </Deployment.Parts>

</Deployment>

And here's the application manifest after the rebuild:

<Deployment xmlns=http://schemas.microsoft.com/client/2007/deployment

  xmlns:x=http://schemas.microsoft.com/winfx/2006/xaml

  EntryPointAssembly="RestDemo" EntryPointType="RestDemo.App"

  RuntimeVersion="3.0.40307.0">

  <Deployment.Parts>

    <AssemblyPart x:Name="RestDemo" Source="RestDemo.dll" />

  </Deployment.Parts>

  <Deployment.ExternalParts>

    <ExtensionPart Source="http://go.microsoft.com/fwlink/?LinkID=142572" />

    <ExtensionPart Source="http://go.microsoft.com/fwlink/?LinkId=142576" />

    <ExtensionPart Source="http://go.microsoft.com/fwlink/?LinkID=142565" />

    <ExtensionPart Source="http://go.microsoft.com/fwlink/?LinkID=141727" />

  </Deployment.ExternalParts>

</Deployment>

Notice the the new <Deployment.ExternalParts> section, which tells the run-time which additional BCL assemblies need to be downloaded and where to download them from.

Silverlight caches the downloaded assemblies in the host operating system's default browser cache. Of course, browsers will cache entire XAP files, too, so even a XAP file that contains embedded assemblies doesn't have to be downloaded every time. One big advantage to assembly caching, however, is that cached assemblies can be shared by all the Silverlight apps on a machine. Once application A has downloaded System.Xml.Linq.dll, for example, application B doesn't have to download it if it, too, is configured to load the assembly from the assembly cache.

Silverlight is a browser plug-in that enables browser-based applications to display rich, XAML-based UIs, execute managed code, and leverage the .NET Framework Base Class Library. Thus it may come as a surprise to some that in Silverlight 3, applications are no longer restricted to the browser.

Silverlight applications that run outside the browser are commonly referred to as out-of-browser applications, or OOBs for short. You install an OOB by running a Silverlight application in your browser, right-clicking the Silverlight control, and selecting "Install appname onto this computer" from the context menu. A dialog box like the one below appears and offers to place a shortcut for the app on the desktop, in the Start menu, or both. If you click OK, the app is installed locally on the host PC.

OOB Install 

When executed, an OOB application starts up in a window of its own (not a browser window) and is hosted in a process named sllauncher.exe (the "Microsoft Silverlight Offline Launcher"). As the name implies, an OOB doesn't require an online connection; it launches locally, just like any app installed on your computer. However, if a connection is available, an OOB goes back to the server the moment it's launched to check for updates. If an update is available, an event fires so the app can notify the user that a newer version is available. The newer version is automatically downloaded and then executed the next time the OOB is started. In Beta 1, there is no option for declining the update, but don't be surprised if that changes in a future release.

OOB Demo

Those are the basics; now here are some of the details. First, there is one step that you, the developer, must take if you wish for your application to support being installed locally and run out-of-browser. That step involves a change to the application manifest, which is generated from the AppManifest.xml file in your Silverlight project. Here's an AppManifest.xml that supports OOBs:

<Deployment xmlns="http://schemas.microsoft.com/client/2007/deployment"

    xmlns:x=http://schemas.microsoft.com/winfx/2006/xaml> 

    <Deployment.Parts>

    </Deployment.Parts>

 

    <Deployment.ApplicationIdentity>

        <ApplicationIdentity

            ShortName="SLOOB"

            Title="Silverlight OOB Demo">

            <ApplicationIdentity.Blurb>

                <!-- Insert description here -->

            </ApplicationIdentity.Blurb>

            <ApplicationIdentity.Icons>

                <Icon Size="16x16">Icons/Icon16.png</Icon>

                <Icon Size="32x32">Icons/Icon32.png</Icon>

                <Icon Size="48x48">Icons/Icon48.png</Icon>

                <Icon Size="128x128">Icons/Icon128.png</Icon>

            </ApplicationIdentity.Icons>

        </ApplicationIdentity>

    </Deployment.ApplicationIdentity>

</Deployment>

Note the new <Deployment.ApplicationIdentity> section, which contains key information used when a Silverlight app is installed locally, including:

  • The short name, which appears in the shortcuts created for the OOB
  • The title, which appears in the title bar of the window that hosts the OOB
  • The blurb, which provides a more detailed description of the OOB
  • An optional set of application icons in varying resolutions used in shortcuts, in the title bar, and in the Install Application dialog. If you don't provide icons, a set of default icons is provided for you. Note that when you add the icons to your Silverlight project, you must change their build action from "Resource" to "Content" so they're packaged in the XAP file as loose resources.

It is the presence of an <ApplicationIdentity> element that prompts Silverlight to include an option for installing an app locally in the right-click context menu.

There are two ways that a Silverlight app can be installed locally. First, the user can right-click the Silverlight control and select "Install appname onto this computer." Second, the application itself can call the new Application.Detach method:

Application.Current.Detach();

Detach displays the same Install Application dialog as the "Install appname" menu item and returns a bool indicating whether the application was successfully detached--that is, installed locally. Detach fails if the app has already been installed locally, if OOB support hasn't been enabled through the application manifest, or if the user cancels the install. It also fails if it wasn't called in response to a user action such as a button click.

Since an OOB application can be launched even if there is no network connection available, it might want to determine at run-time whether it's running in a connected or disconnected state. There's no sense in trying to call a Web service, for example, if you're not even connected to the Internet. One simple call is sufficient to determine whether a network connection is currently available:

if (NetworkInterface.GetIsNetworkAvailable())

{

    // App is connected

}

else

{

    // App is not connected

}

The NetworkInterface class is new in Silverlight 3; it belongs to the System.Net.NetworkInformation namespace. Another System.Net.NetworkInformation class is NetworkChange, which, through its NetworkAddressChanged event, notifies interested parties when the network status changes--for example, when a network connection becomes available or goes away:

NetworkChange.NetworkAddressChanged += Network_NetworkAddressChanged;

  ...

void Network_NetworkAddressChanged(object sender, EventArgs e)

{

    if (NetworkInterface.GetIsNetworkAvailable())

    {

        // App is connected

    }

    else

    {

        // App is not connected

    }

}

These calls work in Silverlight apps hosted in the browser, too. And they're useful in both places. An application running offline could, for example, elect to store data locally in isolated storage rather than submit the data to a service if it finds it has no network connection. Incidentally, the moment an application is detached, its isolated storage quota is automatically increased to 25 MB in Beta 1. This affects both the detached version of the app and instances of the app hosted in a browser.

Can an application that has been detached determine at run-time whether it was launched or in a browser or in a stand-alone window? You bet:

if (Application.Current.RunningOffline)

{

    // Launched in sllauncher.exe

}

else

{

    // Launched in a browser

}

The new Application.RunningOffline property returns true or false indicating whether the app was launched in a browser (false) or in a separate window (true). This is useful because some Silverlight features that are available to browser-based application instances aren't available to OOB instances. HTML DOM support is one such feature. An OOB app can't interrogate the browser DOM or call JavaScript functions because outside of a browser, these features do not exist. In Beta 1, an application running outside the browser has limited control over the environment in which it is executing. It cannot, for example, change the size of the window in which it is hosted. This may change before final release in response to feedback from developers.

One final tidbit regarding out-of-browser applications is that the Application class now exposes a public property named ExecutionState. Among other things, this property can be used to determine whether an updated version of an app launched outside the browser is available. Each time an app is launched outside the browser, it sends a request to the server to determine whether a new XAP file is available. If the answer is yes, the XAP is automatically downloaded. (Again, in Beta 1, there is not an option for declining the update). The new version isn't executed automatically; instead, it's executed the next time the app is started outside the browser. An OOB can use the new Application.ExecutionStateChanged event to learn when a newer version is available and prompt the user to restart the app to launch the new version:

Application.Current.ExecutionStateChanged += Application_ExecutionStateChanged;

  ... 

void Application_ExecutionStateChanged(object sender, EventArgs e)

{

    if (Application.Current.ExecutionState ==

        ExecutionStates.DetachedUpdatesAvailable)

        MessageBox.Show("An updated version of this application is available. " +

        "Close the application and restart it to run the new version.");

}

I have been mostly unsuccessful in getting the automatic update feature to work in Beta 1, but I suppose that's why they call it a beta.

For more on Silverlight 3's out-of-browser application support, see Tim Heuer's excellent blog post on the subject.

Another new and notable feature of Silverlight 3 is support for hardware acceleration. In Silverlight 1 and 2, all rendering was done in software, which meant that the performance of complex animations and video playback depended heavily on the capabilities of the host PC. Silverlight 3, however, can take advantage of hardware GPUs. You can see this for yourself in Beta 1 and even measure the impact that it has on performance and CPU usage.

To demonstrate, I created a Silverlight 3 project named GPUDemo and added a bunch of XAML depicting a penguin to it. I set the penguin's opacity to 0.5 and also defined some animations that rotate and scale the penguin continuously--all actions designed to stress a software rendering pipeline.

GPU Test

Here's what the XAML looked like:

<UserControl x:Class="GPUDemo.MainPage"

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

    <Grid x:Name="LayoutRoot">

        <Grid.Background>

            <LinearGradientBrush StartPoint="0.5,0.0" EndPoint="0.5,1.0">

                <GradientStop Color="#e08080" Offset="0" />

                <GradientStop Color="#8080e0" Offset="1" />

            </LinearGradientBrush>

        </Grid.Background>

        <Canvas x:Name="Penguin" Width="340" Height="322" Opacity="0.5">

            <Canvas.Triggers>

                <EventTrigger RoutedEvent="Canvas.Loaded">

                    <BeginStoryboard>

                        <Storyboard>

                            <DoubleAnimation

                                Storyboard.TargetName="Rotate"

                                Storyboard.TargetProperty="Angle"

                                From="0" To="360" Duration="0:0:0.5"

                                RepeatBehavior="Forever" />

                            <DoubleAnimation

                                Storyboard.TargetName="Scale"

                                Storyboard.TargetProperty="ScaleX"

                                From="1.0" To="1.5" Duration="0:0:0.5"

                                AutoReverse="True" RepeatBehavior="Forever" />

                            <DoubleAnimation

                                Storyboard.TargetName="Scale"

                                Storyboard.TargetProperty="ScaleY"

                                From="1.0" To="1.5" Duration="0:0:0.5"

                                AutoReverse="True" RepeatBehavior="Forever" />

                        </Storyboard>

                    </BeginStoryboard>

                </EventTrigger>

            </Canvas.Triggers>

            <Canvas.RenderTransform>

                <TransformGroup>

                    <RotateTransform x:Name="Rotate"

                        CenterX="167" CenterY="202" />

                    <ScaleTransform x:Name="Scale"

                        CenterX="167" CenterY="202" />

                </TransformGroup>

            </Canvas.RenderTransform>

            <!-- Penguin XAML goes here -->

        </Canvas>

    </Grid>

</UserControl>

 

When I ran the program, I used Task Manager to gauge CPU usage. The reading varied from 13% to 35%, with the average being in the mid to high 20s. When a single application is using a third of the CPU, that application is obviously stressing the host PC.

Then I repeated the test with GPU support enabled. First, I opened GPUDemoTestPage.html and added one line to the <OBJECT> element that instantiates the Silverlight control:

<param name="EnableGPUAcceleration" value="true" />

Second, I added the following XAML to the penguin canvas to enable the entire penguin to be cached as a bitmap and rendered by the GPU:

<Canvas.CacheMode>

    <BitmapCache />

</Canvas.CacheMode>

When I ran the application again, CPU usage ranged from 2% to 8%, clearly demonstrating that much of the rendering load had been offloaded to the GPU.

You can achieve similar results when you play video in a MediaElement, especially if the video is displayed at a non-native resolution, or if the video is an H.264/MP4 video--another new feature of Silverlight 3.

Incidentally, Silverlight 3 offers a cache visualization feature that's enabled by including the following <PARAM> element in the control's <OBJECT> tag:

<param name="EnableCacheVisualization" value="true" />

When cache visualization is enabled, objects that are cached (that is, handled by the GPU) are displayed in their normal colors, while objects that aren't cached are tinted.

SIDE NOTE: Things have changed a lot in India since I was last here in November. Cars have to queue for inspection before they pull up to a hotel, and before you're allowed into a hotel, you go through airport-like security. Security is especially tight at hotels in Mumbai, but you see it in here in Hyderabad, too. I don't mind a little inconvenience to know that I'm safe in my hotel room and that there likely won't be a repeat of the attacks of 26/11.

One of the improvements you can look forward to in Silverlight 3 is element data binding, also known as element-to-element data binding. Silverlight 2's {Binding} expression allowed you to specify the name of the property you were binding to, but not the element that owned the property. This typically meant you had to write a line of code to complete the binding. In Silverlight 3, {Binding} expressions can identify elements as well as properties.

Here's an example:

<Grid x:Name="LayoutRoot" Background="White">

    <Border>

        <Border.Projection>

            <PlaneProjection x:Name="Projector" />

        </Border.Projection>

        <user:Penguin />

    </Border>

    <StackPanel Orientation="Vertical" VerticalAlignment="Top">

        <Slider Minimum="0" Maximum="360"

            Value="{Binding RotationX, ElementName=Projector, Mode=TwoWay}"

            Width="400" VerticalAlignment="Top" Margin="0,20,0,0" />

        <Slider Minimum="0" Maximum="360"

            Value="{Binding RotationY, ElementName=Projector, Mode=TwoWay}"

            Width="400" VerticalAlignment="Top" Margin="0,20,0,0" />

        <Slider Minimum="0" Maximum="360"

            Value="{Binding RotationZ, ElementName=Projector, Mode=TwoWay}"

            Width="400" VerticalAlignment="Top" Margin="0,20,0,0" />

    </StackPanel>

</Grid>

This XAML declares a user control that draws a penguin, and then wires up a trio of sliders to the RotationX, RotationY, and RotationZ properties of a PlaneProjection. The result? You can rotate the penguin about the X, Y, and Z axes by moving the sliders. And there's no code required.

Element Data Binding 

Pretty cool. But element data binding has a couple of limitations/nuances of which you should be aware. First, I applied the PlaneProjection to a Border containing the penguin user control because element data binding didn't work when I applied it directly to the user control. I'm assuming this is simply a beta bug and that in the final release, I can get rid of the Border.

Second, and more importantly, the target of an element data binding must be a FrameworkElement derivative. I would have preferred to write my XAML like this:

<Grid x:Name="LayoutRoot" Background="White">

    <Border>

        <Border.Projection>

            <PlaneProjection

                RotationX="{Binding Value, ElementName=SliderX}"

                RotationY="{Binding Value, ElementName=SliderY}"

                RotationZ="{Binding Value, ElementName=SliderZ}"

            />

        </Border.Projection>

        <user:Penguin />

    </Border>

    <StackPanel Orientation="Vertical" VerticalAlignment="Top">

        <Slider x:Name="SliderX" Minimum="0" Maximum="360" Value="0"

            Width="400" VerticalAlignment="Top" Margin="0,20,0,0" />

        <Slider x:Name="SliderY" Minimum="0" Maximum="360" Value="0"

            Width="400" VerticalAlignment="Top" Margin="0,20,0,0" />

        <Slider x:Name="SliderZ" Minimum="0" Maximum="360" Value="0"

            Width="400" VerticalAlignment="Top" Margin="0,20,0,0" />

    </StackPanel>

</Grid>

But that produces a XAML parsing error since PlaneProjection isn't a FrameworkElement. It is unclear at the moment whether the requirement that an element data binding target a FrameworkElement will change before Silverlight 3 ships. The good news is that lots of elements do derive from FrameworkElement. It is perfectly legal, for example, to bind a Slider representing a volume control to the Volume property of a MediaElement since MediaElement derives from FrameworkElement (and Volume is a dependency property).

Incidentally, I'm always interested to hear about companies who adopt Silverlight and put it to work in creative ways. But when John Robbins told me that Playboy magazine is now using Silverlight Deep Zoom, I wasn't sure what to think. I wondered why John had suddenly developed an interest in Silverlight. :-)

I've had a blast this week blogging about Silverlight 3, and I have many more tips and samples to share. However, I'm leaving for Microsoft India in a few hours, so I won't be blogging much for the next week or so. I plan to resume my series on Silverlight 3 the week after next and hope to be posting new entries right up to Devscovery.

Finally, as I wrote the sentence "element data binding has a couple of limitations/nuances of which you should be aware," I was reminded of a quotation from Winston Churchill:

Churchill 

Which is why I sometimes do allow myself to end sentences with prepositions!

One of the features of Silverlight 3 that developers are going to love is local connections, which enable Silverlight control instances to communicate with each other. Local connections use a publish/subscribe mechanism to connect senders and receivers. And they work just as well whether the control instances are in the same page (and the same process) or in different pages and different processes. In Silverlight 2, you could do a lot of manual work to allow control instances in the same page to message each other, but it was next to impossible to bridge the gap between two control instances in two different browsers.

I wrote a sample app that demonstrates the basics of local connections. One of the test pages in the solution--LocalConnectionDemoTestPage.html--declares two Silverlight control instances. The control instance at the top of the page is a throwback to the old MFC Scribble tutorial from years ago: you scribble in it by holding down the left mouse button. The control instance at the bottom of the page mirrors the strokes drawn in the first control instance:

Local Connections (1) 

When the first control instance starts up, it initializes a LocalMessageSender object in order to send messages to the second control:

private LocalMessageSender _sender =

    new LocalMessageSender("StrokeReflector");

When the left mouse button goes down, the first control records the mouse coordinates and then sends them to the second control:

_last = e.GetPosition((FrameworkElement)sender);

_sender.SendAsync(String.Format(CultureInfo.InvariantCulture,

    "S{0},{1}", _last.X, _last.Y));

And when the mouse moves with the left button held down, the first control draws a line from the previous mouse coordinates to the current coordinates and fires off a message to the second control containing the current coordinates:

// Add a line

Point current = e.GetPosition((FrameworkElement)sender);

 

Line line = new Line();

line.X1 = _last.X;

line.Y1 = _last.Y;

line.X2 = current.X;

line.Y2 = current.Y;

line.Stroke = new SolidColorBrush(Colors.White);

line.StrokeThickness = 8.0;

line.StrokeStartLineCap = PenLineCap.Round;

line.StrokeEndLineCap = PenLineCap.Round;

ScribbleSurface.Children.Add(line);

 

_last = current;

 

// Notify the other control

_sender.SendAsync(String.Format(CultureInfo.InvariantCulture,

    "D{0},{1}", current.X, current.Y));

For its part, the second control initializes a LocalMessageReceiver when it starts up:

private LocalMessageReceiver _receiver =

    new LocalMessageReceiver("StrokeReflector");

Then it registers a handler for MessageReceived events and calls LocalMessageReceiver.Listen to listen for messages:

_receiver.MessageReceived +=

    new EventHandler<MessageReceivedEventArgs>(OnMessageReceived);

_receiver.Listen();

The event handler decodes each message transmitted by the first control and either records the anchor point if the mouse button just went down in the first control, or duplicates the stroke drawn in the first control if the mouse was moved:

void OnMessageReceived(object sender, MessageReceivedEventArgs e)

{

    string command = e.Message.Substring(0, 1);

 

    switch (command)

    {

        case "S":

            _last = GetPointFromMessage(e.Message);

            break;

 

        case "D":

            Point point = GetPointFromMessage(e.Message);

 

            Line line = new Line();

            line.X1 = _last.X;

            line.Y1 = _last.Y;

            line.X2 = point.X;

            line.Y2 = point.Y;

            line.Stroke = new SolidColorBrush(Colors.White);

            line.StrokeThickness = 8.0;

            line.StrokeStartLineCap = PenLineCap.Round;

            line.StrokeEndLineCap = PenLineCap.Round;

            ScribbleSurface.Children.Add(line);

 

            _last = point;

            break;

    }

}

It really gets interesting when you run the app's other two test pages. SenderTestPage.html creates an instance of the first control (the "sender"), and ReceiverTestPage.html creates an instance of the second control (the "receiver"). Without changing a line of code, you can start each page in a different browser instance and draw in the sender and see your strokes duplicated in the receiver. The screen shot below was taken with the sender running in IE, and the receiver running in Firefox--two different browsers and two different processes.

Local Connections (2) 

But don't believe me: download the sample app and try it yourself!

Easing is a new feature of Silverlight 3 for dressing up animations. I've often told students and conference audiences that you can't simulate physics with simple linear animations (also known as "from/to" animations), and that you have to use key-frame animations instead. In Silverlight 2, for example, it's easy to animate a ball dropping to the floor. But if you want it to look real--that is, if you want the ball to accelerate as it approaches the floor and bounce when it hits--well, that requires a lot more work.

Not so in Silverlight 3. Animation easing makes it incredibly easy to add effects to your animations. Silverlight 3 Beta 1 comes with about a dozen different easing classes, including BounceEase, which adds bounce to animations, and ElasticEase, which adds oscillations. You can also write easing classes of your own by deriving from the EasingFunctionBase class. But the predefined easing classes are so rich and varied that you may never need to write your own.

Here's a simple way to see easing at work. Begin by creating a new Silverlight 3 project. Then edit MainPage.xaml to look like this:

<UserControl x:Class="EasingDemo.MainPage"

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

    <Grid x:Name="LayoutRoot" Background="White">

        <Canvas x:Name="Backdrop" Width="300" VerticalAlignment="Bottom">

            <Ellipse x:Name="Ball" Canvas.Left="125" Canvas.Top="50" Width="50"

                Height="50" MouseLeftButtonDown="Ball_MouseLeftButtonDown">

                <Ellipse.Resources>

                    <Storyboard x:Name="Drop">

                        <DoubleAnimation x:Name="DropAnimation"

                            Storyboard.TargetName="Ball"

                            Storyboard.TargetProperty="(Canvas.Top)"

                            Duration="0:0:1">

                            <DoubleAnimation.EasingFunction>

                                <BounceEase Bounces="10" Bounciness="2"

                                    EasingMode="EaseOut" />

                            </DoubleAnimation.EasingFunction>

                        </DoubleAnimation>

                    </Storyboard>

                    <Storyboard x:Name="Reset">

                        <DoubleAnimation x:Name="ResetAnimation"

                            Storyboard.TargetName="Ball"

                            Storyboard.TargetProperty="(Canvas.Top)"

                            To="50" Duration="0:0:1">

                            <DoubleAnimation.EasingFunction>

                                <ElasticEase Oscillations="1" Springiness="8" />

                            </DoubleAnimation.EasingFunction>                           

                        </DoubleAnimation>

                    </Storyboard>

                </Ellipse.Resources>

                <Ellipse.Fill>

                    <RadialGradientBrush GradientOrigin="0.25,0.25">

                        <GradientStop Offset="0.25" Color="White" />

                        <GradientStop Offset="1.0" Color="Red" />

                    </RadialGradientBrush>

                </Ellipse.Fill>

            </Ellipse>

        </Canvas>

    </Grid>

</UserControl>

Now open MainPage.xaml.cs and edit the MainPage class to look like this:

public partial class MainPage : UserControl

{

    public MainPage()

    {

        InitializeComponent();

        Backdrop.Height = Application.Current.Host.Content.ActualHeight;

    }

 

    private void Ball_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)

    {

        double y = (double)Ball.GetValue(Canvas.TopProperty);

 

        if (y == 50.0)

        {

            DropAnimation.To = Backdrop.Height - 50.0;

            Drop.Begin();

        }

        else

        {

            Reset.Begin();

        }

    }

}

When you run the application, a red ball will appear at the top of the page. Click it to drop it to the floor. Once the ball comes to rest, click it again to reset it to the top of the page. You'll see a BounceEase at work when the ball falls, and an ElasticEase at work when it flies back to the top of the page.

If any of your coworkers fail to be impressed, challenge them to duplicate what they just saw in Silverlight 2!

Silverlight 3 is loaded with new graphical goodies, and one of the goodies I'm most excited about is pixel shaders. A pixel shader is an object that transforms pixels output from the rendering pipeline before they're rendered to the display surface. Silverlight 3 Beta 1 comes with two pixel shaders: BlurEffect and DropShadowEffect. The following example uses DropShadowEffect to dress up some text:

<TextBlock Text="Silverlight" Foreground="Black" FontFamily="Calibri"

    FontSize="64" FontWeight="Bold">

    <TextBlock.Effect>

        <DropShadowEffect BlurRadius="8" ShadowDepth="8" Opacity="0.5" />

    </TextBlock.Effect>

</TextBlock>

Here's the output:

DrodShadowEffect

What's really cool is that you can write custom pixel shaders that can be applied using the same simple, declarative syntax as built-in shaders. Here's how.

First, if you haven't already, download and install the latest DirectX SDK. You need the effects compiler (Fxc.exe) from the SDK to compile custom effects.

Next, create an FX file to hold your new effect and implement the effect using Microsoft's High-Level Shading Language (HLSL). For this example, I wrote the following HLSL in a file named WateryEffect.fx:

sampler2D input : register(S0);

 

float4 main(float2 uv : TEXCOORD) : COLOR

{

    uv.y = uv.y  + (sin(uv.y*100)*0.03);

    return tex2D( input , uv.xy);

}

The next step is to compile the FX file into a PS file. To do that, open a DirectX SDK command prompt window and run the effects compiler using a command like this one, which compiles WateryEffect.ps from WateryEffect.fx:

fxc /T ps_2_0 /Fo WateryEffect.ps WateryEffect.fx

As an alternative to running Fxc.exe from the command line, you can download Walt Ritscher's excellent Shazzam utility and use it to compile PS files. An added bonus with Shazzam is that you can test and debug your effects before compiling them.

Now that your effect has been compiled into a PS file, you need to write a custom Silverlight shader and connect it to the PS file. You do that by deriving a class from System.Windows.Media.Effects.ShaderEffect and providing a default constructor that initializes the PixelShader property with a PixelShader reference. Here's an example:

public class WateryEffect : ShaderEffect

{

    public WateryEffect()

    {

        this.PixelShader = new PixelShader() { UriSource = new Uri

            ("CustomShaderDemo;component/WateryEffect.ps",

            UriKind.Relative) };

    }

}

Be sure to replace "CustomerShaderDemo" with the name of your application assembly. In order for this code to work, you'll need to add the PS file to your Silverlight project and change its build action to "Resource." That's all the constructor code is really doing: creating a PixelShader that's initialized from a resource named WateryEffect.ps.

Now you're ready to use your custom shader. First declare an XML namespace prefix that references the namespace (and optionally assembly) in which the WateryEffect class is implemented:

xmlns:custom="clr-namespace:CustomShaderDemo" 

Finally, apply your effect to a XAML object:

<TextBlock Text="Silverlight" Foreground="Black" FontFamily="Calibri"

    FontSize="64" FontWeight="Bold">

    <TextBlock.Effect>

        <WateryEffect />

    </TextBlock.Effect>

</TextBlock>

To demonstrate, I implemented WateryEffect as described above and applied it to a user control depicting my favorite penguin. Here's the XAML. Note that two copies of the user control are declared. The one on top has a DropShadowEffect applied to it, while the one on bottom has a WateryEffect applied to it:

<UserControl x:Class="CustomShaderDemo.MainPage"

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    xmlns:custom="clr-namespace:CustomShaderDemo">

    <Grid x:Name="LayoutRoot" Background="White">

        <StackPanel Orientation="Vertical" VerticalAlignment="Center">

            <custom:Penguin>

                <custom:Penguin.Effect>

                    <DropShadowEffect ShadowDepth="20" BlurRadius="20" Opacity="0.4" />

                </custom:Penguin.Effect>

            </custom:Penguin>

            <custom:Penguin>

                <custom:Penguin.Effect>

                    <custom:WateryEffect />

                </custom:Penguin.Effect>   

                <custom:Penguin.RenderTransform>

                    <TransformGroup>

                        <TranslateTransform Y="-322" />

                        <ScaleTransform ScaleY="-1" />

                    </TransformGroup>

                </custom:Penguin.RenderTransform>

            </custom:Penguin>

        </StackPanel>

    </Grid>

</UserControl>

And here is the result:

 

Penguin with WateryEffect Reflection

I based my HLSL code on a sample I found in an excellent blog post by Tamir Khason. HLSL examples abound on the Internet, and the fact that Silverlight 3 supports HLSL opens up a whole new world of possibilities for Silverlight developers.

One of the WPF features that I miss in Silverlight is VisualBrush. Among other things, VisualBrush makes it easy to generate simulated reflections. To create reflections in Silverlight, we typically declare a copy of all the XAML we want to reflect, use a ScaleTransform to flip the copy upside-down, and apply an Opacity or OpacityMask to complete the illusion.

In Silverlight 3, you can use the new WriteableBitmap class to generate reflections without duplicating any XAML. In addition to letting you create bitmapped images from scratch, WriteableBitmap features a Render method that you can use to render all or part of the visual tree to a bitmap. To generate a reflection, you simply declare an Image in the scene and position it where you want the reflection to appear, and then use WriteableBitmap.Render to render everything you want reflected into the Image.

To demonstrate, I built a sample whose XAML is structured like this:

<UserControl x:Class="ReflectionDemo.MainPage"

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

    <Grid x:Name="LayoutRoot" Background="White">

        <StackPanel Orientation="Vertical" VerticalAlignment="Center">

 

            <!-- XAML to be reflected -->

            <Canvas x:Name="Penguin" Width="340" Height="322">

              ...

            </Canvas>

 

            <!-- Reflection -->

            <Image x:Name="Reflection" Stretch="None">

                <Image.RenderTransform>

                    <TransformGroup>

                        <TranslateTransform Y="-322" />

                        <ScaleTransform ScaleY="-1" />

                    </TransformGroup>

                </Image.RenderTransform>

                <Image.OpacityMask>

                    <LinearGradientBrush StartPoint="0.0,0.0" EndPoint="0.0,1.0">

                        <GradientStop Offset="1.0" Color="#80000000" />

                        <GradientStop Offset="0.5" Color="#00000000" />

                    </LinearGradientBrush>

                </Image.OpacityMask>

            </Image>

        </StackPanel>

    </Grid>

</UserControl> 

Then I added the following statements to MainPage's constructor:

WriteableBitmap bitmap = new WriteableBitmap ((int)Penguin.Width,

    (int)Penguin.Height, PixelFormats.Bgr32);

bitmap.Render(Penguin, new TranslateTransform());

Reflection.Source = bitmap;

And here's the result:

Reflection 

I could have built the transforms for the reflected image into the code, but I reasoned that it's better to apply the transforms declaratively so you can control where and how the reflection is positioned. If you wanted to reflect around the Y-axis instead of the X, for example, you could simply change ScaleY="-1" to ScaleX="-1" in the ScaleTransform.

Another of the exciting new features coming in Silverlight 3 is the WriteableBitmap class. Silverlight 2 had no API for creating bitmaps, but WriteableBitmap makes creating bitmaps on the fly easy as pie. In Beta 1, you can create bitmaps from scratch, but you can't (yet) edit existing bitmaps--for example, bitmaps assigned to XAML images or loaded from the local hard disk. Expect that to change once Microsoft has had time to fully ponder the security implications of editing bitmaps and can lock down the API to prevent malicious applications from exploiting this feature.

To demonstrate how WriteableBitmap works, I built a Silverlight 3 fractal viewer that creates images of the Mandelbrot set on the client using the same basic algorithm that the Deep Zoom fractal viewer I blogged about last week uses to generate fractal images on the server. Here are some screen shots from the Silverlight 3 version:

Silverlight 3 Mandelbrot (1)Silverlight 3 Mandelbrot (2)Silverlight 3 Mandelbrot (3)

The work of drawing the Mandelbrot set is peformed by a helper method named DrawMandelbrotSet, which takes as parameters a reference to a XAML Image object, the coefficients of the equation that describes the horizontal and vertical extents of the region of the set to be rendered, and the desired width and height of the generated bitmap. Here's the source code:

private void DrawMandelbrotSet(Image image, double rmin, double rmax,

    double imin, double imax, int width, int height)

{

    // Create a WriteableBitmap

    WriteableBitmap bitmap = new WriteableBitmap(width, height, PixelFormats.Bgr32);

    bitmap.Lock();

 

    // Compute increments for real and imaginary components

    double dr = (rmax - rmin) / (width - 1);

    double di = (imax - imin) / (height - 1);

 

    // Check each pixel for inclusion in the Mandelbrot set

    for (int x = 0; x < width; x++)

    {

        double cr = rmin + (x * dr);

 

        for (int y = 0; y < height; y++)

        {

            double ci = imin + (y * di);

            double zr = cr;

            double zi = ci;

            int count = 0;

 

            while (count < _max)

            {

                double zr2 = zr * zr;

                double zi2 = zi * zi;

 

                if (zr2 + zi2 > _escape)

                {

                    bitmap[(y * width) + x] = ColorMapper.GetColor(count, _max);

                    break;

                }

 

                zi = ci + (2.0 * zr * zi);

                zr = cr + zr2 - zi2;

                count++;

            }

 

            if (count == _max)

                bitmap[(y * width) + x] = 0; // Black

        }

    }

 

    bitmap.Invalidate();

    bitmap.Unlock();

 

    // Copy the WriteableBitmap bits to the XAML Image

    image.Source = bitmap;

}

The individual pixels in the bitmap are exposed through WriteableBitmap's indexer: bitmap[0] is the first pixel (the one in the upper left corner), bitmap[1] is the second pixel (the one to the right of bitmap[0]), and so on. It's hard to imagine how the API could be much simpler than that.

This is one of many Silverlight 3 samples I'll be presenting at Devscovery next month in New York. In fact, I'm doing an entire session on all the new stuff in Silverlight 3, so I'd love to see you there! I'll be posting the source code for most of these samples a little later, but I'm going to hold off until after Devscovery so Devscovery attendees are the first to receive them.

One of the many, many new features coming in Silverlight 3 (and featured in Silverlight 3 Beta 1, which shipped this week) is the perspective transform. In the Silverlight MyComix viewer I built for Silverlight 1.0, I faked the 3D rotation of a comic book cover around the Y-axis by using a ScaleTransform to squeeze the cover image horizontally and a SkewTransform to alter the angle of the top and bottom edges, but the effect wasn't perfect because Silverlight gave me no way to add perspective by displaying the top and bottom edges at different angles. In addition, I had to compute the parameters for the transforms using trigonometry.

Silverlight 3's PlaneProjection class makes it incredibly easy to implement such effects, and to do so with the added benefit of perspective. To rotate an image around the Y-axis through its center, you declare the image like this:

<Image x:Name="CoverImage" Source="...">

    <Image.Projection>

        <PlaneProjection x:Name="Rotator" />

    </Image.Projection>

</Image>

 

And to perform the rotation, you simply manipulate the value of the PlaneProjection's RotationY property using an animation, a MouseMove handler, element-to-element binding, or whatever.

To demonstrate, I wrote a simple viewer that uses a PlaneProjection to rotate a cover image as you move the mouse across the page with the left button depressed. I included extra logic to swap the front and back cover images at 90 and 270 degrees to create the illusion that you really are spinning a comic book:

 

Here's the XAML code-behind class containing the logic that manipulates the PlaneProjection. The mouse-event handlers are attached to the Grid control that contains the Image:

public partial class MainPage : UserControl

{

    private bool _down = false;

    private double _lastx = -1;

    private double _increment = 2.0;

    private BitmapImage _front =

        new BitmapImage(new Uri("Images/Front.jpg", UriKind.Relative));

    private BitmapImage _back =

        new BitmapImage(new Uri("Images/Back.jpg", UriKind.Relative));

    private bool _isfront = true;

 

    public MainPage()

    {

        InitializeComponent();

    }

 

    private void LayoutRoot_MouseLeftButtonDown(object sender,

        MouseButtonEventArgs e)

    {

        _lastx = e.GetPosition((FrameworkElement)sender).X;

        _down = true;

    }

 

    private void LayoutRoot_MouseLeftButtonUp(object sender,

        MouseButtonEventArgs e)

    {

        _lastx = -1;

        _down = false;

    }

 

    private void LayoutRoot_MouseMove(object sender, MouseEventArgs e)

    {

        if (_down)

        {

            double x = e.GetPosition((FrameworkElement)sender).X;

            double angle = Rotator.RotationY;

 

            if (x > _lastx) // Rotate right

            {

                angle -= _increment;

 

                if (angle < 0.0)

                    angle += 360.0;

 

                if (angle == 270.0)

                {

                    CoverImage.Source = _isfront ? _back : _front;

                    _isfront = !_isfront;

                    angle = 90.0;

                }

 

                Rotator.RotationY = angle;

            }

            else if (x < _lastx) // Rotate left

            {

                angle += _increment;

 

                if (angle >= 360.0)

                    angle -= 360.0;

 

                if (angle == 90.0)

                {

                    CoverImage.Source = _isfront ? _back : _front;

                    _isfront = !_isfront;

                    angle = 270.0;

                }

 

                Rotator.RotationY = angle;

            }

 

            _lastx = x;

        }

    }

 

    private void LayoutRoot_MouseLeave(object sender, MouseEventArgs e)

    {

        _lastx = -1;

        _down = false;

    }

}

Wasn't that easy? And best of all: no trig!

More Posts Next page »