Sunday, August 8, 2010

Pass Parameters from ASP.NET to Silverlight

It is very straight forward to pass parameters from an asp.net page to a silverlight application. In my case, I had an asp.net application and wanted to integrate silverlight in one of my pages. I needed to pass some values such as ID and user role. Let's look at some code. First, I created a class, in my Silverlight application, to encapsulate my values. The constructor of my class takes in an IDictionary.

public class Dashboard
{
private string _ParishId;
private bool _IsReadOnly;
private bool _IsGSTSOrSuperParish;
internal Dashboard(IDictionary<string, string> parameters)
{
_ParishId = parameters["pid"];
_IsReadOnly = Convert.ToBoolean(parameters["iro"]);
_IsGSTSOrSuperParish = Convert.ToBoolean(parameters["igs"]);
}
public string ParishId
{
get { return _ParishId; }
}
public bool IsReadOnly
{
get { return _IsReadOnly; }
}
public bool IsGSTSOrSuperParish
{
get { return _IsGSTSOrSuperParish; }
}
}

Now, in the App.xaml application start up event i new up an instance of the class i created passing in the initParams of the StartupEventArgs.
Below is the markup needed for this.

private void Application_Startup(object sender, StartupEventArgs e)
{
Dashboard db = new Dashboard(e.InitParams);
this.RootVisual = new MainPage(db.ParishId,db.IsReadOnly,db.IsGSTSOrSuperParish);
}

Now that i have things set up for the mainPage.xaml, all i have to do is get those values on mainPage. And that's it on the silverlight side. I still have to set up how to get the actual values from ASP.NET. Below is the code for the mainPage.xaml.

public partial class MainPage : UserControl
{
private string _id;
private bool _IsReadOnly;
private bool _IsGSTSOrSuperParish;
public MainPage(string id, bool isReadOnly, bool isGSTSOrSuperParish)
{
InitializeComponent();
_id = id;
_IsReadOnly = isReadOnly;
_IsGSTSOrSuperParish = isGSTSOrSuperParish;
Loaded += new RoutedEventHandler(Page_Loaded);
}
...

Finally, I added a parameter (to the asp.net user control where my silverlight reference is) and called it initParam and gave it an ID of 'prm' so I can use it in code behind of this user control. First, let's take a look at the HTML.

<div id="silverlightControlHost">
<object id="tempSil" data="data:application/x-silverlight-2," type="application/x-silverlight-2" width="100%" height="100%">
<param name="source" value="../ClientBin/DB.Silverlight.xap"/>
<param name="onError" value="onSilverlightError" />
<param name="background" value="white" />
<param name="minRuntimeVersion" value="4.0.50401.0" />
<param name="autoUpgrade" value="true" />
<param name="initParams" runat="server" id="prm" />
<param name="windowless" value="true" />
 
<a href="http://go.microsoft.com/fwlink/?LinkID=149156&v=4.0.50401.0" style="text-decoration:none">
<img src="http://go.microsoft.com/fwlink/?LinkId=161376" alt="Get Microsoft Silverlight" style="border-style:none"/>
</a>
</object><iframe id="_sl_historyFrame" style="visibility:hidden;height:0px;width:0px;border:0px"></iframe></div>

And here is the code behind and is pretty straight forward..just passing in values.

public partial class UserControls_VE_db : System.Web.UI.UserControl
{
protected void Page_Load(object sender, EventArgs e)
{
using (var proxy = new CapitalAreaService.ServiceClient())
{
this.prm.Attributes["value"] = string.Format("pid={0},iro={1},igs={2}", 
proxy.GetParishId(SystemRole.GetParisAbbreviation()),SystemRole.IsReadOnly(),
SystemRole.IsGSTSOrSuperParish());
}
}
}

That's all it take to pass values from ASP.NET to Silverlight as needed.

Saturday, August 7, 2010

Silverlight Form Flip Animation

In my recent project I had a datagrid and needed to have a search option where the user can do a basic search or an advanced search, so I thought about implementing this using a form flip animation. Basic search on one side and advanced search on the other. This saved me space and all i had to do is add a search button above the grid and upon click pop up the search form. Below is a screen shots of what this looks like.



When the user clicks the search icon on the first screen shot, it pops up the basic search form and upon clicking Advanced search the form flips and shows the advanced search form. Now, let's look at some code.

First I am going to add a stack panel for the search icon then right underneath that I am addding a popup control in mainpage.xaml.

<StackPanel Orientation="Horizontal">
<Button Height="23"  
HorizontalAlignment="Left"   
Name="btnLaunchSearch" VerticalAlignment="Top"  
Click="btnLaunchSearch_Click">
<Button.Content>
<Image Source="./images/search.jpg"   ToolTipService.ToolTip="Search" />
</Button.Content>
</Button>
</StackPanel>
<Popup x:Name="SearchFormPopup" VerticalOffset="100" HorizontalOffset="100">
</Popup>
Now, I am going to add a view for the search form. I created a folder called UserControls then added a Search.xaml user control. I put the styles in a separate file - used a resource dictionary.

<UserControl x:Class="DB.Silverlight.UserControls.Search"
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:DesignHeight="200" d:DesignWidth="500">
<UserControl.Resources>
<ResourceDictionary x:Key="SD">
<ResourceDictionary.MergedDictionaries >
<ResourceDictionary  Source="../Dictionaries/SearchDictionary.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary> 
<Storyboard x:Name="StartingPosition">
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00"
Storyboard.TargetName="FormBack"
Storyboard.TargetProperty="(UIElement.RenderTransform).TransformGroup.Children[3].(TranslateTransform.X)">
<SplineDoubleKeyFrame KeyTime="00:00:00"
Value="5000" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Name="AdvancedSearch">
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00"
Storyboard.TargetName="PanelProjection"
Storyboard.TargetProperty="RotationY">
<SplineDoubleKeyFrame KeyTime="00:00:00"
Value="0" />
<SplineDoubleKeyFrame KeyTime="00:00:01"
Value="90" />
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00"
Storyboard.TargetName="FormFront"
Storyboard.TargetProperty="(UIElement.RenderTransform).TransformGroup.Children[3].(TranslateTransform.X)">
<SplineDoubleKeyFrame KeyTime="00:00:00"
Value="0" />
<SplineDoubleKeyFrame KeyTime="00:00:01"
Value="0" />
<SplineDoubleKeyFrame KeyTime="00:00:01.01"
Value="5000" />
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00"
Storyboard.TargetName="FormBack"
Storyboard.TargetProperty="(UIElement.RenderTransform).TransformGroup.Children[3].(TranslateTransform.X)">
<SplineDoubleKeyFrame KeyTime="00:00:00"
Value="5000" />
<SplineDoubleKeyFrame KeyTime="00:00:00.99"
Value="5000" />
<SplineDoubleKeyFrame KeyTime="00:00:01"
Value="0" />
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00"
Storyboard.TargetName="BackPanelProjection"
Storyboard.TargetProperty="RotationY">
<SplineDoubleKeyFrame KeyTime="00:00:01"
Value="270" />
<SplineDoubleKeyFrame KeyTime="00:00:02"
Value="360" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Name="BasicSearch">
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00"
Storyboard.TargetName="FormBack"
Storyboard.TargetProperty="(UIElement.RenderTransform).TransformGroup.Children[3].(TranslateTransform.X)">
<SplineDoubleKeyFrame KeyTime="00:00:00"
Value="0" />
<SplineDoubleKeyFrame KeyTime="00:00:01"
Value="0" />
<SplineDoubleKeyFrame KeyTime="00:00:01.01"
Value="5000" />
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00"
Storyboard.TargetName="BackPanelProjection"
Storyboard.TargetProperty="RotationY">
<SplineDoubleKeyFrame KeyTime="00:00:00"
Value="0" />
<SplineDoubleKeyFrame KeyTime="00:00:01"
Value="-90" />
</DoubleAnimationUsingKeyFrames>
 
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00"
Storyboard.TargetName="PanelProjection"
Storyboard.TargetProperty="RotationY">
<SplineDoubleKeyFrame KeyTime="00:00:01"
Value="-270" />
<SplineDoubleKeyFrame KeyTime="00:00:02"
Value="-360" />
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00"
Storyboard.TargetName="FormFront"
Storyboard.TargetProperty="(UIElement.RenderTransform).TransformGroup.Children[3].(TranslateTransform.X)">
<SplineDoubleKeyFrame KeyTime="00:00:00"
Value="5000" />
<SplineDoubleKeyFrame KeyTime="00:00:00.99"
Value="5000" />
<SplineDoubleKeyFrame KeyTime="00:00:01"
Value="0" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</UserControl.Resources>
<Canvas>
<StackPanel x:Name="FormFront">
<StackPanel.RenderTransform>
<TransformGroup>
<ScaleTransform />
<SkewTransform />
<RotateTransform />
<TranslateTransform />
</TransformGroup>
</StackPanel.RenderTransform>
<StackPanel.Projection>
<PlaneProjection x:Name="PanelProjection"
RotationX="0"
RotationY="0"
RotationZ="0" />
</StackPanel.Projection>
<Border Name="mainBorder" Style="{StaticResource ModalDialogBorder}" > 
<Grid x:Name="LayoutRoot" Width="399" Margin="10,10,10,10">
<Grid.RowDefinitions>
<RowDefinition Height="70" />
<RowDefinition Height="1*" />
<RowDefinition Height="1*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Grid.Row="0" >
<TextBlock Margin="20" Text="Note:Search by user's first name, last name, or by business name."></TextBlock>
</StackPanel>
<TextBox TabIndex="0" Name="txtSearch" Grid.Row="1" Height="30" Width="260" Margin="0,5,0,20"/>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Grid.Row="2" Margin="89,0,89,55">
<Button x:Name="SearchButton" Height="30" Width="100"  Content="Search"/>
<Button x:Name="CancelButton" Height="30" Width="100"  Content="Cancel"/>
</StackPanel>
<Button x:Name="Advanced"
Content="Advanced Search"
HorizontalAlignment="Left"
VerticalAlignment="Bottom"
Width="Auto"
Background="DarkGreen"
FontFamily="Georgia"
FontSize="10"
Grid.Row="5"
Margin="10,0"
Grid.Column="0" />
</Grid>
</Border>
</StackPanel>
<StackPanel x:Name="FormBack">
<StackPanel.RenderTransform>
<TransformGroup>
<ScaleTransform />
<SkewTransform />
<RotateTransform />
<TranslateTransform />
</TransformGroup>
</StackPanel.RenderTransform>
<StackPanel.Projection>
<PlaneProjection x:Name="BackPanelProjection"
RotationX="0"
RotationY="0"
RotationZ="0" />
</StackPanel.Projection>
<Border Style="{StaticResource ModalDialogBorder}" >
<Grid x:Name="theForm" Width="399">
<Grid.RowDefinitions>
<RowDefinition Height="1*" />
<RowDefinition Height="1*" />
<RowDefinition Height="1*" />
<RowDefinition Height="1*" />
<RowDefinition Height="1*" />
<RowDefinition Height="1*" />
<RowDefinition Height="1*" />
<RowDefinition Height="1*" />
<RowDefinition Height="1*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="130" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<TextBlock Text="Placard Type"
Grid.Row="0"
Grid.Column="0"
Style="{StaticResource Prompt}" />
<ComboBox  
Name="cbPlacardType"   
Grid.Row="0"
Grid.Column="1" 
Style="{StaticResource EntryDrop}" />
<TextBlock Text="Status"
Grid.Row="1"
Grid.Column="0"
Style="{StaticResource Prompt}" />
<ComboBox  
Name="cbStatus"   
Grid.Row="1"
Grid.Column="1" 
Style="{StaticResource EntryDrop}" />
<TextBlock Text="Date Entered"
Grid.Row="2"
Grid.Column="0"
Style="{StaticResource Prompt}" />
 
 
<TextBlock Text="From" Margin="8,0,231,0"
Grid.Row="2"
Grid.Column="1" Width="30" />  
<TextBox x:Name="txtFrom"
Grid.Row="2"
Grid.Column="1"
Width="80" Margin="37,0,162,0" />
<TextBlock Text="To" Width="30"
Grid.Row="2"
Grid.Column="1" Margin="116,0,123,0" />
<TextBox x:Name="txtTo"
Grid.Row="2"
Grid.Column="1"
Width="80" Margin="150,0,49,0" />
<TextBlock Text="Placard Id"
Grid.Row="3"
Grid.Column="0"
Style="{StaticResource Prompt}" />
<TextBox x:Name="txtPlacardId"
Grid.Row="3"
Grid.Column="1"
Style="{StaticResource Entry}"  />
<TextBlock Text="Business Name"
Grid.Row="4"
Grid.Column="0"
Style="{StaticResource Prompt}" />
<TextBox x:Name="txtBusinessName"
Grid.Row="4"
Grid.Column="1"
Style="{StaticResource Entry}" />
<TextBlock Text="Contact First Name"
Grid.Row="5"
Grid.Column="0"
Style="{StaticResource Prompt}" />
<TextBox x:Name="txtContactFirstName"
Grid.Row="5"
Grid.Column="1"
Style="{StaticResource Entry}" />
<TextBlock Text="Contact Last Name"
Grid.Row="6"
Grid.Column="0"
Style="{StaticResource Prompt}" />
<TextBox x:Name="txtContactLastName"
Grid.Row="6"
Grid.Column="1"
Style="{StaticResource Entry}" />
<Button x:Name="btnAdvancedSearch"
Content=" Search "
HorizontalAlignment="Left"
VerticalAlignment="Bottom"
Width="95"
Background="DarkGreen"
FontFamily="Georgia"
FontSize="12"
Grid.Row="7"
Margin="30,0,0,10" />
<Button x:Name="btnAdvancedSearchCancel"
Content=" Cancel "
HorizontalAlignment="Left"
VerticalAlignment="Bottom"
Width="95"
Background="DarkGreen"
FontFamily="Georgia"
FontSize="12"
Grid.Row="7"
Margin="10,10"
Grid.Column="1" />
<Button x:Name="Basic"
Content="Switch to Basic Search"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Width="112"
Background="DarkGreen"
FontFamily="Georgia"
FontSize="10"
Grid.Row="8"
Margin="0,0,12,5"
Grid.Column="1" />
</Grid>
</Border>
</StackPanel>
</Canvas>
</UserControl>

Now, in the code behind of the search form I added the mecanics to do the flip animation.

public partial class Search : UserControl
{
public Search()
{
InitializeComponent();
Loaded += new RoutedEventHandler(Search_Loaded);
Advanced.Click += new RoutedEventHandler(Advanced_Click);
Basic.Click += new RoutedEventHandler(Basic_Click);
}
void Search_Loaded(object sender, RoutedEventArgs e)
{
StartingPosition.Begin();
//TODO: Load comboboxes..
}
void Advanced_Click(object sender, RoutedEventArgs e)
{
AdvancedSearch.Begin();
}
void Basic_Click(object sender, RoutedEventArgs e)
{
BasicSearch.Begin();
}
}

All i have left now is the code to handle the search button in mainpage.xaml and it's pretty simple - i just have to new up an instance of the popup control that i created in mainpage and also new up an instance of the search form user control and ofcourse code to handle my data. Since my data code involves WCF and is a bit lengthy I am just gonna leave that up to you to add your own data handling here. Below is the code needed in mainpage.

private void btnLaunchSearch_Click(object sender, RoutedEventArgs e)
{
SearchFormPopup = new Popup();
form = new Search();
form.CancelButton.Click += new RoutedEventHandler(CancelButton_Click);
form.SearchButton.Click += new RoutedEventHandler(SearchButton_Click);
form.btnAdvancedSearchCancel.Click += new RoutedEventHandler(CancelButton_Click);
form.btnAdvancedSearch.Click += new RoutedEventHandler(AdvancedSearchButton_Click);
SearchFormPopup.Child = form;
SearchFormPopup.IsOpen = true;
}
private void AdvancedSearchButton_Click(object sender, RoutedEventArgs e)
{
LoadFilterdDataboardDataAdvancedSearch();
SearchFormPopup.IsOpen = false;
}
private void CancelButton_Click(object sender, RoutedEventArgs e)
{
SearchFormPopup.IsOpen = false;
}
private void SearchButton_Click(object sender, RoutedEventArgs e)
{
LoadFilteredDashBoardDataSearch();
SearchFormPopup.IsOpen = false;
}

And that's it. If you have any comments or better ways to do this, let me know.

Sunday, July 25, 2010

Silverlight 4.0 Datagrid printing

Recently I had a requirement to print data from Silverlight datagrid which i developed and integrated it in an asp.net project to serve as a dashboard for client administrative tasks. There is one feature that is not supported in Silverlight 4.0 and it's the ability to specify the print preference option in code. I needed this feature because the dadagrid is quite long and needs to be printed as landscape. Anyhow, the printing is straight forward, we just need to new up a silverlight user control to server as the header for each page being printed and we can also do the same if we need a footer. Below is the header user control i created. We need to add this to our silverlight project.

<UserControl x:Class="DB.Silverlight.Views.ApplicationPrintHeadView"
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:DesignHeight="100" d:DesignWidth="422" Width="1100">
<Grid Margin="20,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="100" />
<ColumnDefinition Width="100" />
<ColumnDefinition Width="130" />
<ColumnDefinition Width="30" />
<ColumnDefinition Width="90" />
<ColumnDefinition Width="90" />
<ColumnDefinition Width="120" />
<ColumnDefinition Width="400" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition  ></RowDefinition>
</Grid.RowDefinitions>
<TextBlock Grid.Column="0" Name="h1" >First Name</TextBlock>
<TextBlock Grid.Column="1" Name="h2" >Last Name</TextBlock>
<TextBlock Grid.Column="2" Name="h3">Phone</TextBlock>
<TextBlock Grid.Column="3" Name="h4">Business Name</TextBlock>
<TextBlock Grid.Column="4" >Tier</TextBlock>
<TextBlock Grid.Column="5" >Status</TextBlock>
<TextBlock Grid.Column="6" >Date Applied</TextBlock>
<TextBlock Grid.Column="7" >Number Approved</TextBlock>
<TextBlock Grid.Column="8" >Industry</TextBlock>
<Border x:Name="TimelineBorder"  
Background="LightGray"  
BorderBrush="Black"  
BorderThickness="1"
Margin="0,20,140,0" VerticalAlignment="Top" Grid.ColumnSpan="9">
</Border>
</Grid>
</UserControl>
Now that we have the page header set up, we're gonna use that user control in our code along with the printing procedure. In my case i have a datagrid with paging, so i want to print all the data and not just the data displayed on the screen. To do that I did a small workaround, basically setting the page size to the Total Item Count before printing and set the size back to its original state. I dont like this work around but works fine for now. This will alllow us to print every row returned by our datagrid. Furthemore, I created another user control that's gonna act as my data container.
<UserControl x:Class="DB.Silverlight.Views.ApplicationPrintView"
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:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
xmlns:converter="clr-namespace:DB.Silverlight"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="422" Width="1100">
<UserControl.Resources>
<converter:ApplicationStatusConverter x:Key="ApplicationStatusConverter" />
</UserControl.Resources>
<Grid Margin="20,0">
<StackPanel Name="appData" Orientation="Horizontal">
<TextBlock Width="100" Text="{Binding FirstName}" />
<TextBlock Width="100" Text="{Binding LastName}" />
<TextBlock Width="100" Text="{Binding PhoneNumber}" />
<TextBlock Width="130" Text="{Binding BusinessName}" TextWrapping="Wrap" />
<TextBlock Width="30" Text="{Binding TierCode}" />
<TextBlock Width="90"  Text="{Binding applicationStatus,Converter={StaticResource ApplicationStatusConverter}}" />
<TextBlock Width="90" Text="{Binding DateApplied}" />
<TextBlock Width="100" Text="{Binding NumberApproved}" TextAlignment="Center"  />
<TextBlock Width="290" Text="{Binding IndustryName}" TextWrapping="Wrap" />
</StackPanel>
</Grid>
</UserControl>
ApplicationPrintView is the container that binds all fields of my datagrid(which lives in mainpage.xaml) by assigning the binding property which ties it to the PagedCollectionView. And below is the button click event that fires printing.
private void PrintGrid_Click(object sender, RoutedEventArgs e)
{
PrintDocument pd = new PrintDocument();
int ItemIndex = 0;
pd.PrintPage += (s, e) =>
{
PagedCollectionView items = (PagedCollectionView)dgDashboard.ItemsSource;
StackPanel itemHost = new StackPanel();
ApplicationPrintHeadView head = new ApplicationPrintHeadView();
itemHost.Children.Add(head);
items.PageSize = items.TotalItemCount;
try
{
while (ItemIndex < items.TotalItemCount)
{
ApplicationPrintView itemsContainer = new ApplicationPrintView();
itemsContainer.appData.DataContext = items[ItemIndex];
itemHost.Children.Add(itemsContainer);
itemHost.Measure(new Size(e.PrintableArea.Width, double.PositiveInfinity));
if (itemHost.DesiredSize.Height > e.PrintableArea.Height && itemHost.Children.Count > 1)
{
itemHost.Children.Remove(itemsContainer);
e.HasMorePages = true;
break;
}
ItemIndex += 1;
}
}
catch (Exception)
{
MessageBox.Show("An Unexpected Error Has Occured.  Please try again.");
}
finally
{
items.PageSize = 20;
}
e.PageVisual = itemHost;
};
pd.Print("My Document");
}
This will print all data from datagrid across multiple pages with a nicely formatted page header and spaced up page footer so no row gets cut.
In summary, this is what i did to set up printing: in my silverlight project I added a folder called Views then added 2 user controls(page header and container) to it. And ofcourse my datagrid lives in mainPage.xaml that's pulling its data thru WCF (I reused the same service used by my ASP.NET application). Another option would be to use Ria Services. I had my silverlight project configured to be hosted in my ASP.NET application, so all i had to do is build the silverlight project to update the xap file.

References: http://channel9.msdn.com/learn/courses/Silverlight4/