Silverlight 2 MSN Video ListBox - Tutorial by Robertjan Tuit

Robertjan Tuit
Robertjan Tuit

This article shows you how to build your own MSN Video ListBox in Silverlight 2, using a step by step, hands-on walktrough. Techniques covered are among others: data binding, templating and styling.

There are a lot of development techniques and functionality that we are only used to have on the desktop. But with Silverlight 2 (SL2) a lot of these have come to the browser. One of these techniques is data binding, not the "fake" data binding done in ASP.net, but the real deal. In this article I'm going to touch a little styling and templates as well.

To show you the power of data binding in SL2 we are going to create a ListBox filled with images and videos from the MSN Video website. I will walk you through every step of the way, so at the end you will know how to do it yourself. And just to make it a little bit interesting, this is what the end result will look like, although the exact pictures will be different:

Click to Enlarge

As I said we will be using the MSN Video Service as our data source, this means that you need a live internet connection when building the application. /p>

Before we really begin, some of us (lazy like me) like to have the complete source code upfront, so click here to download the complete source code.

Requirements

  • Visual Studio 2008 SP1
  • Silverlight 2 Beta 2 Tools for Visual Studio 2008 SP1
  • Silverlight 2 Beta 2 SDK
  • .NET Framework 3.5
  • .NET Framework 3.5 SP1 Beta
  • Blend 2.5 2008 June Preview
  • Connection to the internet

New Project

We start out by creating a new Silverlight Project named MSNVideoListBox.Silverlight. Also create a new solution named MSNVideoListBox, like in the picture below.

Click to Enlarge

In the next screen select "Add a new Web to the solution for hosting the control" and select "Web Application Project", name it MSNVideoListBox.Web.

After the 2 projects are created, set MSNVideoListBox.SilverlightTestPage.aspx as your start page.

Your solutions file should be looking like this:

Click here to get the source code in this state.

Getting the Video Feed

Now that we have set up our solution, let's get some real data. The URL from which we can get 10 random videos from MSN Video is http://catalog.video.msn.com/randomVideo.aspx?mk=us&vs=0&df=99&c=10. You can just open it in your browser to see the xml it returns.

First we have to set up the code that is going to get the feed. If you are only into designing, download the already made source code here and continue to the next chapter, those of you who do want to know how it all works, please continue.

First create a new class file in the Silverlight project called VideoDS.cs and let the class use the INotifyPropertyChanged (requires System.ComponentPanel in the using dirfectives) implement it, add a private constant string named _RandomVideoUrl and set it to the URL. Then add a public method LoadVideos. You code should look like this:

public class VideoDS : INotifyPropertyChanged
{
    private const string _RandomVideoUrl = " http://catalog.video.msn.com/randomVideo.aspx?mk=us&vs=0&df=99&c=10 " ;
    public ObservableCollection RandomVideos { get ; set ; }
    private void LoadVideoFeed()
    {
    }
    #region INotifyPropertyChanged Members
    public event PropertyChangedEventHandler PropertyChanged ;
#endregion
}

In the LoadVideoFeed method we are going to use a WebClient object to get the feed. Remember that all network classes in SL2 work asynchronously. Luckily we have C# 3.0 at our disposal, so we can use one of my favorite new language features, lambda expressions, to handle the event cleanly. Also add System.Net to the usings.

var wc = new WebClient() ;
wc.DownloadStringCompleted += ( sender , e ) =>
{
    //TODO: Handle the Result
} ;
wc.DownloadStringAsync ( new Uri ( _RandomVideoUrl ));

Now that we have the feed, we need to parse it. Here we will be using another nice new language feature: Linq to Xml. But first we need to create a class in which we can put the data we will parse from the xml document.
Create a new class file in the Silverlight Project named VideoFeedItem.cs and paste the code below.

public class VideoFeedItem
{
    public string Title { get ; : set ; : }
    public string Source { get ; : set ; : }
    public string PublishDate { get ; : set ; : }
    public string Description { get ; : set ; : }
    public string ImageUrl { get ; : set ; : }
    public string VideoUrl { get ; : set ; : }
    public string ViewCount { get ;  set ;  }
}


Back to VideoDS.cs. Add a new method GetVideoFeedItemsFromXmlString(string Xml) with a List <VideoFeedItem> return type, this will require you to add System.Collections.Generic as a using.

In the method we are going to create a new XDocument with the contents of the string. Then we use Linq to Xml to parse out all the items we need. First add a reference to the System.Xml.Linq assembly and add it as a using. Also add System.Linq to the usings.

Because the Xml uses namespaces, we also need to add this to every node we are trying to access. The resulting code looks like this:

public List< VideoFeedItem> GetVideoFeedItemsFromXmlString( string Xml)
{
    XNamespace ns = "urn:schemas-microsoft-com:msnvideo:catalog";
    var doc = XDocument. Parse( Xml);
    var videos =
      from video in doc. Descendants( ns + "video")
      select new VideoFeedItem()
     {
        Title = ( string) video. Element( ns + "title"),
        Source = ( string)( video. Element( ns + "source"). Attribute( "friendlyName")),
        PublishDate = DateTime. Parse(( string) video. Element( ns + "startDate")). ToShortDateString(),
        Description = ( string) video. Element( ns + "description"),
        ImageUrl = GetUriAsset( video. Element( ns + "files"), "file", "2007", ns),
        VideoUrl = GetUriAsset( video. Element( ns + "videoFiles"), "videoFile", "1002", ns),
        ViewCount = ( string) video. Element( ns + "usage"). Element( ns + "usageItem"). Attribute( "totalCount"),
     };
    return videos. ToList();
}


As you might have noticed, the Image and Video Url need a little more work. The following method does all the hard work for us.

private string GetUriAsset( XContainer element, string nodeName, string formatCode, XNamespace ns)
{
   var uris = from file in element. Descendants( ns + nodeName)
     where ( string) file. Attribute( "formatCode") == formatCode
     select ( string) file. Element( ns + "uri");
   return (( uris. Count< string>() > 0) ? uris. First< string>() : string. Empty);
}


So now that we have all our methods set up, let's go back to the LoadVideoFeed method and finish up. We use the GetVideoFeedItemsFromXmlString to convert the string to a collection and then add them to an ObservableCollection. The ObservableCollection will handle all the notifications to bound controls. When you bind a control to an observable collection or any other object that implements the INotifyPropertyChanged interface, it will register to an event that fires whenever the object is changed. In this case we need to send that signal ourselves by calling the PropertyChanged method. This will inform our bound ListBox that it can get new data.

wc. DownloadStringCompleted += ( sender, e) =>
{
   var feedItems = GetVideoFeedItemsFromXmlString( e. Result);

   RandomVideos = new ObservableCollection< VideoFeedItem>();
   foreach ( var feedItem in feedItems)
    RandomVideos. Add( feedItem);

   PropertyChanged( this, new PropertyChangedEventArgs( "RandomVideos"));
};


Now all we have to do is call the LoadVideoFeed method from the constructor. The HtmlPage.IsEnabled will make it only work in runtime otherwise it would also try to do it in design time.

public VideoDS()
{
   if ( HtmlPage. IsEnabled)
   LoadVideoFeed();
}


So that wraps up most of the backend code. Click here for the source code in this state.

Binding

Now that we have setup the classes necessary for the data loading and binding, let's add the ListBox to our page.xaml. And bind it to our data source. We can do this manually by typing in XAML but I prefer using blend.

Open up the page in Blend by right clicking page.xaml and selecting "Open in expression Blend".

In Blend, first select the root [UserControl]. Go to the properties tab and change the size to 600 by 200 pixels.

Click to Enlarge

Add the ListBox, by selecting it in the asset library and then double clicking it in the toolbar.

Click to Enlarge

When you add it this way it defaults to 100 by 100 pixels. We want it to fill the the canvas so we remove the sizes by clicking on the small square next to the properties and selecting reset or the button next to it to set it to auto. And change the aligment to stretch both ways.

Go back to the project tab, and click +CLR Object. This will open up a window where you enter the name VideoDS and select the VideoDS object we created to make it a data source.

Click to Enlarge

It will add the VideoDS to the Data panel.

Now we can bind the ListBox to the data source by dragging the VideoDS item in the panel to the ListBox. You will have to open it up first and drag the RandomVideos item onto it. When you drop it on the ListBox a menu will pop up where you select "Bind VideoDS to ListBox". A window will pop up where you select the ItemsSource property to bind to. Press OK the create the binding.

Go back to Visual Studio and run the application. The result will look simmlar to this:

Click to Enlarge

Click here for the source code in this state.

Template

As you see here, the ListBox has found our collection but has no clue whatsoever what to do with it. To tell it how it should display our data, we add ListBox.ItemTemplate and we change the orientation using the ListBox.ItemsPanel property:

<ListBox ItemsSource="{ Binding RandomVideos, Source={ StaticResource VideoDS}} ">
  < ListBox.ItemTemplate>
   < DataTemplate>
    < TextBlock Text="{ Binding Title} "/>
   </ DataTemplate>
 </ ListBox.ItemTemplate>
 < ListBox.ItemsPanel>
   < ItemsPanelTemplate>
    < StackPanel Orientation= "Horizontal"/>
   </ ItemsPanelTemplate>
 </ ListBox.ItemsPanel>
</ListBox>


This should result in the following, with other data of course since it is random:

Click to Enlarge

The latest version of blend has tooling built in to do this visually, so a designer could change the look of the items in the ListBox without the need for XAML.

Style

I'm always a big fan of the separation of design and behavior, so let's move the design to a style. First remove the properties and add a Style to the ListBox:

<ListBox Style="{ StaticResource ListBoxStyle} "
    HorizontalAlignment= "Stretch"
    VerticalAlignment= ItemsSource="{ Binding RandomVideos, Mode= OneWay, Source={ StaticResource VideoDS}}"/>


And then add a Style to the pages UserControl.Resources:

<UserControl.Resources>
 < MSNVideoListBox_Silverlight: VideoDS x: Key= "VideoDS" d: Isdata source= "True"/>
 < Style x: Key= "ListBoxStyle" TargetType= "ListBox">
  < Setter Property= "ItemsPanel">
   < Setter.Value>
  ,  < ItemsPanelTemplate>
     < StackPanel Orientation= "Horizontal"/>
    </ ItemsPanelTemplate>
   </ Setter.Value>
  </ Setter>
  < Setter Property= "ItemTemplate">
   < Setter.Value>
    < DataTemplate>
     < TextBlock Text="{ Binding Title} " />
    </ DataTemplate>
   </ Setter.Value>
   </ Setter>
  </ Style>
</ UserControl.Resources>


The above style does exactly the same thing as with the properties, so no changes in result here.

Image Binding

Next we will add an Image to the mix and add a StackPanel and some Margin to make it look a little bit neater.

<Setter Property= "ItemTemplate">
  <Setter.Value>
   <DataTemplate>
    <StackPanel Width= "200">
    < TextBlock Text="{ Binding Title} " HorizontalAlignment= "Center" Margin= "5,5,5,0"/>
    < Image Height= "120" Stretch= "Uniform" Source="{ Binding ImageUrl} " HorizontalAlignment= "Center" Margin= "10,10,10,10"/>
   </ StackPanel>
  </ DataTemplate>
 </ Setter.Value>
</ Setter>


Build and run the project, it should look something like this:

Click to Enlarge

UserControl in a Style

To get a little bit more control over the video, lets move the content of the <DataTemplate> to a UserControl. Add a new Silverlight UserControl to the project with the name : VideoFeedItemControl.

Move the XAML from the <DataTemplate> to the new UserControl, Don't forget to remove the width and height from the UserControl.

<UserControl x: Class= "MSNVideoListBox.Silverlight.VideoFeedItemControl"
   xmlns= "http://schemas.microsoft.com/client/2007"
   xmlns: x= "http://schemas.microsoft.com/winfx/2006/xaml"> <
   Grid x: Name= "LayoutRoot" Background= "White">
   < StackPanel Width= "200">
    < TextBlock Text="{ Binding Title} " HorizontalAlignment= "Center" Margin= "5,5,5,0"/>
    < Image Height= "120" Stretch= "Uniform" Source="{ Binding ImageUrl} " HorizontalAlignment= "Center" Margin= "10,10,10,10"/>
   </StackPanel>
  </Grid>
</UserControl>


And place the <VideoFeedItemControl> in the Page.xaml <DataTemplate>:

<Setter Property= "ItemTemplate">
 < Setter.Value>
  < DataTemplate>
   < MSNVideoListBox_Silverlight: VideoFeedItemControl/>
  </ DataTemplate>
 </ Setter.Value>
</ Setter>


Ready to go, build and run. Let's see our end result:

Click to Enlarge

Hey, wait a minute!? That is not what we were aiming for. It is empty!

We did not get any errors, everything seems to be working correctly, what happened here?

The fix

The error is with the automatic namespace declaration, that blend created for us:

xmlns: MSNVideoListBox_Silverlight= "clr-namespace:MSNVideoListBox.Silverlight"


add: ;assembly=MSNVideoListBox.Silverlight to the end. After which it should look like:

xmlns: MSNVideoListBox_Silverlight= "clr-namespace:MSNVideoListBox.Silverlight ;assembly=MSNVideoListBox.Silverlight"


We build and we get:

Click to Enlarge

That looks more like it!

Video

Open up the VideoFeedItemControl.xaml file and add a grid and video element like this:

<StackPanel Width= "200"> <
   TextBlock Text="{ Binding Title} " HorizontalAlignment= "Center" Margin= "5,5,5,0"/>
  < Grid>
   < Image Height= "120" Stretch= "Uniform" Source="{ Binding ImageUrl} " HorizontalAlignment= "Center" Margin= "10,10,10,10"/>
   < MediaElement Source="{ Binding VideoUrl} " Height= "120" Stretch= "Uniform" x: Name= "Video" AutoPlay= "False" Visibility= "Collapsed"/>
  </ Grid>
</ StackPanel>


The MediaElement is hidden and has a name Video. Add a MouseLeftButtonUp handler to the image element. If you type it in, Visual Studio will offer to create the handler automatically

Right click on the event handler and click the "Navigate to Event Handler" item.

Then in the codebehind start the video and make the video visible.

private void Image_MouseLeftButtonUp( object sender, MouseButtonEventArgs e)
{
   Video. Play();
   Video. Visibility = Visibility. Visible;
}


Start the application and click a few of them to enjoy the scene, also turn up your volume to get the full effect, especially if you have co-workers around.

Click to Enlarge

Conclusion

So that was fun, I think it was anyway. I Hope I have showed you some new stuff today. If you have any questions or remarks please send them to me trough the contact form on my blog www.robertjantuit.nl and I will gladly answer any questions you might have on SL2 and with article.

click here to download the complete source code.

Stay in the light!