Jeff Blankenburg is a passionate technologist with a wide range of interests.
This website is dedicated to discussing the ideas that pass through his head.

The main header for this website was created using Microsoft Silverlight.
Install it to see what you're missing!

Get Silverlight to see the whole site!

Click The Button - The Contest

Friday, January 01, 2010

I have created a small game I call "Click The Button" in Silverlight. In fact, you can launch it by clicking the button right below this paragraph. The first five people to finish the entire puzzle will be awarded the e-book of their choice from O'Reilly. (Click here to see the list of books they offer.) So get to it! What are you waiting for?

Labels: , ,

posted by Jeff Blankenburg, 1:00 AM | link | 1 comments |

Day #31: Geocoding and More Fun In Bing Maps for Silverlight

Thursday, December 31, 2009

Can you believe it? We're at the end of this (longer than it should have been) journey. Today is the last day of the 31 Days of Silverlight. We are going to be building a more advanced version of the application we started yesterday, Day #30, related to Bing Maps in Silverlight.

1. Geocoding an address

For those of you relatively new to mapping, there's this wonderful concept called Geocoding which makes our lives as developers much, much simpler. The basic premise is that we can provide our application with an actual address, and have a geocoding service figure out the latitude/longitude coordinates of that address. This way, we don't have to tell our users to figure it out for themselves. Can you imagine going to Bing Maps to look at your house, only to be told you need to know the lat/long of that location? Not exactly user friendly. So, let's get to it.

2. Adding the Geocoding service to our application

Before we can geocode an address, we need to add a reference to Bing's geocoding web service. So let's start there. Click on your Silverlight project, and choose "Add service reference..." Use this address as your address, and click Go:

http://dev.virtualearth.net/webservices/v1/GeocodeService/GeocodeService.svc

You should end up with a window that looks like this:



3. Creating an asynchronous call to that service

Much like talking to any web service, we have a couple of things we need to do. The first is to create a new instance of the service. The second is to attach a Completed event handler to the service, so that we know when a response has occurred. Finally, we need to create our service request, and call the service asynchronously. In my example, I created a method called Geocode that takes the same two parameters as the web service: an address, and a waypointIndex. I'll explain waypointIndex later, when we talk about routing and directions. So, here's what my method looks like:

        private void Geocode(string address, int waypointIndex)
{
GeocodingService.GeocodeServiceClient geocodingService = new GeocodingService.GeocodeServiceClient("BasicHttpBinding_IGeocodeService");
geocodingService.GeocodeCompleted += new EventHandler<SilverlightBingMapsGeocoding.GeocodingService.GeocodeCompletedEventArgs>(geocodingService_GeocodeCompleted);

GeocodingService.GeocodeRequest request = new GeocodingService.GeocodeRequest();
request.Credentials = new Credentials();
request.Credentials.ApplicationId = ((ApplicationIdCredentialsProvider)myMap.CredentialsProvider).ApplicationId;
request.Query = address;

geocodingService.GeocodeAsync(request, waypointIndex);
}


You can see that we also have to attach our API Credentials that we got in Day #30 to our request. Without a valid API key, we can't make requests. Since I stored mine in the XAML, the reference you see in my code above is to that XAML location. For the Completed event handler, we need the event method for that as well. In mine, I am capturing the new Location object it returns, and adding that as a pushpin to my map, like we did yesterday. This solution only works when you're certain the service will only be returning one value to you. Here's the code:

        void geocodingService_GeocodeCompleted(object sender, SilverlightBingMapsGeocoding.GeocodingService.GeocodeCompletedEventArgs e)
{
Location center = e.Result.Results[0].Locations[0];
myMap.Center = center;
Pushpin pushpin = new Pushpin();
pushpin.Location = center;
myMap.Children.Add(pushpin);
}


After all of that is done, we just need to call our Geocode() method. In my example, I'm using the address of my office again. Here's the method call:

Geocode("8800 Lyra Avenue, Columbus, OH  43240", 1);


4. Geocoding's easy. How about turn-by-turn?

Directions are a little trickier, but not significantly. In fact, we wouldn't need to change our Geocode method one bit. The major changes we make to our application include using another web service, the RouteService, and adding the Waypoints that it returns to your map.

There's an excellent explanation of this on the MSDN reference for Bing Maps in Silverlight. You can read it here. http://msdn.microsoft.com/en-us/library/ee681887.aspx

I was originally planning on writing out an entire tutorial for this section as well, but after reading their explanation, I realized I'd only be taking away from an excellent article.

Summary

Getting an address to a latitude and longitude point is nothing to sneeze at. I don't even want to think about what is required to happen inside those services. I'm happy to have this nice, thick layer of abstraction to manipulate. I think you'll find as you go forward that the route plotting is really just as simple.

To see this Silverlight Geocoding example running in another window, click here.

To download the source of this Silverlight Geocoding for Bing Maps applciation, click here.

This series on Silverlight has certainly been quite an adventure. I hope that you had an opportunity to learn a few things along the way. I certainly know that I did.

This coming February 2010, I am going to be starting another series, with a slightly less focused approach. They will all be related to software development and technology, but I'm going to branch out a bit. My first two posts will cover creating a theme in Windows 7, and the second will cover XNA development for the Zune. I also have plans to cover topics like ASP.NET MVC 2, among others. If there's something you'd like to see me cover in this as-to-be-named series, please let me know, and I'll do my best to accomodate you. For now, however, I'm signing off of this 31 Days of Silverlight, and look forward to the next adventure.

P.S. Tune in tomorrow for a game I created in Silverlight. The first 5 people to finish it will win a free O'Reilly ebook of their choice!

Labels: , , , , ,

posted by Jeff Blankenburg, 1:00 AM | link | 1 comments |

Day #30: Bing Maps In Silverlight

Wednesday, December 30, 2009

Today we continue our exploration through the 31 Days of Silverlight with something new. We now have the ability to add a Bing Maps control to our Silverlight application. There's some downloading and installation necessary, so let's start there.

1. Get the Bing Maps Silverlight Control SDK

It sounds as simple as it is. You can download and install the Bing Maps Silverlight Control SDK here. Once you've installed it, jump to the next step.



2. Get a Bing Maps API key

Next, you need to head over to http://www.bingmapsportal.com. Log in with your Live ID, and create a key. You'll need this to build your application, so even though you're thinking about skipping this step, don't. Go get it.

3. Create a new Silverlight project

This is Day #30 of my Silverlight series, so I'm going to assume at this point that you know how to do this.

4. Add the Map .dll references

When you installed the SDK, it dropped two DLLs on your hard drive. If you left the default path during installation, you should be able to find them here: C:\Program Files\Bing Maps Silverlight Control\V1\Libraries. We're almost ready to add a map to our page.



5. Add the namespace declaration

The final "prep" step we need to take is to add the XML namespace to the top of our XAML file. We have done this in previous projects, so I'll just show you what you need to add:

xmlns:bing="clr-namespace:Microsoft.Maps.MapControl;assembly=Microsoft.Maps.MapControl"


6. Adding a map to your page

To take it very simply, we can add one line of XAML to get a map on our page. In fact, here's what it looks like:

<bing:Map CredentialsProvider="put your api key here" />


And here's what the control looks like (see this in a new window):



7. Customize away

There's an absolutely crazy amount of stuff you can do to customize these maps. I'm going to show you a couple of the simple ones, saving some cooler stuff for the next post. Specifically, how to have it start on a specific location, and how to add points to a map.

8. Defining a starting position

The simple way to do this is just to add a latitude/longitude value directly to at map's Center property. You can do this via the codebehind, or directly in your XAML. Like this:

themap.Center = new Location(40.1449, -82.9754);
themap.ZoomLevel = 15;


OR

<bing:Map Name="themap" Center="40.1449, -82.9754" ZoomLevel="15" CredentialsProvider="your api key"  />


You'll notice that I also set the ZoomLevel in both examples, so that we were zoomed in near the location we centered on. That's pretty simple, but it doesn't show the user the location we were trying to call their attention to. Next, we'll actually put a pushpin on the map.

9. Adding a pushpin to the map

There's actually a Pushpin class, which is convenient, especially when we need to have collections of pushpins. I have named my map "themap" for this example. Also, you'll notice that I have moved my latitude/longitude values into a seperate Location object. This way, I can reference that location multiple times, without having to continue to hardcode the coordinates.

What you will see in the following code is that after setting the center of my map (and its zoom level), I create a new Pushpin, set its location, and then add it as a Child to my map. Here's the code:

            Location myOffice = new Location(40.1449, -82.9754);
themap.Center = myOffice;
themap.ZoomLevel = 15;
Pushpin myOfficePin = new Pushpin();
myOfficePin.Location = myOffice;
themap.Children.Add(myOfficePin);


And here's how it looks on the map (see this in a new window):



To download this Bing Maps Silverlight solution, click here.

Summary

Tomorrow, on our final day of this series, we'll be diving deeper into this mapping control, geocoding (turning an address into latitude/longitude), and directions from one point to another. See you then!

Labels: , , , ,

posted by Jeff Blankenburg, 1:00 AM | link | 0 comments |

Day #29: Using Isolated Storage in Silverlight

Tuesday, December 29, 2009

Today is day #29 in my 31 Days of Silverlight series. We're going to cover how to utilize the isolated storage available on the user's machine today. We can use isolated storage when we are in out-of-browser mode, and I recently dedicated 3 entire posts (Days #23 - #25) to getting your application ready for out-of-browser. We will be starting with that codebase, and you can download my Silverlight Out of Browser application here.

1. Adding some using statements

First things first, we're going to need some way to write files, and the way we do that is with the System.IO namespace family. We're going to add two different using statements to our MainPage.xaml.cs file:

using System.IO;
using System.IO.IsolatedStorage;


These allow us to read and write files, as well as access the Isolated Storage on the user's machine.

2. Adding to our XAML file

For this tutorial, I am going to be adding a textbox to our page that will contain the lyrics to the song "Big Bang Theory," by the Barenaked Ladies. We're making this box editable, however, so that a user can write in their own lyrics, or fix typos they may find. They will be saving these changes locally to their isolated storage. Here's what our new XAML file looks like...the only real additions are a TextBox and a Button (for saving):

<UserControl x:Class="SilverlightOutOfBrowser.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480">
<
Canvas x:Name="LayoutRoot">
<
Canvas.Background>
<
LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<
GradientStop Color="#FF2A2A2A" Offset="1"/>
<
GradientStop Color="#FF9D9D9D"/>
</
LinearGradientBrush>
</
Canvas.Background>
<
Image x:Name="networkimage" Height="48" Width="48" Canvas.Left="532" Canvas.Top="10" Source="network_unknown.png" Stretch="Fill" ToolTipService.ToolTip="Network Connectivity"/>
<
MediaElement x:Name="videobox" Height="384" Width="512" Source="/BigBangTheory.wmv" Stretch="Fill" Canvas.Left="12" Canvas.Top="10"/>
<
Image x:Name="outofbrowserimage" Height="48" Width="48" Canvas.Left="585" Canvas.Top="10" Source="outofbrowser_unknown.png" Stretch="Fill" ToolTipService.ToolTip="Install This Application"/>
<
TextBox x:Name="lyrics" Canvas.Top="400" Canvas.Left="12" Width="512" Height="80" AcceptsReturn="True" TextWrapping="Wrap" VerticalScrollBarVisibility="Auto"/>
<
Button x:Name="LyricSave" Click="LyricSave_Click" Width="100" Height="80" Content="Save Lyrics" Canvas.Top="400" Canvas.Left="530" />
</
Canvas>
</
UserControl>


3. Saving a file in isolated storage

Before we can retrieve our data from a file, we should probably write it, right? I am going to create a saveLyricsFile method that takes a string for the lyrics we want to save. All it really does is open a pathway to the isolated storage, creates a file (in our case, a .txt file), and then uses a StreamWriter to write the lyrics to the file. Here's what my method looks like:

        string lyricsfile = "BigBangTheoryLyrics.txt";

private void saveLyricsFile(string lyricsToSave)
{
using (IsolatedStorageFile isf = IsolatedStorageFile.GetUserStoreForApplication())
{
using (IsolatedStorageFileStream isfs = isf.OpenFile(lyricsfile, FileMode.Create, FileAccess.Write))
{
using (StreamWriter sw = new StreamWriter(isfs))
{
sw.WriteLine(string.Format("{0}", lyricsToSave));
sw.Close();
}
}
}
getLyrics();
}


You'll notice that I also call a new method called getLyrics() at the end of this method. Once I've saved the file to the user's machine, I want a different method to retrieve the contents of that file, and write them to the TextBox on my page. That way, each time that the page loads, the lyrics box will have the most up-to-date version of the user's lyrics file.

4. Retrieving the info in the file

Now that we have a file on the user's machine, we need a way to get that information back, and use it. Using many of the same mechanisms from step #3, we are going to find our file in isolated storage, open it, and use a StreamReader to read the contents of the file into a string variable. Finally, we write that string to our TextBox. Here's the method:

        string lyricstext = "Our whole universe was in a hot dense state,\nThen nearly fourteen billion years ago expansion started. Wait...\nThe Earth began to cool,\nThe autotrophs began to drool,\nNeanderthals developed tools,\nWe built a wall (we built the pyramids),\nMath, science, history, unraveling the mysteries,\nThat all started with the big bang!\n\nSince the dawn of man is really not that long,\nAs every galaxy was formed in less time than it takes to sing this song.\nA fraction of a second and the elements were made.\nThe bipeds stood up straight,\nThe dinosaurs all met their fate,\nThey tried to leap but they were late\nAnd they all died (they froze their asses off)\nThe oceans and pangea\nSee ya, wouldn't wanna be ya\nSet in motion by the same big bang!\n\nIt all started with the big BANG!\n\nIt's expanding ever outward but one day\nIt will cause the stars to go the other way,\nCollapsing ever inward, we won't be here, it wont be hurt\nOur best and brightest figure that it'll make an even bigger bang!\n\nAustralopithecus would really have been sick of us\nDebating out while here they're catching deer (we're catching viruses)\nReligion or astronomy, Encarta, Deuteronomy\nIt all started with the big bang!\n\nMusic and mythology, Einstein and astrology\nIt all started with the big bang!\nIt all started with the big BANG!";

private void getLyrics()
{
using (IsolatedStorageFile isf = IsolatedStorageFile.GetUserStoreForApplication())
{
if (isf.FileExists(lyricsfile))
{
using (IsolatedStorageFileStream isfs = new IsolatedStorageFileStream(lyricsfile, FileMode.Open, isf))
{
using (StreamReader sr = new StreamReader(isfs))
{
string lyricsdata = sr.ReadToEnd();
lyrics.Text = lyricsdata;
}
}
}
else
{
saveLyricsFile(lyricstext);
}
}
}

Notice that I set up an external string variable that is my original lyrics text. If the file doesn't exist yet (a distinct possibility, since the user can delete it, or this may be their first time to the site), we need to populate it when we create it. So in my "else" statement, I call the saveLyricsFile method, passing those original lyrics in. Otherwise, if the file exists, we just read the contents of the file, and set those contents to the TextBox.Text value.

5. Calling the getLyrics() method when we load

We already have a Loaded event handler method, so let's use that to call our getLyrics() method. This way, when the page loads, we'll retrieve the contents of the file (and if the file doesn't exist, we'll create it.)

6. Making the button commit the "save"

The last step we need to take is to get our button to actually save the contents of the TextBox to the isolated storage file. The XAML I gave you earlier already had an event handler defined, so "navigate to event handler," and add this line:

saveLyricsFile(lyrics.Text);

This will take the contents of our TextBox, and save it to the user's local file. If you hit refresh in your browser, you should see that the new version of your text appears now. Because I embedded audio in my application, I am providing a link to see my Silverlight Isolated Storage application running. Normally I would just embed it here, but I don't want to embarrass you at the office. :)

You can download my solution for Silverlight Isolated Storage here.

7. Summary

Isolated Storage in Silverlight works both in and outside the browser, but there are different storage requirements depending on how you use it. Out of browser applications have access to 25 MB of storage, while in-brower applications only have 1 MB. The reason for this makes sense, though. When we're running out of browser, we could be running disconnected, which would require us to store all of the user's changes locally until we reconnect to the network.

In short, using isolated storage is not very different from writing to the file system in WPF, but we don't get to specify paths on the user's machine. All of our data and files reside inside a sandboxed area, that has no access to other parts of the user's machine. This keeps them safe from malicious attacks that could occur under other circumstances. Here's a look at where these files are stored:

Windows Vista/7
\Users\\AppData\LocalLow\Microsoft\Silverlight\is


Windows XP
\Documents and Settings\\Local Settings\Application Data\Microsoft\Silverlight\is


Mac OS X
/Users//Library/Application Support/Microsoft/Silverlight/is



Only a few more days left in the 31 Days of Silverlight, and then a fun little Silverlight game for you on January 1st. I hope you are enjoying this series!

Labels: , , ,

posted by Jeff Blankenburg, 1:00 AM | link | 0 comments |

Day #28: Silverlight Application Themes

Monday, December 28, 2009

Welcome to Day #28. We've nearly finished the 31 Days of Silverlight, but we have a few fun posts remaining. Today's post is on creating Themes in Silverlight.

Theming is one of those pieces of functionality you see in consumer apps quite a bit. Basically changing the application to look and feel the way I want it. However, it's definitely more than that. It's the way you should write your styling information in Silverlight. For the most part, it's advanced Styling, which we covered on Day #10: Styling A Silverlight Control. The difference is that we are creating external style files that we can then swap between inside the application.

1. Download the Silverlight 3 Toolkit

This article will cover the advanced features that Theming offers you, and assumes that you are somewhat familiar with styling. First, you need to download the Silverlight 3 Toolkit. If you haven't done this already, head over to http://silverlight.codeplex.com and download it.

2. Add references to two assemblies

We need to add two references to our Silverlight project. They are System.Windows.Controls, and System.Windows.Controls.Theming (this one was added by the Toolkit). Go ahead and add them to your project.



3. Add an XML namespace reference to the Theming assembly

The next thing, as usual, is to add an XML namespace reference. To do this, I recommend relying on Intellisense for it, and just start typing the xmlns:theming=" before it recommends a list of assemblies. If you don't want to type it, however, you can just use my snippet of code:

xmlns:theming="clr-namespace:System.Windows.Controls.Theming;assembly=System.Windows.Controls.Theming.Toolkit"


4. Add some controls to your page

Before we can start styling our application, we need some controls on our page. I've added a TextBlock, a Button, a TextBox, and a Slider to my StackPanel base control. Here's what my full XAML looks like right now:

<UserControl x:Class="SilverlightTheming.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:theming="clr-namespace:System.Windows.Controls.Theming;assembly=System.Windows.Controls.Theming.Toolkit"
mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480">
<
StackPanel x:Name="LayoutRoot" Width="200" HorizontalAlignment="Center" VerticalAlignment="Center">
<
TextBlock Text="This is our form." Margin="0,0,0,10"/>
<
Button Content="Save" Margin="0,0,0,10"/>
<
TextBox Text="Something to edit." Margin="0,0,0,10"/>
<
Slider/>
</
StackPanel>
</
UserControl>


5. Creating a ResourceDictionary

Our next step is going to be creating a ResourceDictionary file. If you choose "Add New Item...", one of your choices is "Silverlight Resource Dictionary." Even though it creates a XAML file (like nearly everything else in Silverlight), this will have a base tag of ResourceDictionary instead of UserControl. This is where we will define our theme. You'll notice I named my file Green.xaml. This is so that I can remember that this is the green theme. Eventually, when you lather, rinse, and repeat, you'll have Blue, Red, Black, White, Pink, etc. Effective naming is always useful. I never recommend going with names like "Professional," "Glass," and "Techno," because it's harder to remember what those look like just by reading the name.



One important note before you move on from this point: By default, your new resource dictionary will have a "Build Action" of "Page" in its properties. You need this to be "Content" instead. If you don't change this, you will get an error similar to: AG_E_PARSER_BAD_PROPERTY_VALUE. Just change it. It will make your life easier than mine just was.

Editing our new ResourceDictionary

This is where the work comes in. For theming an entire application, we really need to consider the styles for every possible control we might use. In my example, I know I'm only going to be using 4 controls: TextBlock, TextBox, Button, and Slider. But as your application grows, you can see how this would grow quickly as well. (If you don't want the theme to apply to everything, your life gets easier. This effort only applies to those controls you actually want to change visually.) To make my life easier, I have pre-defined some colors and brushes that I will be using in my theme. This way, if I ever want to change them in the future, I don't have to change them in 19 different places. Here's the entirety of my Blue.xaml file...don't be intimidated. It's big, but no worse than any of the stylesheets you've ever created. :)

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

<!--DEFINING A LIST OF COLORS FOR RE-USE-->
<Color x:Key="TextBrush">#FF000000</Color>

<
Color x:Key="NormalBrushGradient1">#FFBAE4FF</Color>
<
Color x:Key="NormalBrushGradient2">#FF398FDF</Color>
<
Color x:Key="NormalBrushGradient3">#FF006DD4</Color>
<
Color x:Key="NormalBrushGradient4">#FF0A3E69</Color>

<
Color x:Key="NormalBorderBrushGradient1">#FFBBBBBB</Color>
<
Color x:Key="NormalBorderBrushGradient2">#FF737373</Color>
<
Color x:Key="NormalBorderBrushGradient3">#FF646464</Color>
<
Color x:Key="NormalBorderBrushGradient4">#FF000000</Color>

<
Color x:Key="ShadeBrushGradient1">#FF62676A</Color>
<
Color x:Key="ShadeBrushGradient2">#FFD1D4D6</Color>
<
Color x:Key="ShadeBrushGradient3">#FFFFFFFF</Color>

<
Color x:Key="SliderBorderGradient1">#FF3F3F3F</Color>
<
Color x:Key="SliderBorderGradient2">#FFADADAD</Color>

<!--DEFINING A LIST OF BRUSHES FOR RE-USE-->
<LinearGradientBrush x:Key="NormalBrush" EndPoint="0.5,1" StartPoint="0.5,0">
<
GradientStop Color="{StaticResource NormalBrushGradient1}" Offset="0" />
<
GradientStop Color="{StaticResource NormalBrushGradient2}" Offset="0.41800001263618469" />
<
GradientStop Color="{StaticResource NormalBrushGradient3}" Offset="0.418" />
<
GradientStop Color="{StaticResource NormalBrushGradient4}" Offset="1" />
</
LinearGradientBrush>

<
LinearGradientBrush x:Key="NormalBorderBrush" EndPoint="0.5,1" StartPoint="0.5,0">
<
GradientStop Color="{StaticResource NormalBorderBrushGradient1}" />
<
GradientStop Color="{StaticResource NormalBorderBrushGradient2}" Offset="0.38" />
<
GradientStop Color="{StaticResource NormalBorderBrushGradient3}" Offset="0.384" />
<
GradientStop Color="{StaticResource NormalBorderBrushGradient4}" Offset="1" />
</
LinearGradientBrush>

<
LinearGradientBrush x:Key="ShadeBrush" EndPoint="0.5,1" StartPoint="0.5,0">
<
GradientStop Color="{StaticResource ShadeBrushGradient2}" Offset="0" />
<
GradientStop Color="{StaticResource ShadeBrushGradient3}" Offset="0.1" />
<
GradientStop Color="{StaticResource ShadeBrushGradient3}" Offset="1" />
</
LinearGradientBrush>

<!--Button-->
<Style TargetType="Button">
<
Setter Property="Background" Value="{StaticResource NormalBrush}"/>
<
Setter Property="Foreground" Value="#FF000000"/>
<
Setter Property="Padding" Value="3"/>
<
Setter Property="BorderThickness" Value="2"/>
<
Setter Property="BorderBrush" Value="{StaticResource NormalBorderBrush}" />
</
Style>

<!--TextBox-->
<Style TargetType="TextBox">
<
Setter Property="BorderThickness" Value="1"/>
<
Setter Property="Background" Value="{StaticResource NormalBrushGradient1}"/>
<
Setter Property="Foreground" Value="#FF000000"/>
<
Setter Property="Padding" Value="2"/>
<
Setter Property="BorderBrush" Value="{StaticResource NormalBorderBrush}" />
</
Style>

<!--Slider-->
<Style TargetType="Slider">
<
Setter Property="BorderThickness" Value="1"/>
<
Setter Property="Maximum" Value="10"/>
<
Setter Property="Minimum" Value="0"/>
<
Setter Property="Value" Value="0"/>
<
Setter Property="BorderBrush">
<
Setter.Value>
<
LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<
GradientStop Color="#FFA3AEB9" Offset="0"/>
<
GradientStop Color="#FF8399A9" Offset="0.375"/>
<
GradientStop Color="#FF718597" Offset="0.375"/>
<
GradientStop Color="#FF617584" Offset="1"/>
</
LinearGradientBrush>
</
Setter.Value>
</
Setter>
</
Style>

</
ResourceDictionary>


6. Using the ImplicitStyleManager

That last step was the hard part. Defining styles for your controls, picking colors, and getting all of that XAML created. The ImplicitStyleManager makes the rest of our life easy. We can use it two different ways, and I'll show you each. The first is the simple way: in XAML. To our "LayoutRoot" element, we just need to apply two properties:

<StackPanel Name="LayoutRoot" theming:ImplicitStyleManager.ApplyMode="Auto" theming:ImplicitStyleManager.ResourceDictionaryUri="Blue.xaml" HorizontalAlignment="Center" VerticalAlignment="Center"> 


Obviously, you need to make sure that where I put "Blue.xaml," that you put the location of your ResourceDictionary. After that, run your application. If you used my style information, you should have slightly different looking button and slider controls, and a textbox that has a blue background. Each and every textbox in my application will look like that, unless I explicitly override that styling.

But Jeff, can I call this from code?

Why yes, you can! The other way to invote the ImplicitStyleManager is to just create a reference to it in code. Keep in mind that to do this, you'll need a using statement for the System.Windows.Controls.Theming assembly, but otherwise, here's the code:

            Uri theme = new Uri("Blue.xaml", UriKind.Relative);
ImplicitStyleManager.SetResourceDictionaryUri(LayoutRoot, theme);
ImplicitStyleManager.SetApplyMode(LayoutRoot, ImplicitStylesApplyMode.Auto);
ImplicitStyleManager.Apply(LayoutRoot);


If you're planning on giving your user the ability to change their theme on the fly, I'd recommend going the code route. You can add that code to an event handler, and just choose the appropriate Dictionary as the user selected it. So, that's it! Pretty simple, huh?

Summary

I highly recommend Theming your application, regardless of whether you plan to allow your user to choose a theme or not. By externalizing all of your styling information, you create a familiar feel to traditional web development with CSS. In addition, you could technically create a seperate dictionary for Buttons, TextBlocks, etc. You'd then pull of those together in yet another master dictionary, keeping the code seperated and clean. Go forth and theme!

To download my application sample for Silverlight Theming, click here.

Labels: , , , ,

posted by Jeff Blankenburg, 1:00 AM | link | 0 comments |

Day #27: Templating Controls In Silverlight

Sunday, December 27, 2009

Ah, we've made it to Day #27 of the 31 Days of Silverlight. Today, we're going to be decontructing one of the most common XAML elements: the Button. The reason we're doing this is to demonstrate how templates work, and how even complex controls are really just compositions of much simpler ones.

1. Add a button to your XAML page

Button, button, who's got the button? Just add a simple button to your page. If you need help, you can use this XAML:

<Button Content="Click Me!" Width="200" Height="200" />

2. Open your solution in Expression Blend 3

If you haven't already installed Expression Blend 3, you can get a 60 day trial here.

Open MainPage.xaml, and you should see your button in the middle of the design surface. If you right click on the button, you should see a menu option: Edit Template. From there we're going to choose "Create Empty..."



3. Creating a style resource

When you choose that menu item, you are presented with the "Create ControlTemplate Resource" dialog box. Here, you can name your new Template, as well as define its scope. For my example, I am choosing Application level scope, so that I can use this across all of my pages. What you will see is that you have added a big style-type definition to your App.xaml file. (For more info on styling, you can check out Day #10: Styling Silverlight Controls.)



4. Looking at our template

If you look at the "Objects and Timeline" panel in Expression Blend, you'll see very little in the way of structure. The only thing that is provided is a Grid. If you open your "States" window, you'll find that a button has several different states. This is where we will be spending most of our time.

5. Adding an image to our template

I called this Template an ImageButton, because I am going to create a button that has a consistent image in the background. So, to add an image, just double-click on the Image control in your toolbox.



I recommend stretching it to the size of your template...that way, your button will be proportional when you create other sizes in your application. Also, you need to add an image to your project that you're going to use. For my example, I'm using a picture of my face. Think of it as a public service for you to hit me in the face over and over. :)

6. We also need a ContentPresenter

We need to add a ContentPresenter control, but that's what it is. This will show the "Content" property of our buttons. I'm putting this control on my forehead. Here's what it looks like:



If you go back to Visual Studio and run this project, you should now have a button on your page that instead looks like your image, but still displays your button Content. Give it a try. Mine says "Click Me!" like the initial XAML I gave you.

7. Playing with States

If you open the States window in Expression Blend 3, you'll see that we already have a bunch of States defined for a button. We're going to be modifying some of these, so that our ImageButton actually does something. You may have noticed that while our Content showed up appropriately, the button didn't have that "click" feel we're used to. This will fix that. Here's a shot of the States window:



What we are going to do is add some behaviors to the "Pressed" state so that the button reacts when it's clicked.

8. Adding the Pressed state

In the States window, click on the "Pressed" item. You'll notice that immediately the design pane is outlined in red. This is because we've started recording our actions. The changes you make at this point will be the changes that happen when you "Press" the button. In my example, I am going to have my Image control change its opacity. At this point, we're just creating a simple animation. Look back to Day #11 in this series to see more about animation. I am going to create an animation that changes the opacity from 100% to 33% in just 0.1 seconds. Here's what the entire contents of my App.xaml file looks like now:

<Application xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
x:Class="SilverlightTemplating.App"
mc:Ignorable="d"
>
<
Application.Resources>

<
ControlTemplate x:Key="ImageButton" TargetType="Button">
<
Grid Height="200" Width="200">
<
VisualStateManager.VisualStateGroups>
<
VisualStateGroup x:Name="CommonStates">
<
VisualState x:Name="Pressed">
<
Storyboard>
<
DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="image" Storyboard.TargetProperty="(UIElement.Opacity)">
<
EasingDoubleKeyFrame KeyTime="00:00:00" Value="1"/>
<
EasingDoubleKeyFrame KeyTime="00:00:00.1000000" Value="0.33"/>
</
DoubleAnimationUsingKeyFrames>
</
Storyboard>
</
VisualState>
</
VisualStateGroup>
</
VisualStateManager.VisualStateGroups>
<
Image x:Name="image" Source="jeffblankenburgheadshot.jpg" Width="200" Height="200"/>
<
ContentPresenter Height="24" VerticalAlignment="Top" Margin="53,60,53,0"/>
</
Grid>
</
ControlTemplate>
</
Application.Resources>
</
Application>


You may notice, when you run this project, that when you click on the button, it goes transparent, but when you release the mouse, it doesn't "un-transparentize." We need to tell it to do that as well. This is a completely open slate we're working with, and nothing is assumed in a template. This is a plus, however, because although we are working in blank template, if we like 90% of a certain control, we can use that as our starting point. Let's add the "MouseOver" state now.

9. Adding the "MouseOver" state

Since we want the button to return to its original state when we lift the mouse button, we also create a MouseOver state. This is the state that the control will revert to when we lift the mouse button. I actually copied and pasted the XAML from my Pressed state into this new MouseOver state, and changed the opacity values. Here's my new XAML:

<Application xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
xmlns:ic="clr-namespace:Microsoft.Expression.Interactivity.Core;assembly=Microsoft.Expression.Interactions"
x:Class="SilverlightTemplating.App"
>
<
Application.Resources>

<
ControlTemplate x:Key="ImageButton" TargetType="Button">
<
Grid Height="200" Width="200">
<
VisualStateManager.CustomVisualStateManager>
<
ic:ExtendedVisualStateManager/>
</
VisualStateManager.CustomVisualStateManager>
<
VisualStateManager.VisualStateGroups>
<
VisualStateGroup x:Name="CommonStates">
<
VisualState x:Name="MouseOver">
<
Storyboard>
<
DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0010000" Storyboard.TargetName="image" Storyboard.TargetProperty="(UIElement.Opacity)">
<
EasingDoubleKeyFrame KeyTime="00:00:00" Value="1"/>
</
DoubleAnimationUsingKeyFrames>
</
Storyboard>
</
VisualState>
<
VisualState x:Name="Pressed">
<
Storyboard>
<
DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0010000" Storyboard.TargetName="image" Storyboard.TargetProperty="(UIElement.Opacity)">
<
EasingDoubleKeyFrame KeyTime="00:00:00" Value="0.33"/>
</
DoubleAnimationUsingKeyFrames>
</
Storyboard>
</
VisualState>
</
VisualStateGroup>
</
VisualStateManager.VisualStateGroups>
<
Image x:Name="image" Source="jeffblankenburgheadshot.jpg" Width="200" Height="200"/>
<
ContentPresenter Height="24" VerticalAlignment="Top" Margin="53,60,53,0"/>
</
Grid>
</
ControlTemplate>
</
Application.Resources>
</
Application>


Summary

In short, this is a robust piece of functionality in Silverlight. Not only can we completely overhaul any of the controls, but we can also use them as templates for future, robust controls that we may imagine tomorrow. If there's ANYTHING you don't like about the default controls in Silverlight, you have the power to change them. That is incredibly liberating. Here's what my new pseudo-button looks like:



Also, if you would like to download the source code for this Silverlight templating example, you can get it here.

Labels: , , ,

posted by Jeff Blankenburg, 1:00 AM | link | 0 comments |

Day #26: Silverlight Data Grid

Saturday, December 26, 2009

Welcome to day #26 in my series called the 31 Days of Silverlight. Today, we're going to be talking about how to use a DataGrid control in your Silverlight applications. Let's get right to it.

1. Add a new reference to your Silverlight project

In order to use the Silverlight DataGrid control, we first need to add a new reference to our project. Go find System.Windows.Controls.Data, and add a reference to that assembly to your project. Make sure you're only including this reference if you're using a DataGrid. It's sizeable, and you really don't want the burden of this DLL unless you fully intend to use it.



2. Add the XML namespace to your XAML.

In order to use that reference, we need to add the XML namespace for that assembly to our page. This will allow us to add an actual DataGrid to our page. Here's what my entire XAML page looks like before we begin:

<UserControl x:Class="SilverlightDataGrid.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480">
<
Canvas x:Name="LayoutRoot">

</
Canvas>
</
UserControl>


3. Add a DataGrid to your page

Adding the DataGrid is the simple part of this tutorial. We'll spend most of our time today manipulating its data. To add the grid to the page, however, use this little snippet of code inside your <Canvas>:

<data:DataGrid x:Name="citygrid" AutoGenerateColumns="True" />


Now, before you get too excited and start running your project, remember that we haven't added any data to this DataGrid yet. We should probably do that first.

4. Adding data to the DataGrid from code

In order to have some data with some substance, I have set up a new Class file that defines a simple "Location." Here's how it looks:

public class Location
{
//Fields
private string _city;
private int _population;
private double _latitude;
private double _longitude;
private double _elevation;

//Properties
public string City
{
get { return _city; }
set { _city = value; }
}

public int Population
{
get { return _population; }
set { _population = value; }
}

public double Latitude
{
get { return _latitude; }
set { _latitude = value; }
}

public double Longitude
{
get { return _longitude; }
set { _longitude = value; }
}

public double Elevation
{
get { return _elevation; }
set { _elevation = value; }
}
}


Next, I'm going to create a List of Locations, and use that as my data source. For my locations, I have the population, elevation, and latitude/longitude or the 16 largest cities in the United States (according to the 2000 US Census). Here's my data:

List<Location> locations = new List<Location>{
new Location {City="New York, NY", Population=8363710, Latitude=40.714269, Longitude=-74.005973, Elevation=10},
new Location {City="Los Angeles, CA", Population=3833995, Latitude=34.052234, Longitude=-118.243685, Elevation=71},
new Location {City="Chicago, IL", Population=2853114, Latitude=41.850033, Longitude=-87.650052, Elevation=182},
new Location {City="Houston, TX", Population=2242193, Latitude=29.763284, Longitude=-95.363271, Elevation=13},
new Location {City="Phoenix, AZ", Population=1567924, Latitude=33.448377, Longitude=-112.074037, Elevation=340},
new Location {City="Philadelphia, PA", Population=1540351, Latitude=39.952335, Longitude=-75.163789, Elevation=12},
new Location {City="San Antonio, TX", Population=1351305, Latitude=29.424122, Longitude=-98.493628, Elevation=198},
new Location {City="Dallas, TX", Population=1279910, Latitude=32.783330, Longitude=-96.800000, Elevation=131},
new Location {City="San Diego, CA", Population=1279329, Latitude=32.715329, Longitude=-117.157255, Elevation=22},
new Location {City="San Jose, CA", Population=948279, Latitude=37.339386, Longitude=-121.894955, Elevation=26},
new Location {City="Detroit, MI", Population=912062, Latitude=42.331427, Longitude=-83.045754, Elevation=183},
new Location {City="San Francisco, CA", Population=808976, Latitude=37.774929, Longitude=-122.419415, Elevation=16},
new Location {City="Jacksonville, FL", Population=807815, Latitude=30.332184, Longitude=-81.655651, Elevation=5},
new Location {City="Indianapolis, IN", Population=798382, Latitude=39.768377, Longitude=-86.158042, Elevation=218},
new Location {City="Austin, TX", Population=757688, Latitude=30.267153, Longitude=-97.743061, Elevation=149},
new Location {City="Columbus, OH", Population=754885, Latitude=39.961176, Longitude=-82.998794, Elevation=275}
};


Once we have some data to work with, we just need to set the ItemsSource property of our grid. I named my grid "citygrid," so our statement will look like this:

citygrid.ItemsSource = locations;


If you run your project at this point, you should have a working grid, with all of the data populating it.

5. Using "some" of the data

Much like we did in Day #15, we're going to use LINQ to select the cities we want. In this case, perhaps we only want cities with more than 1,000,000 citizens. Here's how you can modify your ItemsSource statement to select just a portion of the entire set of data:

            citygrid.ItemsSource = from location in locations
where location.Population > 1000000
orderby location.City, location.Population
select location;


6. Checking out the built in functionality

If you haven't run your project yet, give it a try. You should see a grid on your page, with either some or all of your data, depending on what you did in step #5. The grid has some built in functionality that makes our lives a little easier, and required ZERO code.

The first thing is that the columns auto-sort. So you can click on a column heading, and it will sort your cities, populations, etc. from top to bottom, and reversed.

Second, the cells are editable. So you can change the values we have stored in code without writing anything additionally. If you want to turn that off, just add the IsReadOnly="True" property to your grid.

Third, the grid also has native validation. So, for example, if you try to edit one of the longitude cells, and enter a string for a value, it will prompt you that the value you entered is invalid. A nice bit of functionality for free, huh? Here's my working Silverlight DataGrid in action (see it in a new window):



You can download my Silverlight DataGrid application here.

7. Summary

In short, it's incredibly refreshing to have a DataGrid control for Silverlight. There's thousands of business applications for this, and if you dig a little deeper, you'll discover just how customizable this control really is. It doesn't have to look like a grid at all, to be honest. Dan Wahlin has a good tutorial on customizing a grid to look like a timesheet application.

Labels: , , , ,

posted by Jeff Blankenburg, 1:00 AM | link | 1 comments |

Day #25: Silverlight Outside The Browser (Part 3 of 3)

Friday, December 25, 2009

If you're wondering, I am certainly not writing these on the day they publish. These were written many, many days prior, and are only being published over the holidays. I'm spending my time celebrating with my family, exchanging gifts, and catching up on some sleep. Have a happy holiday season everyone.

Detecting A Network Connection

Today is the final part of a three part series on "Out of Browser", which is a part of a larger series, the 31 Days of Silverlight.

We continued yesterday with a simple Silverlight project that had Out of Browser enabled, and was capable of installation and detected its state. You can download that out of browser application here. We'll be adding to that project today.

Our next step is to determine whether a user has a network connection while running our application.

1. Checking the network connection

We have to add an additional namespace in order to work with our network connection, so let's start there. Add this using statement to the top of your MainPage.xaml.cs file:

using System.Net.NetworkInformation;

Once we've added that, we can use the NetworkInterface API. Since we know we're going to need to extract this code into its own method, let's just do that out of the gate. I am creating a function that has an if statement checking another boolean value, this time to determine if there is a network connection. If there is, my network status image is green, and if not, the image is red. Here's the entire method:

        private void CheckNetworkStatus()
{
if (NetworkInterface.GetIsNetworkAvailable())
{
networkimage.Source = new BitmapImage(new Uri("network_on.png", UriKind.Relative));
}
else
{
networkimage.Source = new BitmapImage(new Uri("network_off.png", UriKind.Relative));
}
}


2. Setting up an event handler for changes

Because our network status can change while we're running the application, we need to make sure that we've subscribed to the NetworkAddressChanged event. This way, our application will always be aware of the current network status. Here's my event handler code:

NetworkChange.NetworkAddressChanged += new NetworkAddressChangedEventHandler(NetworkChange_NetworkAddressChanged);


Hooking everything up

Now, we've got an CheckNetworkStatus() method, but it's not being called yet. I'm actually going to call it from two places: once when our apoplication loads, and the other in the event handler method. Here's what our final code looks like:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Net.NetworkInformation;

namespace SilverlightOutOfBrowser
{
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
Loaded += new RoutedEventHandler(MainPage_Loaded);
NetworkChange.NetworkAddressChanged += new NetworkAddressChangedEventHandler(NetworkChange_NetworkAddressChanged);
outofbrowserimage.MouseLeftButtonUp += new MouseButtonEventHandler(installBigBangTheory);
}

void NetworkChange_NetworkAddressChanged(object sender, EventArgs e)
{
CheckNetworkStatus();
}

void installBigBangTheory(object sender, MouseButtonEventArgs e)
{
if (Application.Current.InstallState == InstallState.NotInstalled)
{
Application.Current.Install();
}
}

void MainPage_Loaded(object sender, RoutedEventArgs e)
{
checkOOB();
CheckNetworkStatus();
}

private void CheckNetworkStatus()
{
if (NetworkInterface.GetIsNetworkAvailable())
{
networkimage.Source = new BitmapImage(new Uri("network_on.png", UriKind.Relative));
}
else
{
networkimage.Source = new BitmapImage(new Uri("network_off.png", UriKind.Relative));
}
}

private void checkOOB()
{
if (Application.Current.IsRunningOutOfBrowser)
{
outofbrowserimage.Source = new BitmapImage(new Uri("outofbrowser_on.png", UriKind.Relative));
ToolTipService.SetToolTip(outofbrowserimage, "Big Bang Theory is installed on your machine.");
}
else
{
outofbrowserimage.Source = new BitmapImage(new Uri("outofbrowser_off.png", UriKind.Relative));
ToolTipService.SetToolTip(outofbrowserimage, "Install Big Bang Theory");
}
}
}
}


Summary

For many applications, network status may seem like something you don't need. But if you're talking to web services, data, etc., you absolutely need to know that those things are not available. In general, I'd recommend setting up a local data cache, and synchronizing that in the background, so that you can pass the data back and forth with the confidence that you won't lose it if the user loses their connection. But that's an article for another time.

Launch this completed Silverlight Out of Browser application.

You can download the final source code for this Out of Browser Silverlight project here.

Labels: , ,

posted by Jeff Blankenburg, 1:00 AM | link | 0 comments |

Day #24: Silverlight Outside The Browser (Part 2 of 3)

Thursday, December 24, 2009

Detecting Whether A User Is In Or Out Of Browser

Today is the second part of a three part series on "Out of Browser", which is a part of a larger series, the 31 Days of Silverlight.

We started yesterday with a simple Silverlight project that had Out of Browser enabled. You can download that out of browser application here. We'll be adding to that project today.

In our next step, we're going to need to know when the application is running in an out of browser state vs. a traditional in-browser state. With some simple event handlers, we can make this happen. We can also change our application to accommodate these changes.

1. Setting up a Loaded event handler

The first thing we need to do is tell our application to call an initial method when it loads. To do this, I added a simple event handler in my MainPage() method.

        public MainPage()
{
InitializeComponent();
Loaded += new RoutedEventHandler(MainPage_Loaded);
}


This also creates the event handler method for us, and I'm going to add a simple if/then/else statement to determine our browser state.

2. Detecting the browser state

In our new event handler method, I've created an if statement that just checks the boolean value of Application.Current.IsRunningOutOfBrowser. In each case, I change the image to either red or green, and adjust the tooltip to read the appropraite text for the state. Here's what the method looks like:

        void MainPage_Loaded(object sender, RoutedEventArgs e)
{
if (Application.Current.IsRunningOutOfBrowser)
{
outofbrowserimage.Source = new BitmapImage(new Uri("outofbrowser_on.png", UriKind.Relative));
ToolTipService.SetToolTip(outofbrowserimage, "Uninstall Big Bang Theory");
}
else
{
outofbrowserimage.Source = new BitmapImage(new Uri("outofbrowser_off.png", UriKind.Relative));
ToolTipService.SetToolTip(outofbrowserimage, "Install Big Bang Theory");
}
}


Because I might want to call this functionality at other times than when the application loads, I'm going to refactor this code a little. If you haven't used this functionality of Visual Studio 2008 before, I highly recommend it.

3. Refactoring our code

Highlight the code you want to move to its own method, and right click on it. Like this:



It will prompt you to give this code a new method name. I called my method CheckOOB, since that's what it will do.



So here's what my entire code-behind file looks like now:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Media.Imaging;

namespace SilverlightOutOfBrowser
{
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
Loaded += new RoutedEventHandler(MainPage_Loaded);
}

void MainPage_Loaded(object sender, RoutedEventArgs e)
{
checkOOB();
}

private void checkOOB()
{
if (Application.Current.IsRunningOutOfBrowser)
{
outofbrowserimage.Source = new BitmapImage(new Uri("outofbrowser_on.png", UriKind.Relative));
ToolTipService.SetToolTip(outofbrowserimage, "Uninstall Big Bang Theory");
}
else
{
outofbrowserimage.Source = new BitmapImage(new Uri("outofbrowser_off.png", UriKind.Relative));
ToolTipService.SetToolTip(outofbrowserimage, "Install Big Bang Theory");
}
}
}
}


The next thing I want to do it make it so that the image I am using to show status is also a button for the user to click. I don't want them to have to discover the right-click menu to install, instead, I want to make it an obvious choice. Let's do that next.

4. Creating an Install button

We will need an event handler on the image, so let's start there:
outofbrowserimage.MouseLeftButtonUp += new MouseButtonEventHandler(outofbrowserimage_MouseLeftButtonUp);

In the subsequent method, we can simply call the Application.Current.Install() method. We need to wrap this statement in a check to make sure that the application has not already been installed. Where this becomes the biggest issue is when the user has installed your application, but is back at the website version of your app. If they click the Install button again, your application will throw an error, because it's already been installed. Here's the code:

void installBigBangTheory(object sender, MouseButtonEventArgs e)
{
if (Application.Current.InstallState == InstallState.NotInstalled)
{
Application.Current.Install();
}
}


An important thing to note about this Install() function is that it MUST be called by a user-instantiated action. This means that you can't just call it when the application loads, or in the background. It has to have a code pathway that resulted from a user action.

Also, regarding uninstalling...there's no way to do it via code. To uninstall, your user will have to right-click on the application, or go to their control panel to uninstall it. I'm hoping that we can provide a more user-friendly solution to this in future versions of Silverlight, but for SL3, this is the way it is.

Summary

This is actually a pretty simple process. Check a boolean value, and act accordingly. But, as we learned from the Spiderman movies, "with great power comes great responsibility." Make sure that you're providing clear instructions for your users regarding how to install and uninstall your application. Installing, as a word, is frightening to many users and most every systems administrator.

To see this application running, and to install it, click here.

If you'd like to download this code for this project, you can get my Out of Browser Detection solution here.

Labels: , ,

posted by Jeff Blankenburg, 1:00 AM | link | 0 comments |

Day #23: Silverlight Outside The Browser (Part 1 of 3)

Wednesday, December 23, 2009

Enabling Out Of Browser in Your Silverlight App

For the next three days in the 31 Days of Silverlight, we're going to cover some of the intricacies of getting a Silverlight application to run outside the user's browser. This is a relatively simple change to your app, but it also introduces new challenges, like the need to detect which mode the user is in, and whether they have network connectivity or not. The next two articles will cover these topics specifically.

Today, we're going to show you how to get out-of-browser working. So let's get started.

To make this a little easier, I have created a sample project for you to start with. Inside, you'll find a simple application that has a video player, and two images. The first image indicates whether there is network connectivity, and the other is to allow the user to "Install" the application to their machine.

But before we deal with any of that, we need to get our application "enabled" for the out of browser experience. By default, Silverlight 3 applications are not enabled.

1. Go to your Silverlight project properties

Opening the properties of your Silverlight project will give you the choice to "Enable running the application out of the browser, in addition to some new configuration choices. We'll get to those in a second. Here's what the screens look like:





2. Customize Your Settings

Once you've enabled Out of Browser for your Silverlight application, you get a new button to customize the settings for your installation. It includes things like titles, descriptions, and icons of different sizes for the shortcuts on your user's machines. Here's my completed Settings screen:



In case you're curious, these settings are saved in a new XML file in your project. Look for OutOfBrowserSettings.xml in your Silverlight project's Properties folder. Here's what my settings file looks like:

<OutOfBrowserSettings ShortName="Big Bang Theory" EnableGPUAcceleration="False" ShowInstallMenuItem="True">
<
OutOfBrowserSettings.Blurb>This application will play the theme song for the CBS television show Big Bang Theory in a video found on YouTube. You can also find the video here: http://www.youtube.com/watch?v=lhTSfOZUNLo</OutOfBrowserSettings.Blurb>
<
OutOfBrowserSettings.WindowSettings>
<
WindowSettings Title="Big Bang Theory" Height="480" Width="640" />
</
OutOfBrowserSettings.WindowSettings>
<
OutOfBrowserSettings.Icons>
<
Icon Size="16,16">globe_16.png</Icon>
<
Icon Size="32,32">globe_32.png</Icon>
<
Icon Size="48,48">globe_48.png</Icon>
<
Icon Size="128,128">globe_128.png</Icon>
</
OutOfBrowserSettings.Icons>
</
OutOfBrowserSettings>


3. Installing the Application

I've chosen not to embed my application below (you can launch it here), because it has audio, and I don't want to have that playing while you're reading this post. If you right click on the application, you should have a second choice: Installing the application. If you choose this, it will save your application down to the local sandbox on your machine, and also gives you some choices for where to install it. When you're done, it will also launch the local version for you as well. At that point, you can close your browser, and the application still works.

Here is a screenshot of the installation dialog (with my custom icon):



And here is a screenshot of the application running in the out-of-browser window.



Summary

You can see that this is a pretty simple process to enable Out of Browser functionality for your Silverlight application. In the next two days, we're going to work on adding to this application, detecting whether the user has installed it or not, and then whether or not they have a network connection. Yes, Virginia, your Silverlight application can now run in a disconnected mode. See you tomorrow!

If you'd like to download this starter application, you can get my Out of Browser solution here.

Labels: , ,

posted by Jeff Blankenburg, 1:00 AM | link | 2 comments |

Day #22: Using The Farseer Physics Engine in Silverlight

Tuesday, December 22, 2009

Today we're going to venture into something you may never have played with before: a physics engine. This is article #22 in my series 31 Days of Silverlight. We are going to take a look at the Farseer Physics Engine for Silverlight.

What IS a physics engine?

Specifically, this is a 2-dimensional physics engine. What that means is that we can create elements that are smart enough to bounce off of each other, react to "gravity", and other things that we might expect to see in the real world. There's even properties like friction that we can apply to our XAML elements.

How do we get started?

The first thing you'll want to do is download the actual Farseer engine from CodePlex. I provided a link to the project home page in the opening paragraph, but if you want a link to the specific version we'll be using for this demo, you can also download the Farseer Engine 2.1.1 Silverlight here.

We're going to grab another open source project, a Physics Helper. While the Farseer engine is incredibly useful, it could really use an additional layer of abstraction, and this Physics Helper does exactly that.

In order to make this incredibly simple, however, I'm providing a link to download the 3 DLLs we will need to add to our project. Download the Farseer and Physics Helper DLLs here.

1. Start a new Silverlight project.

Create a new Silverlight project. I named mine SilverlightPhysicsPyramid. You can name yours whatever you like.

2. Add some references to the DLLs.

Since you've already downloaded the three DLLs we will need, store them in a safe place, and add references to them in your project. Go ahead. I'll wait. Once you've got that done, you'll also want to add a reference to the System.Windows.Interactivity assembly as well. This is found in the ".NET" tab of the Add Reference dialog box.





3. Open your MainPage.xaml file.

For an application using these physics DLLs, we will always want to use a <Canvas> tag. So change the <Grid> tag to a <Canvas>. Don't forget to change the closing tag as well. Next, I've created some XAML that we will use, so copy and paste this XAML into your <Canvas> tag. Here's what the contents of your XAML file should look like:

<UserControl x:Class="SilverlightPhysicsPyramid.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480">
<
Canvas x:Name="LayoutRoot" Background="Black">
<
Rectangle Fill="White" Stroke="#FFB9B9B9" StrokeThickness="2" Height="25" Width="25" Canvas.Left="215" Canvas.Top="433"/>
<
Rectangle Fill="White" Stroke="#FFB9B9B9" StrokeThickness="2" Height="25" Width="25" Canvas.Left="245" Canvas.Top="433"/>
<
Rectangle Fill="White" Stroke="#FFB9B9B9" StrokeThickness="2" Height="25" Width="25" Canvas.Left="275" Canvas.Top="433"/>
<
Rectangle Fill="White" Stroke="#FFB9B9B9" StrokeThickness="2" Height="25" Width="25" Canvas.Left="305" Canvas.Top="433"/>
<
Rectangle Fill="White" Stroke="#FFB9B9B9" StrokeThickness="2" Height="25" Width="25" Canvas.Left="335" Canvas.Top="433"/>
<
Rectangle Fill="White" Stroke="#FFB9B9B9" StrokeThickness="2" Height="25" Width="25" Canvas.Left="365" Canvas.Top="433"/>
<
Rectangle Fill="White" Stroke="#FFB9B9B9" StrokeThickness="2" Height="25" Width="25" Canvas.Left="230" Canvas.Top="403"/>
<
Rectangle Fill="White" Stroke="#FFB9B9B9" StrokeThickness="2" Height="25" Width="25" Canvas.Left="290" Canvas.Top="283"/>
<
Rectangle Fill="White" Stroke="#FFB9B9B9" StrokeThickness="2" Height="25" Width="25" Canvas.Left="305" Canvas.Top="313"/>
<
Rectangle Fill="White" Stroke="#FFB9B9B9" StrokeThickness="2" Height="25" Width="25" Canvas.Left="275" Canvas.Top="313"/>
<
Rectangle Fill="White" Stroke="#FFB9B9B9" StrokeThickness="2" Height="25" Width="25" Canvas.Left="320" Canvas.Top="343"/>
<
Rectangle Fill="White" Stroke="#FFB9B9B9" StrokeThickness="2" Height="25" Width="25" Canvas.Left="290" Canvas.Top="343"/>
<
Rectangle Fill="White" Stroke="#FFB9B9B9" StrokeThickness="2" Height="25" Width="25" Canvas.Left="260" Canvas.Top="343"/>
<
Rectangle Fill="White" Stroke="#FFB9B9B9" StrokeThickness="2" Height="25" Width="25" Canvas.Left="335" Canvas.Top="373"/>
<
Rectangle Fill="White" Stroke="#FFB9B9B9" StrokeThickness="2" Height="25" Width="25" Canvas.Left="305" Canvas.Top="373"/>
<
Rectangle Fill="White" Stroke="#FFB9B9B9" StrokeThickness="2" Height="25" Width="25" Canvas.Left="275" Canvas.Top="373"/>
<
Rectangle Fill="White" Stroke="#FFB9B9B9" StrokeThickness="2" Height="25" Width="25" Canvas.Left="245" Canvas.Top="373"/>
<
Rectangle Fill="White" Stroke="#FFB9B9B9" StrokeThickness="2" Height="25" Width="25" Canvas.Left="350" Canvas.Top="403"/>
<
Rectangle Fill="White" Stroke="#FFB9B9B9" StrokeThickness="2" Height="25" Width="25" Canvas.Left="320" Canvas.Top="403"/>
<
Rectangle Fill="White" Stroke="#FFB9B9B9" StrokeThickness="2" Height="25" Width="25" Canvas.Left="290" Canvas.Top="403"/>
<
Rectangle Fill="White" Stroke="#FFB9B9B9" StrokeThickness="2" Height="25" Width="25" Canvas.Left="260" Canvas.Top="403"/>
<
Rectangle Fill="White" StrokeThickness="2" Height="29" Width="640" Canvas.Left="0" Canvas.Top="475"/>
<
Ellipse Fill="Red" Stroke="#FFB9B9B9" StrokeThickness="2" Height="25" Width="25" Canvas.Top="173" Canvas.Left="255"/>
</
Canvas>
</
UserControl>


If you run your project at this point, you should see a black background, and a pyramid of squares over a white platform with a red ball above them. Here's a screenshot:



4. Adding those XML namespace entries

So, we added our DLLs as references, but because we're going to do this work in our XAML file, we need to make sure that we have referenced the ones we're using directly at the top of our file. We need to add the Spritehand.PhysicsBehaviors assembly, as well as System.Windows.Interactivity. If you've never used these statements before, just think of them like "using" statements in C#. Your <UserControl> tag should now look like this:

<UserControl x:Class="SilverlightPhysicsPyramid.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:pb="clr-namespace:Spritehand.PhysicsBehaviors;assembly=Spritehand.PhysicsBehaviors"
mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480">


I gave them nicknames of "i" and "pb" respectively, but you can call them whatever works for you. They're just going to be shortcuts for adding our physics behaviors to our XAML elements.

5. Adding our PhysicsControllerBehavior

The first thing we need to do is define our PhysicsController. In most cases, this will be your primary <Canvas>, but you can really make any XAML container your PhysicsController. To do this in XAML, we going to add a Behaviors node to our <Canvas> and then specify the physics behavior we want to use. It looks like this:

<Canvas x:Name="LayoutRoot" Background="Black">
<
i:Interaction.Behaviors>
<
pb:PhysicsControllerBehavior MousePickEnabled="True"/>
</
i:Interaction.Behaviors>


You can see that I have added the PhysicsControllerBehavior to our <Canvas>. I also enabled the MousePickEnabled property, which will allow us to pick up and move any of our XAML elements that are specified as PhysicsObjects. Let's do that next.

6. Creating PhysicsObjects

Much like we did with the PhysicsController, we are going to add a behavior to elements of our XAML document. The first two we're going to start with are the <Ellipse> and the white rectangular platform at the bottom. For each of these, we'll have to expand the tags to not be self-closing, but they'll have to have both opening and closing tags. As an example, here's what the <Ellipse> should look like now:

        <Ellipse Fill="Red" Stroke="#FFB9B9B9" StrokeThickness="2" Height="25" Width="25" Canvas.Top="173" Canvas.Left="255">
<
i:Interaction.Behaviors>
<
pb:PhysicsObjectBehavior/>
</
i:Interaction.Behaviors>
</
Ellipse>


Our <Rectangle> platform will have the same thing done to it, but we are going to add one property: IsStatic. This defines the platform as an object that will participate in the physics engine, but doesn't have any gravity affecting it. By default, each element that is a PhysicsObject will automatically have gravity, mass, etc. Here's what our platform looks like:

        <Rectangle Fill="White" StrokeThickness="2" Height="29" Width="640" Canvas.Left="0" Canvas.Top="475">
<
i:Interaction.Behaviors>
<
pb:PhysicsObjectBehavior IsStatic="True"/>
</
i:Interaction.Behaviors>
</
Rectangle>


If you run your project now, you should see the red ball fall behind the pyramid of blocks, and "land" on the platform. Also, because we used the MousePickEnabled property on our PhysicsController, you can click on the ball and drag it around the screen, throwing it in the air, rolling it along the platform, etc. Have some fun with it! Please note that the "pyramid" of blocks don't interact with the ball AT ALL. This is because they have not been defined as PhysicsObjects yet. That's our next step.

7. Creating a falling pyramid.

For each of the <Rectangles> in our pyramid, we are going to add the same code that we added to our <Ellipse>. Once you've done that, your entire XAML file should look like this:

<UserControl x:Class="SilverlightPhysicsPyramid.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:pb="clr-namespace:Spritehand.PhysicsBehaviors;assembly=Spritehand.PhysicsBehaviors"
mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480">
<
Canvas x:Name="LayoutRoot" Background="Black">
<
i:Interaction.Behaviors>
<
pb:PhysicsControllerBehavior MousePickEnabled="True"/>
</
i:Interaction.Behaviors>
<
Rectangle Fill="White" Stroke="#FFB9B9B9" StrokeThickness="2" Height="25" Width="25" Canvas.Left="215" Canvas.Top="433">
<
i:Interaction.Behaviors>
<
pb:PhysicsObjectBehavior/>
</
i:Interaction.Behaviors>
</
Rectangle>
<
Rectangle Fill="White" Stroke="#FFB9B9B9" StrokeThickness="2" Height="25" Width="25" Canvas.Left="245" Canvas.Top="433">
<
i:Interaction.Behaviors>
<
pb:PhysicsObjectBehavior/>
</
i:Interaction.Behaviors>
</
Rectangle>
<
Rectangle Fill="White" Stroke="#FFB9B9B9" StrokeThickness="2" Height="25" Width="25" Canvas.Left="275" Canvas.Top="433">
<
i:Interaction.Behaviors>
<
pb:PhysicsObjectBehavior/>
</
i:Interaction.Behaviors>
</
Rectangle>
<
Rectangle Fill="White" Stroke="#FFB9B9B9" StrokeThickness="2" Height="25" Width="25" Canvas.Left="305" Canvas.Top="433">
<
i:Interaction.Behaviors>
<
pb:PhysicsObjectBehavior/>
</
i:Interaction.Behaviors>
</
Rectangle>
<
Rectangle Fill="White" Stroke="#FFB9B9B9" StrokeThickness="2" Height="25" Width="25" Canvas.Left="335" Canvas.Top="433">
<
i:Interaction.Behaviors>
<
pb:PhysicsObjectBehavior/>
</
i:Interaction.Behaviors>
</
Rectangle>
<
Rectangle Fill="White" Stroke="#FFB9B9B9" StrokeThickness="2" Height="25" Width="25" Canvas.Left="365" Canvas.Top="433">
<
i:Interaction.Behaviors>
<
pb:PhysicsObjectBehavior/>
</
i:Interaction.Behaviors>
</
Rectangle>
<
Rectangle Fill="White" Stroke="#FFB9B9B9" StrokeThickness="2" Height="25" Width="25" Canvas.Left="230" Canvas.Top="403">
<
i:Interaction.Behaviors>
<
pb:PhysicsObjectBehavior/>
</
i:Interaction.Behaviors>
</
Rectangle>
<
Rectangle Fill="White" Stroke="#FFB9B9B9" StrokeThickness="2" Height="25" Width="25" Canvas.Left="290" Canvas.Top="283">
<
i:Interaction.Behaviors>
<
pb:PhysicsObjectBehavior/>
</
i:Interaction.Behaviors>
</
Rectangle>
<
Rectangle Fill="White" Stroke="#FFB9B9B9" StrokeThickness="2" Height="25" Width="25" Canvas.Left="305" Canvas.Top="313">
<
i:Interaction.Behaviors>
<
pb:PhysicsObjectBehavior/>
</
i:Interaction.Behaviors>
</
Rectangle>
<
Rectangle Fill="White" Stroke="#FFB9B9B9" StrokeThickness="2" Height="25" Width="25" Canvas.Left="275" Canvas.Top="313">
<
i:Interaction.Behaviors>
<
pb:PhysicsObjectBehavior/>
</
i:Interaction.Behaviors>
</
Rectangle>
<
Rectangle Fill="White" Stroke="#FFB9B9B9" StrokeThickness="2" Height="25" Width="25" Canvas.Left="320" Canvas.Top="343">
<
i:Interaction.Behaviors>
<
pb:PhysicsObjectBehavior/>
</
i:Interaction.Behaviors>
</
Rectangle>
<
Rectangle Fill="White" Stroke="#FFB9B9B9" StrokeThickness="2" Height="25" Width="25" Canvas.Left="290" Canvas.Top="343">
<
i:Interaction.Behaviors>
<
pb:PhysicsObjectBehavior/>
</
i:Interaction.Behaviors>
</
Rectangle>
<
Rectangle Fill="White" Stroke="#FFB9B9B9" StrokeThickness="2" Height="25" Width="25" Canvas.Left="260" Canvas.Top="343">
<
i:Interaction.Behaviors>
<
pb:PhysicsObjectBehavior/>
</
i:Interaction.Behaviors>
</
Rectangle>
<
Rectangle Fill="White" Stroke="#FFB9B9B9" StrokeThickness="2" Height="25" Width="25" Canvas.Left="335" Canvas.Top="373">
<
i:Interaction.Behaviors>
<
pb:PhysicsObjectBehavior/>
</
i:Interaction.Behaviors>
</
Rectangle>
<
Rectangle Fill="White" Stroke="#FFB9B9B9" StrokeThickness="2" Height="25" Width="25" Canvas.Left="305" Canvas.Top="373">
<
i:Interaction.Behaviors>
<
pb:PhysicsObjectBehavior/>
</
i:Interaction.Behaviors>
</
Rectangle>
<
Rectangle Fill="White" Stroke="#FFB9B9B9" StrokeThickness="2" Height="25" Width="25" Canvas.Left="275" Canvas.Top="373">
<
i:Interaction.Behaviors>
<
pb:PhysicsObjectBehavior/>
</
i:Interaction.Behaviors>
</
Rectangle>
<
Rectangle Fill="White" Stroke="#FFB9B9B9" StrokeThickness="2" Height="25" Width="25" Canvas.Left="245" Canvas.Top="373">
<
i:Interaction.Behaviors>
<
pb:PhysicsObjectBehavior/>
</
i:Interaction.Behaviors>
</
Rectangle>
<
Rectangle Fill="White" Stroke="#FFB9B9B9" StrokeThickness="2" Height="25" Width="25" Canvas.Left="350" Canvas.Top="403">
<
i:Interaction.Behaviors>
<
pb:PhysicsObjectBehavior/>
</
i:Interaction.Behaviors>
</
Rectangle>
<
Rectangle Fill="White" Stroke="#FFB9B9B9" StrokeThickness="2" Height="25" Width="25" Canvas.Left="320" Canvas.Top="403">
<
i:Interaction.Behaviors>
<
pb:PhysicsObjectBehavior/>
</
i:Interaction.Behaviors>
</
Rectangle>
<
Rectangle Fill="White" Stroke="#FFB9B9B9" StrokeThickness="2" Height="25" Width="25" Canvas.Left="290" Canvas.Top="403">
<
i:Interaction.Behaviors>
<
pb:PhysicsObjectBehavior/>
</
i:Interaction.Behaviors>
</
Rectangle>
<
Rectangle Fill="White" Stroke="#FFB9B9B9" StrokeThickness="2" Height="25" Width="25" Canvas.Left="260" Canvas.Top="403">
<
i:Interaction.Behaviors>
<
pb:PhysicsObjectBehavior/>
</
i:Interaction.Behaviors>
</
Rectangle>
<
Rectangle Fill="White" StrokeThickness="2" Height="29" Width="640" Canvas.Left="0" Canvas.Top="475">
<
i:Interaction.Behaviors>
<
pb:PhysicsObjectBehavior IsStatic="True"/>
</
i:Interaction.Behaviors>
</
Rectangle>
<
Ellipse Fill="Red" Stroke="#FFB9B9B9" StrokeThickness="2" Height="25" Width="25" Canvas.Top="173" Canvas.Left="255">
<
i:Interaction.Behaviors>
<
pb:PhysicsObjectBehavior/>
</
i:Interaction.Behaviors>
</
Ellipse>
</
Canvas>
</
UserControl>


8. A quick summary

So, in a few quick minutes, we've created an environment that behaves like the real world. Try grabbing and dragging one of the bottom blocks from the pyramid. The structure crumbles much like you'd expect a stack of blocks to crumble. By starting with the simple, we can start to add more and more robust behaviors to our XAML elements, creating some really interesting user interfaces. If you'd like to see more examples of how to use these tools and behaviors, head over to the Codeplex project for the PhysicsHelper and download their Silverlight examples. There's some neat examples of fluid dynamics, explosions, and even a Pinball game. Here's our finished application:

Labels: , , , ,

posted by Jeff Blankenburg, 1:00 AM | link | 4 comments |

Day #21: Rapid Silverlight Prototyping In SketchFlow

Monday, December 21, 2009

Today is day #21 of the 31 Days of Silverlight, and today we are going to be talking about a new tool available to developers called SketchFlow. While SketchFlow can be used in several different ways, I'm going to focus on how it can help you save time (and money) in rapidly getting a "proof of concept" application up and running quickly.

Getting SketchFlow

SketchFlow is a feature of Expression Blend 3, and you can download a 60-day trial of Blend 3 + SketchFlow from the Microsoft Expression website.

Creating a SketchFlow project

In Blend, choose "New Project," and choose Silverlight 3 SketchFlow application.



What you'll find is that it creates a pretty standard Silverlight project for you, and then there is an additional project that is the screens of our SketchFlow prototype. Let's get into the details of what we can do with SketchFlow.

Creating our application's pages

At the bottom of Expression Blend 3, you will see a tab named "SketchFlow Map." In this window, there is one blue rectangle named "Screen 1." Think of this as your home page. In fact, I double-clicked on mine and renamed it "Home." When you roll over the box, you might also notice that there's a submenu that appears. To start creating pages for our application, all we need to do is start dragging shapes from the "Home" box.



When you drag them, it will create a new, connected box that represents a new page of your application. I have dragged a few new pages onto my Map. I also changed the color of each section, so that we can visually see which pages belong to which section as it grows. You can do this from the icon on the right of the little submenu.



Representing our navigation

Here's a more robust version of our site map:



One of the cool things we can do with SketchFlow is represent the navigation visually as well. On the second icon from the left in the submenus, you can connect any page to any other page. Our next step will show why this is an important thing to consider. Drag your pages together, so that they represent how a user would navigate the site. Would there be a button on the "About" page to get to the "Products" page? Then they should be connected! Can you get back to the "Home" page from our 5 navigation sections? Then they should be connected! Hook up your pages like a user should be able to navigate, and then meet me at the next step.

The SketchFlow Player

Go ahead and press your F5 key. (Or choose "Project > Run Project" from the Expression Blend 3 menu.) You're going to see something new, called the SketchFlow Player. If you were diligent, and actually followed my instructions :), you should see a list of pages on the left hand side of the player. This is a list of pages that your "Home" page is linked to. As you click on those items, you'll notice that we are actually navigating to those seperate pages, and that the pages that are "linked" to will change. You can always click the Home icon at the top to return to the home page. (click to enlarge)



What I want you to notice is that navigation in this manner is awkward. The reason for that is because
  1. We haven't put any content on our pages, so it's not always clear where we are
  2. We don't have a way to define our hierarchy. All we have done to this point is create a bunch of pages, and link them together.
So let's add some content to make this much easier to use.

Adding content to our pages

Like any of the other Silverlight tutorials I have done, each "page" of our SketchFlow application is actually just a XAML file. I am going to add a visual title to each page (so we can see easily which is which). Here's a screenshot of my Products page (click to enlarge):



You'll notice that I'm not going for "pretty" here in my prototype application. There's a very specific reason for this.

<soapbox>
When you start working on a prototype application, one of the first tendencies everyone has is to start thinking about "what it will look like", rather than "how it will work." This is a fatal flaw in our thinking, and actually penalizes us later. Have you ever presented screenshots of a potential application to your client/manager/sponsor, with the intention of discussing how pages will work, only to be derailed by the fact that they don't like the color orange? Or that they wish the logo were bigger?

Take an opportunity to call this a "prototype." It doesn't have to look pretty, it has to function properly. We can make that functionality look any way the client desires. But it can often be difficult to shoehorn our functionality into the look and feel that has been determined. Ugly prototypes, For The Win!
</soapbox>

Blend 3 even includes a bunch of fonts and control styles that are specifically meant to look hand-written and hand-drawn, so that we can effectively communicate that this is meant to be looked at from a "how it works" perspective. Nobody is going to assume that this ugly look-and-feel is how it will look when we're done.

If you run the SketchFlow player again (F5), and as you click through the pages in the left navigation, the screen for each page changes as well. Since this is a prototype, let's add some controls to some of the pages. I'm going to add a few buttons for navigation on the home page, and a few cool things to the product page.

Our SketchFlow navigation

Ideally, in a prototype, our users should be able to click on buttons and links, navigating through the app as if it works like the real thing. They shouldn't have to use that confusing navigation in the SketchFlow player. I have added 6 buttons to my Home page, and for each of them, I am setting a "Navigate To" property on them. You can do this with any Silverlight element, it doesn't have to be Buttons, but for my example, buttons suffice. Here's a shot of my home page with the Navigate To menu open (click to enlarge):



Making A Component Screen

We need this navigation on each page of this site. It would be silly to have to go create this on each page, even if we have to copy and paste it. If we later decide to make a change to it, I want to be able to make that change once. We can do this by creating a Component Screen. Highlight all of the XAML buttons you created (I wrapped mine in a StackPanel, but it's certainly not necessary), and right-click to choose "Make Into Component Screen." I named mine Navigation. You should now see a green oval-shaped element on your SketchFlow Map. Drag the "Insert a Component Screen" option from the green oval to each of your top level pages.

You might have to adjust its position, but you should now have your navigation on each main page of your site. Here's what my SketchFlow Map looks like now:



More about the SketchFlow player

Inside that player are a couple of other cool features, including a feedback component that lets you draw on and markup the prototype. This is a great way to collect feedback from your clients on the app you're about to build.

You can also access the SketchFlow Map in that same region of the screen. It allows the user to see the full scope of the entire site, much like a site map does. (Click to enlarge.)



Summary

The best part of building a prototype using SketchFlow is that once you've gotten sign-off from your customer/client/boss, you've got a working Silverlight application. You can re-style your controls, add some live data (if you didn't already), and you're off and running. No need to throw your work away!

Click here to download my sample Sketchflow prototype from this demo.

Labels: , , ,

posted by Jeff Blankenburg, 1:00 AM | link | 1 comments |

Day #20: Adding Audio to Silverlight Events

Sunday, December 20, 2009

And...we're back. That might be the longest span of time I've gone without blogging in quite a while. Anyways, I'm going to pretend like we never stopped this little series I'm calling the 31 Days of Silverlight, and we'll continue down the path.

Today we're going to be discussing how to add a sound to a button click. This activity translates to nearly any event in your Silverlight application, but for simplicity's sake, we'll stick with the standard button click. So, let's get started...

1. Find a sound effect.

I found my sound effects at a site called Sound Jay. He offers a whole pile of different sound effects for free, and they're pretty good. They are offered in .WAV and .MP3, so make sure to download the MP3 version of the sound effect you choose.

2. Add the sound to your ClientBin.

You don't HAVE to do this, but I recommend using this method over packaging your sound effects directly into the .XAP file. Your application will load faster, and your files will actually be less likely to be "borrowed." To add it, choose "Add > Existing Item..." from the Solution Explorer in Visual Studio.

3. Add a MediaElement to your XAML page.

This MediaElement tag will be the container that holds our audio file. Here's the code for my tag:

<MediaElement x:Name="Beep" Source="beep-7.mp3" AutoPlay="False" />


4. We also need a button.

Nothing special about it, but for this example, we'll need a button on our page. Here's the code for that:

<Button Click="Button_Click" x:Name="Button" Width="100" Height="100" Content="Beep!" />


5. Hook up the event handler.

In the codebehind, we need one line of code. Here it is:

Beep.Play();


After that, we're done, right? Wrong. Following the above instructions gets you close, but if you run your application, you'll notice that only the first "click" of your button works, and after that, nothing. Thankfully, we're 90% of the way to the "correct" way to handle this. Here's what we need to do to make this work EVERY time we click the button:

6. Define the source in code.

We are going to remove the Source property from our MediaElement tag. By defining this each time we click the button, we create a new instance of the file. This means that if you have a longer audio file (> 1 second), the audio will start over each time the button is clicked. Here's the code in our event handler now:

            Beep.Source = new Uri("beep-7.mp3", UriKind.Relative);
Beep.Play();


You should now find that your button click results in your sound effect each time. If you chose a longer sound effect (>1 second), you should also find that the sound effect will stop and start over if you click the button a second time before the sound completes. This is because we are destroying that instance of the sound, and creating a new one on each click.

Click here to download this solution for adding audio to a Silverlight click event.

Tune in tomorrow for another exciting episode of the 31 Days of Silverlight!

kick it on DotNetKicks.com

Labels: , , , ,

posted by Jeff Blankenburg, 1:00 AM | link | 0 comments |

The Completion of the 31 Days of Silverlight

Friday, December 18, 2009

Yes, I am actually going to finish this thing. Sometimes work just gets overwhelming, and blogging takes a backseat for a while. But I'm back, and I hope you are too. Take some time this holiday season, when you're watching Christmas Vacation for the third time, and open your laptop. These are simple, easy to follow tutorials that will get you up to speed on Silverlight in no time at all.

Here's what coming:

12/20/2009 - Day #20: Adding Audio to Silverlight Events

12/21/2009 - Day #21: Rapid Silverlight Prototyping In SketchFlow

12/22/2009 - Day #22: Using The Farseer Physics Engine in Silverlight

12/23/2009 - Day #23: Silverlight Outside The Browser (Part 1 of 3)

12/24/2009 - Day #24: Silverlight Outside The Browser (Part 2 of 3)

12/25/2009 - Day #25: Silverlight Outside The Browser (Part 3 of 3)

12/26/2009 - Day #26: Silverlight Data Grid

12/27/2009 - Day #27: Templating Controls In Silverlight

12/28/2009 - Day #28: Silverlight Application Themes

12/29/2009 - Day #29: Using Isolated Storage in Silverlight

12/30/2009 - Day #30: Bing Maps in Silverlight

12/31/2009 - Day #31: Geocoding and More Fun in Bing Maps for Silverlight


1/1/2010 - Bonus Day: Click The Button Game (Winners get an e-book)

In case you need something more to read, you can always revisit the first 19 days of the 31 Days of Silverlight as well:

Day #1: Mouse Events in Silverlight

Day #2: Silverlight Screen Transitions

Day #3: Custom Silverlight Loading Screen

Day #4: Communicating Between Two Silverlight Controls

Day #5: Silverlight Drag and Drop

Day #6: Silverlight and the Twitter "Hello, World!"

Day #7: Using WCF Web Services With Silverlight (and LINQ)

Day #8: Custom Fonts in Silverlight

Day #9: Using Keystrokes in Silverlight

Day #10: Styling Silverlight Control

Day #11: Animating Your Silverlight Application

Day #12: Jumping From XAML to XAML in Silverlight

Day #13: Binding Elements In Silverlight 3

Day #14: Perspective 3D in Silverlight

Day #15: Silverlight Charting

Day #16: Silverlight AutoComplete Textbox

Day #17: Silverlight Layout Options

Day #18: Silverlight Effects

Day #19: Silverlight Pixel Shader Effects

Labels: ,

posted by Jeff Blankenburg, 1:00 AM | link | 1 comments |

Silverlight Is For Breakfast Now!

Monday, August 10, 2009

Tell me if you've ever experienced this before:

You start working on a development project, and after reviewing the requirements, you declare that some new technology would be the perfect fit for it. You talk to your manager, and he/she decides the team shouldn't try anything new. "It's too risky," they say. "It's only going to slow our efforts down," they say.

You scour the web for examples of how the technology has saved countless hours of development, deployment, and maintenance. All of it falls on deaf ears, because unfamiliarity often breeds unwillingness.

If this describes an experience you've had, I've got some good news. On Wednesday, August 19th, I am hosting a Silverlight Breakfast with HMB (a Microsoft Gold Certified Partner) for "technology decision makers."

Attendees can expect to learn how incorporating the power of Silverlight can lead to improved maintainability, decreased time to market, and higher levels of user acceptance in their applications.

To get registered, you can head over to the registration site. The meeting is being held in the Microsoft office on Polaris Parkway from 7:30 - 9:30 AM (on August 19, 2009), on the north side of Columbus, OH.

8800 Lyra Ave. #400
Columbus, OH 43240
Link to Map

Labels:

posted by Jeff Blankenburg, 10:40 AM | link | 0 comments |

Day #19: Silverlight Pixel Shader Effects

Sunday, July 19, 2009

Today's post was written by Matt Casto, a former colleague of mine, and my partner in crime for the Stir Trek event we held last May. You can read more from Matt at http://programwith.net.

Pixel shaders, also known as shader effects, are a cool new feature the became available with Silverlight 3. The two most common effects – drop shadow and blur – are included with Silverlight. Its easy to add these effects to any UIElement.

Lets build upon the simple form that was introduced in Jeff’s post Day #10: Styling Silverlight Controls. We can add a drop shadow effect to the email address textbox by changing the Page.xaml to look like this.

<TextBox x:Name="Email" Text="" Grid.Row="4" Style="{StaticResource TextBox}">
    <TextBox.Effect>
        <DropShadowEffect ShadowDepth="2" BlurRadius="3" Direction="300" />
    </TextBox.Effect>
</TextBox>

 


Running the application will show the drop shadow effect has been applied to the textbox.


PixelShaders_1


You can adjust the ShadowDepth, BlurRadius and Direction dependency properties to change the way the effect is rendered. For instance, if we change all of these values to be 10 we end up with this.


PixelShaders_2


Let's also change the Page.xaml.cs code behind to set a Blur effect on the submit button while the Mailer service is being called.



void Clicky_Click(object sender, RoutedEventArgs e)
{
    if (Email.Text == ""){
        Email.Focus();
    }
    else
    {
        MailerReference.MailerSoapClient mailerClient = new MailerReference.MailerSoapClient();
        mailerClient.SendMailCompleted += new EventHandler<SilverlightStyling.MailerReference.SendMailCompletedEventArgs>(mailerClient_SendMailCompleted);
        string name = FirstName.Text.ToString() + " " + LastName.Text.ToString();
        string body = FirstName.Text.ToString() + "\n" + LastName.Text.ToString() + "\n" + Address.Text.ToString() + "\n" + CityStateZip.Text.ToString();
        mailerClient.SendMailAsync("jeff@jeffblankenburg.com", Email.Text.ToString(), name, body);
 
        Clicky.Effect = new System.Windows.Media.Effects.BlurEffect { Radius = 3 };
    }
}
 
void mailerClient_SendMailCompleted(object sender, SilverlightStyling.MailerReference.SendMailCompletedEventArgs e)
{
    Clicky.Content = "Sent!";
    Clicky.IsEnabled = false;
    Clicky.Effect = null;
}

Run the application now and click Submit after filling in an email address and you’ll see the button get blurry until the service call has completed.


Pixel shaders modify the pixels before they’re rendered to display. This takes place at runtime and doesn’t use hardware acceleration at this point, so don’t expect these effects to be the most performant.


Adding Your Own Shader


You can create your own pixel shaders using High-Level Shading Language (HLSL) compiled with the DirectX SDK. Or you can download the Shazzam application to create, test and debug your pixel shader. Shazzam was created for WPF but all of these effects should work in Silverlight as well. Note that you’ll need the DirectX SDK installed first.


I pulled the Invert Color sample from Shazzam’s Shader Loader samples into the project by selecting Tools->View Compiled Shaders, copying the file (InvertColor.ps) and pasting it into the SilverlightStyling project. Also, I made sure the build action for the file is set to Resource. I then created a class to load the InvertColor.ps file.



using System;
using System.Windows.Media.Effects;
 
namespace SilverlightStyling
{
    public class InvertColor : ShaderEffect
    {
        public InvertColor()
        {
            var uri = new Uri(@"/SilverlightStyling;component/InvertColor.ps", UriKind.Relative);
            PixelShader = new PixelShader { UriSource = uri };
        }
    }
}

Then I applied the shader to the entire form by adding the effect to the LayoutRoot grid element.



<UserControl x:Class="SilverlightStyling.Page"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:SilverlightStyling="clr-namespace:SilverlightStyling" 
    Width="400" Height="300">
    <Grid x:Name="LayoutRoot" Background="White">
        <Grid.Effect>
            <SilverlightStyling:InvertColor />
        </Grid.Effect>

Now our entire form has a beautiful effect.


PixelShaders_3


There are a lot more pixel shaders available from the Windows Presentation Foundation Pixel Shader Effects Library CodePlex project which was updated recently to work with Silverlight 3.

Labels: , , ,

posted by Jeff Blankenburg, 1:00 AM | link | 2 comments |

Day #17: Silverlight Layout Options

Friday, July 17, 2009

Today is Day #17 of the 31 Days of Silverlight, and today we are going to focus on layout of your Silverlight application. There are several layout controls that I have used in the past tutorials, and I have completely glossed over how each of these work. Today we will discuss those options available to you.

The Canvas


In this first part of three, we're going to be talking about the <Canvas> option. I equate this to a more familiar concept in HTML known as absolute positioning. Each element will be given its own specific location on the page, and nothing but code can move them. This is also one of the downfalls of the <Canvas>, but for many apps, this may not matter.

With elements absolutely positioned, things just don't adjust. If a user resizes their browser, the elements don't move. If a user tries to see your application on a smaller screen than you anticipated, part of the app is going to be hidden. (Think mobile phones, for example...if you position everything out to fit in a window 800 x 600, that 320 x 200 screen is not going to show very much).

However, this is still one of the fastest ways to get your elements positioned on a page, and the taboos that came with absolute positioning in CSS are erased, because we're now developing in a universal plugin, not 17 flavors of browser and platform combinations.

So how do we do it? Here's some example code:

<UserControl x:Class="SilverlightLayoutOptions.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480">
<
Canvas x:Name="LayoutRoot" Background="White">
<
Ellipse Width="100" Height="100" Fill="Red" Canvas.Left="100" Canvas.Top="100"/>
<
Rectangle Width="100" Height="100" Fill="Blue" Canvas.Left="200" Canvas.Top="200"/>
<
Ellipse Width="100" Height="100" Fill="Green" Canvas.Left="300" Canvas.Top="0"/>
</
Canvas>
</
UserControl>

As you can see, it's pretty straightforward. We have a button, an ellipse, and a rectangle, and their positions are defined by the Canvas.Left and Canvas.Top attributes. You can also nest <Canvas> tags, and the Left and Top attributes apply to the immediate parent of the element you are positioning.








Get Microsoft Silverlight



Click here to see the code above running in another browser.

The StackPanel

We are now looking at a second way to lay out your interface in Silverlight. This will be far more familiar to the CSS fanatics out there. The primary concept is simple...we'll be using a StackPanel control to position our elements.

For those of you less familiar with CSS, this is more of a flow-based layout. As the size of the Silverlight container grows and shrinks, you will find that the elements will move to accomodate that space. I, personally, would not recommend approaching your entire interface with this technique, but it certainly makes sense for small portions, like navigations and lists of data.

Most of our positions will be determined by manipulating the margins of the individual elements on the page. In doing so, we can place our elements specific distances apart without having to specify their exact position on that page.

In the example below, I have 6 buttons that I want to arrange in a 3x2 block. I start with an outer stack panel that, by default, will stack my elements vertically. However, I want to have two rows of 3 buttons each, so I am going to nest two more stack panels. Each of these will have the Orientation attribute of the StackPanel set to Horizontal, to create the rows.

<UserControl x:Class="SilverlightLayoutOptions.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480">
<
StackPanel x:Name="LayoutRoot" Background="White">
<
StackPanel Orientation="Horizontal">
<
Button Content="Top Left" Width="100" Height="50" Margin="0,0,0,0" />
<
Button Content="Top Center" Width="100" Height="50" Margin="0,0,0,0" />
<
Button Content="Top Right" Width="100" Height="50" Margin="0,0,0,0" />
</
StackPanel>
<
StackPanel Orientation="Horizontal">
<
Button Content="Bottom Left" Width="100" Height="50" Margin="0,10,10,0" />
<
Button Content="Bottom Center" Width="100" Height="50" Margin="0,10,10,0" />
<
Button Content="Bottom Right" Width="100" Height="50" Margin="0,10,10,0" />
</
StackPanel>
</
StackPanel>
</
UserControl>









Get Microsoft Silverlight



For demonstration purposes, I also modified the margins of a few of the buttons, just to show that we are merely stacking these elements. This is not a grid, or table, and certainly not absolute positioning. These elements are merely stacked, either horizontally or vertically, with margins as the leverage to move things around a bit.

NOTE: I have a major complaint about how margins were implemented in Silverlight.

Again, for those of you familiar with CSS, the de-facto standard for specifying margin sizes was one of two formats:

margin-left:10px;
margin-right:10px;
etc.

OR

margin:0 10px 0 10px;

Each of those examples would have given you a 10 pixel margin on both the left and right of the element you were styling. In the second example, we are using a bit of shorthand to specify ALL of the margins in one line. They start with the top, and go clockwise. margin:Top Right Bottom Left;

In the margin implementation for Silverlight, it would appear that they also offer a shorthand version for margins (actually, the only version is shorthand), and they did not follow the standard convention mentioned above. Instead, they chose to use Margin="Left, Top, Right, Bottom". Since this is a web technology, I would have liked to see them stick with the web convention for margins.

The Grid

In the third and final section of Silverlight Layout, we are going to be discussing my preferred option for interface creation: The Grid. In many ways, I would compare this to table based layout in HTML (which is generally a bad word), but I will come to its defense in Silverlight today.

There are many reasons why table-based layouts should not be used in HTML. A few of them are:

1) Tables are very heavy. With all of the <tr> and <td> tags you need, the page bloats quickly.

2) CSS is the right way to do it. Once you harness the power of CSS, you have far greater control of your layout (not to mention accessibility) than you do with tables.

3) Tables are very difficult to manipulate once they are in place. If you've ever tried to move things around in a legacy table layout, you know what I'm talking about.

The reason I am recommending this type of layout is because those problems have been addressed in Silverlight with the <Grid>. So let's take a look at the code.

<UserControl x:Class="SilverlightLayoutOptions.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480">
<
Grid x:Name="LayoutRoot" Background="White" ShowGridLines="True">
<
Grid.ColumnDefinitions>
<
ColumnDefinition Width="100" />
<
ColumnDefinition Width="100" />
<
ColumnDefinition Width="100" />
<
ColumnDefinition Width="*" />
</
Grid.ColumnDefinitions>
<
Grid.RowDefinitions>
<
RowDefinition Height="100" />
<
RowDefinition Height="100" />
<
RowDefinition Height="*" />
</
Grid.RowDefinitions>
<
Rectangle Fill="Red" Grid.Row="0" Grid.Column="0" />
<
Rectangle Fill="Orange" Grid.Row="0" Grid.Column="1" />
<
Rectangle Fill="Yellow" Grid.Row="0" Grid.Column="2" />
<
Rectangle Fill="Green" Grid.Row="1" Grid.Column="0" />
<
Rectangle Fill="Blue" Grid.Row="1" Grid.Column="1" />
<
Rectangle Fill="Purple" Grid.Row="1" Grid.Column="2" />
<
Rectangle Fill="Black" Grid.Row="1" Grid.Column="3" />
</
Grid>
</
UserControl>









Get Microsoft Silverlight



You'll notice that this doesn't really look like HTML table layout that you've seen in the past. Instead, we've seperated (still in the same file, mind you) the content from the layout. We don't have to wrap each element in a set of table tags. We define a grid, and then we assign each element to a cell of that grid. I've also turned on the ShowGridLines attribute, so we can see exactly what the grid looks like. We can also assign height and width values to the cells of the grid, up to and including the wildcard "*" character. This tells the application to use the rest of the remaining space for that cell. If you have more than one wildcard cell, it will split the unclaimed space evenly between them. Please note that the grid starts with 0,0, not 1,1. So our Red Rectangle is assigned to Grid.Cell="0" and Grid.Row="0" which puts it in the top left corner. Each of the respective rectangles after that is assigned to their respective grid location as well. The design pane of Visual Studio 2008 looks like this with the XAML I just used:



You'll notice that the Black rectangle is missing. That is the one assigned to the wildcard space. In the design pane, there's not a Canvas width and height defined. In the XAML, I did not specify a width and height for my Grid. This causes it to fill the entire space of the window it is rendered in. When we run the application, however, you'll see that the black box is, in fact, there, and takes up the rest of your browser window.

Summary

Today we talked about the layout controls available to you in Silverlight. They each serve specific purposes, and I wouldn't say that one is necessarily better than the other. If you'd like to download the sample code for this project, you can download the StackPanel, Canvas, and Grid Layout Options for Silverlight sample here.

Labels: , ,

posted by Jeff Blankenburg, 1:00 AM | link | 0 comments |

Day #16: Silverlight AutoComplete TextBox

Thursday, July 16, 2009

Yesterday we talked about charting controls in Silverlight in our 31 Days of Silverlight series. These were part of the Silverlight Toolkit, and today, we're going to focus on another useful feature of the Silverlight Toolkit, the AutoComplete TextBox. This will be a relatively short tutorial, only because it's simple to do, but I think this is one of the more useful things you can start using today in your applications.

Make sure you've got the Silverlight Toolkit

This is your last reminder. After today, you're on your own. :)
You can download the Silverlight 3 Toolkit here.

AutoComplete?

For those of you that might be unfamiliar with what an AutoComplete TextBox is, think of the little search box on your browser. When you type search terms in, it starts recommending choices that match what you have typed. This type of control is extremely valuable when you want the user to pick a specific value, but have way too many choices for a standard select list. An AutoCompleteBox allows a user the freedom of typing, while still choosing from a pre-determined list of values. This type of control is so important, I have also written an article on Creating An AutoComplete TextBox with AJAX.

Our good friend User Input Form

He's back, and we're using him again. This time, I have swapped out the TextBox for City and State for an AutoCompleteBox control. You'll also notice that I had to add an extra namespace to the top of my XAML for this to be included: System.Windows.Controls. Here's the XAML:

<UserControl xmlns:input="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Input"  x:Class="SilverlightAutoComplete.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480">
<
Grid x:Name="LayoutRoot" Background="White" Height="300" Width="400">
<
Grid.Projection>
<
PlaneProjection CenterOfRotationX="0" CenterOfRotationY="0"/>
</
Grid.Projection>
<
Grid.ColumnDefinitions>
<
ColumnDefinition Width="0.445*" />
<
ColumnDefinition Width="0.555*" />
</
Grid.ColumnDefinitions>
<
Grid.RowDefinitions>
<
RowDefinition Height="50" />
<
RowDefinition Height="50" />
<
RowDefinition Height="50" />
<
RowDefinition Height="50" />
<
RowDefinition Height="50" />
<
RowDefinition Height="50" />
</
Grid.RowDefinitions>
<
TextBlock Text="First Name:" Grid.Row="0" Style="{StaticResource FormLabel}" />
<
TextBlock Text="Last Name:" Grid.Row="1" Style="{StaticResource FormLabel}" />
<
TextBlock Text="Address:" Grid.Row="2" Style="{StaticResource FormLabel}" />
<
TextBlock Text="City, State:" Grid.Row="3" Style="{StaticResource FormLabel}" />
<
TextBlock Text="Email Address:" Grid.Row="4" Style="{StaticResource FormLabel}" FontWeight="Bold" />
<
TextBox x:Name="FirstName" Text="Jeff" Grid.Row="0" Style="{StaticResource TextBox}" Margin="0,13,0,12" />
<
TextBox x:Name="LastName" Text="Blankenburg" Grid.Row="1" Style="{StaticResource TextBox}" Margin="0,13,0,12" />
<
TextBox x:Name="Address" Text="8800 Lyra Ave. #400" Grid.Row="2" Style="{StaticResource TextBox}" Margin="0,13,0,12" />
<
input:AutoCompleteBox x:Name="CitiesBox" Text="" Grid.Row="3" HorizontalAlignment="Left" Height="25" Width="190" Grid.Column="1" Margin="0,13,0,12" />
<
TextBox x:Name="Email" Text="" Grid.Row="4" Style="{StaticResource TextBox}" Margin="0,13,0,12" />
<
Button x:Name="Clicky" Content="Submit" Grid.Column="1" Grid.Row="5" Height="25" Width="100" HorizontalAlignment="Left" Margin="0,13,0,12" d:LayoutOverrides="HorizontalAlignment" />
</
Grid>
</
UserControl>


Adding our "preset" options

Setting up a list of data for the AutoCompleteBox to use is as simple as it gets. Remember our data binding example from Day #13? We can do it that way. We can also do it from code, and to keep this simple, that's exactly what I will do in this example. I am just going to set the ItemsSource to an array of Strings. That's it. The control takes care of EVERYTHING else. Here's my code-behind:

using System;
using System.Windows.Controls;

namespace SilverlightAutoComplete
{
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
CitiesBox.ItemsSource = new String[]
{"Cleveland, OH", "Columbus, OH", "Cincinnati, OH", "Detroit, MI", "Grand Rapids, MI", "Lansing, MI", "Ann Arbor, MI", "Louisville, KY", "Lexington, KY", "Memphis, TN", "Nashville, TN", "Chattanooga, TN", "Knoxville, TN"};
}
}
}


Summary

It may seem simple, but I wanted to make sure that you were specifically aware of this option when building Silverlight applications. It's a highly useful, and highly user-friendly way to accept input. You could even go so far as to bind the list to a set of data returned from a web service. (See my AJAX example for that.) Our simple example is running below. Try typing the name of a major city from Michigan, Ohio, Kentucky, or Tennessee. If you can't see it, you can click here to see the Silverlight AutoCompleteBox example. You can also download the AutoCompleteBox example code here.








Get Microsoft Silverlight

Labels: , ,

posted by Jeff Blankenburg, 1:00 AM | link | 7 comments |

Day #15: Silverlight Charting

Wednesday, July 15, 2009

Today is Day #15 of the 31 Days of Silverlight, and today we are going to cover creating a Pie Chart and a Bar Chart, two of the many items in the Silverlight Toolkit. We will spend the next couple of days covering some of the important features of the Silverlight Toolkit.

Charting

In general, charting has become of those things that we all dread as developers. In today's demo, you're going to see how easy it is to create some robust charts with very little effort. Let's get started.

Creating some data

The first thing we need to do when creating a chart is to have some data to populate it with. I have created a simple class, City, that has two properties: Name, and Population. We are going to be charting a list of cities by their populations in the 2008 U.S. Census. Here's the code for my class:


namespace SilverlightPieChart
{
public class City
{
public string Name { get; set; }
public int Population { get; set; }
}
}


Next, in our code-behind, I have created a simple list of Cities, with their appropriate data. This list of objects could have been populated from anywhere, but for the focus of this demo, we'll just have them populated manually. Here's our list:

        List<City> cities = new List<City>{
new City { Name="Cleveland, OH", Population=2250871},
new City { Name="Columbus, OH", Population=1773120},
new City { Name="Cincinnati, OH", Population=2155137},
new City { Name="Detroit, MI", Population=4425110},
new City { Name="Grand Rapids, MI", Population=1324516},
new City { Name="Lansing, MI", Population=454044},
new City { Name="Ann Arbor, MI", Population=341847},
new City { Name="Louisville, KY", Population=1268323},
new City { Name="Lexington, KY", Population=436684},
new City { Name="Memphis, TN", Population=1280533},
new City { Name="Nashville, TN", Population=1521437},
new City { Name="Chattanooga, TN", Population=518441},
new City { Name="Knoxville, TN", Population=1029155}};


Creating our chart

(I am assuming you have downloaded and installed the Silverlight Toolkit at this point. If you haven't, I'll wait while you get that done.)

OK, we're back. Now let's discuss what we're creating before we actually create it. When creating charts in Silverlight, we need to create a Chart control, and inside that control, we can create Pie, Bar, Bubble, and more charts. The Chart holds things like the Title of the chart, design elements, sizes, etc. In our first example, our "PieSeries" control will create a pie chart based on our list of cities above. What's incredibly awesome about these controls is that they do all of the work for you. I am going to bind a set of data to our PieSeries, and it figures out the total population of all of the cities in our list, the percentage that each city makes up, and even chooses different colors for each slice. It also creates a legend for our pie chart, so that each slice is labeled. Please also notice that we have to add an additional namespace for these controls: System.Windows.Controls.DataVisualization.Charting. Here's our XAML:

<UserControl xmlns:chartingToolkit="clr-namespace:System.Windows.Controls.DataVisualization.Charting;assembly=System.Windows.Controls.DataVisualization.Toolkit"  x:Class="SilverlightPieChart.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480">
<
Canvas x:Name="LayoutRoot">
<
chartingToolkit:Chart x:Name="pieChart" Title="Cities In The Heartland"
BorderBrush="Gray"
Margin="1"
Canvas.Top="100" Canvas.Left="100" Width="400" Height="300">
<
chartingToolkit:PieSeries
IndependentValueBinding="{Binding Path=Name}"
DependentValueBinding="{Binding Path=Population}" />
</
chartingToolkit:Chart>
</
Canvas>
</
UserControl>


You'll notice that while we bound two values to our PieSeries control, we didn't actually define the source of the data. We'll do that in our code-behind. In case it's not clear, the IndependentValueBinding is the name of each slice of the pie, and the DependentValueBinding is the one that will cause the representation of the slice. This is because the Pie Chart is "dependent" on the values that you provide in this property.

Our final PieSeries code

Because of our nested containers (Chart > PieSeries), we are going to bind our data in code. In the two relevant lines in our MainPage() method, we initialize a reference to our PieSlice element, and in the second, we set the ItemsSource of that element. You'll see that we have to refer to it as a PieSeries control, and this will change when we change this to a BarSeries. Here's the code:

using System.Collections.Generic;
using System.Windows.Controls;
using System.Windows.Controls.DataVisualization.Charting;

namespace SilverlightPieChart
{
public partial class MainPage : UserControl
{
List<City> cities = new List<City>{
new City { Name="Cleveland, OH", Population=2250871},
new City { Name="Columbus, OH", Population=1773120},
new City { Name="Cincinnati, OH", Population=2155137},
new City { Name="Detroit, MI", Population=4425110},
new City { Name="Grand Rapids, MI", Population=1324516},
new City { Name="Lansing, MI", Population=454044},
new City { Name="Ann Arbor, MI", Population=341847},
new City { Name="Louisville, KY", Population=1268323},
new City { Name="Lexington, KY", Population=436684},
new City { Name="Memphis, TN", Population=1280533},
new City { Name="Nashville, TN", Population=1521437},
new City { Name="Chattanooga, TN", Population=518441},
new City { Name="Knoxville, TN", Population=1029155}};

public MainPage()
{
InitializeComponent();
PieSeries pieSlice = ourChart.Series[0] as PieSeries;
pieSlice.ItemsSource = cities;
}
}
}


Here's what it looks like (you can also click to see the Silverlight Pie Chart here):








Get Microsoft Silverlight



What?! Someone changed their mind???

I'm sure this never happens on your software projects, but after I showed the pie chart to my manager, he decided we had too many data points for a pie chart to be effective. Then he dropped the question:

Would it be hard to make this a Bar Chart instead?

Answer: "I should be able to finish it by the end of the day..." You can make 2 minutes of changes to your code, and then go back to working on the Toughest Developer Puzzle Ever with all of your free time. We have 3 small changes to make, specifically, and those are just changing the word PieSeries to BarSeries. I also added a Title property to the BarSeries control, but this is certainly not necessary. It just populates the legend that is created for us. You could also change the name of our variable "pieSlice," but it's also not going to affect how this chart functions. Below is the XAML and C# for the BarSeries example:

XAML


<UserControl xmlns:chartingToolkit="clr-namespace:System.Windows.Controls.DataVisualization.Charting;assembly=System.Windows.Controls.DataVisualization.Toolkit"  x:Class="SilverlightPieChart.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006">
<
Canvas x:Name="LayoutRoot">
<
chartingToolkit:Chart x:Name="ourChart" Title="Cities In The Heartland"
BorderBrush="Gray"
Margin="1"
Canvas.Top="0" Canvas.Left="0" Width="490" Height="390">
<
chartingToolkit:BarSeries
IndependentValueBinding="{Binding Path=Name}"
DependentValueBinding="{Binding Path=Population}"
Title="Population"/>
</
chartingToolkit:Chart>
</
Canvas>
</
UserControl>


C#


using System.Collections.Generic;
using System.Windows.Controls;
using System.Windows.Controls.DataVisualization.Charting;

namespace SilverlightPieChart
{
public partial class MainPage : UserControl
{
List<City> cities = new List<City>{
new City { Name="Cleveland, OH", Population=2250871},
new City { Name="Columbus, OH", Population=1773120},
new City { Name="Cincinnati, OH", Population=2155137},
new City { Name="Detroit, MI", Population=4425110},
new City { Name="Grand Rapids, MI", Population=1324516},
new City { Name="Lansing, MI", Population=454044},
new City { Name="Ann Arbor, MI", Population=341847},
new City { Name="Louisville, KY", Population=1268323},
new City { Name="Lexington, KY", Population=436684},
new City { Name="Memphis, TN", Population=1280533},
new City { Name="Nashville, TN", Population=1521437},
new City { Name="Chattanooga, TN", Population=518441},
new City { Name="Knoxville, TN", Population=1029155}};

public MainPage()
{
InitializeComponent();
BarSeries pieSlice = ourChart.Series[0] as BarSeries;
pieSlice.ItemsSource = cities;
}
}
}


Here's what the BarSeries chart looks like in our example, using the same data (click here to see our Silverlight Bar Chart in another window):








Get Microsoft Silverlight



Summary

In this article, we discovered how easy it is to add charts to our Silverlight applications. Keep in mind that you could also be building standalone Silverlight applications that are ONLY the chart, and add those to your existing ASP.NET pages. If you'd like to download the source code for this Silverlight Charting project, click here.

Labels: , ,

posted by Jeff Blankenburg, 1:00 AM | link | 4 comments |

Day #14: Perspective 3D In Silverlight 3

Tuesday, July 14, 2009

Today is Day #14 in my series 31 Days of Silverlight. Today, we're going to talk about a new feature in Silverlight: Perspective 3D. It adds another dimension to how we can think about our user interface, and can provide some cool interactions as well.

Instead of building out an entire application today, we are going to use the application we built on Day #10, Styling Silverlight Controls. You can download the source for that project here.

Refreshing your memory

In case you're working through this code for the first time, this application is just a simple form, which we moved all of the styles into StaticResources in our app.xaml file. For some extra polish, I also made the form capable of sending an email. Type your email address in the appropriate box, and it will send the contents of the form to you.

When you submit the form, the Sumbit button becomes disabled, and the text changes to the word "Sent!" Instead, we're going to have the box rotate in 3D space so that it appears to fold into the side of the application. Let's get started.

Understanding Cartesian geometry

I am going to be using Expression Blend 3 again for this, but there's nothing stopping you from writing this XAML in Visual Studio. I just find it easier to drag shapes than to type markup.

In our initial application, we placed all of our elements in the root Grid of the page. For our new tasks, we're going to want to make this something other than the primary element on the page. The easiest way to do this in Blend is to right-click on the Grid named LayoutRoot in the Objects and Timeline panel. Choose "Group Into" and choose Canvas. This will wrap a canvas around the element you selected. I changed the background color of the new Canvas to gray, just so it would look different from the white background of the form.

To create our 3D effect, we're going to use a Projection transform. We can transform an object on its X, Y, or Z axis. The X axis is like there is a horizontal bar running through the middle of the element that it rotates around. The Y axis is as if there is a vertical bar running through the middle of the element that it rotates around. The Z axis is determined only by where the X and Y planes intersect, and extends towards you. You can also read more about Cartesian coordinates on Wikipedia. Here's a visual of what that means:



Manipulating our Y axis

We are going to use a PlaneProjection to modify our form in our animation. So, I create my animation (like I did in Day #10's demo), and move the Y axis to -101 in 1 second. I also created a second animation to return the form to normal. Here's what the animations look like in XAML:

        <Storyboard x:Name="ClosePanel">
<
DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="LayoutRoot" Storyboard.TargetProperty="(UIElement.Projection).(PlaneProjection.RotationY)">
<
EasingDoubleKeyFrame KeyTime="00:00:00" Value="0"/>
<
EasingDoubleKeyFrame KeyTime="00:00:01" Value="-101"/>
</
DoubleAnimationUsingKeyFrames>
</
Storyboard>
<
Storyboard x:Name="OpenPanel">
<
DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="LayoutRoot" Storyboard.TargetProperty="(UIElement.Projection).(PlaneProjection.RotationY)">
<
EasingDoubleKeyFrame KeyTime="00:00:00" Value="-101"/>
<
EasingDoubleKeyFrame KeyTime="00:00:01" Value="0"/>
</
DoubleAnimationUsingKeyFrames>
</
Storyboard>


Calling our animation

Now it's as simple as calling our animations from code. I've modified the original Page.xaml.cs file, so that our new animation, ClosePanel gets called when we click the Submit button. The OpenPanel animation gets called when our email service gives us a positive response. At this point, go ahead and run the application, and when you enter an email address and click Submit, your panel should rotate inward. Here's the code:

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

namespace SilverlightStyling
{
public partial class Page : UserControl
{
public Page()
{
InitializeComponent();
Clicky.Click += new RoutedEventHandler(Clicky_Click);
}

void Clicky_Click(object sender, RoutedEventArgs e)
{
if (Email.Text == ""){
Email.Focus();
}
else
{
ClosePanel.Begin();
MailerReference.MailerSoapClient mailerClient = new MailerReference.MailerSoapClient();
mailerClient.SendMailCompleted += new EventHandler<SilverlightStyling.MailerReference.SendMailCompletedEventArgs>(mailerClient_SendMailCompleted);
string name = FirstName.Text.ToString() + " " + LastName.Text.ToString();
string body = FirstName.Text.ToString() + "\n" + LastName.Text.ToString() + "\n" + Address.Text.ToString() + "\n" + CityStateZip.Text.ToString();
mailerClient.SendMailAsync("jeff@jeffblankenburg.com", Email.Text.ToString(), name, body);
}
}

void mailerClient_SendMailCompleted(object sender, SilverlightStyling.MailerReference.SendMailCompletedEventArgs e)
{
OpenPanel.Begin();
Clicky.Content = "Sent!";
Clicky.IsEnabled = false;
}
}
}


Gratuitous use of 3D

Here's when I have to my a <soapbox> statement. Just because you CAN move things in a 3D space does NOT mean that you should. Please don't make this cool ability join the ranks of flaming logos and the <blink> tag. Be responsible. Now you know. And knowing is half the battle.

Summary

This was a relatively simple example of Perspective 3D, but it does demonstrate how simple it is to manipulate anything in a Silverlight app. Click here to download this Silverlight Perspective 3D example. Our example application is running below, but if you're having trouble seeing it, you can see the Silverlight Perspective 3D example here.








Get Microsoft Silverlight

Labels: , ,

posted by Jeff Blankenburg, 1:00 AM | link | 2 comments |

Day #13: Binding Elements in Silverlight 3

Monday, July 13, 2009

This is post #13 in my series of 31 Days of Silverlight. Today we are going to be discussing binding two Silverlight elements together. Now, we have been able to do this in the past, but in Silverlight 3, we have a new syntax that makes this operation MUCH simpler.

Make sure that, at this point in the series, you have updated your tools to the Silverlight 3 tools. You can get the Silverlight 3 tools here. You can also download the Web Platform Installer, which is like Windows Update for web developers. It will let you know when there are new updates to download in the future, including Beta versions.

Creating some XAML elements

In this example, we're going to go simple. I'm going to add a Slider and a TextBlock to the page. I think this is probably the default demo for Element Binding, but it's also an effective way to communicate it, so we'll expand on it later. Also note that if you've never used a Slider control before, that we have the ability to specify a minimum and maximum value for it. This can be a huge help when we want a user to pick a specific range of values. You'll see that my range is from 0 to 1, and I'll explain why a bit later. For now, your XAML should look like this:

<UserControl x:Class="SilverlightElementBinding.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008">
<
Canvas x:Name="LayoutRoot" Width="400" Height="300" Background="White">
<
Slider x:Name="Slidy" Width="150" Maximum="1" Minimum="0" Canvas.Left="178" Canvas.Top="34" />
<
TextBlock x:Name="ValueBox" Width="150" Canvas.Left="178" Canvas.Top="56" FontFamily="Arial Black" FontSize="26" Text="0" TextAlignment="Center"/>
</
Canvas>
</
UserControl>


Writing code to bind these elements

In Silverlight 2, we didn't have much choice. The way to bind two elements together was to write some code. We create an event handler on the one that will change, and in the event method, update the other control with the results of the first. Since you will probably still need to do things like this in the future, here's what the code would look like:

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

namespace SilverlightElementBinding
{
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
Slidy.ValueChanged += new RoutedPropertyChangedEventHandler<double>(Slidy_ValueChanged);
}

void Slidy_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
ValueBox.Text = Slidy.Value.ToString();
}
}
}


Binding elements in Silverlight 3

To demonstrate binding, I'm going to use a slightly different example than binding to a TextBlock, and this is why we chose a range from 0 to 1. In this example, I am adding an Image element to the page, and we will be binding our slider value directly to the Opacity value of the Image. This is why we chose a range from 0 to 1 on our slider. Opacity can have a value anywhere between those two numbers. Here's the XAML for the new image (you'll need to actually add an image to your project using "Add Existing Item..."):

<Image x:Name="Lighthouse" Source="Lighthouse.jpg" Width="400" Height="300" />


You'll notice that there's not a reference to the Opacity yet. We can add that (with the data binding) in just a few short characters. I'll show you the markup, and then I'll explain it.

<Image x:Name="Lighthouse" Source="Lighthouse.jpg" Width="400" Height="300" Opacity="{Binding Value, Mode=OneWay, ElementName=Slidy}" />


The first segment of the Opacity statement is "Binding Value," which means that we are going to be taking the value of the element we specify. The second element, "Mode=OneWay," specifies that we are only moving data from the Slider to the Image, not the other way around, and certainly not both. But those options do exist, and your other options are TwoWay, and OneTime. TwoWay should be obvious, as it means that if either value changes, the other element would be updated. OneTime means that it will only happen, not surprisingly, one time. This is primarily used when you want to initialize a value, but not update it on subsequent changes or updates.

Finally, we have the "ElementName=Slidy,", which just names the control that we wanted the value from. That's it. There's nothing secret going on behind the curtain, just a simple specification of the element to bind to, and which direction the data should travel. Pretty simple, huh? It definitely saves you the time of writing individual event handlers each time, that's for sure.

Summary

In this post, I discussed the fact that there is now inline data binding in Silverlight 3. I also demonstrated how to effectively bind data in code. You should be able to see my example application running below. If it is not displayed, you can view the Silverlight 3 Element Binding demo here. You can download the code for this Silverlight Element Binding example here.








Get Microsoft Silverlight

Labels: , ,

posted by Jeff Blankenburg, 1:00 AM | link | 1 comments |

Day #12: Jumping from XAML to XAML in Silverlight

Sunday, July 12, 2009

This is post #12 in my series 31 Days of Silverlight. On Day #2, we talked briefly about screen transitions, and how to add some drama to moving between screens. Today, we're going to address how you would handle this on a larger scale.

Also, starting today, you need to have AT LEAST the Silverlight 3 Beta plugin to view the demos. With Silverlight 3 released, we're going to be moving on to some of the new things you can do with the new release. You can download the Silverlight 3 Beta plugin here.

The shortcomings of this.Content

For an application that has only one or two screen transitions (moving from one XAML file to another XAML file), this.Content is probably perfectly acceptable. The shortcoming is that each time you use this.Content to move from one file to the next, you're not destroying the previous instance of the UserControl. So you end up building a large tree of UserControls, and eventually your app is going to collapse from the weight. If you're managing a large application, where your user is constantly jumping from one screen to another, you're going to find that you might want to approach it from a different angle.

The switcher

The reason that this post is written entirely seperate is because it almost requires a difference in architecture for your Silverlight application. The first sign of this is what I am calling the Switcher UserControl. It is a seperate XAML file (I name it Switcher.xaml, not surprisingly) that has the sole purpose of being the parent UserControl for our application. We then bring our child UserControls in as the Content of the Switcher, and when each one is "switched," the previous instance gets marked for the Garbage Collector. Which means we are conserving memory by only holding the immediate UserControl in memory, instead of ALL of them, which is what this.Content would result in.

The switcher.xaml file has nothing in its body. Here's the XAML for it:

<UserControl x:Class="SilverlightSwitcher.Switcher"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
</
UserControl>


It's the code-behind for Switcher that has all of the magic. In our startup method, we set the content of Switcher to our first page of our application. We also create another method, Navigate(), that we will call from each page to move to the next one. Here's the source for the Switcher.xaml.cs file:

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

namespace SilverlightSwitcher
{
public partial class Switcher : UserControl
{
public Switcher()
{
InitializeComponent();
if (this.Content == null)
{
this.Content = new Page();
}
}

public void Navigate(UserControl nextPage)
{
this.Content = nextPage;
}

}
}


Making Switcher where we start

Ever wonder how your Silverlight application just knows to start with Page.xaml as your first page? It's defined in your App.xaml file. There's a method called Application_Startup that we need to modify in order to make Switcher the thing we load first. Here's the modified method code:

        private void Application_Startup(object sender, StartupEventArgs e)
{
this.RootVisual = new Switcher();
}


Moving from page to page

Now that we've fundamentally changed how we intend to navigate between XAML pages, you'd expect that we're also going to have to do something different to get to those pages. You're very insightful. On each page, we're going to be calling the new Navigate function that we created in Switcher, instead of this.Content. To do this, we'll need to get a reference to our parent UserControl, Switcher, and then call its Navigate() method. Here's what the code looks like to do this:

            Switcher switcher = this.Parent as Switcher;
switcher.Navigate(new Page2());


In each case, we are passing a new instance of the next UserControl to the parent, Switcher, as its content. I've created a very simple app below that jumps between two different XAML files using the method I've described in this article. If you'd like to see it running in a seperate browser, you can click here to see the Silverlight XAML Switching demo. If you'd like to download the demo code for this XAML Switching example, you can get it here.








Get Microsoft Silverlight

Labels: , ,

posted by Jeff Blankenburg, 1:00 AM | link | 0 comments |

Day #11: Animating Your Silverlight Application

Saturday, July 11, 2009

Today is Day #11 of the 31 Days of Silverlight, and today we're going to take an intimate look at animation, and how you do it. We're going to focus much more on the tools today than the code, which is how most of these tutorials have gone this far. On Day #2, we worked on Silverlight Screen Transitions, and while we had a slight animation in it, we didn't spend much time talking about how it was created. Today's post will answer that question.

Introducing Expression Blend


Expression Blend is a tool for making your Silverlight and WPF development vastly simpler. As you may have noticed in the previous examples, we are writing ALL of our XAML by hand. That is certainly not the optimal way to do development. I should be able to drag my shapes around on the canvas, to get them in exactly the right position. That's where Expression Blend comes in. We're going to use Blend today to create our animation.

Our solution structure

At this point, you should be familiar with the structure of a Silverlight project, and the accompanying web project that make up our solution. Blend uses these same files. The same solution files, project files, everything. When you work on a project in Blend, you're working on the same exact files you used in Visual Studio. You'll see many of the features of Blend show up in Visual Studio 10 as well.

Drawing a car

For this article, we are going to create a car, a road, and some happy little trees. Then, we're going to make them move in such a way that it appears the car is driving down a road. So let's start with the car. In Blend, open up your Page.xaml file. By default, and for consistency in my demos, our canvas is 400 x 300. But since you can zoom in and out, size doesn't matter for once. Since this isn't a tutorial on being an artist, I'm not going to spend a ton of time making it look nice. I'm going to focus on animating. Here's our car, visually:



Nothing spectacular to look at, but it will certainly get the job done. Nobody will question that it's a car, even if it is amateurish. Here's the XAML that Blend created for me that represents the car. You'll notice that I've grouped certain elements into seperate Canvas elements, and this is because we're going to have spinning tires, among other movement in this animation.

<UserControl x:Class="SilverlightCarAnimation.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="400" Height="300" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d">
<
Grid x:Name="LayoutRoot" Background="White">
<
Canvas Margin="83.499,104,115.501,84" x:Name="Car">
<
Canvas Height="79" x:Name="CarBody" Width="201">
<
Rectangle Height="40" Width="86" Canvas.Left="58.501" Fill="#FFFF0000" Stroke="{x:Null}"/>
<
Rectangle Fill="#FFFF0000" Stroke="{x:Null}" Height="40" Width="201" Canvas.Top="39" RadiusX="0" RadiusY="0"/>
<
Rectangle Height="27" Width="46" Canvas.Left="98.502" Canvas.Top="12" Stroke="{x:Null}" RadiusX="0" RadiusY="0">
<
Rectangle.Fill>
<
LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<
GradientStop Color="#FF188CB7" Offset="0"/>
<
GradientStop Color="#FFFFFFFF" Offset="1"/>
</
LinearGradientBrush>
</
Rectangle.Fill>
</
Rectangle>
</
Canvas>
<
Canvas Height="49" x:Name="BackWheel" Width="49" Canvas.Left="14.168" Canvas.Top="62.662">
<
Ellipse Height="49.333" Width="49.333" Fill="#FF434343" Stroke="{x:Null}"/>
<
Ellipse Height="27" Width="27" Canvas.Left="11.333" Canvas.Top="11.672" Fill="#FFBBB9B9" Stroke="{x:Null}"/>
<
Rectangle Height="27" Width="2" RenderTransformOrigin="0.5,0.5" Canvas.Left="24.333" Canvas.Top="11.339" Fill="#FF000000" Stroke="{x:Null}">
<
Rectangle.RenderTransform>
<
TransformGroup>
<
ScaleTransform/>
<
SkewTransform/>
<
RotateTransform Angle="45"/>
<
TranslateTransform/>
</
TransformGroup>
</
Rectangle.RenderTransform>
</
Rectangle>
<
Rectangle Height="27" Width="2" RenderTransformOrigin="0.5,0.5" Fill="#FF000000" Stroke="{x:Null}" Canvas.Left="24.333" Canvas.Top="11.339">
<
Rectangle.RenderTransform>
<
TransformGroup>
<
ScaleTransform/>
<
SkewTransform/>
<
RotateTransform Angle="135"/>
<
TranslateTransform/>
</
TransformGroup>
</
Rectangle.RenderTransform>
</
Rectangle>
</
Canvas>
<
Canvas Height="49" x:Name="FrontWheel" Width="49" Canvas.Left="143.168" Canvas.Top="63">
<
Ellipse Height="49.333" Width="49.333" Fill="#FF434343" Stroke="{x:Null}"/>
<
Ellipse Height="27" Width="27" Fill="#FFBBB9B9" Stroke="{x:Null}" Canvas.Left="11" Canvas.Top="11"/>
<
Rectangle Height="27" Width="2" RenderTransformOrigin="0.5,0.5" Fill="#FF000000" Stroke="{x:Null}" Canvas.Left="24.666" Canvas.Top="11">
<
Rectangle.RenderTransform>
<
TransformGroup>
<
ScaleTransform/>
<
SkewTransform/>
<
RotateTransform Angle="45"/>
<
TranslateTransform/>
</
TransformGroup>
</
Rectangle.RenderTransform>
</
Rectangle>
<
Rectangle Height="27" Width="2" RenderTransformOrigin="0.5,0.5" Fill="#FF000000" Stroke="{x:Null}" Canvas.Left="24.666" Canvas.Top="11">
<
Rectangle.RenderTransform>
<
TransformGroup>
<
ScaleTransform/>
<
SkewTransform/>
<
RotateTransform Angle="135"/>
<
TranslateTransform/>
</
TransformGroup>
</
Rectangle.RenderTransform>
</
Rectangle>
</
Canvas>
</
Canvas>
</
Grid>
</
UserControl>


Adding a road

The road, in a static picture would be pretty easy to create. A black rectangle with some yellow dashes on it. However, our car will be driving, so those yellow lines will have to move as well. What I plan to do is make a line of yellow dashes that are twice as wide as my Silverlight app. This way, I can slide it to the left, and when I get to the end, I can just repeat the scrolling from the original position over and over. Here's a picture of our car on the new road:



Here's the new XAML:

<UserControl x:Class="SilverlightCarAnimation.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="400" Height="300" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d">
<
Grid x:Name="LayoutRoot" Background="White">
<
Rectangle Height="84" VerticalAlignment="Bottom" Fill="#FF000000" Stroke="{x:Null}" x:Name="Road"/>
<
StackPanel Height="24" VerticalAlignment="Bottom" Orientation="Horizontal" Margin="0,0,-448,30" x:Name="LineStripe">
<
Rectangle Height="9" Width="40" Fill="#FFFFD300" Stroke="{x:Null}" Margin="0,0,20,0"/>
<
Rectangle Height="9" Width="40" Fill="#FFFFD300" Stroke="{x:Null}" Margin="0,0,20,0"/>
<
Rectangle Height="9" Width="40" Fill="#FFFFD300" Stroke="{x:Null}" Margin="0,0,20,0"/>
<
Rectangle Height="9" Width="40" Fill="#FFFFD300" Stroke="{x:Null}" Margin="0,0,20,0"/>
<
Rectangle Height="9" Width="40" Fill="#FFFFD300" Stroke="{x:Null}" Margin="0,0,20,0"/>
<
Rectangle Height="9" Width="40" Fill="#FFFFD300" Stroke="{x:Null}" Margin="0,0,20,0"/>
<
Rectangle Height="9" Width="40" Fill="#FFFFD300" Stroke="{x:Null}" Margin="0,0,20,0"/>
<
Rectangle Height="9" Width="40" Fill="#FFFFD300" Stroke="{x:Null}" Margin="0,0,20,0"/>
<
Rectangle Height="9" Width="40" Fill="#FFFFD300" Stroke="{x:Null}" Margin="0,0,20,0"/>
<
Rectangle Height="9" Width="40" Fill="#FFFFD300" Stroke="{x:Null}" Margin="0,0,20,0"/>
<
Rectangle Height="9" Width="40" Fill="#FFFFD300" Stroke="{x:Null}" Margin="0,0,20,0"/>
<
Rectangle Height="9" Width="40" Fill="#FFFFD300" Stroke="{x:Null}" Margin="0,0,20,0"/>
<
Rectangle Height="9" Width="40" Fill="#FFFFD300" Stroke="{x:Null}" Margin="0,0,20,0"/>
</
StackPanel>
<
Canvas Margin="49,0,150,24" x:Name="Car" VerticalAlignment="Bottom" Height="112">
<
Canvas Height="79" x:Name="CarBody" Width="201">
<
Rectangle Height="40" Width="86" Canvas.Left="58.501" Fill="#FFFF0000" Stroke="{x:Null}"/>
<
Rectangle Fill="#FFFF0000" Stroke="{x:Null}" Height="40" Width="201" Canvas.Top="39" RadiusX="0" RadiusY="0"/>
<
Rectangle Height="27" Width="46" Canvas.Left="98.502" Canvas.Top="12" Stroke="{x:Null}" RadiusX="0" RadiusY="0">
<
Rectangle.Fill>
<
LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<
GradientStop Color="#FF188CB7" Offset="0"/>
<
GradientStop Color="#FFFFFFFF" Offset="1"/>
</
LinearGradientBrush>
</
Rectangle.Fill>
</
Rectangle>
</
Canvas>
<
Canvas Height="49" x:Name="BackWheel" Width="49" Canvas.Left="14.168" Canvas.Top="62.662">
<
Ellipse Height="49.333" Width="49.333" Fill="#FF434343" Stroke="{x:Null}"/>
<
Ellipse Height="27" Width="27" Canvas.Left="11.333" Canvas.Top="11.672" Fill="#FFBBB9B9" Stroke="{x:Null}"/>
<
Rectangle Height="27" Width="2" RenderTransformOrigin="0.5,0.5" Canvas.Left="24.333" Canvas.Top="11.339" Fill="#FF000000" Stroke="{x:Null}">
<
Rectangle.RenderTransform>
<
TransformGroup>
<
ScaleTransform/>
<
SkewTransform/>
<
RotateTransform Angle="45"/>
<
TranslateTransform/>
</
TransformGroup>
</
Rectangle.RenderTransform>
</
Rectangle>
<
Rectangle Height="27" Width="2" RenderTransformOrigin="0.5,0.5" Fill="#FF000000" Stroke="{x:Null}" Canvas.Left="24.333" Canvas.Top="11.339">
<
Rectangle.RenderTransform>
<
TransformGroup>
<
ScaleTransform/>
<
SkewTransform/>
<
RotateTransform Angle="135"/>
<
TranslateTransform/>
</
TransformGroup>
</
Rectangle.RenderTransform>
</
Rectangle>
</
Canvas>
<
Canvas Height="49" x:Name="FrontWheel" Width="49" Canvas.Left="143.168" Canvas.Top="63">
<
Ellipse Height="49.333" Width="49.333" Fill="#FF434343" Stroke="{x:Null}"/>
<
Ellipse Height="27" Width="27" Fill="#FFBBB9B9" Stroke="{x:Null}" Canvas.Left="11" Canvas.Top="11"/>
<
Rectangle Height="27" Width="2" RenderTransformOrigin="0.5,0.5" Fill="#FF000000" Stroke="{x:Null}" Canvas.Left="24.666" Canvas.Top="11">
<
Rectangle.RenderTransform>
<
TransformGroup>
<
ScaleTransform/>
<
SkewTransform/>
<
RotateTransform Angle="45"/>
<
TranslateTransform/>
</
TransformGroup>
</
Rectangle.RenderTransform>
</
Rectangle>
<
Rectangle Height="27" Width="2" RenderTransformOrigin="0.5,0.5" Fill="#FF000000" Stroke="{x:Null}" Canvas.Left="24.666" Canvas.Top="11">
<
Rectangle.RenderTransform>
<
TransformGroup>
<
ScaleTransform/>
<
SkewTransform/>
<
RotateTransform Angle="135"/>
<
TranslateTransform/>
</
TransformGroup>
</
Rectangle.RenderTransform>
</
Rectangle>
</
Canvas>
</
Canvas>
</
Grid>
</
UserControl>


Building some scenery

We've got a couple more things to add to this scene to make it complete. A sky, for one, would be useful. After that, I'm going to add a couple of clouds, and a tree. For simplicity's sake, I'm not going to show you the progressive XAML for each step, instead, here's what our final scene looks like, as well as the XAML to create it:



<UserControl x:Class="SilverlightCarAnimation.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="400" Height="300" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d">
<
Grid x:Name="LayoutRoot" Background="White">
<
Rectangle Margin="0,0,0,84" Stroke="#FF000000" x:Name="Sky">
<
Rectangle.Fill>
<
LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<
GradientStop Color="#FF77C4C4"/>
<
GradientStop Color="#FFFFFFFF" Offset="1"/>
</
LinearGradientBrush>
</
Rectangle.Fill>
</
Rectangle>
<
Path Height="82" HorizontalAlignment="Right" VerticalAlignment="Top" Width="140" Fill="#FFFFFFFF" Stretch="Fill" Stroke="#FFCECECE" StrokeThickness="3" Data="M106,0.5 C112.0061,0.49999714 117.15929,4.1516743 119.36052,9.3559399 L119.37662,9.4078083 L120.74696,7.7469482 C123.37094,5.1229649 126.99594,3.4999971 131,3.499999 C136.00508,3.4999971 140.41788,6.0358849 143.02364,9.8929119 L143.02887,9.9025707 L145.35594,8.6394768 C147.0907,7.9057388 148.99797,7.4999971 151,7.4999943 C155.00406,7.4999971 158.62906,9.1229649 161.25305,11.746948 L162,12.652259 L162.74695,11.746948 C165.37094,9.1229649 168.99594,7.4999971 173,7.4999943 C180.00711,7.4999971 185.85335,12.470336 187.20541,19.077738 L187.39322,20.940781 L189.64406,21.639479 C193.54726,23.290394 196.57713,26.601799 197.84811,30.688139 L198.44806,34.656448 L200,34.499996 C208.00813,34.499996 214.5,40.991867 214.5,48.999996 C214.5,51.502537 213.86603,53.857006 212.74992,55.91156 L210.99924,58.375053 L212.10709,58.976372 C215.96411,61.582123 218.5,65.994919 218.5,71 C218.5,75.004066 216.87703,78.629066 214.25305,81.253052 L213.41562,81.943993 L215.02364,83.892914 C216.58708,86.20713 217.5,88.996956 217.5,92 C217.5,100.00813 211.00813,106.5 203,106.5 C197.74466,106.5 193.14232,103.70418 190.59921,99.518776 L190.28737,98.890793 L190.5,101 C190.5,109.00813 184.00813,115.5 176,115.5 C171.99594,115.5 168.37094,113.87703 165.74695,111.25304 L164.98895,110.33434 L164.40079,111.51878 C161.85768,115.70418 157.25534,118.5 152,118.5 C149.99797,118.5 148.0907,118.09425 146.35594,117.36052 L144.84227,116.53892 L144.25305,117.25304 C141.62906,119.87703 138.00406,121.5 134,121.5 C131.99797,121.5 130.0907,121.09426 128.35594,120.36052 L126.15287,119.16473 L124.10709,121.02363 C121.79287,122.58708 119.00305,123.5 116,123.5 C110.99492,123.5 106.58213,120.96411 103.97637,117.10709 L103.00845,115.32383 L101.27191,115.26254 L101.20541,115.92226 C99.85334,122.52966 94.007111,127.5 87,127.5 C78.991875,127.5 72.5,121.00813 72.5,113 L72.575935,112.24673 L72.488472,112.23186 L72.360519,112.64405 C70.159302,117.84832 65.006096,121.5 59,121.5 C54.995934,121.5 51.370934,119.87703 48.746952,117.25304 L47.686768,115.96809 L46.922256,116.20541 C45.97834,116.39857 45.001015,116.5 44,116.5 C37.993904,116.5 32.840702,112.84832 30.639482,107.64405 L29.883694,105.2093 L26.999996,105.5 C19.992887,105.5 14.14666,100.52966 12.79459,93.922256 L12.525257,91.250526 L12.077744,91.205414 C5.4703388,89.85334 0.5,84.007111 0.5,77 C0.5,71.994919 3.0358872,67.582123 6.8929138,64.976372 L8.70366,63.993526 L8.8091211,62.954491 L6.746954,61.253044 C4.1229677,58.629063 2.5,55.004063 2.500001,50.999996 C2.5,42.991867 8.9918709,36.499996 17,36.5 C18.001017,36.499996 18.97834,36.601433 19.922255,36.794582 L22.276466,37.525379 L22.639484,36.355953 C24.840702,31.151674 29.993904,27.499996 35.999996,27.499992 C37.001015,27.499996 37.97834,27.601433 38.922256,27.79459 L39.333214,27.922153 L38.639477,26.644054 C37.905743,24.909294 37.5,23.002029 37.5,21 C37.5,12.991868 43.991871,6.4999971 52,6.4999943 C60.008129,6.4999971 66.5,12.991868 66.5,21 L66.488525,21.113777 L67.688309,20.867964 L67.5,19 C67.5,10.991869 73.991875,4.4999971 82,4.499999 C85.503555,4.4999971 88.716888,5.7425823 91.223343,7.8110962 L92.618721,9.4228325 L92.639481,9.3559399 C94.840706,4.1516743 99.993904,0.49999714 106,0.5 z" x:Name="Cloud1" Margin="0,17,33,0"/>
<
Rectangle Height="84" x:Name="Road" VerticalAlignment="Bottom" Fill="#FF000000" Stroke="{x:Null}"/>
<
StackPanel Height="24" VerticalAlignment="Bottom" Orientation="Horizontal" Margin="0,0,-448,30" x:Name="LineStripe">
<
Rectangle Height="9" Width="40" Fill="#FFFFD300" Stroke="{x:Null}" Margin="0,0,20,0"/>
<
Rectangle Height="9" Width="40" Fill="#FFFFD300" Stroke="{x:Null}" Margin="0,0,20,0"/>
<
Rectangle Height="9" Width="40" Fill="#FFFFD300" Stroke="{x:Null}" Margin="0,0,20,0"/>
<
Rectangle Height="9" Width="40" Fill="#FFFFD300" Stroke="{x:Null}" Margin="0,0,20,0"/>
<
Rectangle Height="9" Width="40" Fill="#FFFFD300" Stroke="{x:Null}" Margin="0,0,20,0"/>
<
Rectangle Height="9" Width="40" Fill="#FFFFD300" Stroke="{x:Null}" Margin="0,0,20,0"/>
<
Rectangle Height="9" Width="40" Fill="#FFFFD300" Stroke="{x:Null}" Margin="0,0,20,0"/>
<
Rectangle Height="9" Width="40" Fill="#FFFFD300" Stroke="{x:Null}" Margin="0,0,20,0"/>
<
Rectangle Height="9" Width="40" Fill="#FFFFD300" Stroke="{x:Null}" Margin="0,0,20,0"/>
<
Rectangle Height="9" Width="40" Fill="#FFFFD300" Stroke="{x:Null}" Margin="0,0,20,0"/>
<
Rectangle Height="9" Width="40" Fill="#FFFFD300" Stroke="{x:Null}" Margin="0,0,20,0"/>
<
Rectangle Height="9" Width="40" Fill="#FFFFD300" Stroke="{x:Null}" Margin="0,0,20,0"/>
<
Rectangle Height="9" Width="40" Fill="#FFFFD300" Stroke="{x:Null}" Margin="0,0,20,0"/>
</
StackPanel>
<
Canvas HorizontalAlignment="Left" Margin="8,30,0,84" x:Name="Tree" Width="151">
<
Rectangle Height="104" Width="19" Fill="#FF6E4100" Stroke="#FF000000" StrokeThickness="3" Canvas.Left="66" Canvas.Top="82"/>
<
Path Width="151" Fill="#FF3FA33D" Stretch="Fill" Stroke="#FF000000" Data="M48,0 C56.284271,0 63.784271,3.3578644 69.213203,8.7867975 L72.336609,12.572392 L73.496292,11.902077 C77.628952,10.011786 81.991112,9 86.5,9.000001 C97.259842,9 107.18409,14.76185 115.14809,24.474262 L117.88708,28.136744 L119.32265,27.357546 C122.9118,25.839466 126.85786,25 131,25.000002 C147.56854,25 161,38.431458 161,55 C161,59.142136 160.16054,63.088203 158.64246,66.677353 L156.6917,70.271347 L160.04605,70.609497 C173.71654,73.406883 184,85.502525 184,100 C184,116.56854 170.56854,130 154,130 L150.82446,129.67987 L151,132 C151,148.56854 137.56854,162 121,162 C114.7868,162 109.01475,160.11121 104.22672,156.87646 L103.91651,156.62053 L100.62505,158.56071 C96.162949,160.7959 91.418785,162 86.5,162 C79.94162,162 73.69368,159.85936 68.010857,155.98825 L66.897095,155.12416 L64.773285,156.87646 C59.985252,160.11121 54.213203,162 48,162 C31.431458,162 18,148.56854 18.000002,132 C18,124.2335 20.951248,117.1563 25.793457,111.82867 L26.981792,110.69574 L23.953953,110.3905 C10.283459,107.59312 0,95.497475 0,81 C0,66.502525 10.283459,54.406883 23.953953,51.609493 L26.906376,51.311863 L26.786798,51.213203 C21.357864,45.784271 18,38.284271 18.000002,30.000002 C18,13.431458 31.431458,0 48,0 z" StrokeThickness="3" Height="113"/>
</
Canvas>
<
Canvas Height="112" Margin="49,0,150,24" x:Name="Car" VerticalAlignment="Bottom">
<
Canvas Height="79" x:Name="CarBody" Width="201">
<
Rectangle Height="40" Width="86" Canvas.Left="58.501" Fill="#FFFF0000" Stroke="{x:Null}"/>
<
Rectangle Fill="#FFFF0000" Stroke="{x:Null}" Height="40" Width="201" Canvas.Top="39" RadiusX="0" RadiusY="0"/>
<
Rectangle Height="27" Width="46" Canvas.Left="98.502" Canvas.Top="12" Stroke="{x:Null}" RadiusX="0" RadiusY="0">
<
Rectangle.Fill>
<
LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<
GradientStop Color="#FF188CB7" Offset="0"/>
<
GradientStop Color="#FFFFFFFF" Offset="1"/>
</
LinearGradientBrush>
</
Rectangle.Fill>
</
Rectangle>
</
Canvas>
<
Canvas Height="49" x:Name="BackWheel" Width="49" Canvas.Left="14.168" Canvas.Top="62.662">
<
Ellipse Height="49.333" Width="49.333" Fill="#FF434343" Stroke="{x:Null}"/>
<
Ellipse Height="27" Width="27" Canvas.Left="11.333" Canvas.Top="11.672" Fill="#FFBBB9B9" Stroke="{x:Null}"/>
<
Rectangle Height="27" Width="2" RenderTransformOrigin="0.5,0.5" Canvas.Left="24.333" Canvas.Top="11.339" Fill="#FF000000" Stroke="{x:Null}">
<
Rectangle.RenderTransform>
<
TransformGroup>
<
ScaleTransform/>
<
SkewTransform/>
<
RotateTransform Angle="45"/>
<
TranslateTransform/>
</
TransformGroup>
</
Rectangle.RenderTransform>
</
Rectangle>
<
Rectangle Height="27" Width="2" RenderTransformOrigin="0.5,0.5" Fill="#FF000000" Stroke="{x:Null}" Canvas.Left="24.333" Canvas.Top="11.339">
<
Rectangle.RenderTransform>
<
TransformGroup>
<
ScaleTransform/>
<
SkewTransform/>
<
RotateTransform Angle="135"/>
<
TranslateTransform/>
</
TransformGroup>
</
Rectangle.RenderTransform>
</
Rectangle>
</
Canvas>
<
Canvas Height="49" x:Name="FrontWheel" Width="49" Canvas.Left="143.168" Canvas.Top="63">
<
Ellipse Height="49.333" Width="49.333" Fill="#FF434343" Stroke="{x:Null}"/>
<
Ellipse Height="27" Width="27" Fill="#FFBBB9B9" Stroke="{x:Null}" Canvas.Left="11" Canvas.Top="11"/>
<
Rectangle Height="27" Width="2" RenderTransformOrigin="0.5,0.5" Fill="#FF000000" Stroke="{x:Null}" Canvas.Left="24.666" Canvas.Top="11">
<
Rectangle.RenderTransform>
<
TransformGroup>
<
ScaleTransform/>
<
SkewTransform/>
<
RotateTransform Angle="45"/>
<
TranslateTransform/>
</
TransformGroup>
</
Rectangle.RenderTransform>
</
Rectangle>
<
Rectangle Height="27" Width="2" RenderTransformOrigin="0.5,0.5" Fill="#FF000000" Stroke="{x:Null}" Canvas.Left="24.666" Canvas.Top="11">
<
Rectangle.RenderTransform>
<
TransformGroup>
<
ScaleTransform/>
<
SkewTransform/>
<
RotateTransform Angle="135"/>
<
TranslateTransform/>
</
TransformGroup>
</
Rectangle.RenderTransform>
</
Rectangle>
</
Canvas>
</
Canvas>
</
Grid>
</
UserControl>


Time to start animating

Now that we have all of our XAML elements defined, we can start animating them. The first thing I have decided is that my driving animation will take 15 seconds. This should give me a good amount of time to see the car moving, and get some trees and clouds moving as well. First, you want to actually create the animation, called a StoryBoard. We do this by clicking the "+" symbol on the Objects and Timeline panel.



After you name your timeline (I called mine "Driving"), Blend will be in "recording" mode. What this basically means is that it will be recording the changes you make as part of the animation. You can turn recording on and off by clicking the red circle at the top of the window.

Making the wheels spin

Select the "BackWheel" element from our list of Objects, and click the "Record KeyFrame" button. If you're not familiar with this button, it looks like this:



Creating a keyframe at Zero seconds basically means that this is the position we want this wheel to start at. Next, move the yellow vertical time marker to the "1" position. This is one second of animation time. Here, click the KeyFrame button again, and then change the RenderTransform angle to 360 degrees. This can be found in the Properties Tab, under the Transform section. After that, click the Play button. (It's right above the KeyFrame button.) You should see your tire spin one revolution in 1 second. Now make the same animation on the FrontWheel element. You've now got two moving wheels on your car. But the car itself doesn't move yet.

To move the car, we need to select the "Car" object from our list. (Make sure the yellow time slider is back to zero, too.) We want it to move across the screen from left to right. So, create the initial KeyFrame at zero, and then move the slider out to 15 seconds. Here, drag the car to a position off of the right side of the screen. You're not going to be able to drag it perfectly horizontal, so just make your best effort. We'll clean up the XAML to correct this. In your XAML, look near the top for a block that looks like this:

    <UserControl.Resources>
<
Storyboard x:Name="Driving">
<
DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="BackWheel" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[2].(RotateTransform.Angle)">
<
SplineDoubleKeyFrame KeyTime="00:00:00" Value="0"/>
<
SplineDoubleKeyFrame KeyTime="00:00:01" Value="360"/>
</
DoubleAnimationUsingKeyFrames>
<
DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="FrontWheel" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[2].(RotateTransform.Angle)">
<
SplineDoubleKeyFrame KeyTime="00:00:00" Value="0"/>
<
SplineDoubleKeyFrame KeyTime="00:00:01" Value="360"/>
</
DoubleAnimationUsingKeyFrames>
<
DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="Car" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.X)">
<
SplineDoubleKeyFrame KeyTime="00:00:00" Value="0"/>
<
SplineDoubleKeyFrame KeyTime="00:00:15" Value="500"/>
</
DoubleAnimationUsingKeyFrames>
<
DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="Car" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.Y)">
<
SplineDoubleKeyFrame KeyTime="00:00:00" Value="0"/>
<
SplineDoubleKeyFrame KeyTime="00:00:15" Value="-3"/>
</
DoubleAnimationUsingKeyFrames>
</
Storyboard>
</
UserControl.Resources>


You can see that two of the <DoubleAnimationUsingKeyFrames> blocks reference "Car" at their Target. One of them is for the X axis, and the other is for the Y axis. Since we want our car to move only on the X axis, you can completely delete the Y axis block. My X value is 500, but you can make it travel faster or slower by changing this number.

If you click the Play button now, your car should move down the road, tires a-spinning. What you may notice, however, is that the tires only spin for the first second of animation, and then they stop. That's because we haven't told them to continue repeating their animation forever. The easiest way to do this is just to edit the XAML, but I'm working in Expression Blend today, so I'll show you how to do it with just your mouse. If you expand the arrows next to "BackWheel" completely, you will find an "Angle" attribute listed there. Here's a visual:



If you right-click on this "Angle" attribute, it allows you to "Edit Repeat Count." You can specify any integer value in this box, but there's also an infinity button to the right of the box. This means that we want this animation to continue on for as long as our animation is going. Set this value to infinity for both the front and back wheels of our car. While this change will not be reflected when you click the "Play" button, when you build and run the application (F5), your tires will continue to spin. (Animation not starting? That's because we need to tell the application to start it. In your XAML code-behind, add one line to your startup method:

Driving.Begin();


So there we go. We've got a moving car.

Animating the other stuff

The other things I wanted to animate in this little movie are the lines on the road, the tree, and the cloud. I'm going to be using the exact same methods that I used for moving the car, just in the other direction. In order to be appropriate in speed, the tree and the lines will have to move the same speed as the car. This should almost give the effect that there is a cameraman that isn't quite keeping up with the moving car. Once we get everything moving appropriately, we end up with some XAML that looks something like this:

<UserControl x:Class="SilverlightCarAnimation.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="400" Height="300" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d">
<
UserControl.Resources>
<
Storyboard x:Name="Driving">
<
DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="BackWheel" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[2].(RotateTransform.Angle)" RepeatBehavior="Forever">
<
SplineDoubleKeyFrame KeyTime="00:00:00" Value="0"/>
<
SplineDoubleKeyFrame KeyTime="00:00:01" Value="360"/>
</
DoubleAnimationUsingKeyFrames>
<
DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="FrontWheel" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[2].(RotateTransform.Angle)" RepeatBehavior="Forever">
<
SplineDoubleKeyFrame KeyTime="00:00:00" Value="0"/>
<
SplineDoubleKeyFrame KeyTime="00:00:01" Value="360"/>
</
DoubleAnimationUsingKeyFrames>
<
DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="Car" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.X)">
<
SplineDoubleKeyFrame KeyTime="00:00:00" Value="0"/>
<
SplineDoubleKeyFrame KeyTime="00:00:15" Value="500"/>
</
DoubleAnimationUsingKeyFrames>
<
DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="Car" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.Y)">
<
SplineDoubleKeyFrame KeyTime="00:00:00" Value="0"/>
<
SplineDoubleKeyFrame KeyTime="00:00:15" Value="-3"/>
</
DoubleAnimationUsingKeyFrames>
<
DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="LineStripe" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.X)">
<
SplineDoubleKeyFrame KeyTime="00:00:00" Value="0"/>
<
SplineDoubleKeyFrame KeyTime="00:00:15" Value="-500"/>
</
DoubleAnimationUsingKeyFrames>
<
DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="Tree" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.X)">
<
SplineDoubleKeyFrame KeyTime="00:00:00" Value="0"/>
<
SplineDoubleKeyFrame KeyTime="00:00:15" Value="-500"/>
</
DoubleAnimationUsingKeyFrames>
<
DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="Cloud" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.X)">
<
SplineDoubleKeyFrame KeyTime="00:00:00" Value="0"/>
<
SplineDoubleKeyFrame KeyTime="00:00:15" Value="-147"/>
</
DoubleAnimationUsingKeyFrames>
<
DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="Cloud" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.Y)">
<
SplineDoubleKeyFrame KeyTime="00:00:00" Value="0"/>
<
SplineDoubleKeyFrame KeyTime="00:00:15" Value="11"/>
</
DoubleAnimationUsingKeyFrames>
</
Storyboard>
</
UserControl.Resources>
<
Grid x:Name="LayoutRoot" Background="White">
<
Rectangle Margin="0,0,0,84" Stroke="#FF000000" x:Name="Sky">
<
Rectangle.Fill>
<
LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<
GradientStop Color="#FF77C4C4"/>
<
GradientStop Color="#FFFFFFFF" Offset="1"/>
</
LinearGradientBrush>
</
Rectangle.Fill>
</
Rectangle>
<
Path Height="82" HorizontalAlignment="Right" VerticalAlignment="Top" Width="140" Fill="#FFFFFFFF" Stretch="Fill" Stroke="#FFCECECE" StrokeThickness="3" Data="M106,0.5 C112.0061,0.49999714 117.15929,4.1516743 119.36052,9.3559399 L119.37662,9.4078083 L120.74696,7.7469482 C123.37094,5.1229649 126.99594,3.4999971 131,3.499999 C136.00508,3.4999971 140.41788,6.0358849 143.02364,9.8929119 L143.02887,9.9025707 L145.35594,8.6394768 C147.0907,7.9057388 148.99797,7.4999971 151,7.4999943 C155.00406,7.4999971 158.62906,9.1229649 161.25305,11.746948 L162,12.652259 L162.74695,11.746948 C165.37094,9.1229649 168.99594,7.4999971 173,7.4999943 C180.00711,7.4999971 185.85335,12.470336 187.20541,19.077738 L187.39322,20.940781 L189.64406,21.639479 C193.54726,23.290394 196.57713,26.601799 197.84811,30.688139 L198.44806,34.656448 L200,34.499996 C208.00813,34.499996 214.5,40.991867 214.5,48.999996 C214.5,51.502537 213.86603,53.857006 212.74992,55.91156 L210.99924,58.375053 L212.10709,58.976372 C215.96411,61.582123 218.5,65.994919 218.5,71 C218.5,75.004066 216.87703,78.629066 214.25305,81.253052 L213.41562,81.943993 L215.02364,83.892914 C216.58708,86.20713 217.5,88.996956 217.5,92 C217.5,100.00813 211.00813,106.5 203,106.5 C197.74466,106.5 193.14232,103.70418 190.59921,99.518776 L190.28737,98.890793 L190.5,101 C190.5,109.00813 184.00813,115.5 176,115.5 C171.99594,115.5 168.37094,113.87703 165.74695,111.25304 L164.98895,110.33434 L164.40079,111.51878 C161.85768,115.70418 157.25534,118.5 152,118.5 C149.99797,118.5 148.0907,118.09425 146.35594,117.36052 L144.84227,116.53892 L144.25305,117.25304 C141.62906,119.87703 138.00406,121.5 134,121.5 C131.99797,121.5 130.0907,121.09426 128.35594,120.36052 L126.15287,119.16473 L124.10709,121.02363 C121.79287,122.58708 119.00305,123.5 116,123.5 C110.99492,123.5 106.58213,120.96411 103.97637,117.10709 L103.00845,115.32383 L101.27191,115.26254 L101.20541,115.92226 C99.85334,122.52966 94.007111,127.5 87,127.5 C78.991875,127.5 72.5,121.00813 72.5,113 L72.575935,112.24673 L72.488472,112.23186 L72.360519,112.64405 C70.159302,117.84832 65.006096,121.5 59,121.5 C54.995934,121.5 51.370934,119.87703 48.746952,117.25304 L47.686768,115.96809 L46.922256,116.20541 C45.97834,116.39857 45.001015,116.5 44,116.5 C37.993904,116.5 32.840702,112.84832 30.639482,107.64405 L29.883694,105.2093 L26.999996,105.5 C19.992887,105.5 14.14666,100.52966 12.79459,93.922256 L12.525257,91.250526 L12.077744,91.205414 C5.4703388,89.85334 0.5,84.007111 0.5,77 C0.5,71.994919 3.0358872,67.582123 6.8929138,64.976372 L8.70366,63.993526 L8.8091211,62.954491 L6.746954,61.253044 C4.1229677,58.629063 2.5,55.004063 2.500001,50.999996 C2.5,42.991867 8.9918709,36.499996 17,36.5 C18.001017,36.499996 18.97834,36.601433 19.922255,36.794582 L22.276466,37.525379 L22.639484,36.355953 C24.840702,31.151674 29.993904,27.499996 35.999996,27.499992 C37.001015,27.499996 37.97834,27.601433 38.922256,27.79459 L39.333214,27.922153 L38.639477,26.644054 C37.905743,24.909294 37.5,23.002029 37.5,21 C37.5,12.991868 43.991871,6.4999971 52,6.4999943 C60.008129,6.4999971 66.5,12.991868 66.5,21 L66.488525,21.113777 L67.688309,20.867964 L67.5,19 C67.5,10.991869 73.991875,4.4999971 82,4.499999 C85.503555,4.4999971 88.716888,5.7425823 91.223343,7.8110962 L92.618721,9.4228325 L92.639481,9.3559399 C94.840706,4.1516743 99.993904,0.49999714 106,0.5 z" x:Name="Cloud" Margin="0,17,33,0" RenderTransformOrigin="0.5,0.5">
<
Path.RenderTransform>
<
TransformGroup>
<
ScaleTransform/>
<
SkewTransform/>
<
RotateTransform/>
<
TranslateTransform/>
</
TransformGroup>
</
Path.RenderTransform>
</
Path>
<
Rectangle Height="84" x:Name="Road" VerticalAlignment="Bottom" Fill="#FF000000" Stroke="{x:Null}"/>
<
StackPanel Height="24" VerticalAlignment="Bottom" Orientation="Horizontal" Margin="0,0,-608,30" x:Name="LineStripe" RenderTransformOrigin="0.5,0.5">
<
StackPanel.RenderTransform>
<
TransformGroup>
<
ScaleTransform/>
<
SkewTransform/>
<
RotateTransform/>
<
TranslateTransform/>
</
TransformGroup>
</
StackPanel.RenderTransform>
<
Rectangle Height="9" Width="40" Fill="#FFFFD300" Stroke="{x:Null}" Margin="0,0,20,0"/>
<
Rectangle Height="9" Width="40" Fill="#FFFFD300" Stroke="{x:Null}" Margin="0,0,20,0"/>
<
Rectangle Height="9" Width="40" Fill="#FFFFD300" Stroke="{x:Null}" Margin="0,0,20,0"/>
<
Rectangle Height="9" Width="40" Fill="#FFFFD300" Stroke="{x:Null}" Margin="0,0,20,0"/>
<
Rectangle Height="9" Width="40" Fill="#FFFFD300" Stroke="{x:Null}" Margin="0,0,20,0"/>
<
Rectangle Height="9" Width="40" Fill="#FFFFD300" Stroke="{x:Null}" Margin="0,0,20,0"/>
<
Rectangle Height="9" Width="40" Fill="#FFFFD300" Stroke="{x:Null}" Margin="0,0,20,0"/>
<
Rectangle Height="9" Width="40" Fill="#FFFFD300" Stroke="{x:Null}" Margin="0,0,20,0"/>
<
Rectangle Height="9" Width="40" Fill="#FFFFD300" Stroke="{x:Null}" Margin="0,0,20,0"/>
<
Rectangle Height="9" Width="40" Fill="#FFFFD300" Stroke="{x:Null}" Margin="0,0,20,0"/>
<
Rectangle Height="9" Width="40" Fill="#FFFFD300" Stroke="{x:Null}" Margin="0,0,20,0"/>
<
Rectangle Height="9" Width="40" Fill="#FFFFD300" Stroke="{x:Null}" Margin="0,0,20,0"/>
<
Rectangle Height="9" Width="40" Fill="#FFFFD300" Stroke="{x:Null}" Margin="0,0,20,0"/>
<
Rectangle Height="9" Width="40" Fill="#FFFFD300" Stroke="{x:Null}" Margin="0,0,20,0"/>
<
Rectangle Height="9" Width="40" Fill="#FFFFD300" Stroke="{x:Null}" Margin="0,0,20,0"/>
<
Rectangle Height="9" Width="40" Fill="#FFFFD300" Stroke="{x:Null}" Margin="0,0,20,0"/>
<
Rectangle Height="9" Width="40" Fill="#FFFFD300" Stroke="{x:Null}" Margin="0,0,20,0"/>
</
StackPanel>
<
Canvas HorizontalAlignment="Left" Margin="8,30,0,84" x:Name="Tree" Width="151" RenderTransformOrigin="0.5,0.5">
<
Canvas.RenderTransform>
<
TransformGroup>
<
ScaleTransform/>
<
SkewTransform/>
<
RotateTransform/>
<
TranslateTransform/>
</
TransformGroup>
</
Canvas.RenderTransform>
<
Rectangle Height="104" Width="19" Fill="#FF6E4100" Stroke="#FF000000" StrokeThickness="3" Canvas.Left="66" Canvas.Top="82"/>
<
Path Width="151" Fill="#FF3FA33D" Stretch="Fill" Stroke="#FF000000