Building a great user interface for a professional application is challenging, given all of the competing priorities, such as presenting data, interaction models, network connectivity, security, managing background tasks, and localization. It’s important to build a rich application on a solid architecture that helps the different pieces work together.
This chapter delves more deeply into user interface development with Silverlight, covering a wide range of topics. It builds on previous chapters, but especially on Chapter 2, which covered Silverlight user interface development. This chapter extends the topic of user interface development to include how to architect an application with separation of concerns without sacrificing support for tooling or ease of development.
This chapter starts with an introduction of the most prevalent application architecture model in Silverlight development, Model-View-ViewModel (MVVM), which provides excellent support for well-designed user interfaces with separation of concerns. With Windows Phone OS 7.1 (Mango), Windows Phone adds compatibility with the Silverlight 4 programming model including much improved commanding support.
After MVVM, the Silverlight for Windows Phone Toolkit is next, covering the additional controls and capabilities including the new controls available in the update for Windows Phone OS 7.1 (Mango). The section following is on creating transitions and interactivity in Expression Blend. The final section is on the Microsoft Advertising SDK, which provides an excellent vehicle to monetize applications.
The Model-View-ViewModel (MVVM) architecture originated when the Microsoft Windows Presentation Foundation (WPF) team were building the first version of Expression Blend. WPF is Microsoft’s desktop XAML development model, and Expression Blend is written in WPF. MVVM is similar to other separation-of-concerns architectures, like the tried-and-true Model-View-Controller (MVC) model; however, MVVM is optimized to take advantage of XAML’s rich data binding, data templates, commands, and event routing capabilities. The next section covers the architecture in more detail.
In this section, the MVVM pattern is defined to help you grasp how it works with XAML. If you are familiar with MVC, MVVM will look somewhat familiar to you—but it is much more than just MVC. MVVM relies heavily on XAML data-binding capabilities to allow the UI to bind to both data and commands. Figure 6-1 depicts the MVVM architecture.
In Chapter 4, there is a simple example that displays a list of fake Vendor
data made available via JSON. The REST+JSON service project, named WcfRemoteServicesSimpleRestJSON,
is added to the Chapter 6 solution. The BasicMVVM
sample re-architects the AdventureWorksRestJSONPage.xaml
page from the Chapter 4 CallingRemoteServices
project to use MVVM in the Chapter 6 project named BasicMVVM
.
The BasicMVVM
and the WcfRemoteServicesSimpleRestJSON
projects are configured as the startup project. Three folders are added to the project, named Model
, View
, and ViewModel
. The sections that follow cover the major components of MVVM in the BasicMVVM
example.
The Model contains the building blocks of the application. It consists of the underlying data objects that are populated via a data access layer. Examples of Model classes are Customer
, Store
, Product
, and so on. When you create a class to represent an object in an application, it most likely belongs as part of the Model. The Model sits behind the ViewModel. The View will data bind to lists or individual objects based on classes in the Model.
To get started, copy over the Vendor
class from the WcfRemoteServicesSimpleRestJSON
services project to the BasicMVVM
Models
folder. The class implements the INotifyPropertyChanged
interface to support data binding at the class level. The INotifyPropertyChanged
interface ensures that changes to the underlying object are propagated to the UI and vice versa. See Listing 6-1 for the code.
using System;
using System.ComponentModel;
using System.Runtime.Serialization;
namespace BasicMVVM.Model
{
//Copied from services project
[DataContract()]
public class Vendor : INotifyPropertyChanged
{
private string AccountNumberField;
private byte CreditRatingField;
private string NameField;
[DataMemberAttribute()]
public string AccountNumber
{
get
{
return this.AccountNumberField;
}
set
{
this.AccountNumberField = value;
NotifyPropertyChanged("AccountNumber");
}
}
[DataMemberAttribute()]
public byte CreditRating
{
get
{
return this.CreditRatingField;
}
set
{
this.CreditRatingField = value;
NotifyPropertyChanged("CreditRating");
}
}
[DataMemberAttribute()]
public string Name
{
get
{
return this.NameField;
}
set
{
this.NameField = value;
NotifyPropertyChanged("Name");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
if (null != PropertyChanged)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
The fact that the View or UI data binds to the ViewModel suggests that a ViewModel consists of the data containers for the application, which is correct. Lists of objects defined in the Model are created and managed by the ViewModel. In addition, the ViewModel consists of the majority of application logic as well.
Next create the VendorViewModel
class. The VendorViewModel
class in the BasicMVVM
project supports the following four major features:
INotifyPropertyChanged
The Vendor
-specific business logic is pretty straightforward. It consists of a read-only collection of Vendor
objects from the Model
and a couple of update operations that we cover in just a bit.
Note While the VendorViewModel.Vendors
collection is read-only— it has just a get
property accessor— you can still add and remove Vendor
objects in the collection. You just cannot assign a new collection to the property.
It is critical to implement INotifyPropertyChanged
for data binding to work. Otherwise, changes are not propagated back to the UI, and vice versa. It is simple enough to do. Add an instance of the PropertyChangedEventHandler
class named PropertyChanged
and a method that takes a property name as a string and then fires the PropertyChanged
event instance.
To detect design time, the System.ComponentModel.DesignerProperties
class has a static bool
property named IsInDesignTool
that indicates whether the code is running in a design-time tool. The VendorViewModel
constructor checks if an instance of the class is running at design-time. If it is at design-time, the constructor calls the LoadSampleData
method. Otherwise, at run-time, it calls LoadData
, which invokes a remote REST+JSON service.
The VendorViewModel
class implements two data operations on the Vendors
collection, one to insert a vendor and another to delete a vendor. The data operations are implemented via two ICommand
implementations, one to add a Vendor
object and another to remove a Vendor
object from the VendorViewModel.Vendors
collection. The ICommand
interface is newly available in Windows Phone OS 7.1, brought over from Silverlight 4. This interface enables rich control over business logic execution via data binding. Commanding is an advanced feature of Silverlight that enables stronger separation of concerns between the View and the ViewModel.
In the previous edition of the book, event handlers had to be wired up in the View’s code-behind class file, and code like this executed in the handler:
private void insertVendorAppBarBtn_Click(object sender, EventArgs e)
{
VendorViewModel vm = LayoutRoot.DataContext as VendorViewModel;
vm.AddVendor();
}
Instead, you can now data bind in XAML to the ICommand
directly without having to write any code. I’ll discuss how that works when I cover the VendorsView
view object. The last major functionality for the VendorsViewModel
class is making the remote service call, which is covered in detail in Chapter 4. The interesting change for this scenario is that the service call and asynchronous callback live in the VendorsViewModel
class, so the code does not have direct access to the View and the View UI elements as in Chapter 4. The callback cannot have code like the following:
vendorsListbox.Dispatcher.BeginInvoke(…);
The solution is to make the call using this line of code instead:
Deployment.Current.Dispatcher.BeginInvoke(..);
This code ensures that the correct Dispatcher
instance is used to notify the UI that data changes occurred. The next challenge is that the callback function needs to update the Vendors
collection property. Remember that Vendors
is a read-only collection, because we do not want external classes to be able to assign a new collection to it. We want the data to only come from the remote services. The code instead assigns the collection to the underlying _vendors
collection private member variable.
The final issue is that the code still needs to notify the UI that data changes occurred—that is, that the Vendors
collection is loaded. Since the _vendors
collection is updated directly, NotifyPropertyChanged("Vendors")
is called in the anonymous delegate by BeginInvoke
. Again, the code could make Vendors
read/write and have a set accessor function like this, but maintaining data integrity is preferred, so the set function is commented out, as in the following:
set
{
_vendors = value;
NotifyPropertyChanged("Vendors");
}
Listing 6-2 has the full source code for VendorViewModel
as well as the two ICommand
implementations in the AddVendorCommand
and RemoveVendorCommand
classes.
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Net;
using System.Runtime.Serialization.Json;
using System.Windows;
using BasicMVVM.Models;
using System.Windows.Input;
namespace BasicMVVM.ViewModels
{
public class VendorViewModel : INotifyPropertyChanged
{
public VendorViewModel()
{
if (InDesignTime)
{
LoadSampleData();
}
else
{
LoadData();
}
}
#region Design-time support
private bool InDesignTime
{
get
{
return DesignerProperties.IsInDesignTool;
}
}
private void LoadSampleData()
{
_vendors = new ObservableCollection<Vendor>()
{
new Vendor(){AccountNumber="111111", CreditRating=65,
Name="DesignTime - Fabrikam Bikes" },
new Vendor(){AccountNumber="222222", CreditRating=40,
Name="Contoso Sports" },
new Vendor(){AccountNumber="333333", CreditRating=30,
Name="Duwamish Surfing Gear" },
new Vendor(){AccountNumber="444444", CreditRating=65,
Name="Contoso Bikes" },
new Vendor(){AccountNumber="555555", CreditRating=40,
Name="Fabrikam Sports" },
new Vendor(){AccountNumber="666666", CreditRating=30,
Name="Duwamish Golf" },
new Vendor(){AccountNumber="777777", CreditRating=65,
Name="Fabrikam Sun Sports" },
new Vendor(){AccountNumber="888888", CreditRating=40,
Name="Contoso Lacross" },
new Vendor(){AccountNumber="999999", CreditRating=30,
Name="Duwamish Team Sports" },
};
}
#endregion
#region Vendors Data Load
HttpWebRequest httpWebRequest;
private void LoadData()
{
httpWebRequest =
HttpWebRequest.CreateHttp(
"http://localhost:9191/AdventureWorksRestJSON.svc/Vendors");
httpWebRequest.BeginGetResponse(new AsyncCallback(GetVendors), null);
}
//add a reference to System.Servicemodel.web to get DataContractJsonSerializer
void GetVendors(IAsyncResult result)
{
HttpWebResponse response =
httpWebRequest.EndGetResponse(result) as HttpWebResponse;
DataContractJsonSerializer ser =
new DataContractJsonSerializer(typeof(ObservableCollection<Vendor>));
_vendors =
ser.ReadObject(response.GetResponseStream()) as ObservableCollection<Vendor>;
//Vendors is read-only so cannot set directly
//Must call NotifyPropertyChanged notifications on UI thread
//to update the UI and have data binding work properly
Deployment.Current.Dispatcher.BeginInvoke(() =>
{
NotifyPropertyChanged("Vendors");
});
}
#endregion
#region Vendors Business Logic
private ObservableCollection<Vendor> _vendors;
public ObservableCollection<Vendor> Vendors
{
get
{
return _vendors;
}
//set
//{
// _vendors = value;
// NotifyPropertyChanged("Vendors");
//}
}
public Vendor GetVendorByAccountNumber(string accountNumber)
{
var vendor = from v in _vendors
where v.AccountNumber == accountNumber
select v;
return vendor.First<Vendor>();
}
public ICommand AddVendor
{
get { return new AddVendorCommand(this); }
}
public ICommand RemoveVendor
{
get { return new RemoveVendorCommand(this); }
}
#endregion
#region INotifyPropertyChanged interface members
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(String property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
#endregion
}
public class AddVendorCommand : ICommand
{
VendorViewModel _vendorViewModel;
public AddVendorCommand(VendorViewModel vendorViewModel)
{
_vendorViewModel = vendorViewModel;
}
public bool CanExecute(object parameter)
{
if (_vendorViewModel != null)
return true;
else
return false;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
_vendorViewModel.Vendors.Add(new Vendor()
{
AccountNumber = "111111",
CreditRating = 65,
Name = "Fabrikam Bikes - Added"
});
}
}
public class RemoveVendorCommand : ICommand
{
VendorViewModel _vendorViewModel;
public RemoveVendorCommand(VendorViewModel vendorViewModel)
{
_vendorViewModel = vendorViewModel;
}
public bool CanExecute(object parameter)
{
if (_vendorViewModel != null)
return true;
else
return false;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
Vendor vendor = parameter as Vendor;
if (null != vendor)
_vendorViewModel.Vendors.Remove((Vendor)vendor);
}
}
}
Commanding is implemented by first declaring an ICommand
object as part of the ViewModel
class, in this case VendorViewModel.AddVendor
and VendorViewModel.RemoveVendor
. Both return an instantiation of the appropriate ICommand
implementation, passing in the current instance of the VendorViewModel
. The next section covers how to make the Model and ViewModel objects available to the UI as well as how to wire in the ICommand
support
The View is the actual XAML of an application. It is the MainPage.xaml
file in a typical Windows Phone project, and is what the user interacts with directly, presenting the underlying data and application logic. The View data binds to the ViewModel, which is covered in the previous section.
The goal when building the View is not to have any code in the code-behind for the .xaml
file, if possible, to maximize separation of concerns. This means that all logic is in the ViewModel, which is nonvisual, making it much more unit-testable. The other advantage of the separation of concerns here is that the design team can focus on building out the View without interfering with business logic in event handlers. A View always has a reference to the ViewModel, because it data binds to it.
Remove the MainPage.xaml
from the BasicMVVM
project and add a new View (.xaml
page) to the Views
folder named VendorsView.xaml
. Next, edit the WMAppManifest.xml file by changing the NavigationPage
attribute to point to the new default task, as in the following:
<DefaultTask Name ="_default" NavigationPage="Views/CustomersView.xaml"/>.
Note In general, the WMAppManifest.xml
file should not be manually edited, but in this case it is required.
In Expression Blend, add a ListBox
to VendorsView.xaml
and configure the ItemsSource
to data bind to the VendorViewModel.Vendors
collection by clicking the Advanced Options button next to the ItemsSource
property in the Expression Blend Properties window and selecting Data Binding to bring up the Create Data Binding dialog. Click the +CLR Object button, select VendorViewModel,
and then click OK.
Tip If the VendorViewModel
class— or any .NET CLR class that you want to data bind—does not show up in the dialog box, make sure to compile the application. Static collections will not appear either.
This generates a new Data Source named VendorViewModelDataSource
in the left pane. Select Vendors
in the right pane and then click OK. This configuration updates the XAML in three places. It adds a new resource to the VendorsView
page, as in the following:
<phone:PhoneApplicationPage.Resources>
<BasicMVVM_ViewModels:VendorViewModel
x:Key="VendorViewModelDataSource" d:IsDataSource="True"/>
</phone:PhoneApplicationPage.Resources>
It configures LayoutRoot
Grid
’s DataContext
property to point to the VendorViewModel
class:
DataContext="{Binding Source={StaticResource VendorViewModelDataSource}}"
Next, Configure the vendorsListBox
ItemsSource
property to data bind to the VendorViewModel.Vendors
collection like so: ItemsSource="{Binding Vendors}" in Expression Blend.
Generally in Windows Phone, developers should use the Application Bar as much as possible as the standard UI to implement in-page functionality. In this case we use two standard buttons to demonstrate the concept of commanding. The ListBox
configured above is added to a StackPanel
container, and another StackPanel
with Orientation set to Horizontal is added below the ListBox
with two Buttons objects. Figure 6-2 shows the UI.
One of the goals of MVVM and separating concerns is to make the View as “thin” as possible. Windows Phone OS 7.1 (Mango) now supports UI element Buttons to data bind to methods on the ViewModel
via Commanding and the ICommand
interface. This means that instead of having event handlers in the code-behind for the view, everything is instead configured via data binding in XAML.
As mentioned above, Windows Phone OS 7.1 with Silverlight 4 compatibility includes support for Commanding. ButtonBase
and Hyperlink
support Command
and CommandParameter
properties. The Command
property can reference an ICommand
implementation that comes from a view-model data source, through a {Binding}
usage. The command is then interpreted at run-time. CommandParameter
can pass data into the Command
, such as which record to delete.
In Expression Blend, select each button and bring up the data binding UI, and both of the VendorViewModel ICommand
objects are displayed. For the “Remove Vendor” button, also data bind the CommandParameter
property to the ListBox.SelectedItem
property. Save the changes and run the project to test out Commanding support, and you’ll see that it works like a charm.
While using Commanding with Button
objects is great, unfortunately support for Commanding is not available with the Application Bar Buttons. Luckily, there are third-party, open-source frameworks that provide extensions to Silverlight that enable better support for MVVM, which we cover in the next section.
Because Silverlight for Windows Phone is based on Silverlight 3, it falls short of the MVVM capabilities available in Silverlight 4 and WPF, such as full Commanding support. Luckily, there are quite a few third-party MVVM frameworks available to choose from that provide Commanding support and more. The following lists a few in no particular order:
The major benefit that these frameworks have in varying degrees is increasing separation of concerns, unit testing support, support for Messaging, and additional Commanding support. You can find arguments for and against the available frameworks, so please investigate the available options. For this example, let’s take MVVM Light Toolkit for a spin, as it is this author’s opinion that MVVM Light provides a nice balance of power and simplicity that is a great fit for phone application development. Many others would suggest Caliburn instead for similar reasons. The most important thing is to pick a helper SDK, learn it, and use it.
For Windows Phone development, my preference is GalaSoft’s MVVM Light Toolkit. In my opinion it has the right combination of power and as the name says, lightness, for mobile application development.
The MVVM Light Toolkit is up to version 4 Beta, which has quite a few enhancements over the previous version. All of the code has been updated to work with this latest version as of the time of this writing.
The MVVM Light Toolkit was originally developed to address the Commanding shortfalls in Silverlight 2 and Silverlight 3. The MVVM Light Toolkit also includes customized Visual Studio 2010 templates to help you get started right away. First, download the MVVM Light Toolkit and follow the instructions at this page:
http://galasoft.ch/mvvm/getstarted/
If you like the MVVM Light toolkit, I encourage you to click the Donate button at the bottom of the above page, which goes towards the cost of running the site and the rest goes to charity. The source code is available in CodePlex here:
http://mvvmlight.codeplex.com/
Once you have the toolkit downloaded and installed, you can run the MVVMLightSample available in Chapter 6 to see it in action. The entire range of features of the MVVM Light Toolkit are not described end-to-end in this book, but the next couple of sections cover the features of MVVM Light used to migrate the BasicMVVM to MVVM Light.
In Visual Studio 2010, you can select File New Project, and click on Silverlight for Windows Phone to filter to the Windows Phone project templates. Select MvvmLight (WP71) as the project template to get started with the Mango version of MVVM Light. If you don’t see that option, check to ensure that you installed the toolkit correctly before proceeding.
Once the project is created, run it and the screen in Figure 6-3 should appear, which indicates that all is configured correctly.
The page title, subtitle, and text are all data bound to the MainViewModel
class. Let’s go through the architecture of MVVM Light so that you have a basic understanding as we migrate the BasicMVVM
app to MVVM Light. After a new project is created, the project includes the default App.xaml
and MainPage.xaml
as well as the other items expected in a Silverlight for Windows Phone application but with a couple of additions. There is a Model folder with three code files present that we discuss shortly. There is also a ViewModel
folder that contains two ViewModel
classes named MainViewModel
and ViewModelLocator
. I cover these in the next section.
The ViewModelLocator
class contains a reference to every ViewModel
class in the project. This provides a centralized way to manage creation and allow XAML configuration via a single application resource. By default, in App.xaml
a resource is added for the ViewModelLocator
class. A namespace is added to the <Application>
object that hosts the PhoneApplicationFrame
that contains the XAML pages or View classes as they are navigated:
xmlns:vm="clr-namespace:MvvmLightSample.ViewModel"
The ViewModelLocato
r class is configured as an application level resource like this:
<Application.Resources>
<vm:ViewModelLocator x:Key="Locator"
d:IsDataSource="True" />
</Application.Resources>
This resource is available throughout the entire application, just like any other application-level resource, making the referenced ViewModel
objects available now. Now we move on to explain how MainPage.xaml
data binds to MainViewModel within this architecture. In the XAML, the <PhoneApplicationPage>
element’s DataContext
property data binds to the Locator resource discussed above via this XAML code:
DataContext="{Binding Main, Source={StaticResource Locator}}"
The Path
property for the Binding
object is configured with the value of Main
using the default syntax (it can also be written as Path=Main
). This configuration makes an instance of the MainViewModel
available within MainPage.xaml
and allows the page title, sub-title, and text to data bind to properties available on the MainViewModel
. Here’s an example of one of the Application Title Bindings:
<TextBlock x:Name="ApplicationTitle"
Text="{Binding ApplicationTitle}"
Style="{StaticResource PhoneTextNormalStyle}" />
Listing 6-3 shows the default ViewModelLocator class.
namespace MvvmLightSample.ViewModel
{
/// <summary>
/// This class contains static references to all the view models in the
/// application and provides an entry point for the bindings.
/// <para>
/// Use the <strong>mvvmlocatorproperty</strong> snippet to add ViewModels
/// to this locator.
/// </para>
/// <para>
/// See http://www.galasoft.ch/mvvm/getstarted
/// </para>
/// </summary>
public class ViewModelLocator
{
static ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
if (ViewModelBase.IsInDesignModeStatic)
{
SimpleIoc.Default.Register<IDataService, Design.DesignDataService>();
}
else
{
SimpleIoc.Default.Register<IDataService, DataService>();
}
SimpleIoc.Default.Register<MainViewModel>();
}
/// <summary>
/// Gets the Main property.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance",
"CA1822:MarkMembersAsStatic",
Justification = "This non-static member is needed for data binding purposes.")]
public MainViewModel Main
{
get
{
return ServiceLocator.Current.GetInstance<MainViewModel>();
}
}
/// <summary>
/// Cleans up all the resources.
/// </summary>
public static void Cleanup()
{
}
}
}
Notice in Listing 6-3 how the MainViewModel
is made available. It uses the SimpleIoc
class to register the class MainViewModel
within the application. For the Main
property, the ServiceLocator
class gets the instance of MainViewModel
that is available.
The class name SimpleIoc
is short for Simple Inversion of Control, which is a technique to simplify the creation and the resolution of services by allowing a consumer to request an interface and have SimpleIoc provide a matching concrete class, in this case the ViewModels; it allows for a cleaner syntax in the ViewModelLocator
than in previous versions of MVVM Light.
To add additional ViewModel
objects, manually edit the ViewModelLocator
class and follow the pattern for the MainViewModel
as an example. There is a code snippet available to automate this process. Type mvvmlocatorproperty
and tap the Tab key twice to quickly add the property using Visual Studio 2010’s code snippet template UI. Essentially, type new values for the default and click the Tab key to move through the template to quickly add the property combination in the correct format.
The MainViewModel
takes a type of IDataService
in its constructor. Generally ViewModels need not take parameters for XAML support. With SimpleIoc
, as long as the service is registered, in this case IDataService
, and the ViewModel is registered, SimpleIoc
will handle this for you and pass the registered instance into the constructor and create the MainViewModel
.
Notice also in the ViewModelLocator
class it registers a service for the IDataService
interface in the constructor using an if statement to provide design-time or run-time data depending on the current environment. What’s nice about this method is that the application can simply retrieve an instance of IDataService
and it will be properly populated with data as needed; you won’t have to worry about design-time vs. run-time. You could use the same technique to register an IDataService
that provides different sets of data to authenticated or unauthenticated users, as well as design-time data.
A reference is added to System.Runtime.Serialization
assembly and to System.ServiceModel.Web
to set up the work needed to create the Model and the related data service.
A question that comes up when building MVVM is where to put the code to load up your data collections. MVVM Light provides an answer to this question with the concept of an IDataService
and the inversion-of-control functionality provided by SimpleIoc
. The idea is that you register your data service and ViewModels in the view model locator page, and it connects the ViewModel with the corresponding service. In MVVM Light, the service is placed with the related Model
in the Model
folder.
We first modify the IDataService
provided by the default project template to return the ApplicationTitle
and PageName
so that the MainPage.xaml
View has something to bind to for the Title and Page Name. Note that the data binding code is already there in the default MainPage.xaml
View, but it is not present in the MainViewModel
or the IDataService
. Here is the updated interface:
public interface IDataService
{
void GetApplicationTitle(Action<DataItem, Exception> callback);
void GetPageName(Action<DataItem, Exception> callback);
}
The implementation class, DataService
, is not much more complex. It simply returns the values for the properties:
public class DataService : IDataService
{
public void GetApplicationTitle(Action<DataItem, Exception> callback)
{
var item = new DataItem("CHAPTER SIX");
callback(item, null);
}
public void GetPageName(Action<DataItem, Exception> callback)
{
var item = new DataItem("mvvm light sample");
callback(item, null);
}
}
While a pretty simple modification, it demonstrates that the DataService
provides a callback of the desired data. Let’s now create the IVendorService
, which will return a listing of all of the Vendor
objects:
public interface IVendorsService
{
void GetVendors(Action<IList<Vendor>, Exception> callback);
}
In this case, instead of returning a DataItem
class object, GetVendors
returns an IList
containing Vendor
objects. Where this gets interesting is in the concrete implementation in the VendorService
class shown here:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Net;
using Newtonsoft.Json;
namespace MvvmLightSample.Model
{
public class VendorsService : IVendorsService
{
private const string ServiceUri =
"http://localhost:9191/AdventureWorksRestJSON.svc/Vendors";
public void GetVendors(Action<IList<Vendor>, 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<Vendor>, Exception>;
if (callback == null)
{
return;
}
if (e.Error != null)
{
callback(null, e.Error);
return;
}
ObservableCollection<Vendor> vendors;
vendors = JsonConvert.DeserializeObject<ObservableCollection<Vendor>>(e.Result);
callback(vendors, null);
}
}
}
There are a few things going on in this implementation to note, but the first is that the code uses JSON.NET to deserialize the data. JSON.NET has been tested informally to have better performance than the built-in DataContractSerializer
class. JSON.NET is very flexible with quite a few options that I won’t go into here. I do take advantage of the very simple code it takes to deserialize a string into a collection of objects:
ObservableCollection<Vendor> vendors;
vendors = JsonConvert.DeserializeObject<ObservableCollection<Vendor>>(e.Result);
Otherwise, the code uses the new and improved WebClient
class in Windows Phone OS 7.1 (Mango) to call the REST service and return the data in a string as shown earlier in the code. The last item I cover is the modifications to the Vendor
class. It inherits from ObservableObject
, a new base class available in MVVM Light version 4. Here is the updated Vendor
class:
using System.Runtime.Serialization;
using GalaSoft.MvvmLight;
namespace MvvmLightSample.Model
{
[DataContract()]
public class Vendor : ObservableObject
{
private string AccountNumberField;
private byte CreditRatingField;
private string NameField;
[DataMemberAttribute()]
public string AccountNumber
{
get
{ return this.AccountNumberField; }
set
{ this.AccountNumberField = value;
RaisePropertyChanged("AccountNumber");}
}
[DataMemberAttribute()]
public byte CreditRating
{
get
{ return this.CreditRatingField; }
set
{this.CreditRatingField = value;
RaisePropertyChanged("CreditRating");}
}
[DataMemberAttribute()]
public string Name
{
get
{return this.NameField;}
set
{this.NameField = value;
RaisePropertyChanged("Name");}
}
}
}
The other item is design-time data. The ViewModelLocator
class checks for design time with a call to ViewModelBase.IsInDesignModeStatic
. Depending on whether it is design time or not, the Design
data service is registered with SimpleIoc
or the production data service is registered in the ViewModelLocator
constructor:
if (ViewModelBase.IsInDesignModeStatic)
{
SimpleIoc.Default.Register<IVendorsService, Design.DesignVendorService>();
SimpleIoc.Default.Register<IDataService, Design.DesignDataService>();
}
else
{
SimpleIoc.Default.Register<IVendorsService, VendorsService>();
SimpleIoc.Default.Register<IDataService, DataService>();
}
The default project template for MVVM Light has a Design
folder with a default design-time data source class named DesignDataService
. I modified it to return the ApplicationTitle
and PageName
values at design time. The design-time data class for Vendors
is DesignVendorDataService.cs
in the Design
folder. It creates five records to have some data at design-time. As you can see, having Interface-based services allow for simplified code to instantiate the correct data source as needed. This completes coverage of the Model and data service. Next up on our MVVM journey is the ViewModel.
With the changes in MVVM Light version 4, I rewrote the ViewModel class from BasicMVVM to take advantage of the new features.
Note The first edition of this book leveraged MVVM Light version 3, which does not compile in the new version. Be sure to update to the latest sample code available as a download for this edition.
The VendorViewModel
class inherits from ViewModelBase
to take advantage of the built-in INotifyPropertyChange
implementation. I’ve also added a reference to System.ServiceModel.Web
to make the DataContractJsonSerializer
class available within the VendorsViewModel
.
The new VendorsViewModel
class includes a Vendors property of type ObservableCollection
as before. The constructor for VendorsViewModel
takes an instance of IVendorsService
passed in by SimpleIoc
. In the constructor the data is loaded up as shown here:
public VendorsViewModel(IVendorsService dataService)
{
_vendorsService = dataService;
Vendors = new ObservableCollection<Vendor>();
_vendorsService.GetVendors(
(vendors, error) =>
{
if (error != null)
{
// Report error here
return;
}
Vendors = (ObservableCollection<Vendor>)vendors;
});
}
Whether at design time or runtime, the appropriate concrete implementation of IVendorService
is provided and the appropriate data service provides data to the UI. Next up is the VendorsView
object where the data is displayed.
A new folder named View
is added to the MVVMLightSample
project, and the VendorsView.xaml
page is copied from the BasicMVVM
project to the MVVMLightSample view
folder. Do a Find / Replace with BasicMVVM
to replace it with MvvmLightSample
with the “Look in” set to Current Project. That fixes up namespace references to compile the project successfully.
Next, MainViewModel
is modified to return a more appropriate page title and subtitle as well as string text for the Welcome message property for the MainPage.xaml
page to display the application title and page name. A TextBlock
is added to MainPage.xaml
to provide a link to navigate to the Vendors View page via the NavigateToPageAction
behavior. Clicking on that text will navigate you to the VendorsView.xaml
page. Fix up the margins so that everything aligns at 24px on the left.
If you run the project, it works as before, but let’s configure the VendorsView
to take advantage of the MVVM Light toolkit capabilities. First, add a property combination to the ViewModelLocator
via the mvvmlocatorproperty
code snippet. The code snippet quickly generates this code for you in the ViewModelLocator
class:
static ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
if (ViewModelBase.IsInDesignModeStatic)
{
SimpleIoc.Default.Register<IVendorsService, Design.DesignVendorService>();
SimpleIoc.Default.Register<IDataService, Design.DesignDataService>();
}
else
{
SimpleIoc.Default.Register<IVendorsService, VendorsService>();
SimpleIoc.Default.Register<IDataService, DataService>();
}
SimpleIoc.Default.Register<MainViewModel>();
SimpleIoc.Default.Register<VendorsViewModel>();
}
public VendorsViewModel Vendors
{
get
{
return ServiceLocator.Current.GetInstance<VendorsViewModel>();
}
}
As you can see, if you are not familiar with Visual Studio 2010 the code snippet is quite handy! It adds an implementation of the constructor that registers the ViewModel. We delete that version and edit the existing constructor as shown above. It also adds a property that uses the ServiceLocator
to return the VendorsViewModel
instance.
Now that we have the VendorsView
added to the ViewModelLocator
, we can configure the VendorsView
to data bind the MVVM Light Toolkit way using Expression Blend. First compile the project to make sure everything is up to date, and then remove the DataContext
binding on the LayoutRoot
Grid
. Also remove the VendorViewModelDataSource
from the PhoneApplicationPage.Resources
section in the VendorsView.xaml
file.
In Expression Blend, select the PhoneApplicationPage
root item in the Objects and Timeline tool window. Find the DataContext
property, click the Advanced Options button, and select Data Binding to bring up the Create Data Binding dialog. Select the Locator
data source and then select Vendors, as shown in Figure 6-4.
Run the application and navigate to the VendorsView
, and it displays the data as before. We still have the event handlers in the code-behind. In the next subsection the event handlers are removed and instead the application takes advantage Commanding support provided by the MVVM Light toolkit.
The MVVM Light Toolkit supports Commanding, or data binding events to ViewModel methods, via the RelayCommand
and RelayCommand<T>
classes. With Windows Phone OS 7.1, the programming model is improved with full support for ICommand
and Commanding within the XAML programming model. I demonstrated the command support in the BasicMVVM project. This section covers how to leverage MVVM Light’s Commanding support.
In the VendorViewModel
class, two RelayCommand
instances are added in a region named Commanding
, one that is parameter-less and one that takes a parameter. Here is the declaration:
#region Commanding
public RelayCommand AddAVendorCommand
{
get;
private set;
}
public RelayCommand<Vendor> RemoveAVendorCommand
{
get;
private set;
}
#endregion
The commands are instantiated in the VendorViewModel()
constructor in this code:
//Instantiate Commands
AddAVendorCommand = new RelayCommand(
() => AddVendor());
RemoveAVendorCommand = new RelayCommand<Vendor>(
param => RemoveVendor(param));
The RelayCommand
objects bridge between the ViewModel
methods and the UI events. RelayCommand
has support for one parameter only so if you need to pass more info, consider encapsulating into an object. Now it’s time to data bind the commands in the UI. Currently the application uses the application bar to execute Add and Remove. Unfortunately, the ApplicationBarIconButton
class does not inherit from FrameworkElement
so the ButtonBaseExtension
cannot be attached to a DependencyObject
. You can still call the relay in code-behind as before. Here is an example from the sample:
private void insertVendorAppBarBtn_Click(object sender, EventArgs e)
{
var vm = DataContext as VendorViewModel;
if (vm != null)
{
vm.AddAVendorCommand.Execute(null);
}
}
For this sample, the two Buttons
are added so that we can demonstrate the EventToCommand
Expression Blend behavior, as well as via the code-behind for the application bar buttons. In Expression Blend, switch to the Assets tab and select Behaviors
to filter the list. Drag the EventToCommand
behavior on to the Button
objects and configure the correct command on each button. For the Remove Button
, data bind the EventToCommand
Command
property to the RemoveAVendorCommand RelayCommand
. Also data bind the CommandParameter
for the Remove
EventToCommand
object to the vendorsListBox.SelectedItem
method, which returns a Vendor
object. Here is the resulting markup for Remove
:
<Button x:Name="RemoveButton" Content="Remove" HorizontalAlignment="Right"
VerticalAlignment="Bottom" Margin="0,0,8,7">
<Custom:Interaction.Triggers>
<Custom:EventTrigger EventName="Click">
<GalaSoft_MvvmLight_Command:EventToCommand Command="{Binding RemoveAVendorCommand,
Mode=OneWay}" CommandParameter="{Binding SelectedItem, ElementName=VendorsListBox}"/>
</Custom:EventTrigger>
</Custom:Interaction.Triggers>
</Button>
The RelayCommand
class also supports CanExecute
and CanExecuteChanged
members as well to determine whether to enable or disable the element, in this case a Button
object. The CanExecute
method can be passed in to the constructor as a second parameter. Here’s an example:
AddAVendorCommand = new RelayCommand(
() => AddVendor(), () => CheckEnabled);
We have now completely migrated the sample over to MVVM. Listing 6-4 shows the source code for the updated VendorViewModel
class.
using System.Collections.ObjectModel;
using System.Linq;
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using MvvmLightSample.Model;
namespace MvvmLightSample.ViewModel
{
public class VendorsViewModel : ViewModelBase
{
private readonly IVendorsService _vendorsService;
public const string VendorsPropertyName = "Vendors";
private ObservableCollection<Vendor> _vendors = null;
public ObservableCollection<Vendor> Vendors
{
get
{
return _vendors;
}
set
{
if (_vendors == value)
{
return;
}
_vendors = value;
RaisePropertyChanged(VendorsPropertyName);
}
}
/// <summary>
/// Initializes a new instance of the VendorsViewModel class.
/// </summary>
public VendorsViewModel(IVendorsService dataService)
{
_vendorsService = dataService;
Vendors = new ObservableCollection<Vendor>();
_vendorsService.GetVendors(
(vendors, error) =>
{
if (error != null)
{
// Report error here
return;
}
Vendors = (ObservableCollection<Vendor>)vendors;
});
//Instantiate Commands
AddAVendorCommand = new RelayCommand(
() => AddVendor());
RemoveAVendorCommand = new RelayCommand<Vendor>(
param => RemoveVendor(param));
}
#region Business Logic
public Vendor GetVendorByAccountNumber(string accountNumber)
{
var vendor = from v in Vendors
where v.AccountNumber == accountNumber
select v;
return vendor.First<Vendor>();
}
public void AddVendor()
{
Vendors.Add(new Vendor()
{
AccountNumber = "111111",
CreditRating = 65,
Name = "Fabrikam Bikes - Added"
});
}
public void RemoveVendor(object vendor)
{
if (null != vendor)
Vendors.Remove((Vendor)vendor);
}
#endregion
#region Commanding
public RelayCommand AddAVendorCommand
{
get;
private set;
}
public RelayCommand<Vendor> RemoveAVendorCommand
{
get;
private set;
}
#endregion
////public override void Cleanup()
////{
//// // Clean up if needed
//// base.Cleanup();
////}
}
}In the next couple of sections, I cover additional features of the MVVM Light Toolkit.
The Messenger class provides a means to communicate within an application in a decoupled way. Classes can register to receive messages of different types. The message can be anything from simple values to complex objects. Likewise, messages can specify a target type that should receive the message for fine-tuned control.
MVVM Light includes multiple message classes. The following is a list of possible messages from the docs:
MessageBase:
A simple message class, carrying optional information about the message’s sender.GenericMessage<T>:
A simple message with a Content property of type T.NotificationMessage:
Used to send a notification (as a string) to a recipient. For example, define notifications as constants in a Notifications class, and then send Notifications.Save
to recipients.NotificationMessage<T>:
Same as the previous, but with a generic Content property. It can be used to pass a parameter to the recipient together with the notification.NotificationMessageAction:
Sends a notification to a recipient and allows the recipient to call the sender back.NotificationMessageAction<T>:
Sends a notification to a recipient and allows the recipient to call the sender back with a generic parameter.DialogMessage:
Used to request that a recipient (typically a View) displays a dialog, and passes the result back to the caller (using a callback). The recipient can choose how to display the dialog, either with a standard MessageBox, with a custom popup, or something similar.PropertyChangedMessage<T>:
Used to broadcast that a property changed in the message sender. Fulfills the same purpose as the PropertyChanged
event, but in a decoupled manner.The Messenger
class provides a powerful means to pass data and notifications between application layers and within ViewModels
in a decoupled way.
The Silverlight Toolkit has been in existence for several years. The idea behind it is to be able to more frequently release control and other updates with full source code to developers out-of-band from the normal product release cycle. The toolkit has been updated for Windows Phone OS 7.1 (Mango) in August 2011. It is available at the following URL:
http://silverlight.codeplex.com/
The updated version adds quite a few additional controls highlighted in Table 6.1 in the next section. It also includes improved transitions and ContextMenu performance as well as various bug fixes. The toolkit also includes localized resources to provide support for multiple languages.
Installation is simply a matter of running the MSI and making the controls available in the Toolbox within Visual Studio 2010. Right-click on the Toolbox, select Choose Items, and then put a check box next to controls you want to show up in the Toolbox. You can sort the controls alphabetically as well. The Silverlight for Windows Phone Toolkit includes several phone-specific controls that most developers will want to take advantage of to some degree; they are listed in Table 6-1.
The controls in Table 6-1 were frequently requested by developers during the beta-test phase of the Windows Phone Developer Tools. The product team created the toolkit to supplement the SDK to help match the built-in native UI controls’ look and feel.
Note GestureService
and GestureListener
are covered in Chapter 3.
The best way to proceed is to dive right in and show you how to put the controls to work within a sample project. Luckily, the Silverlight for Windows Phone Toolkit includes a very robust sample that is included with the source code download titled “Silverlight for Windows Phone Toolkit Source & Sample - Aug 2011.zip” available here:
http://silverlight.codeplex.com/releases/view/71550
There isn’t any documentation beyond the sample application, so this section provides an overview of the available additional controls available in the toolkit. There is one additional sample project in the Chapter 6 solution that demonstrates how to data bind the WrapPanel
control.
When you first run the sample app that ships with the source code available for download at the link I just gave you, it launches into a menu shown in Figure 6-5.
Each control is covered in the sections that follow.
The AutoCompleteBox
control allows a user to type letters, which brings up matches from the data source resource configured on the ItemSource
attribute. Here is an example:
<toolkit:AutoCompleteBox VerticalAlignment="Top" ItemsSource="{StaticResource words}"
Margin="0,12"/>
Figure 6-6 shows the AutoCompleteBox
in action. The top AutoCompleteBox
is configured with the above XAML.
The bottom AutoCompleteBox
is configured with an ItemTemplate
to display two lines of text:
<toolkit:AutoCompleteBox
InputScope="Url"
ItemsSource="{StaticResource websites}"
Margin="0,12"
ValueMemberPath="Item1">
<toolkit:AutoCompleteBox.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,7">
<TextBlock
Margin="8,0"
Text="{Binding Item1}"/>
<TextBlock
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="#ff666666"
Margin="8,-6,8,2"
Text="{Binding Item2}"/>
</StackPanel>
</DataTemplate>
</toolkit:AutoCompleteBox.ItemTemplate>
</toolkit:AutoCompleteBox>
The words come from these resources, configured in the PhoneApplicationPage.Resources
section:
<phone:PhoneApplicationPage.Resources>
<data:LoremIpsum x:Key="words"/>
<data:LoremIpsumWebsites x:Key="websites"/>
</phone:PhoneApplicationPage.Resources>
In the Data folder of the toolkit sample solution PhoneToolkitSample
project, there are two classes, LoremIpsum.cs
and LoremIpsumWebsites.cs
, which generate a random collection of words in an IEnumerable
collection. You can data bind to any collection of strings and display the values as the user types.
The AutoCompleteBox
provides a great way to improve UI by populating text fields with most likely values, saving users from having to type.
The ContextMenu
control provides a user interaction unique to Windows Phone with the tap-and-hold gesture. Figure 6-7 shows the test page with in-text hints on functionality, as well as results after tap-and-hold actions.
The ContextMenu
control is bindable to ICommand
objects, so it can work quite nicely with GalaSoft’s MVVM support for Commanding, allowing context menu items to invoke methods on the data bound ViewModel
class.
The DatePicker
and TimePicker
controls make it easy for users to pick date and time within a Windows Phone application. Figure 6-8 shows the controls in action.
You can attach an event hander to the Click
event and assign commands to the Command
property. Here is example XAML for ICommand
support:
<toolkit:ContextMenuService.ContextMenu>
<toolkit:ContextMenu>
<toolkit:MenuItem
Header="Always-on item"
Command="{Binding AlwaysCommand}"/>
<toolkit:MenuItem
Header="Intermittent item"
Command="{Binding IntermittentCommand}"/>
<toolkit:MenuItem
Header="Always-on item with param"
Command="{Binding AlwaysCommand}"
CommandParameter="param1"/>
<toolkit:MenuItem
Header="Intermittent item with param"
Command="{Binding IntermittentCommand}"
CommandParameter="param2"/>
</toolkit:ContextMenu>
</toolkit:ContextMenuService.ContextMenu>
The controls are dependent on having the correct application bar icons in a folder named Toolkit.Content
. The icons are ApplicationBar.Cancel.png
and ApplicationBar.Check.png
and their build action must be set to Content
.
The ListPicker
control provides a full-page, touch-friendly drop-down scrollable list to select an item. Figure 6-9 shows the example.
The ListPicker
control can display a set of inline strings like this:
<toolkit:ListPicker Header="background">
<sys:String>dark</sys:String>
<sys:String>light</sys:String>
<sys:String>dazzle</sys:String>
</toolkit:ListPicker>
The ListPicker
control also has ItemSource
and ItemTemplate
attributes to support data binding and full customization of how items are displayed. Here is example XAML:
<toolkit:ListPicker ItemsSource="{Binding}" Header="accent color"
FullModeHeader="ACCENTS" CacheMode="BitmapCache">
<toolkit:ListPicker.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Rectangle Fill="{Binding}" Width="24" Height="24"/>
<TextBlock Text="{Binding}" Margin="12 0 0 0"/>
</StackPanel>
</DataTemplate>
</toolkit:ListPicker.ItemTemplate>
<toolkit:ListPicker.FullModeItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="16 21 0 20">
<Rectangle Fill="{Binding}" Width="43" Height="43"/>
<TextBlock Text="{Binding}" Margin="16 0 0 0" FontSize="43"
FontFamily="{StaticResource PhoneFontFamilyLight}"/>
</StackPanel>
</DataTemplate>
</toolkit:ListPicker.FullModeItemTemplate
</toolkit:ListPicker>
The ToggleSwitch
control configures a boolean
value as On
or Off
. It can take a simple Header attribute for a text value to display across the top above the switch and current status as shown in the first two ToggleSwitch
controls in Figure 6-10.
The last ToggleSwitch
control is much more customized than the first two. It includes a ToggleSwitch.HeaderTemplate
to adjust the Font
for the header. The ToggleSwitch.ContentTemplate
customizes the ToggleSwitch
status info on the left with additional detail. Simply embed a <ContentControl Content="{Binding}"/>
control inside the ToggleSwitch.ContentTemplate
to have the On/Off status display correctly, as in the following:
<toolkit:ToggleSwitch Header="5:45 AM">
<toolkit:ToggleSwitch.HeaderTemplate>
<DataTemplate>
<ContentControl FontSize="{StaticResource PhoneFontSizeLarge}"
Foreground="{StaticResource PhoneForegroundBrush}" Content="{Binding}"/>
</DataTemplate>
</toolkit:ToggleSwitch.HeaderTemplate>
<toolkit:ToggleSwitch.ContentTemplate>
<DataTemplate>
<StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Alarm: " FontSize="{StaticResource PhoneFontSizeSmall}"/>
<ContentControl HorizontalAlignment="Left"
FontSize="{StaticResource PhoneFontSizeSmall}"
Content="{Binding}"/>
</StackPanel>
<TextBlock Text="every schoolday"
FontSize="{StaticResource PhoneFontSizeSmall}"
Foreground="{StaticResource PhoneSubtleBrush}"/>
</StackPanel>
</DataTemplate>
</toolkit:ToggleSwitch.ContentTemplate>
</toolkit:ToggleSwitch>
The WrapPanel
control works very similarly to the same named control available in the desktop Silverlight toolkit. It arranges child items left to right, row by row or top to bottom, column by column. Figure 6-11 shows the UI.
The WrapPanel
control has a Children
collection that allows you to add child items to the control via code, which is how the toolkit sample adds items. There may be situations in which you prefer to data bind to an ItemSource
property and an ItemTemplate
, as you can in the ListBox
control. The WrapPanelDataBinding
project sample in the Chapter 6 solution demonstrates how to do this.
You can use an ItemsControl
to provide this functionality by changing the ItemsControl.ItemsPanel
to a WrapPanel
. Otherwise, data binding works just like with a ListBox
. Here is the XAML markup:
<ItemsControl ItemsSource="{Binding Strings}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<toolkit:WrapPanel ItemWidth="69"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Width="60" Height="60" Margin="4">
<Rectangle Fill="#FF2A2AC8" Stroke="Black"/>
<TextBlock
Text="{Binding Text}" TextWrapping="Wrap"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
The WrapPanelDataBinding
project has a sample data source generated in Expression Blend to display random text over rectangles. The ItemsControl.ItemsSource
points to the Strings
collection in the sample data source. Figure 6-12 shows the output.
In Table 6-1, I’ve listed the new controls available in the August toolkit for Windows Phone 7.5, but I do not highlight every control in this chapter. However, one control that provides an amazing UI for very little effort is HubTile
. Figure 6-13 shows its UI.
The configuration for the control is very straightforward and can be done via data binding. Here is the XAML for the HubTileSample.xaml
file, which is part of the Phone Toolkit sample project available for download from CodePlex:
<toolkit:HubTile Grid.Row="1" Grid.Column="0"
Margin="12,12,0,0"
Source="/Images/Dessert.jpg"
Title="Dessert"
Notification="2 New
Receipes"
DisplayNotification="True"
GroupTag="Food"/>
<toolkit:HubTile Grid.Row="1" Grid.Column="1"
Margin="12,12,0,0"
Source="/Images/Fruits.jpg"
Title="Fruits"
GroupTag="Food"/>
<toolkit:HubTile Grid.Row="2" Grid.Column="0"
Margin="12,12,0,0"
Source="/Images/Pretzel.jpg"
Title="Pretzel"
Notification="w/fixings"
DisplayNotification="True"
GroupTag="Food"/>
<toolkit:HubTile Grid.Row="2" Grid.Column="1"
Margin="12,12,0,0"
Source="/Images/Shrimp.jpg"
Title="Shrimp"
Message=""Just an amazing work by the
chef""
GroupTag="Food"/>
<toolkit:HubTile Grid.Row="3" Grid.Column="0"
Margin="12,12,0,0"
Source="/Images/SteakSandwich.jpg"
Title="Steak
Sandwich"
Message="@ a great cafe"
GroupTag="Food"/>
<toolkit:HubTile Grid.Row="3" Grid.Column="1"
Margin="12,12,0,0"
Source="/Images/Beignets.jpg"
Title="Beignets"
Notification="New
Orleans"
DisplayNotification="True"
GroupTag="Food"/>
As you can see, configuration of the HubTile
control is very simple but can yield an amazing user interface addition to your application.
A control that we won’t dive into here is PerformanceProgressBar
, which was added to the toolkit in February 2011 and is now a part of the Windows Phone OS 7.1 SDK. Using the control is pretty straightforward, as it models the functionality of the built-in Progressbar
control. I mention it because you will want to use the toolkit PerformanceProgressBar
to improve rendering performance in your Silverlight or Windows Phone applications.
The LongListSelector
control is the uber-ListBox
control available for Windows Phone. It supports flat lists (like in a ListBox
), but it can also support complex grouping and list navigation, which is very useful on the small screen. Try it for your scenario, especially in Windows Phone 7.5. The control has been completely rewritten to take advantage of performance enhancements around scrolling and off-thread touch support available in the Windows Phone OS 7.1 SDK. The LongListSelector
control also supports grouped list, inline “more like these” buttons, and jump list UI.
Given the complexity and power of this control, the next couple of sections describe its visual capabilities, key properties and methods, and coding approaches.
The quickest way to take advantage of the LongListSelector
control’s potential performance and list virtualization advantages is to replace existing ListBox
controls with LongListSelector
controls and set the attribute IsFlatList
to True
. When in IsFlatList=true
mode, the UI looks just like a ListBox
control, though you can provide a header and footer to the list via the ListHeaderTemplate
and ListFooterTemplate
properties, as shown in Figure 6-14.
Listing 6-5 shows the XAML for the control and the three templates that are configured.
<phone:PhoneApplicationPage
x:Class="LongListSelectorSample.pages.LongListSelectorPage2"
…<!—removed standard namespaces for clarity
SupportedOrientations="Portrait" Orientation="Portrait"
mc:Ignorable="d" d:DesignHeight="768" d:DesignWidth="480"
shell:SystemTray.IsVisible="True" Loaded="PhoneApplicationPage_Loaded"
xmlns:toolkit="clr-namespace:Microsoft.Phone.Controls;assembly=
Microsoft.Phone.Controls.Toolkit">
<phone:PhoneApplicationPage.Resources>
<DataTemplate x:Key="ItemTemplate">
<StackPanel Margin="0,0,0,20">
<TextBlock Text="{Binding Name}"
Style="{StaticResource PhoneTextExtraLargeStyle}"/>
<TextBlock Text="{Binding Description}"
Style="{StaticResource PhoneTextSmallStyle}"/>
<TextBlock Text="{Binding Quantity}"
Style="{StaticResource PhoneTextAccentStyle}"/>
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="HeaderDataTemplate">
<Border Background="#FF0027FF">
<TextBlock TextWrapping="Wrap" Text="Chapter Six"
HorizontalAlignment="Center" VerticalAlignment="Center"
Style="{StaticResource PhoneTextLargeStyle}"/>
</Border>
</DataTemplate>
<DataTemplate x:Key="FooterDataTemplate">
<Border Background="#FF0027FF">
<TextBlock TextWrapping="Wrap" Text="Advanced Silverlight UI"
HorizontalAlignment="Center" VerticalAlignment="Center"
Style="{StaticResource PhoneTextLargeStyle}"/>
</Border>
</DataTemplate>
</phone:PhoneApplicationPage.Resources>
<!–LayoutRoot is the root grid where all page content is placed–>
<Grid x:Name="LayoutRoot" Background="Transparent"
DataContext="{Binding Source={StaticResource longlistDataSource}}">
<Grid.RowDefinitions> <RowDefinition Height="Auto"/>
<RowDefinition Height="*"/> </Grid.RowDefinitions>
<!–TitlePanel contains the name of the application and page title–>
<StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
<TextBlock x:Name="ApplicationTitle" Text="CHAPTER 6-LONGLISTSELECTORSAMPLE"
Style="{StaticResource PhoneTextNormalStyle}"/>
<TextBlock x:Name="PageTitle" Text="longlistselector" Margin="9,-7,0,0"
Style="{StaticResource PhoneTextTitle1Style}"/>
</StackPanel>
<!–ContentPanel - place additional content here–>
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<toolkit:LongListSelector IsFlatList="True"
ItemTemplate="{StaticResource ItemTemplate}"
ItemsSource="{Binding Collection}"
ListHeaderTemplate="{StaticResource HeaderDataTemplate}"
ListFooterTemplate="{StaticResource FooterDataTemplate}"/>
</Grid>
</Grid>
</phone:PhoneApplicationPage>
If you forget to set IsFlatList
equal to True
, it will generate an error when data bound to a flat list. This is because by default the LongListSelector
control expects a data structure with a grouped set of items that permits the control to display the long list of content segmented by available groups.
The PhoneToolkit
Sample Solution downloaded from CodePlex when you install the toolkit includes a page named LongListSelectorSample.xaml.
This page demonstrates several ways a developer can implement grouping with the LongListSelector
control. The UI for the LongListSelectorSample.xaml
is a Pivot
control with three PivotItem
pages, titled linq
, code
, and buddies
. The LongListSelector
implementation for the PivotItem
titled linq
has the following XAML:
<toolkit:LongListSelector x:Name="linqMovies" Background="Transparent"
ListHeaderTemplate="{StaticResource movieListHeader}"
GroupHeaderTemplate="{StaticResource movieGroupHeader}"
GroupItemTemplate="{StaticResource groupItemHeader}"
ItemTemplate="{StaticResource movieItemTemplate}">
</toolkit:LongListSelector>
Note that it does not implement a GroupFooterTemplate
. The PivotItem
page titled linq
displays movie data via Category using LINQ to demonstrate how to group items in the correct format using LINQ. The item that is displayed in the LongListSelector
for both the linq
and code
PivotItem
pages is a Movie
class that has fields like Title
, Description
, Year
, and so on. The movies are grouped by Category
, which is of type string. In the code-behind file, the LoadLinqMovies
method creates a flat collection of movies with random data and then builds a LINQ to Object query to group the movies by category.
The LINQ query is dazzlingly simple, taking advantage of the built-in group by support in LINQ that is based on the IGrouping
Interface. Here is the LoadLinqMovies
method
private void LoadLinqMovies()
{
List<Movie> movies = new List<Movie>();
for (int i = 0; i < 50; ++i)
{
movies.Add(Movie.CreateRandom());
}
var moviesByCategory = from movie in movies
group movie by movie.Category into c
orderby c.Key
select new PublicGrouping<string, Movie>(c);
linqMovies.ItemsSource = moviesByCategory;
}
The class PublicGrouping
implements the IGrouping
Interface, which has this definition from metadata:
namespace System.Linq
{
// Summary:
// Represents a collection of objects that have a common key.
//
// Type parameters:
// TKey:
// The type of the key of the System.Linq.IGrouping<TKey,TElement>.
//
// TElement:
// The type of the values in the System.Linq.IGrouping<TKey,TElement>.
public interface IGrouping<TKey, TElement> : IEnumerable<TElement>, IEnumerable
{
// Summary:
// Gets the key of the System.Linq.IGrouping<TKey,TElement>.
//
// Returns:
// The key of the System.Linq.IGrouping<TKey,TElement>.
TKey Key { get; }
}
}
An IEnumerable
collection of PublicGrouping
items is the output from the previous LINQ query:
var moviesByCategory = from movie in movies
group movie by movie.Category into c
orderby c.Key
select new PublicGrouping<string, Movie>(c);
The PublicGrouping
class is generic: Here is the class declaration and constructor:
public class PublicGrouping<TKey, TElement> : IGrouping<TKey, TElement>
…
public PublicGrouping(IGrouping<TKey, TElement> internalGrouping)
{
_internalGrouping = internalGrouping;
}
The LINQ query obtains the Enumerator from the LINQ Query, in this case the ‘c’ variable, which is defined as Category. Use the PublicGrouping
class as a basis for your usage of the LongListSelector
control.
Given the correctly formatted data structure, the LongListSelector
class renders the UI in a grouped format. If you click a group item, it displays a menu of available groups. Figure 6-15 shows the linq
PivotItem
in action, with the screenshot on the right showing the results of clicking the group item. Select a new group item like Comedy to jump to the portion of the list containing the Comedy movies.
The “code PivotItem”
in the PhoneToolkit
project LongListSelectorSample.xaml
page implements an additional feature with the LongListSelector
control. The GroupFooterTemplate
is modified to include a Button
to display “more” of a particular category. Here is the XAML for the code PivotItem LongListSelector
control:
<toolkit:LongListSelector x:Name="codeMovies" Background="Transparent"
ItemsSource="{StaticResource movies}"
ListHeaderTemplate="{StaticResource movieListHeader}"
GroupHeaderTemplate="{StaticResource movieGroupHeader}"
GroupItemTemplate="{StaticResource groupItemHeader}"
ItemTemplate="{StaticResource movieItemTemplate}">
<toolkit:LongListSelector.GroupFooterTemplate>
<DataTemplate>
<local:CommandButton DataContext="{Binding}" Content="{Binding GetMore}"
Command="{StaticResource moreCommand}" CommandParameter="{Binding}"/>
</DataTemplate>
</toolkit:LongListSelector.GroupFooterTemplate>
</toolkit:LongListSelector>
Notice that the GroupFooterTemplate
includes a DataTemplate
with a CommandButton
class instance, which is included in the sample code. The LongListSelector
implementation on the code PivotItem
does not use LINQ to generate the grouped item list. It binds to a StaticResource
defined on the page named moreCommand
, which is a class located in the MoviesByCategory.cs
class file in the Data
folder. More on that in a bit.
Also notice that ItemsSource
data binds to a StaticResource
named movies
, which points to a class named MoviesByCategory
located in the Data
folder. The MoviesByCategory
class is fairly simple. It obtains the Categories, and then randomly generates a set of fake objects for each Category
using the MoviesInCategory
class. It demonstrates how to create properly formatted groups in code and can provide a useful starter example.
The MoreCommand
class implements the ICommand
interface. The ICommand.Execute
method adds additional random objects to the currently selected group that is passed in via a parameter. For a real implementation, some tracking is necessary to identify which records are already displayed so that the correct next set of records is retrieved, if present. Figure 6-16 shows the UI with the More Command
button.
The last PivotItem,
named buddies,
also generates a collection in code. It randomly generates a list of people names via the AllPeople
class, sorts, them, and then generates groups by the alphabet and related group of names starting with each letter in the PeopleByFirstName
class. The GroupItemTemplate
in this case is the alphabet as shown in Figure 6-17.
In this section, we performed an in-depth review of the LongListSelector
control, because it provides rich and very much needed functionality for Windows Phone applications that display lists of data. This section concludes the review of the Silverlight for Windows Phone toolkit except for the transition animations, which we cover as part of the next section.
In previous sections, we used the NavigateToPageAction
behavior to navigate from one page to another. In this section, I focus on showing how to create more interactivity with Expression Blend for page transitions, status changes, and orientation changes. We start by investigating how to add interactivity using the Silverlight for Windows Phone Toolkit. We next focus on the Visual State Manager to create animations and transitions using a state-based management system.
The Silverlight for Windows Phone toolkit enables transitions that match the built-in native transitions available in Windows Phone, allowing your application to have the same look and feel without a lot of work. It is very simple to make the transitions available within an application, which is demonstrated in the Chapter 6 project named ToolkitTransitions
.
The ToolkitTransitions
project has three XAML pages. MainPage.xaml
navigates to TestTransitionsPage.xaml
, which data binds to a collection of sample data. The sample data is present to make the transitions more obvious than with just a mostly blank screen. When an item is selected in the TestTransitionsPage.xaml
’s sampleDataListBox
, the code in the sampleDataListBox_SelectionChanged
event appends the index
of the selected item to a query string and then navigates to TestTransitionsPage2.xaml
. The TestTransitionsPage2.xaml
page displays the full details of the selected item.
An extra page is added to the project beyond MainPage.xaml
because transitions from the Silverlight for Windows Phone toolkit do not override the standard application loading and exiting page transitions, so we need another page to fully demonstrate the transitions. The page in the middle, TestTransitionsPage.xaml,
will have the transitions configured on it to demonstrate the full navigating to and from page transition capabilities because it is not the default item or main page for the application.
To get started, first add a project reference to the Microsoft.Phone.Controls.Toolkit
assembly. Next open the App.xaml.cs
file and change this line of code in the InitializePhoneApplication()
method.
RootFrame = new PhoneApplicationFrame();
to this line of code:
RootFrame = new Microsoft.Phone.Controls.TransitionFrame();
Next, add a second page named toolkittransitionpage.xaml
to a pages folder. A TextBlock
is added to MainPage.xaml
and the NavigateToPageAction
behavior is applied to the TextBlock,
so that when it is touched, the toolkittransitionpage.xaml
is displayed. Run the project and check to make sure the navigation from MainPage
to the toolkittranstiionpage.xaml
is working. It is working, but it still does not have a nice transition to the second page. Now you can add any transition within the application.
There isn’t any Expression Blend support for visually applying transitions via behaviors. You edit the XAML to apply transitions. For each page, you can define four types of transitions:
NavigationInTransition:
Applies when navigating to the current page, either via a forward navigation to the page or via clicking the Back button to navigate back to the page.NavigationOutTransition:
Applies when the current page is navigating out to another page, either via forward navigation to another page or when clicking the Back button on the current page.DatePickerPage:
Applies when the current page is navigating to a DatePicker
control page.TimePickerPage:
Applies when the current page is navigating to a TimePicker
control page.To add a transition in XAML, add a namespace reference to the toolkit:
xmlns:toolkit="clr-namespace:Microsoft.Phone.Controls;assembly=
Microsoft.Phone.Controls.Toolkit"
Next, type <toolkit:TransitionService
to bring up IntelliSense to show the five available transition types. Select NavigationInTransition
and then close the tag to generate the ending element </toolkit:TransitionService.NavigationInTransition
>. A warning appears: “Property ‘NavigationInTransition
’ does not have a value,” and blue squiggly lines appear as shown in Figure 6-18.
The only option that displays is toolkit:Navigation:InTransition
. Within that element you can add the following two additional elements:
NavigationInTransition.Backward
NavigationInTransition.Forward
Within the .Backward
and .Forward
transitions, you can configure one of five options, as shown in Figure 6-19.
We discuss the five possible transitions in detail in the next subsection. NavigationOutTransition
also has a .Backward
and .Forward
option. Figure 6-20 describes the relationship for all four configurations.
In Figure 6-20, all five transitions are configured on the middle page and have independent behavior depending on the relative navigation to the middle page. This is the same configuration as in the ToolkitTransitions
sample project’s TestTransitionsPage.xaml
page, where all five options are configured. Here is the XAML for the ToolkitTransitions
sample transitions:
<toolkit:TransitionService.NavigationInTransition>
<toolkit:NavigationInTransition>
<toolkit:NavigationInTransition.Backward>
<toolkit:RollTransition />
</toolkit:NavigationInTransition.Backward>
<toolkit:NavigationInTransition.Forward>
<toolkit:RotateTransition />
</toolkit:NavigationInTransition.Forward>
</toolkit:NavigationInTransition>
</toolkit:TransitionService.NavigationInTransition>
<toolkit:TransitionService.NavigationOutTransition>
<toolkit:NavigationOutTransition>
<toolkit:NavigationOutTransition.Backward>
<toolkit:TurnstileTransition Mode="BackwardOut" />
</toolkit:NavigationOutTransition.Backward>
<toolkit:NavigationOutTransition.Forward>
<toolkit:TurnstileTransition Mode="ForwardOut" />
</toolkit:NavigationOutTransition.Forward>
</toolkit:NavigationOutTransition>
</toolkit:TransitionService.NavigationOutTransition>
To help further explain, let’s configure transitions on the page right. Since it is a “leaf” page without any additional forward navigation actions, only two transitions need to be configured. One is for when the page is being navigated to and the other when on the page and the back button is touched and the page is navigated from. The two transitions are the forward “in” transition, NavigationInTransition.Forward
, and the backward “out” transition, NavigationOutTransition.Backward
. Here is the XAML:
<toolkit:TransitionService.NavigationInTransition>
<toolkit:NavigationInTransition>
<toolkit:NavigationInTransition.Forward>
<toolkit:RotateTransition />
</toolkit:NavigationInTransition.Forward>
</toolkit:NavigationInTransition>
</toolkit:TransitionService.NavigationInTransition>
<toolkit:TransitionService.NavigationOutTransition>
<toolkit:NavigationOutTransition>
<toolkit:NavigationOutTransition.Backward>
<toolkit:TurnstileTransition Mode="BackwardOut" />
</toolkit:NavigationOutTransition.Backward>
</toolkit:NavigationOutTransition>
</toolkit:TransitionService.NavigationOutTransition>
Run the project in the emulator or, better yet, on a device where the transitions are more apparent. You will see that the transitions are “chained,” meaning that the NavigationOutTranstion.Forward
for page TestTransitionsPage.xaml
does not override the NavigationIn.Forward
for page TestTransitionsPage2.xaml
. Instead, the transitions are chained. Again, this is more visible on a real device than in the emulator.
We briefly mentioned above the five possible types of transitions that can be applied in any configuration, either “in,” or “out,” Forward or Backward:
RollTransition
RotateTransition
SlideTransition
SwivelTransition
TurnstileTransition
All of the transitions except the RollTransition
take a Mode
property that can have these values:
BackwardIn
BackwardOut
ForwardIn
ForwardOut
The Mode
attribute allows you to tell the transition how it should appear based on whether it is a Forward
“in” transition, and so on, so that it matches the native transitions correctly.
In addition to the Backward
and Forward
properties, the NavigationInTransition
and NavigationOutTransition
objects also have two events:
BeginTransition
EndTransition
These events allow you to hook into the transition at the Begin and End portion to perform actions such as data loading, unloading, and the like. Because these are events, you can use the MVVM Light Toolkit EventToCommand
Behavior to bind the transition events to Commands declared in the ViewModel for your application.
Transitions can be applied to any UI Element object. In the ToolkitTransitions
project, click apply transition to rectangle
TextBlock
in MainPage.Xaml
to load the corresponding page, and click the single Application Bar button. The Rectangle
object will slide down and fade in. The Rectangle is named targetRectangle
and its Opacity
is set to 0 in XAML:
private void ApplyTransitionAppBarBtn_Click(object sender, EventArgs e)
{
RotateTransition rotateTransition =
new RotateTransition { Mode = RotateTransitionMode.In180Clockwise};
ITransition transition = rotateTransition.GetTransition(targetRectangle);
transition.Completed +=
(s, eventarg) => { transition.Stop(); targetRectangle.Opacity = 1; };
transition.Begin();
}
You can of course simply create a Storyboard
using Expression Blend and apply it to the Rectangle
as well, but this section demonstrates how to leverage the existing animations available in the Silverlight for Windows Phone toolkit. The next section describes how to create a transition based on a custom Storyboard
object.
In this section I describe how to create a new transition class that leverages a custom Storyboard
object. The steps are to implement the ITransition
Interface with a custom class and implement another class that inherits from the TransitionElement
base class. In the Chapter 6 Solution ToolkitTransitions
project’s MainPage.xaml
, there is a TextBlock
titled “custom transition” that navigates to the CustomTransitionPage.xaml
page. This page demonstrates a custom Transition.
The first step is to create a custom Storyboard animation that applies to the entire page named CustomPageTransitionStoryboard
in Expression Blend. The CustomPageTransitionStoryboard
uses translation to move the Page content from off-screen lower left sliding diagonally into place. The Storyboard
is moved into the App.Resources
section of App.xaml
to make it globally available throughout the application.
Next create a class named TheTransition
that implements ITransition,
as shown in Listing 6-6.
Listing 6-6. TheTransition
Class
public class TheTransition : ITransition
{
private Storyboard _storyboard;
public TheTransition(Storyboard storyBoard)
{
_storyboard = storyBoard;
}
public void Begin()
{
_storyboard.Begin();
}
public event EventHandler Completed;
public ClockState GetCurrentState()
{
return _storyboard.GetCurrentState();
}
public TimeSpan GetCurrentTime()
{
return _storyboard.GetCurrentTime();
}
public void Pause()
{
_storyboard.Pause();
}
public void Resume()
{
_storyboard.Resume();
}
public void Seek(TimeSpan offset)
{
_storyboard.Seek(offset);
}
public void SeekAlignedToLastTick(TimeSpan offset)
{
_storyboard.SeekAlignedToLastTick(offset);
}
public void SkipToFill()
{
_storyboard.SkipToFill();
}
public void Stop()
{
_storyboard.Stop();
}
}
It is a pretty simple class that essentially wraps the Storyboard
object. The class that is actually added in XAML is named MyTransition,
and is shown in Listing 6-7.
Listing 6-7. MyTransition
Class
public class MyTransition : TransitionElement
{
public override ITransition GetTransition(UIElement element)
{
Storyboard myStoryboard = App.Current.Resources["CustomPageTransitionStoryboard"] as
Storyboard;
Storyboard.SetTarget(myStoryboard, element);
return new TheTransition(myStoryboard);
}
Notice how the MyTransition
class obtains the Storyboard
via the App.Current.Resources
collection. In the CustomTransitionPage.xaml
, a namespace named thisPage
is added, as is the custom transition:
xmlns:thisPage="clr-namespace:ToolkitTransitions.pages"
…
<toolkit:TransitionService.NavigationInTransition>
<toolkit:NavigationInTransition>
<toolkit:NavigationInTransition.Forward>
<thisPage:MyTransition/>
</toolkit:NavigationInTransition.Forward>
</toolkit:NavigationInTransition>
</toolkit:TransitionService.NavigationInTransition>
Run the toolkitTransitions
project to see the custom transition in action. It would make sense to add a Mode
parameter to the MyTransitions
class and then apply a custom Storyboard,
depending on whether it is Backward
, Forward
, “in,” or “out,” but this sample demonstrates how to get started if you wish to create a custom transition.
This concludes coverage of the Silverlight for Windows Phone Toolkit and how to create interactivity for page transitions. The next section demonstrates how to use the Visual State Manager to provide interactivity within a page.
The Visual State Manager (VSM) is a tool available within Expression Blend. It allows the developer / designer to visually define Visual State Groups that represent UI state for controls in that state, represented as a Storyboard
. The best way to learn how to work with the VSM is by demonstration, most of which will be in Expression Blend.
This section demonstrates how to create an Orientation
-aware application using the VSM. A new project named VSMVideoPlayer
is added to the Chapter 6 solution based on the MVVMLight (WP71 Application project template. The project is customized to be a basic video player that can play a list of videos from a recent vacation— videos without ownership rights issues that I can use for this example.
A Grid containing a MediaElement
control named mediaPlayer
is added to the ContentPanel Grid
. Below the Grid
containing the MediaElement
, a ListBox
named videosListBox
is added and the Application Bar is enabled. The Application Bar is wired up to provide video controls for the mediaPlayer MediaElement
.
A Model named Video
is added to the Model
folder and a collection of Video
objects is added to the MainViewModel
class. Finally, three videos are added to a folder named Content
and added to the project. Figure 6-21 shows the VSMVideoPlayer
project in Expression Blend with the States tab opened.
The videosListBox
is data bound to the Videos
collection on the MainViewModel
. The Grid
containing the MediaElement
is data bound to the SelectedItem
of the videosListBox
. The Source property for the mediaPlayer
MediaElement
is data bound to the Url
property of the SelectedItem Video
object. Run the project, and when you select a video in the videosListBox
it plays in the MediaElement
. Listing 6-8 shows the XAML for the Content Grid
and Application Bar from MainPage.xaml
.
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<StackPanel d:LayoutOverrides="Width">
<Grid Height="213" DataContext="{Binding SelectedItem, ElementName=videosListBox}">
<MediaElement Source="{Binding Url}" Margin="5,9,7,-9" Name="mediaPlayer"
MediaFailed="mediaPlayer_MediaFailed" Stretch="UniformToFill" />
</Grid>
<ListBox x:Name="videosListBox" ItemsSource="{Binding Main.Videos}"
Margin="12,24,0,0" ItemTemplate="{StaticResource VideoDataTemplate}" />
</StackPanel>
</Grid>
</Grid>
<phone:PhoneApplicationPage.ApplicationBar>
<shell:ApplicationBar IsVisible="True" IsMenuEnabled="True">
<shell:ApplicationBarIconButton x:Name="rewindAppBarBtn"
IconUri="/icons/appbar.transport.rew.rest.png" Text="rewind"
Click="rewindAppBarBtn_Click"/>
<shell:ApplicationBarIconButton x:Name="stopAppBarBtn"
IconUri="/icons/appbar.transport.pause.rest.png" Text="pause"
Click="stopAppBarBtn_Click"/>
<shell:ApplicationBarIconButton x:Name="playAppBarBtn"
IconUri="/icons/appbar.transport.play.rest.png" Text="play"
Click="playAppBarBtn_Click"/>
<shell:ApplicationBarIconButton x:Name="ffAppBarBtn"
IconUri="/icons/appbar.transport.ff.rest.png" Text="fastforward"
Click="ffAppBarBtn_Click"/>
</shell:ApplicationBar>
</phone:PhoneApplicationPage.ApplicationBar>
Listing 6-9 shows the MainPage.xaml.cs
code-behind file where the event handlers control the mediaPlayer MediaElement
.
using Microsoft.Phone.Controls;
using System;
using System.Windows;
namespace VSMVideoPlayer
{
public partial class MainPage : PhoneApplicationPage
{
// Constructor
public MainPage()
{
InitializeComponent();
}
private void rewindAppBarBtn_Click(object sender, System.EventArgs e)
{
if (mediaPlayer.CanSeek)
{
mediaPlayer.Position = mediaPlayer.Position - new TimeSpan(0, 0, 5);
mediaPlayer.Play();
}
}
private void stopAppBarBtn_Click(object sender, System.EventArgs e)
{
mediaPlayer.Pause();
}
private void playAppBarBtn_Click(object sender, System.EventArgs e)
{
mediaPlayer.Play();
}
private void ffAppBarBtn_Click(object sender, System.EventArgs e)
{
if (mediaPlayer.CanSeek)
{
mediaPlayer.Position = mediaPlayer.Position + new TimeSpan(0, 0, 5);
mediaPlayer.Play();
}
}
private void mediaPlayer_MediaFailed(object sender,
System.Windows.ExceptionRoutedEventArgs e)
{
MessageBox.Show("Media Failed: " + e.ErrorException.Message);
}
}
}
The last item to highlight is that the SupportedOrientations
attribute on the PhoneApplicationPage
element in XAML is changed from Portrait to PortraitOrLandscape
. This allows the page to respond to orientation changes via the PhoneApplicationPage.OrientationChanged
event.
Now that everything is configured properly, let’s configure the project to support a full-screen mode for video playback in Expression Blend. With the States tab opened in Expression Blend, the Add state group button creates a Visual State Group that can contain one or more Visual States. The Turn on transition preview button dynamically shows the changes when different states are selected at design time.
Click the Add State Group button and name the Visual State Group “Orientations.” Next, create an “Add state” button to create a Visual State for PortraitUp
. Create another state named LandscapeRight
. The VSM should look like Figure 6-22.
Figure 6-22 also defines the UI available in the VSM to create states as well as customize the transitions. EasingFunctions
provide for a more realistic animation flow. EasingFunctions
are literally functions like quadratic
, cubic
, bounce
, elastic
, and so on. In general, EasingFunctions
are available in Storyboards
. Developers can create custom EasingFunctions
as well. Think of an EasingFunction
as altering the animation speed from a straight line to a curve, where sharp bends represent an increase in speed so that animations appear to better model real-world movement with acceleration and bounce. Figure 6-23 shows the EasingFunction
selector.
The other buttons shown in Figure 6-23 are transition duration, which represents how long the animation should take in real time, regardless of EasingFunction
, and a button to turn on FluidLayout
. FluidLayout
provides an engine that takes a look at the start state, the end state, and then creates a smooth animation between the states based on the selected EasingFunction
. FluidLayout
can help make animations created by non-designers look great. Now that we have the Blend functionality covered, let’s create our states based on current orientation for the sample project.
Look at Figure 6-22, and you see a state at the top called “Base
.” When working in Blend previously, you may not have been aware, but this is the visual state that you were working in. “Base”
represents the default or initial state. All other state modifications are referenced to the Base
state.
Tip Be sure to select the Base
state if you want to make modifications that apply across all states.
In Expression Blend, switch to Landscape in the Device tab and then switch back to the States tab. With the LandscapeRight
Visual State selected, select the TitlePanel Grid
and set Visibility
to Collapsed
, and set Visibility
to Collapsed
for the videosListBox
as well. For the Grid
that contains the mediaPlayer MediaElement
, set the Height
property on the Grid
to 480px. The Objects and Timeline window indicates the changes visually in the timeline keyframe editor as shown in Figure 6-24.
When you run the application, it works as expected when flipped to LandscapeRight,
as shown in Figure 6-25.
If you switch the Emulator to LandscapeLeft
, the UI remains as the Portrait layout, as shown in Figure 6-26.
You could build another state manually, but an easier option is to right-click on the LandscapeRight
state, select Copy State To
, and then select New State
. Rename it LandscapeLeft,
and that is all that’s required to have both Landscape Orientation
values display correctly.
The Visual State Manager provides a powerful tool to customize transitions and animations via a state-based mechanism. The next section covers the Microsoft Advertising SDK, which supports advertising revenue for both Silverlight and XNA Framework applications.
Now available in Windows Phone OS 7.1 SDK is the RichTextBox
control. This is a read-only control that you can use to render Rich Text Format (RTF) content. The control does not accept user input or edits, but you can update the content programmatically or via data binding.
The RichTextBox
control supports formatted text, inline images, and hyperlinks. To put this into context, Windows ships with WordPad, which is a feature-rich text editor, built on the RTF format.
The RichTextBox
control supports the FlowDirect property available in Silverlight 4 for localization. This means that text can flow RightToLeft
or LeftToRight
as needed to support the desired language. The RichTextBox
control supports sub-elements for content formatting including the following:
Paragraph:
Starts a new text paragraph in the content.Run:
Represents a discrete section of formatted or unformatted text.HyperLink
: Web link.InlineUIContainer
: Can be used to render an Image
element.LineBreak:
Causes a new line within the content.Span:
Groups other Inline
descendent content elements.Bold
, Italic
, Underline:
Specify additional text formatting within the content.The RichTextBox
control supports a block-based content model where a block is a collection of Paragraph
elements. A Paragraph
element can contain elements that derive from inline such as Run
, Span
, text formatting like Bold
, Hyperlink
and InLineUIContainer
. Here is an example that displays some text with an inline image:
<RichTextBox>
<Paragraph>
Displaying text with inline image
<InlineUIContainer>
<Image Source="./Sports.jpg" Height="75" Width="75" />
</InlineUIContainer>
</Paragraph>
</RichTextBox>
The RichTextBox
control is great when you have RTF content to render, such as help documentation. In general, Microsoft recommends using the RichTextBox
control instead of the WebBrowser
control if possible since it presents native rendering.
The Microsoft Advertising SDK now ships directly in the Windows Phone OS 7.1 SDK. It provides mobile advertising support for Windows Phone. You are not required to use Microsoft’s advertising SDK and associated advertising network. If you have an existing advertising network or an in-house creative and sales force, you can continue to use those resources for advertising revenue.
If you are new to mobile applications or simply want a fast and easy mobile advertising solution, the Microsoft Advertising SDK can get you up and running in about 20 minutes and about 50 lines of code. This is explained next.
While Windows Phone is a new platform, Microsoft’s advertising solution is not. With the world’s first real-time bidding Mobile Ad Exchange with targeted advertising, multiple purchase models and leading resellers including Microsoft’s sales force, you can tap into the existing adCenter marketplace by simply adding the control to your application.
Register on pubCenter at the https://pubcenter.microsoft.com
link to set up your advertising account. You create a software property where ads are displayed, create a mobile ad unit, and set up a targeting category. After registering in pubCenter, you will have an application ID and an Ad unit ID that you can use to request ads from the Microsoft Advertising Servers.
You can also try out the mobile advertising control and receive test ads without signing up, but you will soon want to take advantage of Microsoft’s contextual advertising platform with over 20,000 advertisers and resellers to get paid.
You can drag the control onto your Silverlight UI from the Toolbox window to add advertising to a Silverlight page. You can receive test ads using the test Application ID and test Ad Unit ID to get a feel for how the advertising works. There are three types of ads you can receive in your application, summarized in Table 6-2.
Even though there is support for a 300 × 50 ad size, it is recommended to always set the size of the AdControl
instance to 480px wide by 80px height. For the X-Large Image Banner, the control will automatically size to display the full 300 × 50, centered at the center of the AdControl
instance.
The AdControl
also has a Location
property to receive targeted ads by supplying a Latitude
and Longitude
value as a Location
type. You can use a GeoCoordinateWatcher
instance to collect location data as shown in Chapter 3 on input. The AdControl class supports the following events:
AdControlError:
Fires when there is a problem receiving or displaying an advertisement.AdEngaged:
Fires when the user clicks the ad and the action dialog appears.AdDisengaged:
Fires when the user clicks any button in the action dialog to dismiss the dialog.NewAd
: Fires when a new advertisement is displayed. Can use to animate the AdControl
to catch the user’s attention.In this section, the AdControl is leveraged in a sample project named AdvertisingSLSample. For the Silverlight sample project, start with the Windows Phone Data bound Application project
template so that you have some data to display. Next, simply drag and drop the control at the bottom of the MainPage.xaml
page, adjusting its settings so it just fits across the bottom of the screen.
It is recommended to place the AdControl
at the top or bottom of a page. For the Panorama
or Pivot
controls, you can place the AdControl
instance inside or over top of the Panorama
or Pivot
control. If placed inside either control, the ad will only display in a single pane. You can choose to have a unique ad on each pane if desired. To have the AdControl
always visible, even when scrolling, place the instance outside of the Panorama
control on top.
Tip It is not recommended to change the parent control of an AdControl
instance at run time.
As you can see, the control is simple to configure. Here is the XAML for the configured ad control in the Silverlight AdvertisingSample project:
<my:AdControl Height="80" HorizontalAlignment="Left" Margin="-16,527,0,0" Name="adControl1"
VerticalAlignment="Top" Width="480" ApplicationId="test_client" AdUnitId="Image480_80"
RenderTransformOrigin="0.5,0.5" IsAutoCollapseEnabled="True" >
<my:AdControl.RenderTransform>
<CompositeTransform/>
</my:AdControl.RenderTransform>
</my:AdControl>
Figure 6-27 shows the control in action, using the test configuration in the previous XAML.
We want to grab the user’s attention when a new ad is available. One possible way to do that is to create an animation that indicates something happened, such as to make the control increase in size slightly and then return to its original size via a Storyboard animation.
In Expression Blend, create the AnimateAdControl
Storyboard
resource by clicking the New button in the Objects and Timeline window. Add a keyframe at zero time and then slide the yellow timeline over to 100 milliseconds. Click on the Transform
section in the Properties window for the AdControl
and switch to the Scale
transform tab. Change X
and Y
to 1.1 from 1. Slide the yellow timeline bar to 200 milliseconds, right-click the first keyframe at zero seconds and then right-click and select Paste.
Switch back to Visual Studio and create an event handler for the NewAd
event on the adControl1
instance and then add this code:
AnimateAdControl.Begin();
For a multi-page application, it is recommended to unload the control when a page is unloaded to minimize resource consumption. This can be done in the PhoneApplicationPage.Unloaded
event handler. Simply set the AdControl
instance to null
.
Although this chapter is focused on Silverlight UX, the next subsection covers how to add advertising support to a Windows Phone XNA Framework-based game.
Adding support for the AdControl in a Windows Phone XNA Framework application is just as easy. Start by adding a reference to the Microsoft.Advertising.Mobile.Xna.dll
using the Add Reference functionality.
Next add a using Microsoft.Advertising.Mobile.Xna
; statement to game1.cs
. Next add a DrawableAd
object as a private member of the Game1 class:
DrawableAd bannerAd;
In the constructor for the Game1 class, add this line of code to initialize and add the static Current AdGameContent
to the Components
list for the game:
AdGameComponent.Initialize(this, "test_client");
Components.Add(AdGameComponent.Current);
Notice that the same application Id, test_client
, is used as before for the testing functionality. Finally, we want to load an ad, which is done in the LoadContent()
method where all game assets are generally loaded for most games:
bannerAd = AdGameComponent.Current.CreateAd(
"Image300_50",
new Rectangle(
10, 390, GraphicsDevice.Viewport.Bounds.Width, 120), true);
That is all that is required to add advertising to a Windows Phone XNA Framework game. Figure 6-28 shows the results.
As you can see from this section, adding support for Advertising revenue to your applications is very straight-forward. For a free or trial application, advertising revenue is a great way to get paid for your hard work.
This chapter began with an overview of the Model-View-ViewModel pattern, which is the architecture to use in XAML-based applications. We next moved on to covering one of the third-party MVVM frameworks that improve upon the built in support for MVVM when building property architected applications.
This was followed by a discussion of the Silverlight for Windows Phone toolkit, including coverage on all of the major controls including detailed coverage on the LongListSelector
control as well as on page transitions. We then explored how to use the Visual State Manager in your applications to greatly simplify UI development.
The coverage of advertising concludes Chapter 6 on advanced user interface development. The next chapter covers advanced programming model topics that allow you to more deeply integrate with Windows Phone, leverage media, as well as other topics.