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.
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:
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.
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
.
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.
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.
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.
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
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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:
The next covers how to use these algorithms to securely store data on Windows Phone.
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:
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.
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
:
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.
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.
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.
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.
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.
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.
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 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:
AlbumCollection
classArtistCollection
classGenreCollection
classPictureCollection
classPlaylistCollection
classSongCollection
classTo 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.
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:
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.
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.
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.
This concludes our coverage of the Windows Phone media library programmable API. Next up is how to create a Photo Extras application.
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.
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.
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:
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 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.
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.
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.
There are several interesting events available with the MediaElement
control. Listing 7–10 shows the available media-related events.
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.
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.
Note For more information on Silverlight and MediaStreamSource check out Silverlight Recipes: A Problem-Solution Approach, Second Edition: http://apress.com/book/view/1430230339.
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.
Note For more information on IIS Smooth Streaming go to http://learn.iis.net/page.aspx/558/getting-started-with-iis-smooth-streaming/
.
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
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/
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.
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
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.
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.
The next section covers how an application can extend 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:
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.
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 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.
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.
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.
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.
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.
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.
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.
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.
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.
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:
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.
Tip In an application retrieving images from server-apis, use the WriteableBitmap
class to create images that are branded to your application.
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.
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:
Disconnecting the device reverts back to normal Zune functionality with the device.
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.
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));
}
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.
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:
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.
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.
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.
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.
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
.
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.
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:
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:
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.
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:
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
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.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.
<?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.
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.
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);
}
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.
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.
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.