C H A P T E R  7

images

Advanced MVVM, Services and App Connect

In this chapter, we cover additional topics related to the programming model starting with more advanced coverage of the MVVM Architecture and data binding. The sample covers how to access RSS feeds, show a ProgressBar control from the ViewModel, how to lazy load images for better performance, and then concludes on how to data bind anything to anything using the IValueConverter Interface.

.The following section covers advanced services starting with encryption services, an important topic and critical for most professionally developed applications. This is followed by a discussion covering advanced media services.

The rest of the chapter covers App Connect. App Connect refers to the various ways that applications can integrate with Windows Phone including integration with the pictures hub, the music+video hub, as well as integration with Bing Search. Next up we start with a discussion of Advanced MVVM programming topics.

Advanced MVVM

In this section, I cover how to access Syndicated Services like RSS feeds from Windows Phone, as well as advanced Model-View-ViewModel (MVVM) techniques to incorporate page navigation, showing progress, and lazy loading images. The last subsection covers the IValueConverter interface, which allows you to data bind any data type to just about any other data.

A sample project based on the MVVM Light project template named AdvancedMVVM is added to the Chapter 7 solution. MVVM is leveraged for this example (and in many samples throughout this chapter) to demonstrate how to handle slightly additional complexity of separating concerns between the View and the ViewModel when dealing with real-world scenarios.

By default, the MainPage.xaml binds to the MainViewModel in the ViewModel folder. MainPage.xaml presents a menu of four items that navigate to individual pages corresponding to the sections that follow:

  • Syndicated Services (/View/SyndicatedServices.xaml)
  • Showing Progress (/View/ShowProgress.xaml)
  • Lazy Load Images (/View/LazyLoadImages.xaml)
  • Data Bind to Anything (/View/DatabindToAnything.xaml)

MainPage.xaml consists of a ListBox data bound to a collection of items that represent the above pages. Here is the XAML:

<ListBox x:Name="listBox1" Margin="24,0,0,0"
  ItemsSource="{Binding Pages}"
  ItemTemplate="{StaticResource PageItemTemplate}" />

In MainViewModel.cs, the ApplicationTitle and PageName properties are added to the code. A PageItems property is added to MainViewModel class that is of type List<PageItem>:

public const string PageItemsPropertyName = "PageItems";
private List<PageItemViewModel> _pages = null;
public List<PageItemViewModel> PageItems
{
  get
  {
    return _pages;
  }

  protected set
  {
    if (_pages == value)
    {
      return;
    }

    var oldValue = _pages;
    _pages = value;

    // Update bindings, no broadcast
    RaisePropertyChanged(PageItemsPropertyName);
  }
}

The MainViewModel class's constructor takes an IPageItemsDataService Interface. MVVM Light then finds a concrete class registered via the inversion of control class SimpleIoc that implements that interface and provides the needed data service and data. I cover this in Chapter 7 in the MVVMLight sample. I recommend going through that section for more details on how MVVM Light works as well as the new Inversion of Control pattern.

Here is the constructor for MainViewModel:

public MainViewModel(IPageItemsDataService dataService)
{
  _pageItemsService = dataService;
  PageItems = new List<PageItem>();

  _pageItemsService.GetPageItems(
      (pageItems, error) =>
      {
        if (error != null)
        {
          // Report error here
          return;
        }
        PageItems = (List<PageItem>)pageItems;
      });
}

ShowProgress.xaml

Normally, the PageItem class would be a simple Model class; however, it has a bit more functionality than previous model classes covered in Chapter 7 with MVVM Light since it needs to implement commanding as shown in Listing 7-1 below The ListBox displaying the Pages data. When an item is selected in the ListBox shown in Figure 7–1, the application navigates to the associated page. We don't want to write code to do this in the MainPage.xaml.cs code-behind file. Instead we take advantage of messaging and commanding support provided by the MVVM Light toolkit.

images

Figure 7–1. AdvancedMVVM MainPage.xaml UI

The selected item in the ListBox on MainPage.xaml should perform navigation to the associated page pointed to by the selected item. This means that the Commanding support is required at the PageItem / ItemTemplate level, not at the ListBox level. To support this functionality, the PageItem model class supports GalaSoft Light Messaging and Commanding (see Chapter 6 for details on GalaSoft MVVM). Listing 7–1 shows the PageItem ,model class.

Listing 7–1. PageItem Model Class File

using System;
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using GalaSoft.MvvmLight.Messaging;

namespace AdvancedMVVM.Model
{
  public class PageItem : ViewModelBase
  {
    public PageItem()
    {
      NavigateToPageCommand = new RelayCommand<Uri>(
       param => SendNavigationRequestMessage(param));
    }

    #region PageItem properties
    public const string PageTitlePropertyName = "PageTitle";
    private string _pageTitle = null;
    public string PageTitle
    {
      get
      {
        return _pageTitle;
      }
      set
      {
        if (_pageTitle == value)
        {
          return;
        }

        var oldValue = _pageTitle;
        _pageTitle = value;
        RaisePropertyChanged(PageTitlePropertyName);
      }
    }


    public const string PageUriPropertyName = "PageUri";
    private Uri _pageUri = null;
    public Uri PageUri
    {
      get
      {
        return _pageUri;
      }
      set
      {
        if (_pageUri == value)
        {
          return;
        }

        var oldValue = _pageUri;
        _pageUri = value;

        // Update bindings, no broadcast
        RaisePropertyChanged(PageUriPropertyName);
      }
    }
    #endregion

    #region Commanding and Messaging
    public RelayCommand<Uri> NavigateToPageCommand
    {
      get;
      private set;
    }

    protected void SendNavigationRequestMessage(Uri uri)
    {
      Messenger.Default.Send<Uri>(uri, "PageNavRequest");
    }
    #endregion
  }
}

The PageItem Model class is broken up into two sections, one for the properties and the other for the Commanding and Messaging support available in MVVM Lite. The PageItemViewModel class has two properties, PageTitle and PageUri. The PageUri points to the appropriate XAML file to navigate to for the PageTitle property value.

The Commanding and Messaging support is pretty simple code. The NavigateToPageCommand is what the MainPage.xaml View DataBinds to when the ListBoxItem is clicked via the MVVM Light EventToCommand Behavior. The NavigateToPageCommand RelayCommand<T> is connected to the MVVM Light Messaging infrastructure via this code in the PageItemViewModel constructor:

NavigateToPageCommand = new RelayCommand<Uri>(
  param => SendNavigationRequestMessage(param));

The NavigateToPageCommand takes an Uri and passes it to the SendNavigationRequestMessage. This is a bit of indirection, as the Uri property comes from the ListBox's template and then is sent back to the View via the MVVM Light Messaging infrastructure but it does promote decoupling of the UI from the ViewModel.

The MainPage.xaml View receives the message via this line of code added to the PhoneApplicationPage constructor and uses an inline Lambda statement to call the NavigationService:

Messenger.Default.Register<Uri>(
        this, "PageNavRequest",
        (uri) => NavigationService.Navigate(uri));

I mentioned earlier that the DataTemplate data binds to the NavigateToPageCommand. Here is the ItemTemplate="{StaticResource PageItemTemplate}" for the MainPage.ListBox control:

<DataTemplate x:Key="PageItemTemplate">
  <Grid Margin="0,15">
    <TextBlock Margin="0,0,1,0" TextWrapping="Wrap" Text="{Binding PageTitle}"
       d:LayoutOverrides="Width, Height" Style="{StaticResource PhoneTextLargeStyle}">
      <Custom:Interaction.Triggers>
        <Custom:EventTrigger EventName="MouseLeftButtonDown">
          <GalaSoft_MvvmLight_Command:EventToCommand Command="{Binding NavigateToPageCommand,
Mode=OneWay}" CommandParameter="{Binding PageUri}"/>
        </Custom:EventTrigger>
      </Custom:Interaction.Triggers>
    </TextBlock>
  </Grid>
</DataTemplate>

The EventToCommand behavior is dragged from the Assets | Behaviors section and is dropped on the TextBlock. Figure 7–2 shows the Properties window for the EventToCommand behavior.

images

Figure 7–2. EventToCommand properties in Expression Blend

In Figure 7–2, the Command value is data bound to the NavigateToPageCommand property on the PageItemViewModel class. The CommandParameter is data bound to PageUri property of the selected item from the MainViewModel Pages collection of type PageItemViewModel.

This was a detailed walkthrough of relatively simple functionality, but it demonstrates the powerful capabilities of MVVM Light's Commanding and Messaging infrastructure to support decoupling UI from the ViewModel via Silverlight's data binding framework. In addition, I recommend exploring the INotifyDataErrorInfo and IDataErrorInfo interfaces now included as part of the Windows Phone OS SDK for error tracking and reporting.

The next section covers how to access syndicated services like RSS feeds from Windows Phone.Syndicated Services

Silverlight 3 and later on the desktop includes excellent support for easily consuming syndicated feeds like RSS, ATOM, and formatted data feeds. Unfortunately, the System.ServiceModel.Syndication.dll is not available in the Silverlight for Windows Phone SDK. You can simply add a reference to the Silverlight 4 version of the assembly for Windows Phone OS 7.1 (Mango), but it is just as easy to use LINQ to XML to parse syndication feeds.

For the AdvancedMVVM project, a page named SyndicatedServices.xaml is added to the View folder and a ViewModel named SyndicatedServicesViewModel is added to the ViewModel folder. The SyndicatedServicesViewModel is added to the ViewModelLocator class in the ViewModel folder.

Clicking the syndicated services label shown in Figure 7–3 navigates to the page. The page is configured with a ListBox named feedListBox and added to the ContentPanel in SyndicatedServices.xaml. The page automatically loads the data in the SyndicatedServicesViewModel when it is created.

As before, when the SyndicatedServicesView.xaml data binds to the SyndicatedServicesViewModel class, the SyndicatedServicesViewModel ViewModel retrieves the data via the FeedItemsDataService class.

The ClientDownloadStringCompleted event has code to parse the response, load the data into an XDocument, and parse the feed into a collection of FeedItem objects that is data bound to the feedListBox.ItemsSource property. Listing 7–2 has the FeedItemDataService class.

Listing 7–2. FeedItem Class File

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Xml.Linq;

namespace AdvancedMVVM.Model
{
  public class FeedItemsDataService : IFeedItemsDataService
  {
    private const string ServiceUri =
"http://public.create.msdn.com/Feeds/CcoFeeds.svc/CmsFeed?group=Education Catalog List";
    public void GetFeedItems(Action<IList<FeedItem>, Exception> callback)
    {
      var client = new WebClient();
      client.DownloadStringCompleted += ClientDownloadStringCompleted;
      client.DownloadStringAsync(new Uri(ServiceUri), callback);
    }
    private static void ClientDownloadStringCompleted(object sender,
DownloadStringCompletedEventArgs e)
    {
      var callback = e.UserState as Action<IList<FeedItem>, Exception>;

      XDocument doc = XDocument.Parse(e.Result);
      List<FeedItem> feedItems = (from results in doc.Descendants("item")
                                  select new FeedItem
                                  {
                                    Title = results.Element("title").Value.ToString(),
                                    Link = new Uri(results.Element("link").Value.ToString(),
UriKind.Absolute),
                                    Description =
results.Element("description").Value.ToString()
                                  }).ToList<FeedItem>();

      if (callback == null)
      {
        return;
      }

      if (e.Error != null)
      {
        callback(null, e.Error);
        return;
      }
      callback(feedItems, null);
    }
  }
}

Since the FeedItemDataService marshals the data via an Asynchronous call back to the SyndicatedServicesViewModel class in the constructor, the Dispatcher. BeginInvoke method is not necessary in order to marshal the data back to the UI thread.

Once the data is returned to the SyndicatedServicesViewModel class it populates the FeedItems property of type List<FeedItem> FeedItems, In the SyndicatedServicesVew.xaml class, a simple DataTemplate is configured on feedListBox.ItemTemplate that binds to FeedItem object properties:

<ListBox.ItemTemplate>
  <DataTemplate>
    <StackPanel Margin="0,0,0,30">
      <TextBlock Text="{Binding Title}"  Margin="0,0,12,0" FontSize="24" FontFamily="Segoe WP
Semibold" />
      <TextBlock Text="{Binding Description}" />
      <HyperlinkButton  NavigateUri="{Binding Link}" FontFamily="Segoe WP SemiLight"
        TargetName="_blank"         FontSize="18.667" FontStyle="Italic"  
        Content="More..." HorizontalAlignment="Left"/>
    </StackPanel>
  </DataTemplate>
</ListBox.ItemTemplate>

Figure 7–3 shows the UI when the SyndicatedServicesVew.xaml loads

images

Figure 7–3. AppHub RSS feed parsed and loaded

If you click More... it loads the URL for the content into the Web Browser. Clicking the Back hardware button takes you back to the page. Notice in Figure 7–3 that the Description is formatted as HTML. I can change the DataTemplate to have a WebBrowser control instead of a TextBlock but when I do this, the HTML does not render.

The WebBrowser control supports navigating to an HTML fragment using the NavigateToString Method, but it does not have a simple HTML property that we can data bind, too. We would rather not write a lot of code to data bind to the HTML fragment. When working in Silverlight, whenever a control is missing a property, you can always add it using XAML Attached properties. The goal is to have a simple HTML property of type text for the HTML fragment from the feed that gets loaded via the NavigateToString method. Listing 7–3 shows the attached property class.

Listing 7–3. WebBrowserHTMLProp Code File

public static class WebBrowserHTMLProp
{
  public static readonly DependencyProperty HtmlProperty =
    DependencyProperty.RegisterAttached(
      "Html", typeof(string), typeof(WebBrowserHTMLProp),
      new PropertyMetadata(OnHtmlPropChanged));

  public static string GetHtml(DependencyObject dependencyObject)
  {
    return (string)dependencyObject.GetValue(HtmlProperty);
  }
    
  public static void SetHtml(DependencyObject dependencyObject, string value)
  {
    dependencyObject.SetValue(HtmlProperty, value);
  }

  private static void OnHtmlPropChanged(DependencyObject dependencyObject,
    DependencyPropertyChangedEventArgs e)
  {
    var webBrowser = dependencyObject as WebBrowser;

    if (webBrowser == null)
      return;

    var html = e.NewValue.ToString();
    webBrowser.NavigateToString(html);
  }
}

It is mostly boilerplate code with the important code in the OnHtmlPropChanged method where the HTML is loaded into the WebBrowser via NavigateToString. Unfortunately, because this code is part of the DataTemplate, an error is thrown stating that “You cannot call WebBrowser methods until it is in the visual tree.” Because the DataTemplate is composited and then added to the Visual Tree, the Attached Property does not help us here but it can assist in situations where the WebBrowser control is embedded into the static XAML. Instead a TextBlock is placed within the DataTemplate. Figure 7–3 shows the output. Clicking “More…” loads the link into IE Mobile.

An alternative approach for this feed would be to just display the Title in the ListBox and then navigate to another page that displays the Description details in a WebBrowser control that is part of the static XAML visual tree of the page.

In this section I covered how to load an RSS feed using LINQ to XML. We will explore adding additional functionality for displaying feed data, showing progress, and lazy loading images in the following sections.

Showing Progress

In this section, I demonstrate how to have your ViewModel drive UI, such as when to show progress bars or not, via data binding. It is a quick example based on the previous example that downloads the AppHub feed but the concept is very important to help you understand how to put more code into your ViewModels and less code in your View.

The SyndicatedServicesViewModel.cs code file is copied and renamed ShowProgressViewModel as well as the constructor. The new ShowProgressViewModel property is added to the ViewModelLocator just as before, and a new View named ShowProgress.xaml is created and data bound to the ShowProgressViewModel. Run the project and it should work just as the previous sample, returning the feed items.

A new Boolean property named ShowProgressBar is added to the ShowProgressViewModel class. The ShowProgressBar property is set to true just before invoking the IFeedItemsDataService.GetFeedItems method in the ShowProgressViewModel .DownloadAppHubFeed method.

public void DownloadAppHubFeed()
{
  ShowProgressBar = true;
  System.Threading.Thread.Sleep(3000);

  _feedItemsService.GetFeedItems(
      (feedItems, error) =>
      {
        if (error != null)
        {
          // Report error here
          return;
        }
        FeedItems = (List<FeedItem>)feedItems;
        ShowProgressBar = false;
      });
}
  …

When the asynchronous call returns and the value assigned to the FeedItems property, ShowProgressBar is set to false. This completes the changes in the ViewModel. The progress bar should be enabled right before making the web call and then disabled once the new data is data bound to the UI. Again the property must be modified on the UI thread as shown above in the DownloadAppHubFeed code snippet. The FeedItemsDataService class performs the download and uses LINQ to XML to create the feedItems collection. Here is the FeedItemsDataService with the GetFeedItems implementation:

public class FeedItemsDataService : IFeedItemsDataService
{
  private const string ServiceUri =
    "http://public.create.msdn.com/Feeds/CcoFeeds.svc/CmsFeed?group=Education Catalog List";
  public void GetFeedItems(Action<IList<FeedItem>, Exception> callback)
  {
    var client = new WebClient();
    client.DownloadStringCompleted += ClientDownloadStringCompleted;
    client.DownloadStringAsync(new Uri(ServiceUri), callback);
  }
  private static void ClientDownloadStringCompleted(object sender,
    DownloadStringCompletedEventArgs e)
  {
    var callback = e.UserState as Action<IList<FeedItem>, Exception>;

    XDocument doc = XDocument.Parse(e.Result);
    List<FeedItem> feedItems = (from results in doc.Descendants("item")
                                select new FeedItem
                                {
                                  Title = results.Element("title").Value.ToString(),
                                  Link = new Uri(results.Element("link").Value.ToString(),
                                    UriKind.Absolute),
                                  Description =
                                      results.Element("description").Value.ToString()
                                }).ToList<FeedItem>();

    if (callback == null)
    {
      return;
    }

    if (e.Error != null)
    {
      callback(null, e.Error);
      return;
    }
    callback(feedItems, null);
  }

}In the ShowProgress.xaml page, add a ProgressBar control that applies to the width of the screen in the middle of the page. The ProgressBar should look like the native WP7 ProgressBar, otherwise known as “the flying dots.” Luckily the Silverlight for Windows Phone Toolkit has been updated to include two new controls, TiltEffect and PerformanceProgressBar. Previously you had to create a customized version of the built-in progress bar in order to have good UI thread performance.

Once the latest Silverlight toolkit for WP7 is installed, add PerformanceProgressBar to the Toolbox window in Visual Studio and drag the control on to the ShowProgress.xaml View. Switch over to Expression Blend and set the HorizontalAlignment and VerticalAlignment to Stretch and reset Margin to zero. Next set the IsIndeterminate Property to True and you can see the flying dots at design-time in Expression Blend. We only want to see the flying dots when loading data so let's data bind the LoadingDataPerfProgressBar's Indeterminate property to the ShowProgressViewModel.ShowProgressBar property. Here is the XAML for the PerformanceProgressBar from ShowProgress.xaml:

<toolkit:PerformanceProgressBar Name="performanceProgressBar1" Grid.Row="1" Margin="0"
  VerticalAlignment="Stretch" Padding="0" HorizontalAlignment="Stretch"
  IsIndeterminate="{Binding ShowProgressBar}" />

By default, the data is obtained by ShowProgressViewModel via the constructor when the application loads and the ShowProgressViewModel is created. We modify the constructor to not automatically call the DownloadAppHubFeed() method. The ShowProgress.xaml View is modified to have an Application Bar button that manually downloads the feed. Clicking the Application Bar button to start the download will enable the progress bar, download the feed, and then disable the progress bar. Figure 7–4 shows the flying dots in the emulator.

images

Figure 7–4. PerformanceProgressBar in action

This example demonstrates the power of XAML and its data binding infrastructure to allow the ViewModel non-visual class to drive the UI based on the applications current state. This technique is critical to effective MVVM development and can be extended to other data-driven UI to indicate state and display additional UI when needed.

Lazy Load Images

When developing for a mobile device with constrained networking and hardware capabilities relative to a desktop computer, you sometimes have to take additional steps beyond the “default” programming model. As an example, loading a ListBox with data and images from a remote server can peg the UI thread resulting in a poor user experience for scrolling and animations. Anytime work can be taken off of the UI thread is a win for performance.

A Silverlight for Windows Phone team member David Anson blogged here about a way to offload image loading to a background thread that results in much better UI performance:

http://blogs.msdn.com/b/delay/archive/2010/09/02/keep-a-low-profile-lowprofileimageloader-
helps-the-windows-phone-7-ui-thread-stay-responsive-by-loading-images-in-the-background.aspx

In the blog post, Dave introduces the LowProfileImageLoader class to address the very specific issue of loading lots of images from the web simultaneously. The LowProfileImageLoader slows the loading of images but the UI remains responsive as images are loaded because the UI thread is not slammed waiting for images to load and then data bind.

To test out the LowProfileImageLoader, let's work with the Netflix API. I picked this API because it has an interesting set of images (movie art) in a well-documented API. The API is available at developer.netflix.com.

images Note You must register an account to use the Netflix APIs at http://developer.netflix.com/member/register.

We will use the OData Client Library for Windows Phone that we covered in Chapter 4 to access the Netflix API. As before we add a reference to System.Data.Services.Client.dll. With built-in support for Odata in Windows Phone OS 7.1.(Mango) developer tools, I simply right-click on References in the AdvancedMVVM Project and select “Add Service Reference.” I enter this URL in the dialog:

http://odata.netflix.com/Catalog/

I next click GO, provide a namespace name of NetflixAPI, and click OK. This adds an OData service reference to the AdvancedMVVM project. Add using AdavncedDataBinding.NetflixAPI to bring the Odata service into the LazyLoadViewModel and NetflixCatalogODataService classes.

The OData access is wired into the LazyLoadViewModel.cs class, which is a copy of the ShowProgressViewModel. It follows the GalaSoft MVMM Light 4.0 pattern in the LazyLoadViewModel that we implemented previously:

public void DownloadNetflixCatalog()
{
  ShowProgressBar = true;
  _netflixCatalogfeedItemsService.GetNetflixCatalogFeedItems(
      (topMovieTitles, error) =>
      {
        if (error != null)
        {
          // Report error here
          return;
        }
        TopMovieTitles = (DataServiceCollection<Title>)topMovieTitles;
        ShowProgressBar = false;
      });
}

Since the code needs to return a DataServiceCollection<Title> collection, the INetflixCatalogOdataService interface is modified accordingly:

using System;
using System.Data.Services.Client;
using AdvancedMVVM.NetflixAPI;

namespace AdvancedMVVM.Model
{
  public interface INetflixCatalogODataService
  {
    void GetNetflixCatalogFeedItems(Action<DataServiceCollection<Title>, Exception> callback);
  }
}

The NetflixCatalogODataService implementation class is where things get a little more interesting. This class must handle the asynchronous call back a little differently because of API differences with OData.

private NetflixCatalog ODataContext;
private Action< DataServiceCollection< Title>, Exception> _callback;
private DataServiceCollection< Title> _topMovieTitles;

I need to save a copy of the callback parameter passed into GetNetflixCatalogFeedItems at the class level so that I can use it when processing the LoadCompleted event handler;


private const string ServiceUri = "http://odata.netflix.com/Catalog/";
public void GetNetflixCatalogFeedItems(Action<DataServiceCollection<Title>, Exception>
callback)
{
  ODataContext = new NetflixCatalog(
    new Uri(ServiceUri,
          UriKind.Absolute));
  //Save a reference to the callback in this case
  //Cannot pass it via the LoadAsync method for OData
  _callback = callback;
  _topMovieTitles = new DataServiceCollection<Title>(ODataContext);
  _topMovieTitles.LoadCompleted +=
    new EventHandler<LoadCompletedEventArgs>(topMovieTitles_LoadCompleted);
  _topMovieTitles.LoadAsync(new Uri("/Titles()", UriKind.Relative));
}
private void topMovieTitles_LoadCompleted(object sender, LoadCompletedEventArgs e)
{
  if (_topMovieTitles == null)
  {
    return;
  }

  if (e.Error != null)
  {
    _callback(_topMovieTitles, e.Error);
    return;
  }
  _callback(_topMovieTitles, null);
}

Once you understand the Data Service pattern, it is fairly easy to customize it to your requirements so don't be tempted to avoid creating a Data Service for your View Model.

The LazyLoad.xaml View page is modified to include a ListBox with a simple DataTemplate that shows the movie title and image. Figure 7–5 shows the UI.

images

Figure 7–5. Netflix API top titles via OData

Not every movie title has box art, but many do. Let's now modify this application to use the LowProfileImageLoader class to asynchronously display the images. Download the source code at the blog post link above and grab the PhonePerformance.dll. Add a reference to it in the AdvancedMVVM project and then add a namespace reference to LazyLoadImage.xaml:

xmlns:delay="clr-namespace:Delay;assembly=PhonePerformance"

Next update the NetflixTopTitleDataTemplate so that the image is not loaded from the built-in Source property but instead is loaded using this XAML:

<Image delay:LowProfileImageLoader.UriSource="{Binding BoxArt.SmallUrl}"
HorizontalAlignment="Left" Stretch="UniformToFill" Width="150"/>

The performance difference is much more obvious on a physical device over 3G, but as you can see it is pretty trivial to add support for the LowProfileImageLoader to see if it can help with any performance issues related to image loading in your applications.

Data Bind to Anything

There are situations where you need to data bind to a value and the value type or format does not quite line up with what you need. The IValueConverter interface allows your application to data bind to just about anything including data that does not match the type expected for a particular control.

As an example, let's say you have a field that takes values of either Yes or No. You could use a ListPicker but a CheckBox makes more sense since it is just two values. You can implement IValueConverter to convert true to a Yes value and No value to false (and vice versa) while still using standard data binding mechanisms.

In this section, a new view is added named DatabindToAnything.xaml that data binds to the existing ShowProgressViewModel. Since the work is at the View level for this demo, there is no need to create a new ViewModel and Data Service.

I create a custom IValueConverter named HtmlToImageUriConverter. This very simple converter parses the AppHub Feed html, previously displayed as raw html text, into a URL that points to an image related to the content.

The URL is extracted from the HTML. Listing 7–4 shows the code for the converter.

Listing 7–4. WebBrowserHTMLProp Code File

using System;
using System.Windows.Data;

namespace AdvancedMVVM.Converters
{
  public class HtmlToImageUriConverter : IValueConverter
  {
    public object Convert(object value, Type targetType,
      object parameter, System.Globalization.CultureInfo culture)
    {
      string html = (string)value;
      string imageUrl = "";
      if (null != html)
      {
        string[] strings = html.Split('"'),
        if (strings.Length > 3)
          imageUrl = strings[3].Trim();
      }
      return imageUrl;
    }

    public object ConvertBack(object value, Type targetType,
      object parameter, System.Globalization.CultureInfo culture)
    {
      throw new NotImplementedException();
    }
  }
}

The converter is somewhat brittle in that it depends on the URL value to be the fourth string when split, but one wouldn't expect the feed format to change very often, and it makes for an interesting sample. The converter is made available to the DatabindToAnything.xaml View via an xml namespace import:

xmlns:converters="clr-namespace:AdvancedMVVM.Converters"

The converter is added as a resource in this XAML:

<phone:PhoneApplicationPage.Resources>
  <converters:HtmlToImageUriConverter x:Key="HtmlToImageConverter"/>
</phone:PhoneApplicationPage.Resources>

Applying the converter is a simple modification in XAML or visually in Expression Blend. Here is the updated ListBox DataTemplate:

<DataTemplate>
  <StackPanel Margin="0,0,0,30">
    <TextBlock Text="{Binding Title}"  Margin="0,0,12,0" Style="{StaticResource PhoneTextLargeStyle}" />
    <Image delay:LowProfileImageLoader.UriSource=
        "{Binding Description, Converter={StaticResource HtmlToImageConverter}}"  
          Margin="0,6,0,4" Width="100" HorizontalAlignment="Left" />
    <HyperlinkButton  NavigateUri="{Binding Link}" FontFamily="Segoe WP SemiLight" TargetName="_blank"
        FontSize="18.667" FontStyle="Italic"  Content="More..." HorizontalAlignment="Left"
Margin="-12,0,0,0"/>
  </StackPanel>
</DataTemplate>

The Binding.Converter parameter is configured to the added StaticResource to apply the converter for the Image object. Note that this sample also implements the LowProfileImageLoader for better performance as well as the PerformanceProgressBar to give better visual status to the user. Figure 7–6 shows the resulting output with a nice image instead of the raw HTML that was displayed previously.

images

Figure 7–6. AppHub feed with articles and corresponding images

In this section, data binding concepts are extended to more real world scenarios while also incorporating MVVM concepts. The next section provides a quick tour of available encryption services in Windows Phone.

Encryption Services

A mobile device no matter what operating system or platform is inherently insecure. A mobile device is not locked safely in a server room within a secure data center like a web server. A mobile device is easily misplaced or stolen. For pretty much any computing platform, loss of physical control is loss of security. Therefore, it is important to minimize storing sensitive data on a mobile device.

However, there are scenarios in which it is convenient to store sensitive data on a device. As an example, a user would never use a video player application that requires the user to memorize and enter a long key to unlock DRM'd content. It is a risk management challenge that weighs the benefits and inconvenience provided by any given security solution. This section in the MSDN documentation provides a detailed overview of balancing security with convenience:

http://msdn.microsoft.com/en-us/library/ff402533(v=VS.92).aspx

In the next few sections, I provide an overview of the capabilities available in the Windows Phone platform.

Secure Sockets Layer

Many mobile applications transfer data to and from the server-side of a mobile application solution. In cases where sensitive data must be transported always use Secure Sockets Layer (SSL) to protect data in transit. This link lists SSL root certificates that ship on Windows Phone:

http://msdn.microsoft.com/en-us/library/gg521150(v=VS.92).aspx

Once sensitive data is on the device, use available encryption services on the platform to secure the data. Windows Phone supports the following cryptographic algorithms:

  • AES
  • HMACSHA1
  • HMACSHA256
  • Rfc2898DeriveBytes
  • SHA1
  • SHA256

The next covers how to use these algorithms to securely store data on Windows Phone.

Securely Encrypting Data

We add a project named EncryptingData to the Chapter 7 solution based on the GalaSoft MVVM project template. The UI takes some data to encrypt, a password and salt to perform the encryption, and then a couple of TextBlock controls to show the encrypted data as well as results from decrypting.

The Application Bar is enabled with four buttons:

  • Encrypt Data
  • Decrypt Data
  • Save to Isolated Storage
  • Load from Isolated Storage

All the UI data fields and event methods have corresponding properties on the MainViewModel class. The encryption operations are actually performed on MainViewModel properties with data binding bringing the results to the UI. Figure 7–7 shows the UI layout.

images

Figure 7–7. EncryptData project UI

The following using statements are added to the MainViewModel class to provide the necessary access for stream-based processing and encryption:

using System.IO;
using System.IO.IsolatedStorage;
using System.Security.Cryptography;
using System.Text;

In addition to the ViewModel data fields backing the UI MainPage.xaml View, four additional methods are added to MainViewModel:

  • EncryptData
  • DecryptData
  • SaveEncryptedDataToIsolatedStorage
  • LoadEncryptedDataFromIsolatedStorage

Each method performs the task described by the method name. These four methods are accessed via the Application Bar buttons in the View as shown in Listing 7–5.

Listing 7–5. MainPage.Xaml.cs Code File

using System.Windows;
using System.Windows.Controls;
using EncryptingData.ViewModel;
using Microsoft.Phone.Controls;

namespace EncryptingData
{
  public partial class MainPage : PhoneApplicationPage
  {
    MainViewModel vm;
    // Constructor
    public MainPage()
    {
      InitializeComponent();
      vm = this.DataContext as MainViewModel;
    }

    private void EncryptAppBarBtn_Click(object sender, System.EventArgs e)
    {
      vm.EncryptData();
    }

    private void DecryptAppBarBtn_Click(object sender, System.EventArgs e)
    {
      vm.DecryptData();
    }

    private void SaveIsoAppBarBtn_Click(object sender, System.EventArgs e)
    {
      vm.SaveEncryptedDataToIsolatedStorage();
    }

    private void LoadIsoAppBarBtn_Click(object sender, System.EventArgs e)
    {
      vm.LoadEncryptedDataFromIsolatedStorage();
    }

    private void PasswordBox_LostFocus(object sender,
      System.Windows.RoutedEventArgs e)
    {
      if (((PasswordBox)sender).Password.Length < 8)
        MessageBox.Show("Salt Value must be at least eight characters.",
          "Minimum Length Error",MessageBoxButton.OK);
    }
  }
}

The four encryption related methods in MainViewModel code are shown in Listing 7–6.

Listing 7–6. Methods from MainViewModel.cs Code File

public void EncryptData()
{
  using (AesManaged aes = new AesManaged())
  {
    Rfc2898DeriveBytes rfc2898 = new Rfc2898DeriveBytes(Password,
      Encoding.UTF8.GetBytes(SaltValue), 10000);
    aes.Key = rfc2898.GetBytes(32);
    aes.IV = rfc2898.GetBytes(16);
    using (MemoryStream memoryStream = new MemoryStream())
    {
      using (CryptoStream cryptoStream = new CryptoStream(memoryStream,
        aes.CreateEncryptor(), CryptoStreamMode.Write))
      {
        //Encrypt Data with created CryptoStream
        byte[] secret = Encoding.UTF8.GetBytes(DataToEncrypt);
        cryptoStream.Write(secret, 0, secret.Length);
        cryptoStream.FlushFinalBlock();
        aes.Clear();
        //Set values on UI thread
        Deployment.Current.Dispatcher.BeginInvoke(() =>
        {
          EncryptedData = Convert.ToBase64String(memoryStream.ToArray());
        });
      }
    }
  }
}

public void DecryptData()
{
  MemoryStream memoryStream = null;

  using (AesManaged aes = new AesManaged())
  {
    //Generate Key and IV values for decryption
    Rfc2898DeriveBytes rfc2898 = new Rfc2898DeriveBytes(Password, Encoding.UTF8.GetBytes(SaltValue), 10000);
    aes.Key = rfc2898.GetBytes(32);
    aes.IV = rfc2898.GetBytes(16);

    using (memoryStream = new MemoryStream())
    {
      using (CryptoStream cryptoStream =
        new CryptoStream(memoryStream, aes.CreateDecryptor(),
                          CryptoStreamMode.Write))
      {
        //Decrypt Data
        byte[] secret = Convert.FromBase64String(EncryptedData);
        cryptoStream.Write(secret, 0, secret.Length);
        cryptoStream.FlushFinalBlock();
        byte[] decryptBytes = memoryStream.ToArray();
        aes.Clear();
        //Update values on UI thread
        Deployment.Current.Dispatcher.BeginInvoke(() =>
        {
          DecryptedData = Encoding.UTF8.GetString(decryptBytes, 0, decryptBytes.Length);  
          DataToEncrypt = Encoding.UTF8.GetString(decryptBytes, 0, decryptBytes.Length);           
        });
      }
    }
  }
}
    
public void SaveEncryptedDataToIsolatedStorage()
{
  //Save secret to Application Settings
  if (EncryptedData != "")
  {
    if (IsolatedStorageSettings.ApplicationSettings.Contains(StorageKeyName))
    {
      IsolatedStorageSettings.ApplicationSettings[StorageKeyName] =
        EncryptedData;
    }
    else
    {
      IsolatedStorageSettings.ApplicationSettings.Add(
        StorageKeyName, EncryptedData);
    }
  }
}

public void LoadEncryptedDataFromIsolatedStorage()
{
  //Retrieve secret from Application Settings
  if (IsolatedStorageSettings.ApplicationSettings.Contains(StorageKeyName))
  {
    Deployment.Current.Dispatcher.BeginInvoke(() =>
    {
      EncryptedData =
        IsolatedStorageSettings.ApplicationSettings[StorageKeyName].ToString();
    });
  }
  else
  {
    Deployment.Current.Dispatcher.BeginInvoke(() =>
    {
      EncryptedData = "";
    });
  }
}

The code generates a Key for AES based on the Password and Salt values provided by the user. The Rfc2898DeriveBytes class is used to generate the HMACSHA1 random number (iteration count of 1,000) for the AES Key and IV values. What's handy with implementing encryption via the ViewModel is that it is easily reusable. The MainViewModel class has zero dependencies on UI.

.

Saving confidential data in a phone's isolated storage is not secure. Encrypting the data will not increase the security if the decryption key resides on the phone, no matter how well the key is hidden.

In Windows Phone 7.5, an encryption method is made available to developers via the unmanaged DPAPI. DPAPI solves the problem of explicitly generating and storing a cryptographic key by using the user and phone credentials to encrypt and decrypt data. I add DPAPI support to the project via the System.Securty.Cryptography.ProtectedData static class.

images Tip If upgrading a project to Windows Phone 7.5, you must add a reference to the mscorlib.Extensions assembly for the ProtectedData class to be available.

A new bool property named SavePasswordAndSalt is added to the MainViewModel class. A CheckBox control with the Content set to “Check to save the Password and Salt” is added to the MainPage.xaml UI. This allows you to maintain the code that determines whether to save or unsave the credentials on the device to the MainViewModel class in Listing 7-7.

Listing 7–7. Encryption Services MainViewModel.cs Methods

public const string SavePasswordAndSaltPropertyName = "SavePasswordAndSalt";
private bool _savePasswordAndSalt = false;
public bool SavePasswordAndSalt
{
  get
  {
    return _savePasswordAndSalt;
  }

  set
  {
    if (_savePasswordAndSalt == value)
    {
      return;
    }
    //Do not check the box unless
    //Password and Salt values are configured
    if ((Password == String.Empty) ||
        (Salt == String.Empty))
    {
      _savePasswordAndSalt = false;
      RaisePropertyChanged(SavePasswordAndSaltPropertyName);
      return;
    }
    _savePasswordAndSalt = value;
    RaisePropertyChanged(SavePasswordAndSaltPropertyName);
    ProcessSaltAndPasswordValues();
  }
}

If both the Password and Salt TextBox controls have data and the user checks the savePasswordandSaltCheckBox CheckBox control, the ProcessSaltAndPasswordValues method is called via data binding in the Setter for the SavePasswordAndSalt property. This method checks whether the user wants to save settings or not based on whether SavePasswordAndSalt is true or false. The password and salt are encrypted and then added to the Application's IsolatedStorageSettings collection. If the user does not want to persist the Password and Salt values, the code checks if the Password and Salt values are saved and then removes them from IsolatedStorageSetings:

private void ProcessSaltAndPasswordValues()
{
  if (SavePasswordAndSalt)
  {
    if (settings.Contains("Password"))
      settings.Remove("Password");
    if (settings.Contains("Salt"))
      settings.Remove("Salt");
    //Set Entropy to null in the Protect method for this demo.  
    //You could instead collect a user PIN and use that value
    //for entropy to provide stronger security.
    settings.Add("Password", ProtectedData.Protect(Encoding.UTF8.GetBytes(Password), null));
    settings.Add("Salt", ProtectedData.Protect(Encoding.UTF8.GetBytes(Salt), null));
  }
  else
  {
    if (settings.Contains("Password"))
      settings.Remove("Password");
    if (settings.Contains("Salt"))
      settings.Remove("Salt");
  }
}

The last item is initializing the UI with the Password and Salt values when first launching the UI. The MainViewModel Constructor checks to see if the Password and Salt are persisted in settings. The code first has to convert the value to a byte[] array and then decrypt using the ProtectedData.Unprotect method, returning a byte[] array of characters, which is then turned into a string. Here is the code:

public MainViewModel()
{
  if (!ViewModelBase.IsInDesignModeStatic)
  {
    if (settings.Contains("Password"))
    {
      byte[] encryptedPasswordBytes = (byte[])settings["Password"];
      byte[] decryptedPasswordBytes = ProtectedData.Unprotect(encryptedPasswordBytes, null);
      Password = Encoding.UTF8.GetString(decryptedPasswordBytes, 0, decryptedPasswordBytes.Length);

    }
    if (settings.Contains("Salt"))
    {
      byte[] encryptedSaltBytes = (byte[])settings["Salt"];
      byte[] decryptedSaltBytes = ProtectedData.Unprotect(encryptedSaltBytes, null);
      Salt = Encoding.UTF8.GetString(decryptedSaltBytes, 0, decryptedSaltBytes.Length);
    }
    if (Password != String.Empty)
      SavePasswordAndSalt = true;
  }
}

Testing this out, you will find that the Password and Salt values are persisted correctly. When testing the UI overall, be sure to follow the pattern of filling in the fields, clicking Encrypt, and then save to isolated storage. Likewise, be sure to click load and then decrypt if launching the app after previously encrypting data.

Data encryption needs to be easily accessible and of course secure. The new ProtectedData class available in Windows Phone OS 7.1 fits the bill for your encryption needs. In the next section we switch gears, covering phone integration with the picture hub and images.

Working with Images on Windows Phone

Windows Phone supports BMP, TIF, GIF, JPEG, and PNG formats in general. However, from a development perspective JPG and PNG are supported for viewing in the Silverlight Image control. The JPG and PNG formats are recommended for displaying images within an application. JPG is preferred when the image will remain opaque. The PNG format supports transparency and is preferred for this scenario.

Images Classes

The Image control is the Silverlight class for displaying images in applications. A related Silverlight object that us very useful is the ImageBrush object. As an example, setting the background on a Panorama object is via an ImageBrush like this:

<controls:Panorama.Background>
  <ImageBrush  ImageSource="PanoramaBackground.png" Opacity="0.785" />
</controls:Panorama.Background>

Note that for the Opacity value to have an effect with an ImageBrush object, the image pointed to via the ImageSource property must be a .PNG file.

The BitmapImage is the utility class that provides the class for the Image.Source property. The BitmapImage supports the JPEG and PNG format. The BitmapImage has two constructors, one that takes a URI and the other that takes a Stream. You can set Image.Source via a BitmapImage without having to save the file to disk, which is better for performance.

The WriteableBitmap class is a versatile class that provides a BitmapSource that can be written to and updated. The WriteableBitmap class has properties to retrieve DPI, Format, Metadata, Palette, and Pixel size. The WriteableBitmap class provides methods to CopyPixels and to WritePixels into the WriteableBitmap. It is possible to generate an image within your application using the WriteableBitmap's constructor that takes a UIElement:

WriteableBitmap bmp =
new WriteableBitmap(element, transform)

The WriteableBitmap class allows you to generate images on the client when needed. One example would be if you need to add branding to an image or somehow modify it before rendering, such as when creating a music+video hub application, covered later in this chapter.

This is a short section highlighting key classes, properties, and capabilities when working with images. These classes are leveraged in the next couple of sections including the next section that demonstrates how to work with the media library on Windows Phone.

The Windows Phone Media Library

The MediaLibrary class provides access to the media available on the device as part of the music+video hub and corresponding Zune service. So if a user rips their CD collection, purchases some music from Zune, and so on, and the content is available in the music+video hub on the device, the MediaLibrary class provides the ability to enumerate and index the content but you cannot add media content to the Media Library except for images, which can be added to the Pictures collection. If you download audio or video content in your application you can only save it to your application's isolated storage area.

The MediaLibrary class does not provide access to media content stored in individual third-party application's isolated storage area. Here is a list of the available media collections on Windows Phone:

  • Albums: AlbumCollection class
  • Artists: ArtistCollection class
  • Genres: GenreCollection class
  • Pictures: PictureCollection class
  • Playlists: PlaylistCollection class
  • Songs: SongCollection class

To gain access to the MediaLibrary class add a reference to the Microsoft.Xna.Framework assembly. The MediaLibrary class was originally available as part of the XNA Game Studio development environment for the Zune music player, and that is how it is made available on Windows Phone. Add a using clause for the Microsoft.Xna.Framework.Media namespace to get started.

images Note To run this sample while debugging, shutdown Zune and run the WPConnect.exe tool from a command-line to enable debugging when accessing media content on a physical device.

The Chapter 7 sample code has a project named WorkingWithMediaLibrary to demonstrate how to access media content. The project is once again based on the MVVM Light SDK and data bound to the UI. This time we use a Panorama control as the main page with a background based on a photo from a recent vacation. There are four PanoramaItem panels:

  • Albums
  • Artists
  • Playlists
  • Songs

Each PanoramaItem includes a ListBox that data binds to a collection of these items. You might be tempted to do something like this for making the media available to the UI:

public AlbumCollection Albums
{
  get { return _mediaLibrary.Albums; }
}

Data binding to this property does not return any data. This is because there could be many artists, albums, and songs in a collection. Instead you must access each item via indexed access into the collection. To make the media items available, declare properties of type List<Album>, and so on, for each property just like with other samples.

The interesting aspect to this code sample is the constructor that loads the data like we have seen before when using GalaSoft MVVM Light as shown in Listing 7–8.

Listing 7–8. MainViwModel Constructor that Populates the Media Collections

public MainViewModel(IMediaLibraryAlbumsService mediaLibraryAlbumsService,
  IMediaLibraryArtistsService mediaLibraryArtistsService,
  IMediaLibraryPlayListsService mediaLibraryPlaylistsService,
  IMediaLibrarySongsService mediaLibrarySongsService)
{
  _mediaLibraryAlbumsService = mediaLibraryAlbumsService;
  _mediaLibraryArtistsService = mediaLibraryArtistsService;
  _mediaLibraryPlaylistsService = mediaLibraryPlaylistsService;
  _mediaLibrarySongsService = mediaLibrarySongsService;
  //Albums
  _mediaLibraryAlbumsService.GetData(
      (albums, error) =>
      {
        if (error != null)
        {
          // Report error here
          return;
        }
        Albums = (List<Album>)albums;
      });
  //Artists
  _mediaLibraryArtistsService.GetData(
(artists, error) =>
{
  if (error != null)
  {
    // Report error here
    return;
  }
  Artists = (List<Artist>)artists;
});
  //Playlists
  _mediaLibraryPlaylistsService.GetData(
  (playlists, error) =>
  {
    if (error != null)
    {
      // Report error here
      return;
    }
    Playlists = (List<Playlist>)playlists;
  });
  //Songs
  _mediaLibrarySongsService.GetData(
  (songs, error) =>
  {
    if (error != null)
    {
      // Report error here
      return;
    }
    Songs = (List<Song>)songs;
  });
}

I had a choice to make for this sample. Should I implement one MainViewModel and load all of the data there, or should each category (Song, Artist, Album, etc.) get its own ViewModel class. I opted for one single MainViewModel class that calls all of the data services but you could easily do it the other way and have individual View Model classes.

The primary change to use multiple View Model classes, one for each category, would be to not set the DataContext for the ViewModelLocator at the Page level for MainPage.xaml. Instead, set the DataContext for each PanoramaItem (or containing Grid control) separately. Here is an example for loading Artist data via its Data Service:

using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework.Media;

namespace WorkingWithMediaLibrary.Model
{
  public class MediaLibraryArtistsService : IMediaLibraryArtistsService
  {
    private MediaLibrary _mediaLibrary;
    public void GetData(Action<List<Artist>, Exception> callback)
    {
      _mediaLibrary = new MediaLibrary();
      List<Artist> artists = new List<Artist>();
      Artist artist = null;
      for (int i = 0; i < _mediaLibrary.Artists.Count; i++)
      {
        artist = _mediaLibrary.Artists[i];
        artists.Add(artist);
      }

      callback(artists, null);
      _mediaLibrary.Dispose();
    }
  }
}

You must access the media item by index into a local variable and then add it to the List<Artist> collections, as shown in Listing 7–8, for each media type in order to have an actual object reference. Note that adding the entire collection as in the sample can be risky from a memory consumption standpoint if the user has a large media collection.

The MediaLibrary.Albums collection has a reference to album art available but it is not directly published as a property on the Album object. The Album object does have a HaveArt property with a GetAlbumArt method. Remember from the previous sample that IValueConverter data converters allow you to data bind to anything. For this sample a simple data converter that creates a BitmapImage object to hold the image returned by the Album.GetAlbumArt() method call. The BitmapImage is then returned as the value for the Image.Source property to display the album art. Listing 7–9 shows the data converter.

Listing 7–9. AlbumArtConverter Code File

using System;
using System.Windows.Data;
using System.Windows.Media.Imaging;
using Microsoft.Xna.Framework.Media;

namespace WorkingWithMediaLibrary.Converters
{
  public class AlbumArtConverter : IValueConverter
  {
    public object Convert(object value, Type targetType, object parameter,
      System.Globalization.CultureInfo culture)
    {
      try
      {
        BitmapImage artImage = new BitmapImage();
        Album album = value as Album;
        if (album.HasArt)
        {
          artImage.SetSource(album.GetAlbumArt());
        }
        return artImage;
      }
      catch (Exception err)
      {
        return "";
      }
    }

    public object ConvertBack(object value, Type targetType, object parameter,
      System.Globalization.CultureInfo culture)
    {
      throw new NotImplementedException();
    }
  }
}

The last item to cover is that you can play Song or SongCollection objects via the MediaPlayer.Play() method, which is covered in more detail below. Figure 7–8 shows the UI in the emulator, which has just test data for a single album and a few songs so you will want to run on a real device with a populated library.

images

Figure 7–8. WorkingWithMediaLibrary UI in the Emulator

This concludes our coverage of the Windows Phone media library programmable API. Next up is how to create a Photo Extras application.

Working with Video and Audio Media

So far, we have discussed working with the Media Library and the Picture hub with Photo Extras applications. In this section, we discuss video and audio media. As mentioned previously, Windows Phone third-party applications cannot add media to the Media Library but third-party applications can add content to Isolated Storage in addition to playing video and audio content in memory.

In this section we cover supported codecs, playing video, audio, and DRM considerations within the context of the programming model.

Supported Codecs and Containers

Windows Phone has extensive codec and container support as detailed at this link:

http://msdn.microsoft.com/en-us/library/ff462087%28VS.92%29.aspx

Container support for a variety of media includes WAV, MP3, WMA, 3GP, MP4, 3G2, M4A, WMV, M4V, 3G2, JPG, and PNG. Of note, GIF, BMP, and TIF are not supported in the Image control in Silverlight.

Decoder support is a long list at the above link, but of note support is available for MP3, WMA Standard v9, AAC-LC, HE-AAC v1, HE-AAC v2, AMR-NB, WMV (VC-1, WMV9) simple, main, and advanced. Also supported are MPEG-4 Part 2, Simple, Advanced Simple, MPEG-4 Part 10 Level 3.0 Baseline, Main, High, and H.263. There are specific requirements in terms of bitrate, constant bitrate vs. variable bitrate, as well as methods of playback, so check the above link when troubleshooting playback issues on a device to see if a capability or playback method is not correct.

Video and Audio in the Emulator

The emulator does not support all of the codecs supported on a device. Specifically, AAC-LC, HE-AAC v1, HE-AAC v2, AMR-NB, MPEG-4 Part 2 simple, MPEG-4 Part 2 advanced, MPEG-4 Part 10 level 3 baseline, main or high, and H.263 are all not supported.

The following codecs are supported but with caveats:

  • WMV (VC-1, WMV9) – Simple Profile – Unsupported above 320 x 176 pixels.
  • WMV (VC-1, WMV9) - Main Profile – Unsupported above 800 x 488 pixels.
  • WMV (VC-1, WMV9) - Advanced Profile – Unsupported above 720 x 480 pixels.

When investigating video support for your content, it is recommended to test on a real device first to ensure that an accurate assessment is made.

Progressive Download Video

Progressive download is generally used for short form video, clips, music videos, and the like. The file is completely downloaded over HTP while playback begins immediately.

We create a project named WorkingWtihVideo in the Chapter 7 solution. The MainPage.xaml page is a menu page to launch additional pages to test out functionality starting with testing progressive play back in the MediaPlayerLauncher, which was covered in Chapter 4 in the LaunchersAndChoosers sample.

Progressive Video with MediaPlayerLauncher

Silverlight supports both progressive and streaming video via the MediaElement control, but a simple way to play progressive non-DRM video is via the Microsoft.Phone.Tasks.MediaPlayerLauncher task. A page named MediaPlayeTask.xaml is added to the WorkingWithVideo project that has a single button titled play video.private void textBlock1_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)

{
  MediaPlayerLauncher mediaPlayerLauncher = new MediaPlayerLauncher();
  mediaPlayerLauncher.Controls = MediaPlaybackControls.FastForward |
    MediaPlaybackControls.Pause | MediaPlaybackControls.Rewind |
    MediaPlaybackControls.Skip | MediaPlaybackControls.Stop;
  mediaPlayerLauncher.Location = MediaLocationType.Data;
  mediaPlayerLauncher.Media = new
Uri("http://ecn.channel9.msdn.com/o9/ch9/8/9/6/6/3/5/WP7Xbox_ch9.mp4");
  mediaPlayerLauncher.Show();
}

As you can see, it is possible to configure what trickplay controls are visible during playback. In the next section, we cover playing video with the MediaElement, which is pretty simple as well – but you will need to build up your own media player for best user experience. This is because the MediaElement is a blank canvas with a highly programmable API. By default, it does not have any media controls and the like. Unless you need specific functionality or want complete control over the playback UI the MediaPlayerLauncher task is easiest.

One situation where the MediaPlayerLauncher is not a good choice is with DRM'd content. I cover DRM'd video later in the chapter. The next section covers playing progressive video with the MediaElement control.

Progressive Video with MediaElement

The MediaElement is a very powerful control with a rich API to provide fined-grained control during playback with a rich event model. We create a simple example that hooks into the interesting events as well as displays values for video related properties.

A new page named MediaElement.xaml is added to the WorkingWithVideo project. A MediaElement control is added to the page. The application bar is available to host a play and pause button that will programmatically control the MediaElement control. Several TextBlock controls are laid out to display values from various MediaElement properties as shown in Figure 7–9.

images

Figure 7–9. MediaElement.xaml UI

There are several interesting events available with the MediaElement control. Listing 7–10 shows the available media-related events.

Listing 7–10. PictureEditorView .xaml.cs Code File

using System;
using System.Windows;
using System.Windows.Threading;
using Microsoft.Phone.Controls;

namespace WorkingWithVideo.pages
{
  public partial class MediaElement : PhoneApplicationPage
  {
    public MediaElement()
    {
      InitializeComponent();
      DispatcherTimer timer = new DispatcherTimer();
      timer.Tick += new EventHandler(timer_Tick);
      timer.Interval = new TimeSpan(0, 0, 1);
      timer.Start();
    }

    void timer_Tick(object sender, EventArgs e)
    {
      CanSeekTextBlock.Text = mediaPlayer.CanSeek.ToString();
      CanPauseTextBlock.Text = mediaPlayer.CanPause.ToString();
      DroppedFramesTextBlock.Text =
        mediaPlayer.DroppedFramesPerSecond.ToString();
    }

    private void mediaPlayer_MediaOpened(object sender,
RoutedEventArgs e)
    {
      mediaPlayer.Play();
    }

    private void mediaPlayer_MediaFailed(object sender,
      ExceptionRoutedEventArgs e)
    {
      MessageBox.Show("Media Failed: " + e.ErrorException.Message);
    }

    private void mediaPlayer_MediaEnded(object sender, RoutedEventArgs e)
    {

    }

    private void PlayAppBarBtn_Click(object sender, EventArgs e)
    {
      mediaPlayer.Source =
        new Uri("http://ecn.channel9.msdn.com/o9/ch9/8/9/6/6/3/5/WP7Xbox_ch9.wmv",
          UriKind.Absolute);
    }

    private void PauseAppBarBtn_Click(object sender, EventArgs e)
    {
      mediaPlayer.Pause();
    }

    private void mediaPlayer_CurrentStateChanged(object sender,
      RoutedEventArgs e)
    {
      CurrentStateTextBlock.Text = mediaPlayer.CurrentState.ToString();
    }

    private void mediaPlayer_BufferingProgressChanged(object sender,
      RoutedEventArgs e)
    {
      BufferingProgressTextBlock.Text = mediaPlayer.BufferingProgress.ToString();
    }

    private void mediaPlayer_DownloadProgressChanged(object sender,
      RoutedEventArgs e)
    {
      DownloadProgressChangedTextBlock.Text = mediaPlayer.DownloadProgress.ToString();
    }
  }
}

Notice that clicking play doesn't start playback. Instead, it sets the Source property on the MediaElement control. If the video configured on the Source property is successfully opened, the MediaOpened event fires, which is where Play is actually called.

MediaStreamSource

For custom video delivery, you can use the MediaElement.SetSource() method in your code to specify the media to be played. One overload of SetSource() accepts a System.IO.Stream, which is suited for the scenario where you decide to acquire the media through some other mechanism rather than have the MediaElement handle the download. When you acquire the media file, you can create a Stream around it (using a more concrete type like System.IO.IsolatedStorage.IsolatedStorageFileStream) and pass it to SetSource().

The second overload of SetSource() accepts an instance of the System.Windows.Media.MediaStreamSource type. The MediaStreamSource type is actually a way to plug a video container file format into Silverlight, for which the MediaElement does not come with a built-in parser. Video container file formats and related specifications are complex topics, and consequently a treatment of MediaStreamSource implementations is beyond the scope of this book.

When the Source is set by either mechanism, for progressive download scenarios the MediaElement immediately starts to download the media. The MediaElement.DownloadProgressChanged event is raised repeatedly as the download progresses. The MediaElement.DownloadProgress property reports the download progress as a percentage value (actually a double between 0 and 1 that you can convert to percentage) that you can use to track and report the download progress in the DownloadProgressChanged event handler.

images Note For more information on Silverlight and MediaStreamSource check out Silverlight Recipes: A Problem-Solution Approach, Second Edition: http://apress.com/book/view/1430230339.

Streaming Video

In addition to progressive download video, streaming video is another technique used to deliver media to a player. Streaming does not require downloading the media file locally, and it is well suited for scenarios involving either live or on-demand broadcasts to a large population of viewers.

Microsoft provides Smooth Streaming, an IIS Media Services extension that enables adaptive streaming of media to Silverlight and other clients over HTTP. Smooth Streaming provides a high-quality viewing experience that scales massively on content distribution networks, making true HD 1080p media experiences a reality.

Smooth Streaming is the productized version of technology first used by Microsoft to deliver on-demand video for the 2008 Summer Olympics at NBCOlympics.com. By dynamically monitoring both local bandwidth and video rendering performance, Smooth Streaming optimizes playback of content by switching video quality in real-time to match current network conditions resulting in a better viewing experience.

Smooth Streaming uses the simple but powerful concept of delivering small content fragments (typically two seconds worth of video) and verifying that each has arrived within the appropriate time and played back at the expected quality level. If one fragment does not meet these requirements, the next fragment delivered will be at a somewhat lower quality level. Conversely, when conditions allow it, the quality of subsequent fragments will be at a higher level if network conditions improve.

images Note For more information on IIS Smooth Streaming go to http://learn.iis.net/page.aspx/558/getting-started-with-iis-smooth-streaming/.

SmoothStreamingMediaElement

The Microsoft.Web.Media.SmoothStreaming.SmoothStreamingMediaElement is a customized version of the MediaElement that works directly with IIS Smooth Streaming technology. The SmoothStreamingMediaElement available in Windows Phone is compatible with the IIS Smooth Streaming Client 1.1. Windows Phone is a subset of the full client support. The phone icon in the API reference identifies members of classes in the Microsoft.Web.Media.SmoothStreaming namespace that are available for Windows Phone development. Review the documentation at the following link:

http://msdn.microsoft.com/en-us/library/microsoft.web.media.smoothstreaming(v=VS.90).aspx

IIS Smooth Streaming combined with the SmoothStreamingMediaElement provide an incredibly powerful and flexible streaming capability with support for multiple video and audio tracks, multiple languages, subtitles, Timeline Markers, Fast Forward and Rewind, and metadata in tracks for “pop-up” information during playback for a rich media experience. Please refer to this link for details on capabilities:

http://msdn.microsoft.com/en-us/library/ee958035(v=VS.90).aspx
Microsoft Media Framework Player Framework

The Silverlight Media Framework (SMF), renamed the Microsoft Media Platform Play Framework (MMP PF), is an open source CodePlex project that provides a robust, scalable customizable media player for IIS Smooth Streaming media delivery. The MMP PF builds on the core functionality of the Smooth Streaming Client (formerly known as the Smooth Streaming Player Development Kit) and adds a large number of additional features, including an extensibility API that allows developers to create plug-ins for the framework. The MMP PF also now includes full support for Windows Phone so developers can incorporate high-end video playback experiences in their Windows Phone applications. Please check out the MMP PF at this link:

http://smf.codeplex.com/

DRM

Most long form video, whether it be TV show episodes, movies, or live TV, has some form of Digital Rights Management (DRM). As the goal of DRM is to prevent illegal copying of digital content, securely storing licenses to content is of paramount concern.

Silverlight for Windows Phone supports DRM via Microsoft PlayReady. Microsoft PlayReady is a platform independent DRM content access technology that is optimized for broad use across a range of devices. Microsoft PlayReady supports subscription, purchase, rental, and pay-per-view business models that can be applied to many digital content types and to a wide range of audio and video formats.

On Windows Phone, there is support for Microsoft PlayReady built into the platform including a secure key store for online and offline DRM key storage. After DRM media is accessed the first time, the license is stored offline enabling offline playback of DRM'd content.

A full discussion of DRM is beyond this book but here is a link for more information on Microsoft PlayReady technology:

www.microsoft.com/PlayReady/Default.mspx

When it comes to DRM protected content available via the Zune Service, either purchased or subscription, you can play that content within Silverlight applications using the MediaElement as well as the MediaPlayer object available in the XNA Framework. License acquisition occurs behind the scenes with no additional work necessary by the developer. For content that is not owned by the end-user, you can use the MarketplaceDetailsTask to show UI that allows the user to purchase the content.

Audio Support

Because audio is critical to video playback, audio support is covered in pretty good detail in concert with what has been discussed above regarding video playback. However, in addition to the MediaElement and SmoothStreamingMediaElement, developers can play audio using the Microsoft.Xna.Framework.Media.MediaPlayer object. We demonstrated this in the Chapter 7 WorkingWithMediaLibrary project in this event handler from MainPanoramaPage.xaml.cs:

private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
  object obj = (sender as ListBox).SelectedItem;
  MediaLibrary library = new MediaLibrary();
  try
  {
    switch (obj.GetType().ToString())
    {
      case "Microsoft.Xna.Framework.Media.Album": MediaPlayer.Play(((Album)(obj)).Songs);
break;
      case "Microsoft.Xna.Framework.Media.Song":
MediaPlayer.Play((Song)(obj)); break;
      case "Microsoft.Xna.Framework.Media.Playlist": MediaPlayer.Play(((Playlist)(obj)).Songs);
break;
      case "Microsoft.Xna.Framework.Media.Artist": MediaPlayer.Play(((Artist)(obj)).Songs); break;
    }
  }
  catch (Exception ex)
  {
    MessageBox.Show("Error: " + ex.Message);
  };
}

The MediaPlayer object can play by Album, Song, Playlist, and Artist objects, but only one of these items at a time. So, for example, for a custom play list you must provide one song at a time. The MediaPlayer object has two events that you can hook, MediaStateChanged and ActiveSongChanged. MediaStateChanged fires when state changes from Paused, Playing, or Stopped to another MediaState value. Here is a code example, not from a specific project but provided as reference:

public MainPage()
{
  InitializeComponent();
  MediaPlayer.MediaStateChanged += new EventHandler<EventArgs>(MediaPlayer_MediaStateChanged);
  MediaPlayer.ActiveSongChanged += new EventHandler<EventArgs>(MediaPlayer_ActiveSongChanged);
}

void MediaPlayer_ActiveSongChanged(object sender, EventArgs e)
{
  if (MediaPlayer.Queue.Count == 0)
  {
    //add a song to Play
    MediaPlayer.Play(nextSong);
  }
}

void MediaPlayer_MediaStateChanged(object sender, EventArgs e)
{
  switch (MediaPlayer.State)
  {
    case MediaState.Paused:
      break;
    case MediaState.Playing:
      break;
    case MediaState.Stopped:
      break;
  }
}

On a related note, you can prompt the user to purchase additional music using MarketplaceSearchTask and passing in a search string containing the artist, album, and song title. You can also use the WebBrowserTask and Zune Http links for content if you have the ID for the content. Here is an example:

WebBrowserTask task = new WebBrowserTask();
task.URL = HttpUtility.UrlEncode("http://social.zune.net/External/LaunchZuneProtocol.aspx?
                    pathuri=navigate%3FalbumID%3Dd1935e06–0100-11db-89ca-0019b92a3933");
task.Show();

You can then use the MediaLibrary object covered above and demonstrated in the WorkingWithMediaLibrary project to find acquired content and show album art as well as purchase the media.

You might guess that the MarketplaceDetailsTask could also show the purchase UI but it is not supported in this release.

For more information on music playback, check out Chapter 10 where I cover background audio playback now available in Windows Phone OS 7.1

App Connect -Extending the picture Hub

Developers always ask about ways to integrate with the underlying platform. This is especially true for a mobile device where mobile devices users want to perform actions with as little effort as possible. In Windows Phone 7.5, Microsoft introduced App Connect to encompass application extensions that integrate with the Windows Phone, including integrating with the pictures Hub, music+video Hub, and Bing Search. In this section I focus on picture Hub integration.

images Note In Windows Phone 7, extending the pictures Hub was called a “Photos Extra” application.

One popular use case is manipulating pictures taken with the built-in camera. Many applications allow photo manipulation directly on the device so integrating these applications as much as possible with the pictures Hub benefits the end-user.

An application that extends the pictures Hub is accessible from within the pictures Hub's Single Photo Viewer (SPV) UI. When in the SPV, swipe up the Application Bar to expose the apps… menu item. Select the apps… menu to see the available third-party applications.

Note that images are the only media type that can be added to the Media Library, so once a picture Hub extension performs its magic on an image; it can be saved back to the media library to be shared with others. Figure 7–10 shows the Photos Extra UI.

images

Figure 7–10. Photos Extensibility via the Share… Menu

The next section covers how an application can extend the pictures Hub.

Extending the pictures Hub

This section covers how easy it is to integrate with the pictures Hub. Extending the pictures Hub includes the following two major steps:

  • Modifying the WMAppManifest.xml file
  • Retrieving the selected photo

A new project named AppConnectPhoto is added to the Chapter 7 solution. We will turn this project into a picture Hub extension and use MVVM Light as part of the sample. We will take advantage of messaging to communicate between the image editing View named ImageEditorView.xaml and the backing ViewModel class named PictureEditingViewModel.cs. We also will use a few IValueConverter classes as well. First we need to edit the project so that it shows up in the SPV “Apps...” menu. The next section covers the steps.

Apps… Menu

For an application that extends the pictures Hub to appear in the Apps… menu, you must add this XML element to the WMAppManifest.xml file:

<Extensions>
      <Extension ExtensionName="Photos_Extra_Hub"
           ConsumerID="{5B04B775-356B-4AA0-AAF8-6491FFEA5632}" TaskID="_default" />
     <Extension ExtensionName="Photos_Extra_Viewer"
           ConsumerID="{5B04B775-356B-4AA0-AAF8-6491FFEA5632}" TaskID="_default" />
</Extensions>

Add the above Xml just past the <Tokens> element within the <App> element in the WmAppManifest.xml file. The first <Extension> element adds the application to the picture Hub Apps menu. The second <Extension> adds support for the Share… menu in the SVP.

The TaskID in the <Extension> element matches the DefaultTask's Name property, “_default”, defined in the WMAppManifest.xml file. This means the extension will launch the DefaultTask, usually MainPage.xaml.

Retrieving the Selected Photo

Retrieving the selected photo is performed in the OnNavigatedTo method override of the application. A set of parameters are passed in when the application is launched from the Single Photo Viewer in the Pictures hub. This allows the application to know when it is launched as a picture Hub extension or when the application is simply launched from the application list or Start screen tile if pinned to the Start screen because no query parameters are present.

To retrieve the query parameters, you use the NavigationContext.QueryString property. The QueryString object is a Dictionary<string, string> object. Look for the pre-defined QueryString[“token”] to know that the application was launched from the SVP. Use the query value to also obtain a handle to the image.

To get started, we override OnNavigatedTo in Main.xaml.cs in order to capture the query string value to set the correct picture on PictureEditingViewModel.PictureToEdit property. Listing 7–11 has the code for the AppConnectPhoto MainPage.xaml.cs code file.

Listing 7–11. AppConnectPhoto MainPage.xaml.cs Code File

using System;
using System.Collections.Generic;
using System.Windows.Controls;
using GalaSoft.MvvmLight.Messaging;
using Microsoft.Phone.Controls;
using Microsoft.Xna.Framework.Media;

namespace AppConnectPhoto
{
  public partial class MainPage : PhoneApplicationPage
  {
    // Constructor
    public MainPage()
    {
      InitializeComponent();
    }

    protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
    {
      base.OnNavigatedTo(e);

      //Process query string
      IDictionary<string, string> queryStrings = this.NavigationContext.QueryString;
      if (NavigationContext.QueryString.Count > 0 &&
        NavigationContext.QueryString.ContainsKey("token"))
      {
        MediaLibrary library = new MediaLibrary();
        Picture picture = library.GetPictureFromToken(queryStrings["token"]);
        //Remove this query string item so that when the user clicks
        //"back" from the ImageEditorView page the app doesn't loop back
        //over to the ImageEditorView in an endless loop of navigation because
        //the query string value is still present and picked up by
        //MainPage.OnNavigateTo each time...
        NavigationContext.QueryString.Remove("token");

        //Send Message with Picture object
        SetPictureAndNavigate(picture);
      }
    }

    private void ListBox_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
    {
      ListBox lb = sender as ListBox;SetPictureAndNavigate(lb.SelectedItem as Picture);
    }

    void SetPictureAndNavigate(Picture picture)
    {
      Messenger.Default.Send<Picture>(picture, "PictureToEdit");
      NavigationService.Navigate(new Uri("/ImageEditorView.xaml", UriKind.Relative));
    }
  }
}

The query string value for the token parameter is obtained and the MediaLibrary.GetPictureFromToken method is called to obtain the correct image from the Picture hub. The picture is passed to the SetPictureAndNavigate method shown in Listing 7–11. This same method is called when an image is selected from the ListBox in MainPage.xaml via the ListBox_SelectionChanged method also in Listing 7–11.

Notice in the SetPictureAndNavigate method, it uses the MVVM Light Messenger.Default.Send to pass the picture as a message. The message is registered for and received in the PictureEditingViewModel class constructor in this line of code:

Messenger.Default.Register<Picture>(
  this, "PictureToEdit",
  (picture) => { PictureToEdit = picture; });

The PictureEditorView.xamlView data binds to the MainViewModel's PictureEditingVM property, which is an instance of PictureEditingViewModel that is created when MainViewModel is constructed. This ensures that an instance of the PictureEditingViewModel is available when the message is sent. Otherwise, if PictureEditorView.xaml data binded to an instance when constructed, the message would have already been sent resulting in a bug that the first time you navigate to a photo the message is lost.

When the message is received by the PictureEditingViewModel, it updates the PictureEditingViewModel.PictureToEdit property so that the Image control on that page can data bind to the PictureToEdit property. This allows the UI to data bind to a ViewModel without directly accessing the ViewModel by using MVVM Light Toolkit Messaging.

Let's finish discussing the MainPage.xaml functionality before diving further into the picture edit page. The ListBox in MainPage.xaml data binds to the List<Picture> Pictures property in the MainViewModel class. The interesting part of the MainViewModel class is loading the images:

private void CreateDataCollections()
{
  Pictures = new List<Picture>();
  Picture picture = null;
  for (int i = 0; i < _mediaLibrary.Pictures.Count; i++)
  {
    picture = _mediaLibrary.Pictures[i];
    Pictures.Add(picture);
    if (i > 30)
      break;
  }
}

The CreateDataCollections method is hard coded to just load the first 30 pictures found into the MainViewModel.Pictures property. Figure 7–11 shows the MainPage.xaml UI with sample images from the Emulator rendered in a basic data template that shows the name, the image, and the album it is from.

images

Figure 7–11. PhotosExtra main page UI

The ListBox data binds to the List<Picture> Pictures collection. The data template includes an Image control. It leverages a value converter named ImageSourceConverter that calls Picture.GetImage() and sets it on the Source property for the Image control in the DataTemplate. The Picture.GetThumbnail property would be a better choice for the ListBox template, because it is a small image but we use the same converter when displaying the image on the edit screen and GetImage returns the full image and so looks better when full screen.

MainPage.xaml only displays when launched from the App List. When an image is tapped, it opens to the picture editing View named PictureEditorView.xaml, which data binds to an instance of PictureEditingViewModel that is a property named PictureEditingVM on the MainViewModel.

When launched from the Photos Extra menu with the QueryStrings value present, the application goes straight to the picture editing view as shown in Figure 7–12.

images

Figure 7–12. PhotosExtra picture editing UI

Notice that in both Figure 7–11 and Figure 7–12 that the name does not include the extension. The TextBlock data binds to the Picture.Name property that includes the extension but it is parsed out. This is accomplished by a simple value converter named ImageNameConverter that has a simple line of code to obtain just the name portion:

return ((string)value).Split('.')[0];

Notice also that in Figure 7–11, when the UI is in Landscape mode the text title disappears to maximize space for image viewing and editing. This is also achieved via a value converter. The TextBlock's Visibility property Element data binds to the Orientation of the Page. The value converter sets Visibility to Collapsed if the phone is in Landscape mode. Otherwise the TextBlock control's Visibility is configured to Visible. The PictureEditorView data binds to the PictureEditingViewModel class shown in Listing 7–12.

Listing 7–12. PictureEditingViewModel.cs Code File

using System.IO;
using System.Windows.Media.Imaging;
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using GalaSoft.MvvmLight.Messaging;
using Microsoft.Xna.Framework.Media;

namespace AppConnectPhoto.ViewModel
{
  public class PictureEditingViewModel : ViewModelBase
  {
    private MediaLibrary mediaLibrary;

    public PictureEditingViewModel()
    {
      mediaLibrary = new MediaLibrary();

      //Register to receive message with picture object from Picture Hub
      //This message is sent from MainPage.xaml.cs in OnNavigateTo
      Messenger.Default.Register<Picture>(
       this, "PictureToEdit",
       (picture) => { PictureToEdit = picture; });

      //Instantiate Commands
      SaveImageCommand = new RelayCommand(
        () => SaveImage());

      SaveImageAsCommand = new RelayCommand<string>(
        param => SaveImageAs(param));

      RevertToSavedImageCommand = new RelayCommand(
        () => EditImage());
      EditImageCommand = new RelayCommand(
        () => RevertToSavedImage());
    }

    #region Image State
    public const string PictureToEditPropertyName = "PictureToEdit";
    private Picture _pictureToEdit = null;
    public Picture PictureToEdit
    {
      get
      {
        return _pictureToEdit;
      }

      set
      {
        if (_pictureToEdit == value)
        {
          return;
        }
        var oldValue = _pictureToEdit;
        _pictureToEdit = value;
        RaisePropertyChanged(PictureToEditPropertyName);
      }
    }


    public const string ModifiedPicturePropertyName = "ModifiedPicture";
    private WriteableBitmap _modifiedPicture = null;
    public WriteableBitmap ModifiedPicture
    {
      get { return _modifiedPicture; }
      set
      {
        if (_modifiedPicture == value)
        { return; }
        var oldValue = _modifiedPicture;
        _modifiedPicture = value;
        RaisePropertyChanged(ModifiedPicturePropertyName);
      }
    }

    //Property to data bind to for SaveAs input UI
    //Used for SaveAs command
    public const string ImageSaveAsNamePropertyName = "ImageSaveAsName";
    private string _imageSaveAsName = null;
    public string ImageSaveAsName
    {
      get { return _imageSaveAsName;}
      set
      {
        if (_imageSaveAsName == value)
        { return; }
        var oldValue = _imageSaveAsName;
        _imageSaveAsName = value;
        RaisePropertyChanged(ImageSaveAsNamePropertyName);
      }
    }

    public const string ImageIsDirtyPropertyName = "ImageIsDirty";
    private bool _imageIsDirety = false;
    public bool ImageIsDirty
    {
      get { return _imageIsDirety; }
      set
      {
        if (_imageIsDirety == value)
        { return; }

        var oldValue = _imageIsDirety;
        _imageIsDirety = value;
        RaisePropertyChanged(ImageIsDirtyPropertyName);
      }
    }
    #endregion

    #region Image Actions for RelayCommand Objects
    private void EditImage()
    {
      //Editing, unsaved changes pending
      ImageIsDirty = true;
    }

    //View must set the writable bitmap area
    //prior to executing this command
    //This Save action takes a new name
    private void SaveImageAs(string saveAsName)
    {
      using (MemoryStream jpegStream = new MemoryStream())
      {
        //Tell the UI to update the WriteableBitmap property
        Messenger.Default.Send<bool>(true, "UpdateWriteableBitmap");
        ModifiedPicture.SaveJpeg(jpegStream, ModifiedPicture.PixelWidth,
          ModifiedPicture.PixelHeight, 0, 100);
        //Update current Picture to reflect new modified image
        PictureToEdit = mediaLibrary.SavePicture(saveAsName, jpegStream);
        //Saved, not editing
        ImageIsDirty = false;
      };
    }

    

    //PictureEditingView registers to receive this message
    //It would clear out any edits at the UI level.
    private void RevertToSavedImage()
    {
      Messenger.Default.Send<bool>(true, "UndoImageChanges");
    }
    #endregion

    #region Image Editing Commmands
    public RelayCommand SaveImageCommand { get; private set; }
    public RelayCommand<string> SaveImageAsCommand { get; private set; }
    public RelayCommand EditImageCommand { get; private set; }
    public RelayCommand RevertToSavedImageCommand { get; private set; }
    #endregion
  }
}

The PictureEditingViewModel class has two primary data properties: PictureToEdit of type Picture and the ModifiedPicture of type WriteableBitmap. The idea is to display the selected picture to be modified in an Image control.

images Note The sample doesn't actually modify the control, but you could enable drawing, etc., just like what was demonstrated in Chapter 2.

The user can then edit the image in the UI. The modifications under the covers would result in additional Silverlight controls added to the XAML with the Grid WritableBitMapSourceArea as the top-level parent. When the user clicks save, the Grid is passed into the WriteableBitmap to turn it into a bitmap image.

You can pass any UIElement into the WriteableBitmap.Render method and it will turn the element and its children into an image. In the SaveImageCommand (SaveImage()) and SaveImageAsCommand (SaveImageAs()) commands, a message is sent to the UI to update the ModifiedPicture property on the VM prior to saving. We could do this in a way without tying the View to the ViewModel via additional messages, but for our example this is sufficient.

The PictureEditingViewModel class also sends a message to the UI to ask the View to undo modifications to the image. Listing 7–13 has the PictureEditorView code-behind file.

Listing 7–13.  PictureEditorView .xaml.cs Code File

using System.Windows.Controls;
using GalaSoft.MvvmLight.Messaging;
using Microsoft.Phone.Controls;
using AppConnectPhoto.ViewModel;

namespace AppConnectPhoto
{
  /// <summary>
  /// Description for ImageEditorView.
  /// </summary>
  public partial class PictureEditorView : PhoneApplicationPage
  {
    PictureEditingViewModel vm;
    /// <summary>
    /// Initializes a new instance of the ImageEditorView class.
    /// </summary>
    public PictureEditorView()
    {
      InitializeComponent();

      vm = DataContext as PictureEditingViewModel;

      //Register for message to undo changes
      Messenger.Default.Register<bool>(
        this, "UndoImageChanges",
        (param) => UndoImageChanges());

      Messenger.Default.Register<bool>(
        this, "UpdateWriteableBitmap",
        (param) => UpdateWriteableBitmap());
    }

    private void UpdateWriteableBitmap()
    {
      //Update WriteableBitmap so it is ready to save
      vm.ModifiedPicture.Render(WriteableBitmapSourceArea, null);
    }

    private void UndoImageChanges()
    {
      //Undo Image changes
      //Reset WriteableBitmapSourceArea Grid to just hold
      //the original image content only
      Image img = ImageToEdit;
      WriteableBitmapSourceArea.Children.Clear();
      WriteableBitmapSourceArea.Children.Add(img);
    }

    private void EditAppBarBtn_Click(object sender, System.EventArgs e)
    {
      vm.EditImageCommand.Execute(null);
    }

    private void saveAppBarBtn_Click(object sender, System.EventArgs e)
    {
      vm.SaveImageCommand.Execute(null);
    }

    private void SaveAsAppMenItem_Click(object sender, System.EventArgs e)
    {
      vm.SaveImageAsCommand.Execute(vm.ImageSaveAsName);
    }

    private void RevertToLastSaveMenuItem_Click(object sender, System.EventArgs e)
    {
      vm.RevertToSavedImageCommand.Execute(null);
    }
  }
}

This code walkthrough demonstrates how to set up all the infrastructure for a picture modifying Photos Extra application. It does not actually perform edits, that we leave to the enterprising reader to build the next great Photos Extra application.

App Connect - music+videos Hub Integration

Applications that play music or video can integrate more deeply into the Windows Phone user experience. This helps to provide consistent quick access to content for the end user, while also allowing your application to surface content and the application itself within the music+videos hub in addition to the App List. Figure 7–13 shows the music+videos hub panorama for reference.

images

Figure 7–13. Microsoft Windows Phone music+videos hub

The music+videos hub has an apps, history, and new section that a music or video application can integrate into for a richer user experience.

An application that calls the Microsoft.Devices.MediaHistory or Microsoft.Devices.MediaHistoryItem classes is considered as a Music + Videos hub application and will appear in the apps list in the hub when installed on the phone. When an application is submitted to the Windows Phone Marketplace, the marketplace detects that the application uses these classes and will automatically update the hub type to Music + Videos in the application's Windows Phone application manifest.

The Windows Phone Certification Requirements Document covers music+videos hub application certification requirements in section 6.5. In this section, we build a sample music+videos hub application that you can use to guide you when building your own applications.

Additional Assets

As you can see in Figure 7–13 that shows the music+videos hub, applications and content are represented by tiles. Here are the rules for tiles:

  • Tile images must be a .jpg image.
  • The application title or logo must be present on each tile.
  • The now playing tile must be 358 pixels x 358 pixels in size.
  • Other tiles shown on the hub must be 173 pixels x 173 pixels in size.
  • The Title property of the MediaHistoryItem class must be set to text that represents the content, such as a station name or video title.

We create two tiles to represent the videos, named ParisVideoTile.jpg and tdfVideoTile.jpg, to correspond to the videos of the Paris skyline video and Tour De France video. We also create two now playing tiles appropriately named NowPlayingParisTile.jpg and NowPlayingtdfTile.jpg.

All of the image tiles are passed into the MediaHistoryItemAPI as a System.IO.Stream object.

images Tip In an application retrieving images from server-apis, use the WriteableBitmap class to create images that are branded to your application.

Testing music+videos Hub Applications

When an application is ingested into marketplace, marketplace detects if the application makes calls to the MediaHistory and MediaHistoryItem classes and updates the application manifest so that the application is ready for publishing. We cover how to work with these APIs in the next section.

You can test music+videos hub applications with Visual Studio and an actual device, but you must edit the manifest file manually. Set the HubType attribute on the <App> element to a value of 1 and be sure to disconnect from Zune when testing if not using the WPConnect tool to allow media interaction, and debugging, while connected via Zune. The APIs will throw an exception if syncing with Zune.

Because the emulator does not ship with the full Windows Phone experience (i.e., the music+videos hub is not available), it is not possible to test music+videos hub applications in the emulator in this release.

Debugging music+videos Hub Applications

There's a tool that allows Visual Studio 2010 to connect to a device without the Zune Client software running. This functionality is especially necessary for applications that play media because it is not possible to play media when connected to the Zune Client. It is also not possible to update music+videos hub items like the “History,” “Now playing,” and “New” sections while connected to Zune.

To enable debugging without the Zune client running, download the WPConnect tool available with the Zune Client in this directory: The steps are as follows:

  1. Connect the device.
  2. Close the Zune software.
  3. Run either the 32-bit or 64-bit version of the WPConnect tool from the command-line based on your operating system

Disconnecting the device reverts back to normal Zune functionality with the device.

music+videos Hub Sample Application

We start with a Windows Phone Application project named AppConnectMusicPlusVideo in the Chapter Six solution. We create a simple UI with two videos from a recent vacation to play as shown in Figure 7–14.

images

Figure 7–14. AppConnectMusicPlusVideo sample application

We add another page to the application called VideoPlayerPage.xaml that contains a MediaElement where the content is actually played. When one of the thumbnails shown in Figure 7–14 is clicked, the associated video is passed as part of the query string and the application navigates to the VideoPlayerPage.xaml page to actually play the video. Here is the method handler for the top thumbnail image when touched or clicked:

private void PlayParisImage_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
  NavigationService.Navigate(new Uri(
    "/VideoPlayerPage.xaml?video=Paris",UriKind.Relative));
}

images Note The videos are royalty-free because the author recorded them, but the videos were shot with an old camera at 320 x 240, resulting in terrible quality on a high-resolution device like Windows Phone. But they are still useful to demonstrate the concepts.

The VideoPlayerPage.xaml page does not manifest any video playing UI as we are laser focused on just the code to create a music+videos hub application. Listing 7–13 has the code for VideoPlayerPage.xaml.cs.

Listing 7–13. Initial VideoPlayerPage.xaml.cs Code File

using System;
using System.Windows;
using Microsoft.Phone.Controls;

namespace AppConnectMusicPlusVideo
{
  public partial class VideoPlayerPage : PhoneApplicationPage
  {
    public VideoPlayerPage()
    {
      InitializeComponent();
    }

  protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
  {
    VideoPlayer.Source = new
        Uri(@"/video/"+NavigationContext.QueryString["video"]+”.wmv”,UriKind.Relative);

    base.OnNavigatedTo(e);
  }

    private void VideoPlayer_MediaOpened(object sender, RoutedEventArgs e)
    {
      VideoPlayer.Play();
    }

    private void VideoPlayer_MediaFailed(object sender, ExceptionRoutedEventArgs e)
    {
      MessageBox.Show(e.ErrorException.Message);
    }
  }
}

In Listing 7–13, we process the query string with the video name in the OnNavigatedTo event handler. If the media is successfully opened we play it. If it fails to open the application displays a MessageBox with the error message.

We now focus on modifying the code to become a music+videos hub application. From the Windows Phone application certification requirements, the application needs to do these items:

  • The application must update the “New” section of the music+videos hub when media is added to the device.
  • The application must update the “Now playing” section of the music+videos hub when the application plays media.
  • The application must update the “History” section of the music+videos hub when the application plays media.
  • The application must launch the playback experience when the user taps on a hub tile in the “History” or “Now playing” area of the music+videos hub.

images Note The “Now playing” tile must update for each media item played, i.e., you cannot have one “Now playing” tile for the entire application or for a video series or an album.

Before proceeding, let's add a using clause for the Microsoft.Devices and System.IO namespaces to MainPage.xaml.cs and VideoPlayerPage.xaml.cs code files. The Microsoft.Devices namespace provides access to the MediaHistory and MediaHistoryItem classes, which we use to update the “Now playing,” “New,” and “History” sections of the music+videos hub. We pass in the image tiles as a System.IO.Stream object.

Update the music+videos “New” Section

When the application acquires new content such as user-selected favorite radio stations or videos, it must update the “New” section in the music+videos hub for each content item individually. For our sample application, we add code to the MainPage.xaml.cs code file for the two media items available, a video of the Paris skyline and a short clip taken by the author at the 2010 Tour De France.

Add a call to the UpdateMusicPlusVideosHub_NewSection() method in the MainPage constructor. In this method we have the following code shown in Listing 7–14.

Listing 7–14. UpdateMusicPlusVideosHub_NewSection Method Code

StreamResourceInfo ParisTileStreamResource =
    Application.GetResourceStream(new Uri("images/hubTiles/ParisTile.jpg",
      UriKind.Relative));
//Create the MediaHistoryItem that has been newly aquired
MediaHistoryItem ParisVideoMediaHistoryItem = new MediaHistoryItem();
ParisVideoMediaHistoryItem.ImageStream = ParisTileStreamResource.Stream;
ParisVideoMediaHistoryItem.Source = "xap";
ParisVideoMediaHistoryItem.Title = "Paris Skyline Video";
//Set State for situation when navigating via click in Music+Videos Hub
ParisVideoMediaHistoryItem.PlayerContext.Add("videoHub", "Paris");
//This method call writes the history item to the 'New' section
MediaHistory.Instance.WriteAcquiredItem(ParisVideoMediaHistoryItem);

//NEW Tour De France Video
StreamResourceInfo tdfTileStreamResource =
Application.GetResourceStream(new Uri("images/hubTiles/TDFTile.jpg",
  UriKind.Relative));
//Create the MediaHistoryItem that has been newly aquired
MediaHistoryItem tdfVideoMediaHistoryItem = new MediaHistoryItem();
tdfVideoMediaHistoryItem.ImageStream = tdfTileStreamResource.Stream;
tdfVideoMediaHistoryItem.Source = "xap";
tdfVideoMediaHistoryItem.Title = "Tour De France Video";
//Set State for situation when navigating via click in Music+Videos Hub
tdfVideoMediaHistoryItem.PlayerContext.Add("videoHub", "TDF");
//This method call writes the history item to the 'New' section
MediaHistory.Instance.WriteAcquiredItem(tdfVideoMediaHistoryItem);

In the UpdateMusicPlusVideosHub_NewSection method code shown in Listing 7–14, we retrieve a stream that contains the desired image using Application.GetResourceStream. Since the images are stored as content in the XAP file, we simply pass in an Uri to the content based on the project folder structure. Images retrieved from server-side APIs work just as well and can be customized with a brand using the WriteableBitmap class as shown previously.

Once we have a stream to the image, we can create a MediaHistoryItem object. We assign the stream pointing to the tile image that we obtained via Application.GetResourceStream to the MediaHistoryItem.ImageStream property. According to IntelliSense, the MediaHistoryItem .Source property is not used but we set it to “xap.” We set MediaHistoryItem.Title to the desired name of the object, “Paris Skyline Video” and “Tour De France Video” for our two videos.

The MediaHistoryItem class contains a dictionary of key/value pairs stored in the PlayerContext property. The values stored in the PlayerContext property are made available to the application when the item is touched in the music+videos hub in the OnNavigatedTo method of the XAML page configured as the DefaultTask for the application in WMAppManifest.xml. I cover how to leverage this information in the section titled “Implement Playback from music+videos hub” later in this chapter.

We add a key/value pair to the MediaHistoryItem.PlayerContext Dictionary object. For our simple application, we just need one value to be stored with the MediaHistoryItem. For a more complex implementation, multiple values may be stored in order to facilitate accessing the media content when the item is selected in the music+videos hub.

The last bit of code is to add the new media item to the “New” section of the music+videos hub by calling the MediaHistory.Instance.WriteAcquiredItem method and passing in our newly created MediaHistoryItem. The next two subsections covers the code added to the VideoPlayerPage.xaml file in order to update the “Now playing” and “History” sections.

Update the music+videos “Now playing” Section

In VideoPlayerPage.xaml.cs we follow the same process as we did in the previous section and create a MediaHistoryItem each time content is played in the UpdateMusicPlusVideoAppNowPlaying() method. This method is called when the media is successfully opened in the VideoPlayer_MediaOpened event where playback begins with a call to VideoPlayer.Play.

We create a MediaHistoryItem but with a few modifications. The tile image is different for “Now playing” in terms of size, which is 358 x 358 pixels square. The other change is that we add a different key/value pair to MediaHistoryItem.PlayerContext using a key name of “videoHub”. This helps us distinguish between values passed within app versus a value made available as a result of clicking on an item in the music+videos hub.

As covered in the previous section, the MediaHistoryItem object makes the PlayerContext values available to the music+videos hub and passes those values back to the application upon activation. The last bit of code in the UpdateMusicPlusVideoAppNowPlaying() method is that we assign the newly created MediaHistoryItem to the MediaHistory.Instance.NowPlaying property.

Update the music+videos “History” Section

In VideoPlayerPage.xaml.cs we follow the same process as we did in the previous section and create a MediaHistoryItem each time content is played in the UpdateMusicPlusVideoAppHistory() method. This method is called when the media is successfully opened in the VideoPlayer_MediaOpened event where playback begins with a call to VideoPlayer.Play.

As before the code acquires an image, this time the same 173 x 173 pixel tile that we display in the “New” section of the music+videos hub. This image includes the book logo in the upper left corner so that the content is associated with the application. This method also adds the “videoHub” key to the PlayerContext as before:

VideoMediaHistoryItem.PlayerContext.Add("videoHub", video);

The other code that is different when adding an item to the music+videos “History” section is a call to MediaHistory.Instance.WriteRecentPlay() method where the code passes in the newly created MediaHistoryItem.

Implement Playback from music+videos Hub

According to the AppHub certification requirements, the play back experience should display when media associated with the application is selected in the music+videos hub. This helps to provide the seamless experience of selecting media and having it immediately start playing without having to find the media by navigating the application.

The application implements a simple scheme to provide this experience by passing in to the application an ID that identifies the correct video. In the sample application, either “Paris” or “TDF” are passed in to identify the correct video. This value is captured in the OnNavigatedTo event of the DefaultTask XAML page, which in this application is MainPage.xaml. Here is the code from MainPage.xaml.cs:

protected override void OnNavigatedTo(
  System.Windows.Navigation.NavigationEventArgs e)
{
  base.OnNavigatedTo(e);

  if (NavigationContext.QueryString.Count > 0 &&
    NavigationContext.QueryString.ContainsKey("videoHub"))
  {
    NavigationService.Navigate(new Uri("/VideoPlayerPage.xaml?video=" +
         NavigationContext.QueryString["videoHub"], UriKind.Relative));
    NavigationContext.QueryString.Remove("videoHub");
  }
}

In general, there are two reasons why the MainPage.OnNavigatedTo event will fire for a XAML page. It will fire as a result of normal application startup with the passed-in query values if launched from the music+video hub, or because the Mainpage.xaml was activated as a result of a “Back” navigation from the VideoPlayerPage as part of normal application back stack function. If it is activated as a result of selecting media in the music+videos hub, the QueryString Dictionary object will contain the videoHub key/value pair that was associated with the item when it was initially added to the hub.

In the OnNavigatedTo method, the code checks to see if there are any values available in the NavigationContext.QueryString dictionary. If there is, it indicates that the page was activated from the music+videos hub as part of application start up. If this is the case, the application navigates to the VideoPlayerPage.xaml file, passing in the video name. It also removes the videoHub key so that we don't enter an endless loop of navigating back and forth between MainPage.xaml and VideoPlayerPage.xaml.

This completes our implementation of a music+videos hub application. In the next section, we shift gears and cover remote media consumption.

App Connect – Bing Search Integration

This is the last section on App Connect, focused on how applications can integrate with the Bing Search functionality on Windows Phone 7.5. This is an exciting new capability that allows your application to be offered in search results on the device, even if the application is not currently installed.

The way it works from an end-user perspective is that when a user performs a web search using the hardware Search button on the phone, it can launch a relevant page in your application based on the Bing search results. Figure 7-15 shows the end-user experience:

images

Figure 7–15. App Connect Bing Search End-User Experience

In Figure 7-15, the end user performs a search for baby doll strollers. The Product Quick Card is returned in the results. The user can view search information and then swipe over to the apps pivot item. Selecting the application will install it if not installed and then the parameters are passed into the application and relevant information is displayed.

From a developer perspective, you register your applications for search extensions that make sense for your application. Extensions are associated by Bing to “quick cards.” Here is a list of available extensions:

  • Product Card – Items listed under the “product” heading in the web pivot page of search results.
  • Place Card – Locations listed in the “local” pivot page of search results.
  • Movie Card – Movies currently playing in theaters listed under the “Movies near…” heading the web pivot page of search results.

There are several extensions associated with each card type. For example, some Product Card extensions are Bing_Products_Arts_and_Crafts, Bing_Products_Baby_and_Nursery, Bing_Products_Beauty_and_Fragrance, etc.

images Note Applications that over-register for non-applicable search extensions risk being pulled from marketplace. Applications must use the App Connect URI parameters in a meaningful way.

Each card type receives launch parameters relevant to the card type. For example, the Product Card returns two parameters:

  • ProductName – The name of the product.
  • Category – A coma-delimited list of extensions related to the product

Here is an example deep link URI with the parameters returned to an application from an end-user product search for “Xbox 360”:

app://046B642D-C70D-4D8D-95E2-D92236763294/_default#/SearchExtras?
ProductName=XBox 360&Category=Bing_Products_Electronics,Bing_Products_Office_Products

Applications parse the query string parameters to process the values and display information relevant to the end-user. This link provides details on the available Quick Card extensions and parameters that are returned:

http://msdn.microsoft.com/en-us/library/hh202958.aspx

Building App Connect Search Applications Overview

Just like with the pictures Hub integration, you modify WMAppManifest.xml for your application. You also add an Extras.xml file where you identify the Quick Card extensions. The application must have at least one page that can process the deep link URI.

Your application needs to surface relevant information for display in the apps pivot page for a quick card. Here is a summary of the modifications that are required.

  • WMAppManifest.xml – Edit the Extensions element to list each Quick Card extension your application can handle.
  • Extras.Xml – Create an Extras.xml to hold the AppTitle element, which is the title displayed in the Quick Card. You can also display caption text below the AppTitle in Bing Search results. You can specify unique caption strings in the CaptionString element for particular Quick Card extensions.

App Connect Search Application Code Sample

In this section I go through the process of adding App Connect for Bing Search to an application. I add a project named AppConnectSearch to the Chapter 7 solution to start. The first step is to review your application requirements and capabilities to identify which Quick Card extensions your application should register for in WMAppManifest.xml. This will ensure the best experience for end users.

With the Quick Card extensions identified, the next step is to update the WMAppManifest.xml file with the supported extensions as shown here:

<Extensions>
      <!-- Production extensions, for products: video games -->
      <Extension
        ExtensionName="Bing_Products_Video_Games"
        ConsumerID="{5B04B775-356B-4AA0-AAF8-6491FFEA5661}"
        TaskID="_default"
        ExtraFile="Extensions\Extras.xml" />

      <!-- Production extensions, for movies. -->
      <Extension
        ExtensionName="Bing_Movies"
        ConsumerID="{5B04B775-356B-4AA0-AAF8-6491FFEA5661}"
        TaskID="_default"
        ExtraFile="Extensions\Extras.xml" />
      
      <!-- Production extensions, for places: travel, food, and dining. -->
      <Extension
        ExtensionName="Bing_Places_Food_and_Dining"
        ConsumerID="{5B04B775-356B-4AA0-AAF8-6491FFEA5661}"
        TaskID="_default"
        ExtraFile="Extensions\Extras.xml" />
    </Extensions>

Next create an Extras.xml in a new folder named Extensions. This allows us to specify the captions that will appear in the apps pivot page for the Quick Card. You can also specify the AppTitle element in multiple languages for the user as well. Listing 7-15 has the Extras.xml file.

Listing 7–15. Example Extras.xml File

<?xml version="1.0" encoding="utf-8" ?>
<ExtrasInfo>
  <!-- Application title -->
  <AppTitle>
    <default>Display App Connect URI Parameters</default>
  </AppTitle>

  <!-- Search-related captions -->
  <Consumer ConsumerID="{5B04B775-356B-4AA0-AAF8-6491FFEA5661}">
    <!-- Products caption for video games -->
    <ExtensionInfo>
      <Extensions>
        <ExtensionName>Bing_Products_Video_Games</ExtensionName>
      </Extensions>
      <CaptionString>
        <default>Product URI Details</default>
      </CaptionString>
    </ExtensionInfo>

    <!-- Movies caption -->
    <ExtensionInfo>
      <Extensions>
        <ExtensionName>Bing_Movies</ExtensionName>
      </Extensions>
      <CaptionString>
        <default>Movie URI Details</default>
      </CaptionString>
    </ExtensionInfo>

    <!-- Places caption for food and dining -->
    <ExtensionInfo>
      <Extensions>
        <ExtensionName>Bing_Places_Food_and_Dining</ExtensionName>
      </Extensions>
      <CaptionString>
        <default>Place URI Details</default>
      </CaptionString>
    </ExtensionInfo>
  </Consumer>
</ExtrasInfo>

The next step is to configure URI mapping for deep link support so that when the user is on the Quick Card in Bing Search and clicks the app, the user is taken to the deep link target page within the application. Add an xmlns namespace to bring in System.Windows.Navigation to App.xaml:

xmlns:nav="clr-namespace:System.Windows.Navigation;assembly=Microsoft.Phone"

This markup below is needed to configure URI mapping by adding this XAML to the Application.Resources element:

<nav:UriMapper x:Key="UriMapper">
  <nav:UriMapper.UriMappings>
    <nav:UriMapping Uri="/SearchExtras" MappedUri="/View/AppConnectSearchTargetPage.xaml"/>
  </nav:UriMapper.UriMappings>
</nav:UriMapper>

In App.xaml.cs in the App class constructor, add this code to declare a UriMapper instance. The UriMapper class converts a URI deep link into a new URI based on rules declared within the UriMapper class. The class is instantiated based on the XAML resource declared just above.

RootFrame.UriMapper = Resources["UriMapper"] as UriMapper;

Now it is time to create a basic MVVM architecture to handle the URI parameters, following guidance on MSDN. Listing 7-16 has the AppConnectSearchUriParams model that can store URI parameters.

Listing 7–16. The AppConnectSearchUriParams Model Class Code File

using System.ComponentModel;

namespace AppConnectBingSearch.Model
{
  public class AppConnectSearchUriParams
  {
    public AppConnectSearchUriParams(string Name, string Value)
    {
      _paramName = Name.Trim();

      if (_paramName == "Category")
      {
        _paramValue = Value.Replace(",", ", ");
      }
      else
      {
        _paramValue = Value;
      }
    }

      private string _paramName;
      public string ParamName
      {
        get { return _paramName; }
        set
        {
          if (_paramName != value)
          {
            _paramName = value;
            NotifyPropertyChanged("ParamName");
          }
        }
      }

      private string _paramValue;
      public string ParamValue
      {
        get { return _paramValue; }
        set
        {
          if (_paramValue != value)
          {
            _paramValue = value;
            NotifyPropertyChanged("ParamValue");
          }
        }
      }

      #region INotifyPropertyChanged Members
      public event PropertyChangedEventHandler PropertyChanged;

      private void NotifyPropertyChanged(string propertyName)
      {
        if (PropertyChanged != null)
        {
          PropertyChanged(this,
            new PropertyChangedEventArgs(propertyName));
        }
      }
      #endregion
    }
  }

With the Model class declared, next I create a View Model class to back the page that will be displayed after processing the deep link. The AppConnectSearchViewModel class uses an instance of the AppConnectUriParameters to receive the values from the deep link and display them on the page.

Listing 7–17. The QuickCardTargetPageViewModel ViewModel Class Code File

using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using AppConnectBingSearch.Model;

namespace AppConnectBingSearch.ViewModel
{
  public class QuickCardTargetPageViewModel : INotifyPropertyChanged
  {
    public QuickCardTargetPageViewModel()
    {
      AppConnectUriParameters =
        new ObservableCollection<AppConnectSearchUriParams>();
    }

    private ObservableCollection<AppConnectSearchUriParams>
      _AppConnectUriParameters;
    public ObservableCollection<AppConnectSearchUriParams>
      AppConnectUriParameters
    {
      get { return _AppConnectUriParameters; }
      set
      {
        if (_AppConnectUriParameters != value)
        {
          _AppConnectUriParameters = value;
          NotifyPropertyChanged("AppConnectUriParameters");
        }
      }
    }
    
    public void LoadUriParameters(IDictionary<string, string> QueryString)
    {
      AppConnectUriParameters.Clear();

       foreach (string KeyName in QueryString.Keys)
      {
        // Default value for parameter
        string KeyValue = "<no value present in deep link>";

        QueryString.TryGetValue(KeyName, out KeyValue);

        AppConnectUriParameters.Add(
          new AppConnectSearchUriParams(KeyName, KeyValue));
      }
    }

    #region INotifyPropertyChanged Members
    public event PropertyChangedEventHandler PropertyChanged;

    private void NotifyPropertyChanged(string propertyName)
    {
      if (PropertyChanged != null)
      {
        PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
      }
    }
    #endregion
  }
}

The next step is to add a folder named View and a new page named AppConnectSearchTargetPage.xaml to present the UI and data bind it to the QuickCardTargetPageViewModel class, which I do in Expression Blend. The AppConnectSearchTargetPage.Loaded event handler loads the deep link parameters into the ViewModel:

void AppConnectSearchTargetPage_Loaded(object sender, RoutedEventArgs e)
{
  ((AppConnectSearchViewModel)DataContext).LoadUriParams(this.NavigationContext.QueryString);
}

Testing an App Connect Search Application

In this section I cover how to test an App Connect Bing Maps search application, which requires an Internet connection to work. The first step is to start debugging to deploy the application to the Emulator or a tethered device for debugging. Once the application is running on the emulator, click the Bing Search hardware button and enter “gears of war 3” or some other search related to video game products. Figure 7-16 shows the first search page with the products heading in the results. The middle screenshot shows the products page on the apps pivot. The right screenshot shows the results after clicking the Display App Connect URI Parameters item in the apps pivot page.

images

Figure 7–16 Testing Bing Search App Connect

Integrating with App Connect Bing Search is pretty straightforward to do. The more difficult aspect is to process the query parameters to provide the best experience for the user that is relevant with what they are trying to accomplish.

Conclusion

This chapter covers a lot of ground on advanced integration with Windows Phone. First it starts off with a discussion of necessary MVVM Techniques to help you build real world applications, covering how to drive the user interface from the ViewModel as well as how to data bind any type to any other type via the IValueConverter Interface.

The chapter next moves on to a discussion of advanced services such as Encryption Services and Audio and Media services. We cover the new DataProtected class available in Windows Phone OS 7.5 to encrypt secrets.

The chapter concludes with a discussion of the various App Connect scenarios such as picture Hub integration, music+video hub integration, and Bing Search integration.

In the next chapter this book shifts gears focusing on 2D game development with the XNA Framework.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset