Part I Part II Part III Part IV Part V
This is the fifth installment of a several part series/journey to use social network resources for Windows Phone 7 development and produce a reusable code base.
In this post, we’re going to discuss how to enhance a sample provided by Laurent Bugnion for Navigation using MVVM Light. The goal is to create a custom PageNavigation object like the MVVM Light navigation service code. Then we refactor some things to make it easy to use. Finally, we add an about page, a way to get to it, and a refresh quotation button.
This requires a refactoring and some architecture changes.
Refactoring
As you can read in Laurent’s blog posting, there is an interface and class for a navigation service. In this implementation of it we change the name slightly from NavigationService to PageNavigation. We want to be able to pass an object context as well as (or instead of) a Url. Because each ViewModel will have a need for a PageNavigation object we move the code to a single location that can be inherited from. To a custom ViewModelBase class. The new class is located in the ViewModels folder and MainViewModel is updated to inherit from it instead of directly to the MVVM Light version.
ViewModelBase.cs
using WindowsPhone.Helpers.Navigation;
namespace WindowsPhoneApplication1.ViewModels
{
public class ViewModelBase : GalaSoft.MvvmLight.ViewModelBase
{
private object context;
public object Context
{
get { return context; }
set
{
if (context == value)
return;
context = value;
RaisePropertyChanged("Context");
}
}
/// <summary>
/// Gets PageNavigation from Container
/// </summary>
public IPageNavigation PageNav
{
get
{
IPageNavigation pageNav =
(IPageNavigation)Container.Instance.Resolve(typeof(PageNavigation), "PageNavigation");
return pageNav;
}
}
}
}
Moving the IoC for Easy Access
This all means that we have to move the container from a class variable in the ViewModelLocator into a singleton class of its own. The line of code in ViewModelLocator is removed and the new class is added. We locate the new container singleton in the ViewModels folder of my main project. Here is a link to a nice explanation of the singleton: http://www.yoda.arachsys.com/csharp/singleton.html.
So, this line in ViewModelLocator.cs….
private readonly IMicroIocContainer container = new MicroIocContainer();
…turns into this class:
Container.cs
using MicroIoc;
namespace WindowsPhoneApplication1.ViewModels
{
public sealed class Container
{
Container()
{
}
public static IMicroIocContainer Instance
{
get
{
return Nested.instance;
}
}
class Nested
{
// Explicit static constructor to tell C# compiler
// not to mark type as before field init
static Nested()
{
}
internal static readonly IMicroIocContainer instance =
new MicroIocContainer();
}
}
}
Now the ViewModelLocator has to be refactored to use Container.Instance.RegisterInstance instead of container.Register.
PageNavigation
The next thing is to add the new IPageNavigation interface. It has one more property than the one the MVVM Light demo article used. Because we want a context to pass through during page navigation, we add a property to contain an object and a method to pass it in. The final interface is below.
IPageNavigation.cs
using System;
using System.Windows.Navigation;
namespace WindowsPhone.Helpers.Navigation
{
public interface IPageNavigation
{
event NavigatingCancelEventHandler Navigating;
object CurrentContext { get; }
void NavigateTo(Uri pageUri);
void NavigateTo(Uri pageUri, object context);
void GoBack();
}
}
The implementation of PageNavigation is almost identical to Laurent’s. With the exception of the additional method and context property.
PageNavigation.cs
using System;
using System.Windows;
using System.Windows.Navigation;
using Microsoft.Phone.Controls;
namespace WindowsPhone.Helpers.Navigation
{
public class PageNavigation : IPageNavigation
{
private PhoneApplicationFrame mainFrame;
private bool EnsureMainFrame()
{
if (mainFrame != null)
{
return true;
}
mainFrame = Application.Current.RootVisual as PhoneApplicationFrame;
if (mainFrame != null)
{
// Could be null if the app runs inside a design tool
mainFrame.Navigating += (s, e) =>
{
if (Navigating != null)
{
Navigating(s, e);
}
};
return true;
}
return false;
}
public event NavigatingCancelEventHandler Navigating;
private object currentContext;
public object CurrentContext
{
get { return this.currentContext; }
}
public void NavigateTo(Uri pageUri)
{
if (pageUri == null)
throw new ArgumentNullException("uri");
if (EnsureMainFrame())
this.NavigateTo(pageUri, null);
}
public void NavigateTo(Uri pageUri, object context)
{
if (pageUri == null)
throw new ArgumentNullException("uri");
if (EnsureMainFrame())
{
this.currentContext = context;
mainFrame.Navigate(pageUri);
}
}
public void GoBack()
{
if (EnsureMainFrame() && mainFrame.CanGoBack)
{
mainFrame.GoBack();
}
}
}
}
The ViewModelLocator now needs to register IPageNavigation so that the custom ViewModelBase (which is explained next) can resolve it. Because page navigation doesn’t occur in design mode it only needs to be registered if not in design mode. The line of code is this:
//Register PageNavigation - only if not design time
Container.Instance.RegisterInstance(typeof(PageNavigation), "PageNavigation");
ViewModelBase
A custom ViewModelBase needs to be created. MVVM Light already includes a ViewModelBase class. So we create a new class that inherits it. It needs one property with get access only for the PageNavigation. The property returns the resolved IPageNavigation object from the container. Later you will see how the context is assigned. We also want a property in every ViewModel to hold the context for the ViewModel. This is helpful for Tombstoning (which is out of scope for this blog series). But the mechanism is in place for Tombstoning our context later. The new ViewModelBase.cs gets created in the ViewModels folder of the main project. It looks like this.
ViewModelBase.cs
using WindowsPhone.Helpers.Navigation;
namespace WindowsPhoneApplication1.ViewModels
{
public class ViewModelBase : GalaSoft.MvvmLight.ViewModelBase
{
private object context;
public object Context
{
get { return context; }
set
{
if (context == value)
return;
context = value;
RaisePropertyChanged("Context");
}
}
/// <summary>
/// Gets PageNavigation from Container
/// </summary>
public IPageNavigation PageNav
{
get
{
IPageNavigation pageNav =
(IPageNavigation)Container.Instance.Resolve(typeof(PageNavigation), "PageNavigation");
return pageNav;
}
}
}
}
This new base class needs to be implemented on the MainViewModel as follows.
public class MainViewModel : WindowsPhoneApplication1.ViewModels.ViewModelBase, IMainViewModel
Passing the Context in VIewModelLocator
Context is all set up to pass through during navigation from one page to the next. Except for one last step. The “magic” that this architecture relies on is the timing in construction of any ViewModel. When a page is navigated to, the View binds to the ViewModel it needs using the ViewModelLocator public properties. Each time a View is navigated to. So, if PageNavigation is given a context object to carry just before navigation begins, at the time the ViewModel is constructed it can be handed that context. Remembering the GetViewModel method in ViewModelLocator, it is easy to conclude that transfer of the context happens in there. Also remember that the ViewModel base has a context property. We can simply assign context from PageNavigation to that property if it is null or not. While it may be tempting to use the CurrentContext of the PageNavigation as the context of any ViewModel, it would be unwise to do so. That property is guaranteed to change or be null when navigating from one View to the next. The intent is to rely on the ViewModelBase.Context property instead. As each ViewModel is created and context is set, the ViewModel can control any logic it needs on the property changed event of its context. The new code becomes as follows:
private T GetViewModel<T>() where T : ViewModelBase
{
// Create a new view model
T vm = Container.Instance.Resolve<T>();
//Assign the Context from PageNavigation to Context property of the ViewModelBase
vm.Context = vm.PageNav.CurrentContext;
return vm;
}
About Page Setup
There are a few steps now to creating new pages. The main architecture is updated to make this a fairly consistent but definitely a manual process. The general steps are 1. create an interface for the new view model, 2. create the view model, 3. implement the interface on the view model, 4. copy the view model to the design project, 5. register the view model in the IoC container (both for production and design modes), 6. expose a property in the locator to resolve the view model, and 7. create a view bound to the locator view model. That is fairly close to the MVVM Light steps to creating a view. The IoC register/resolve and design project steps are extra.
The solution should look something like the following when done. Full source code is available to download so you can take a closer look.
Here is what we do for the Design version of the AboutViewModel. Note that we don’t need to care about context or page navigation yet so these view models don’t yet inherit the custom ViewModelBase.
AboutViewModel.cs
using GalaSoft.MvvmLight;
using WindowsPhoneApplication1.Shared.ViewModels;
namespace WindowsPhoneApplication1.Design.ViewModels
{
/// <summary>
/// This class contains properties that a View can data bind to.
/// <para>
/// Use the <strong>mvvminpc</strong> snippet to add bindable properties to this ViewModel.
/// </para>
/// <para>
/// You can also use Blend to data bind with the tool's support.
/// </para>
/// <para>
/// See http://www.galasoft.ch/mvvm/getstarted
/// </para>
/// </summary>
public class AboutViewModel : ViewModelBase, IAboutViewModel
{
/// <summary>
/// Initializes a new instance of the MainViewModel class.
/// </summary>
public AboutViewModel()
{
}
////public override void Cleanup()
////{
//// // Clean own resources if needed
//// base.Cleanup();
////}
private string aboutText = "This is design time about text";
public string AboutText
{
get { return aboutText; }
set
{
if (aboutText == value)
return;
aboutText = value;
RaisePropertyChanged("AboutText");
}
}
}
}
Using the Custom Application Bar with MVVM Light
In Part III of this series, you read about using MVVM Light with a bindable ApplicationBar control. The reference article is here. In that post we added the control to the WindowsPhone.Controls project. The other piece of a puzzle that we’ll put together here has to do with the MVVM Light RelayCommand. There is a good background article about that here. The goal is to implement the bindable ApplicationBar control with commands in the MainViewModel to navigate to the AboutView and to refresh the quote by calling the web service.
Let’s set up the commands in the MainViewModel. We need a public property for both commands. The type for the command properties is ICommand, which is available from System.Windows.Input. Here they are:
public ICommand NavigateToAboutCommand
{
get;
private set;
}
public ICommand GetRandomQuoteCommand
{
get;
private set;
}
These commands are going to be assigned to the click events on the application bar button and menu item. That is all the View is going to bind to. Within the ViewModel, we can assign a RelayCommand to these properties that takes a method for an action to perform. That is done immediately when the ViewModel is constructed. So the constructor for MainViewModel looks like this:
public MainViewModel()
{
NavigateToAboutCommand = new RelayCommand(() => NavigateToAbout());
GetRandomQuoteCommand = new RelayCommand(() => this.GetRandomQuote());
}
Note that this is not needed in the Design copy of MainViewModel because we won’t be able to perform a click action at design time. So we can see that the ICommand bound to the About menu item will invoke a method called NavigateToAbout. That method uses the PageNav object from the ViewModelBase to go to the relative Url for the AboutView. It looks like this:
private void NavigateToAbout()
{
this.PageNav.NavigateTo(new Uri("/Views/AboutView.xaml", UriKind.Relative));
}
We already had some code to get the random quote so it’s easy to assign that to the command. There is going to be a refresh button and a menu item with “about” as the text. So, we need an application bar button image for refresh. You can find some pre-made icons for the application bar in the Windows Phone SDK folder. It should be in a path similar to the following: C:\Program Files (x86)\Microsoft SDKs\Windows Phone\v7.0\Icons. There is a dark, light, and vector folder in there. We typically want to use the dark icons and specifically the png format. We create a folder in the main phone project and call it “images” to store a copy of the icon for refresh. There is one critical setting we set in the properties window for the file. The Build Action for application bar icons must be set to Content for the icon to appear correctly. The solution and properties windows will look like this:
Implementing the application bar requires another important step. Because the control is in a separate project, we need an xml namespace reference in the XAML for MainView.xaml. The references section requires the following reference:
xmlns:wp7ControlsAppBar="clr-namespace:WindowsPhone.Controls.AppBar;assembly=WindowsPhone.Controls"
Then within the main content control of the PhoneApplicationPage, or the view, we need that application bar.
<wp7ControlsAppBar:BindableApplicationBar
x:Name="AppBar"
BarOpacity="1.0">
<wp7ControlsAppBar:BindableApplicationBarIconButton
Command="{Binding GetRandomQuoteCommand}"
Text="get quote"
IconUri="/images/appbar.sync.rest.png" />
<wp7ControlsAppBar:BindableApplicationBar.MenuItems>
<wp7ControlsAppBar:BindableApplicationBarMenuItem
Text="about"
Command="{Binding NavigateToAboutCommand}" />
</wp7ControlsAppBar:BindableApplicationBar.MenuItems>
</wp7ControlsAppBar:BindableApplicationBar>
You can see in the XAML above that there is a button and a menu item. The button has a Command property bound to GetRandomQuoteCommand from MainViewModel. The menu item has a Command property bound to NavigateToAboutCommand. The default event for each, the click, will invoke the action assigned to those commands in the MainViewModel. There is no code in the MainView.xaml.cs, the code behind for MainView.xaml. The out of the box ApplicationBar for Windows Phone 7 would require code for click events in the code behind of the view. This control wrapper saved us from breaking the MVVM pattern.
Summary
The application is done from a functional point of view. There could be some design improvements at this point. Because this isn’t a series about design for the phone we can simply refer to the Design Resources for Windows Phone. In the next and final post, we’ll go over how to turn this solution into a manageable “template”. Then we can start writing high quality Windows Phone 7 applications!
Source Code
0bdef43a-bbc7-4454-954f-08e4615a37df|0|.0
Code, Silverlight, WP7