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:
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.
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.
Add the ListBox, by selecting it in the asset library and then double clicking it in the toolbar.
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.
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 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:
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:
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:
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:
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.
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!