I have created a video of the application.

Contents

Introduction

This article is the second, and final, part of the Netflix Browser for Windows Phone 7 article. Part 1 focused on exploring the Pivot and Panorama controls in general, and demonstrated the first steps of the demo application walkthrough. We learned how to add the controls to a project, and how to work with the Pivot and Panorama templates. In this part, we will continue with the walk-through of the demo application, and in particular we will look at OData and how to consume OData in a Windows Phone application. We will also explore the Silverlight Toolkit’s WrapPanel control, page navigation and the progress bar.

Demo Application Overview

The demo application is a Panorama application that allows users to browse through Netflix data. At the top layer the user is presented with a list of genres, new releases and highest rated movies. The user can then drill down into each movie to get movie details. Selecting a genre from the genre list will take the user to a pivot which lists movies for the selected genre representing the data in three different ways: all movies for that genre, sorted by year, and sorted by average rating. In an ideal situation, I would like to see the list of all genres to be only the top level genres, instead of a very long list of genres and subgenres; but there wasn’t an easy way to query for this data.

What Will be Covered

Panorama Control (Part 1)

Pivot Control (Part 1)

Consuming OData

WrapPanel

Page Navigation

Saving and Restoring Transient State

Progress Bar

Prerequisites

Windows Vista or Windows 7

Windows Phone 7 Developers Tools which includes Visual Studio Express 2010 for Windows Phone, Windows Phone Emulator, Expression Blend 4 for Windows Phone, and XNA Game Studio 4.0. Visual Studio Express 2010 and Expression Bled 4 will only be installed if you do not have them already installed. If you have previously installed the Beta Tools, you will have to uninstall it before installing the final tools. Note that the installation may take some time; mine took well over an hour. Leave the installation running, it will eventually complete.

Silverlight for Windows Phone Toolkit

OData Client Library for Windows Phone 7 (note that this is a production-ready version announced at PDC 2010; in Part 1 we used a preview version) There are three downloads on the CodePlex site. For the purpose of this demo, all we need is the ODataClient_BinariesAndCodeGenToolForWinPhone.zip. It contains the OData client assemblies and Datasvcutil code generation tool which will be used for generating the proxy classes.



OData

The demo application consumes Netflix data which is exposed using the Open Data Protocol (OData). There is a heap of information on OData on odata.org, but I liked Shayne Burgess’s explanation of what OData is, so I pasted it below.

In very simple terms, OData is a resource-based Web protocol for querying and updating data. OData defines operations on resources using HTTP verbs (PUT, POST, UPDATE and DELETE), and it identifies those resources using a standard URI syntax. Data is transferred over HTTP using the AtomPub or JSON standards. For AtomPub, the OData protocol defines some conventions on the standard to support the exchange of query and schema information. (Source: MSDN Magazine, June 2010 Issue)

The OData protocol enables access to information from a broad range of clients. At present, there are client libraries available for Windows Phone 7, iPhone, Silverlight 4, PHP, AJAX/Javascript, Ruby, or Java. Here you can see a complete list. In the demo, we use the OData Client Library for Windows Phone 7. An OData service can be implemented on any server that supports HTTP. The .NET implementation is supported through WCF Data Services. WCF Data Services is a .NET Framework component which used to be known as ADO.Net Data Services (codename Astoria). The WCF Data Services provides a framework for creating OData Web services and includes a set of client libraries (one for general .NET framework client applications and one for Silverlight applications) for building clients that consume OData feeds.

Services that expose their data using the OData protocol are referred to as OData producers, and clients that consume data exposed using the OData protocol are referred to as consumers. The odata.org website provides a list of current producers and consumers. Among the producers, there are applications such as SharePoint 2010, Windows Azure Storage, or IBM WebSphere, as well as several live OData services such as the Netflix Data service used in the demo application. The consumers list for example includes Excel 2010. Microsoft released the OData specification under Open Specification Promise (OSP) which means that anyone can freely build OData clients and services. There are resources available on how to create an OData service, for example you can check out Scott Hanselman’s post on creating a custom API for StackOverflow. For an example on how to consume OData in a Windows Phone application, see the next section.

Consuming OData in a WP7 application

In a Silverlight project and Visual Studio 2010, all you need to do to start consuming OData is to Add Service Reference and specify the OData service URL as shown in Figure 1. Visual Studio then automatically adds a reference to the System.Data.Services.Client.dll assembly, creates a client-side context class which is used to interact with the data service, and generates a set of proxy classes that represent the types exposed by the service.

The Add Service Reference for OData is missing for Windows Phone applications, but as you will see, you can get up and running with WCF Data Services in your Windows Phone application in a few simple steps:

Add a reference to the System.Data.Services.Client.dll assembly from the OData Client Library for Windows Phone 7 (see Prerequisites for the download link). In Solution Explorer in Visual Studio, right click on References and select Add Reference. Browse to the location where you saved the OData Client Library for Windows Phone 7 and select the System.Data.Services.Client.dll assembly.

Run the Datasvcutil tool to generate the data service class. With the updated version of the OData Client Library for Windows Phone 7, we need to use an updated version of the DataSvcUtil tool. Therefore, if you used the initial version of the client library, as I did in Part 1, you will need to re-generate the service class. The updated DataSvcUtil tool is located in the ODataClient_BinariesAndCodeGenToolForWinPhone.zip file downloaded from Codeplex (see prerequisites). I found the best way to use the updated tool was to generate the class in the directory where I unzipped the content of the ODataClient_BinariesAndCodeGenToolForWinPhone.zip. file and then to manually copy the generated class to the project. Open Visual Studio Command Prompt and change to the directory where you unzipped the content of the ODataClient_BinariesAndCodeGenToolForWinPhone.zip file. Then use the following command to generate the Netflix service class:

DataSvcUtil.exe /uri:http://odata.netflix.com/Catalog/ /out:NetflixModel.cs /Version:2.0 /DataServiceCollection

The generated class is comprised of a context class called NetflixCatalog , and client proxy classes for each type exposed by the Netflix service. Because we used the /DataServiceCollection option in the command above, the generated proxy classes implement the INotifyPropertyChanged interface.

, and client proxy classes for each type exposed by the Netflix service. Because we used the /DataServiceCollection option in the command above, the generated proxy classes implement the interface. Copy the generated class to your project and include it in your project. In the demo, I created a folder called Services where I copied the generated class. Select the Show All button in the Solution Explorer in Visual Studio, right click on the newly generated class and select the Include In Project option.



Working with OData

Once we have our project setup, we can begin to interact with the Netflix OData service. In the demo, we do this in the MainViewModel class.

As a first step, we create the context that will allow us to interact with the Netflix Catalog.

using alias =NetflixCatalog.Model; . . . readonly alias .NetflixCatalog context; public MainViewModel() { context = new alias .NetflixCatalog( new Uri( " http://odata.netflix.com/Catalog/" , UriKind.Absolute)); }

You may be surprised that I used an alias in the above excerpt. I chose this as a workaround for a name conflict. You see, unfortunately when the new DataSvcUtil tool generates the Netflix service class, it assigns the namespace NetflixCatalog.Model, which conflicts with the name NetflixCatalog that the tool assigns to the context class.

Then we go on to querying the service. Recall that the Panorama has three sections: one for displaying a list of all genres, one for new titles, and the last for top titles. For this, I have created three methods: LoadGenres , LoadNewTitles , and LoadTopTitles , where I formulate a query to retrieve the desired Netflix data, and load the query result to a collection.

Before we take a look at these methods, let's first see how the collection members are declared. As you can see we use DataServiceCollection . The DataServiceCollection is an ObservableCollection and makes working with WCF Data Services very easy.

DataServiceCollection<Genre> genres; DataServiceCollection<Title> newTitles; DataServiceCollection<Title> topTitles;

Both the Netflix API and the OData client library for Windows Phone 7, used in Part 1 of this article, were officially in a preview mode. The Netflix API remains in a preview mode; however, an updated, production-ready version of the OData Client Library for Windows Phone 7 has been released. One of the main changes with the update is that the new version doesn’t provide support for LINQ. The preview version gave us some LINQ support, and the demo application in Part 1 used LINQ to formulate queries. This would no longer work with the new library. Therefore, we need to replace the LINQ queries with URI based queries. Note though that the LINQ support may be enabled in future releases. Originally, the LoagGenres method looked like this:

public void LoadGenres() { genres = new DataServiceCollection<genre>(); IQueryable<genre> query = from g in context.Genres select g; genres.LoadAsync(query); genres.LoadCompleted += (sender, args) = > { if (args.Error != null ) { Debug.WriteLine( " Requesting titles failed. " + args.Error.Message); } else { IsDataLoaded = true ; } }; }

As you saw above, we began by instantiating the genres DataServiceCollection . Then we formulated a LINQ query (this is the part we will replace). The DataServiceCollection has a LoadAsync method which handles the asynchronous callback necessary for network calls in Silverlight, and loads query result into the collection. We used the LoadAsync method to load the query result into our genres collection. The method ended with an event handler that handles the LoadCompleted event of the asynchronous callback.

Now we need to replace the LINQ query with an URI query. To get an idea on how to formulate the URI query, we can check out the sample queries and guidance on the Netflix website. Alternatively since we still have our LINQ query, and since the LINQ query gets interpreted into the URI, we can simply use Add Watch while debugging in Visual Studio and extract the query value. We could also use a tool like Fiddler to extract the URI, as it's being sent to the Netflix service.

For the list of genres in the LoadGenres method, the URI query is very simple:

http://odata.netflix.com/Catalog/Genres()

We can then replace the LINQ query with this:

Uri uriQuery = new Uri( " /Genres()" , UriKind.Relative);

Note that we didn’t need to specify all parts of the URI. This is because the first part (http://odata.netflix.com/Catalog) of the URI has already been defined as the DataServiceContext (see the beginning of this section) which as you can see below, we pass as a parameter when we instantiate the genres DataServiceCollection .

The WCF Data Services team has added a LoadAsync(Uri) method to the DataServiceCollection class which makes our life very easy, and we can just replace the parameter in the LoadAsync method with the URI query. The amended method looks like this:

public void LoadGenres() { genres = new DataServiceCollection<genre>(context); Uri uriQuery = new Uri( " /Genres()" , UriKind.Relative); genres.LoadAsync(uriQuery); genres.LoadCompleted += (sender, args) = > { if (args.Error != null ) { Debug.WriteLine( " Requesting titles failed. " + args.Error.Message); } else { IsDataLoaded = true ; } }; }

We want to bind the result of the queries to the Panorama control. So in the MainViewModel , we declare properties for the genres, newTitles and topTitles DataServiceCollection s:

public DataServiceCollection<Genre> Genres { get { return genres; } private set { genres = value ; OnPropertyChanged( " Genres" ); } } public DataServiceCollection<Title> TopTitles { get { return topTitles; } private set { topTitles = value ; OnPropertyChanged( " TopTitles" ); } } public DataServiceCollection<Title> NewTitles { get { return newTitles; } private set { newTitles = value ; OnPropertyChanged( " NewTitles" ); } }

In the MainPage , we set the ItemSource property of each ListBox control in the Panorama control to bind to the corresponding property shown above. Also, we bind the Text property of the TextoBlock control to the appropriate field name. Below is an example of the first PanoramaItem , where we show a list of all genres.

< controls:PanoramaItem Header =" genres" > < ListBox x:Name =" GenreListBox" Margin =" 0,0,-12,0" ItemsSource =" {Binding Genres}" SelectionChanged =" GenreListBoxSelectionChanged" > < ListBox.ItemTemplate > < DataTemplate > < StackPanel Margin =" 0,0,0,17" Width =" 432" > < TextBlock Text =" {Binding Name}" TextWrapping =" Wrap" Foreground =" {StaticResource PanoramaForegroundBrush}" Style =" {StaticResource PhoneTextLargeStyle}" / > < /StackPanel > < /DataTemplate > < /ListBox.ItemTemplate > < /ListBox > < /controls:PanoramaItem >

To complete the data binding, we set the DataContext of the Panorama page (MainPage.cs) to the MainViewModel :

public MainPage() { InitializeComponent(); DataContext = new MainViewModel(); } MainViewModel ViewModel { get { return (MainViewModel)DataContext; } }

WrapPanel

The third panorama item shows top 20 DVDs as thumb images. I decided to utilize the Silverlight for Windows Phone Toolkit, and chose to lay out the images with a WrapPanel . As the name suggests a WrapPanel wraps content inside its boundaries. So you can define the orientation (horizontal by default), width and height and the content will be wrapped nicely inside it (see Figure 2). This is opposite to the StackPanel , which stacks content either horizontally or vertically and doesn’t wrap its content.

To use the toolkit, we need to add a reference to the Microsoft.Phone.Controls.Toolkit.dll assembly, and a namespace reference to MainPage.xaml.

xmlns:toolkit="clr-namespace:Microsoft.Phone.Controls; assembly=Microsoft.Phone.Controls.Toolkit"

Because the content of this Panorama section is wider than the screen, we set the PanoramaItem.Orientation property to Horizontal . As you can see in the excerpt below we use a ListBox control with an ItemsPanelTemplate where we specify the Panel for laying out the items to be the WrapPanel . We set the width of the WrapPanel to 600px. The orientation is horizontal by default. We then set the ListBox DataTemplate to the image and bind its source to the appropriate field:

< controls:PanoramaItem Header =" top rated" Orientation =" Horizontal" > < ListBox Margin =" 0,0,-12,0" x:Name =" TopTitlesListBox" ItemsSource =" {Binding TopTitles}" SelectionChanged =" TitlesListBoxSelectionChanged" > < ListBox.ItemsPanel > < ItemsPanelTemplate > < toolkit:WrapPanel x:Name =" wrapPanel" Width =" 600" / > < /ItemsPanelTemplate > < /ListBox.ItemsPanel > < ListBox.ItemTemplate > < DataTemplate > < Image Source =" {Binding BoxArt.MediumUrl}" Width =" 100" Margin =" 10" / > < /DataTemplate > < /ListBox.ItemTemplate > < /ListBox > < /controls:PanoramaItem >

Page Navigation

Page navigation in Windows Phone Silverlight applications is based on the Silverlight 3 Frame and Page navigation model. However, Windows Phone applications are built using the PhoneApplicationFrame and PhoneApplicationPage instead of the Silverlight’s 3 Frame and Page controls. This is an important point, and you must not use the standard Silverlight Frame and Page types. The PhoneApplicationFrame and PhoneApplicationPage controls are derived from the Frame and Page classes respectively. The PhoneApplicationFrame serves as a container for PhoneApplicaitonPage controls, and contains other elements such as the system tray and application bar. The PhoneApplicaitonPage holds sections of the application’s content. There can be only one PhoneApplicationFrame for an application. The idea is that the single frame container can facilitate the navigating (switching) between pages. You can create as many pages as you like, but you must be aware that each page is retained on the navigation stack, and too many pages in your application, will increase the size of the navigation stack and can degrade performance.

Navigating between pages is similar to browsing the web with a web browser. Backward navigation occurs either through the Windows Phone Back hardware button, which takes the user either to the previous page or exits the application, or via the NavigationService .

Navigating to GenreDvds Page

After selecting a genre from the genre list shown in the first Panorama section, the user is navigated to a GenreDvds page and shown the genre’s titles. The user can then return to the previous page via the hardware back button (see Figure 3).

When the user selects a genre, the ListBox’s SelectionChanged event is triggered. In the handler, we retrieve the selected genre and save it as a Genre object. Then we use a DataLayer class to save the genre’s titles and use the NavigationService ’s Navigate method to navigate to the GenreDvds page. The Navigate method takes a URI as a parameter, which we specify as the path to the GenreDvds page, relative to the projects root directory and pass it a string parameter genreId . Because the gerneId is a genre name, we use the HttpUtility.UrlEncode method to encode it into a string that is URL friendly. At the end of the method, we reset the selected item.

void GenreListBoxSelectionChanged( object sender, SelectionChangedEventArgs e) { var selectedGenre = GenreListBox.SelectedItem as Genre; if (selectedGenre != null ) { string genreId = HttpUtility.UrlEncode(selectedGenre.Name); DataLayer.SetTitlesForGenre(genreId, selectedGenre.Titles); NavigationService.Navigate( new Uri( " /GenreDvds.xaml?name=" + genreId, UriKind.Relative)); } GenreListBox.SelectedItem = null ; }

On the GenreDvds page, we retrieve the string parameter and call the viewmodel ’s LoadGenreTitles method passing it the retrieved genreId . We do this in the OnNavigatedTo method which gets called when the page comes into view.

protected override void OnNavigatedTo(NavigationEventArgs e) { base .OnNavigatedTo(e); string genreId; if (!NavigationContext.QueryString.TryGetValue( " name" , out genreId)) { MessageBox.Show( " No genre provided." ); return ; } viewModel.LoadGenreTitles(genreId); }

The DataContext of the GenreDvds page is set to the GenreDvdsViewModel in the constructor.

readonly GenreDvdsViewModel viewModel = new GenreDvdsViewModel(); public GenreDvds() { InitializeComponent(); DataContext = viewModel; }

In the LoadGenreTitles method, we use the DataLayer class to retrieve the titles of the selected genre and set the SelectedGenreName property to the genre name. We also set the Loaded and Busy properties which we use to set the visibility of the ProgressBar control on the GenreDvds page. This will be explained in the section entitled Progress Bar. Then we asynchronously load the related genre titles. The genreTitles field is a DataServiceCollection , which is declared as a property called GenreTitles used in data binding. The LoadGenreTitles method is in the GenreDvdsViewModel .

public void LoadGenreTitles( string genreName) { string genreId = HttpUtility.UrlEncode(genreName); genreTitles = DataLayer.GetTitlesForGenre(genreId); SelectedGenreName = genreName; Loaded = false ; Busy = true ; if (genreTitles.Count() == 0 ) { genreTitles.LoadAsync(); } else { Loaded = true ; Busy = false ; GetTitlesByYear(genreTitles); GetTitlesByRating(genreTitles); } genreTitles.LoadCompleted += (sender, args) = > { if (args.Error != null ) { Debug.WriteLine( " Requesting titles failed. " + args.Error.Message); } Loaded = true ; Busy = false ; GetTitlesByYear(genreTitles); GetTitlesByRating(genreTitles); }; }

Navigating to Details Page

The navigation to the details page begins when the user selects an item in the second or third Panorama section and is dealt with in the same way as explained above. Again we use the DataLayer class to set the selected title and then navigate to the DvdDetails page, passing through a string parameter.

void TitlesListBoxSelectionChanged( object sender, SelectionChangedEventArgs e) { var selectedDvd = ((ListBox)sender).SelectedItem as Title; if (selectedDvd == null ) { return ; } string titleId = selectedDvd.Id; DataLayer.SetSelectedTitle(titleId, selectedDvd); NavigationService.Navigate( new Uri ( " /DvdDetails.xaml?Id=" + titleId, UriKind.Relative)); ((ListBox)sender).SelectedItem = null ; }

The difference is that when we navigate to the DvdDetails page, we set the DataContext to the selected title (retrieved from the DataLayer class) directly in the OnNavigatedTo method.

protected override void OnNavigatedTo(NavigationEventArgs e) { base .OnNavigatedTo(e); string titleId; if (NavigationContext.QueryString.TryGetValue( " Id" , out titleId)) { DataContext = DataLayer.GetSelectedTitle(titleId); } }

Saving and Restoring Transient State

Although this is not fully demonstrated in the demo application, saving an application’s transient state is an important part of developing Windows Phone applications. I’ve learned about this from reading Daniel’s chapters on the Windows Phone application life-cycle, and will summarise a part of it here.

Whenever your application is interrupted, it may go into a state called tombstoned. Tombstoned means that your application has been terminated, but that it may be re-activated later. When tombstoned, you need to save the current state of the application, so that when the application is re-activated, it will appear to the user as though nothing has happened.

The DataLayer class, which in the demo is used mainly to save and retrieve objects, can be used by the application to save and restore its state across pages and whenever the application is deactivated:

public class DataLayer { static Dictionary<string, DataServiceCollection<Title>> titlesDictionary = new Dictionary<string, DataServiceCollection<Title>>(); static Dictionary<string, Title> titleDictionary = new Dictionary<string, Title>(); const string titlesDictionaryKey = " DataLayer.titlesDictionary" ; const string titleDictionaryKey = " DataLayer.titleDictionary" ; public static void LoadFromDictionary(IDictionary<string, object> stateDictionary) { titlesDictionary = (Dictionary<string, DataServiceCollection<Title>>)stateDictionary[titlesDictionaryKey]; titleDictionary = (Dictionary<string, Title>)stateDictionary[titleDictionaryKey]; } public static void SaveToDictionary(IDictionary<string, object> stateDictionary) { stateDictionary[titlesDictionaryKey] = titlesDictionary; stateDictionary[titleDictionaryKey] = titleDictionary; } public static void SetTitlesForGenre( string genreName, DataServiceCollection<Title> titles) { titlesDictionary[genreName] = titles; } public static DataServiceCollection<Title> GetTitlesForGenre( string genreName) { return titlesDictionary[genreName]; } public static void SetSelectedTitle( string id, Title title) { titleDictionary[id] = title; } public static Title GetSelectedTitle( string id) { return titleDictionary[id]; } }

When the application is deactivated, the App class calls the DataLayer.SaveToDictionary method. This saves the transient state of DataLayer class to a state Dictionary that will survive if the application is tombstoned.

private void Application_Deactivated( object sender, DeactivatedEventArgs e) { IDictionary<string> dictionary = PhoneApplicationService.Current.State; DataLayer.SaveToDictionary(dictionary); }

If and when the application is re-activated, the App class calls the DataLayer.LoadFromDictionary method, which restores the state of the DataLayer class:

private void Application_Activated( object sender, ActivatedEventArgs e) { var dictionary = PhoneApplicationService.Current.State; DataLayer.LoadFromDictionary(dictionary); }

Note that I haven’t saved the result of the OData calls in the demo application, but it’s something that should be done.

Progress Bar

The ProgressBar control supported on the phone is the Silverlight ProgressBar included in the System.Windows.Controls namespace. A progress bar is used to indicate the progress of a lengthy task. It can either be value-based or indeterminate. The value-based ProgressBar indicates the progress of a task based on the beginning and end point values (0 and 100 by default), whereas the indeterminate ProgressBar displays a repeating animation that runs until the activity is completed (see Figure 4). The indeterminate ProgressBar is attained by setting its IsIndeterminate property to true . In the demo, we use the indeterminate ProgressBar on the GenreDvds to indicate that something is happening while the genre’s titles are loading. For the purpose of the demo, I used the built-in ProgressBar control, but if you are building a more serious application, which requires the indeterminate ProgressBar , you should read Jeff Wilcox’s post and use his PerformanceProgressBar instead. This is because the built-in version is costly, performance wise.

Improving ProgressBar Indeterminate Mode Efficiency

As the following excerpt shows, the ProgressBar control is placed inside a StackPanel whose Visibility property is data bound to the viewmodel 's Loaded property. I used Daniel’s IValueConverter , called BooleanToVisibilityConverter , to convert the boolean Loaded property to a Visibility type. As per Jeff’s Wilcox’s advice, the IsIndeterminate property of the ProgressBar control is not directly set as true but rather is data bound to the viewmodel s' Busy property to ensure that the repeating animation is properly turned off. The Visibility property of the ProgressBar is again databound to the viewmodel ’s Loaded property which is converted to the Visibility type via the BooleanToVisibilityConverter that I borrowed from Daniel.

< StackPanel Grid.Row =" 1" Visibility =" {Binding Loaded, Converter={StaticResource BooleanToVisibilityConverter}, ConverterParameter=Collapsed}" Height =" 150" > < TextBlock Text =" Loading..." Style =" {StaticResource PhoneTextTitle2Style}" HorizontalAlignment =" Center" Margin =" 20" / > < ProgressBar IsIndeterminate =" {Binding Busy}" Visibility =" {Binding Loaded, Converter={StaticResource BooleanToVisibilityConverter}, ConverterParameter=Collapsed}" / > < /StackPanel >

Conclusion

In this article, we looked at the Open Data protocol and showed how to consume OData in a Windows Phone application. We also explored the Silverlight Toolkit’s WrapPanel control, looked at page navigation, and at the ProgressBar control. I hope you found this article useful. If you enjoyed my article, please rate it and maybe share your thoughts below.

History

November 2010: Initial release

References