I've been doing more work on Windows Phone 7 applications. Because of the smaller footprint of the phone and the way the application is hosted, I don't believe the phone requires the same types of tools and frameworks as the browser. Sharing code and services is something that is obviously important, but while I am a huge advocate of the Managed Extensibility Framework (MEF) and use it in my own Jounce MVVM Framework, I'm not convinced it is needed for smaller phone applications.
So how do I use MVVM on the phone? I'm still exploring options and admit I have not used some of the existing (and probably fantastic) frameworks that are out there. What I did notice immediately was that I would need about six key things to be successful on the phone:
- Notify Property Changed — a global requirement for MVVM
- Commands — this is fairly universal for data-binding in Silverlight
- Dialogs — again, common but easy on the phone
- Navigation — a decoupled way to use the phone navigation (not interested in rolling my own due to the stringent "back button behavior" requirements)
- Application Bar Bindings — need an effective/easy way to handle getting those icons bound to the view model
- Tombstoning — this is every Windows Phone 7 programmer's bane, I handle that with Sterling and will talk more about it in a later blog post.
After working on a few applications, I came up with an extremely light weight framework that is working well for me, so I'm sharing it here. There is no sample project (I am working on one but it's for an upcoming article and not ready to release) but everything you need is here, in source.
Notify Property Changed
The first thing is to create a base class to handle property changed notification. This is almost verbatim the base class that I use in my Jounce framework. You can see the source for it here and it is pretty close to the code that the Prism framework provides in their MVVM guidance.
Commands
I also copied my command implementation, which is close to the DelegateCommand
or perhaps RelayCommand
implementations you are familiar with. Mine is IActionCommand
and you can browse the interface source and implementation source.
A Common Contract
Next, a common contract for view models to address some of the other concerns. The contract looks like this:
public interface IViewModel { IDialog Dialog { get; set; } Action<string> RequestNavigation { get; set; } Action GoBack { get; set; } IEnumerable<IActionCommand<object>> ApplicationBindings { get; } }
Notice this gives us a decoupled baseline to deal with dialogs and navigation requests. It also returns a list of commands .. called application bindings? Hmmm. Interesting.
A Base Implementation
Next is the base view model, where I add a design attribute and placeholders for the rest:
public abstract class BaseViewModel : BaseNotify, IViewModel { protected bool InDesigner { get { return DesignerProperties.IsInDesignTool; } } public IDialog Dialog { get; set; } public Action<string> RequestNavigation { get; set; } public Action GoBack { get; set; } public abstract IEnumerable<IActionCommand<object>> ApplicationBindings { get; } }
The dialog interface looks like this:
public interface IDialog { bool ShowMessage(string title, string message, bool allowCancel); }
Next comes the fun part. There may be a case for an inversion of control container here, but I'm going to follow a fairly common pattern but instead of shoving a bunch of variables into the App
object, I'll create a GlobalManager
class for the MVVM concerns. Now this is a static class and of course that concerns some people for becoming problematic down the road because the views will be tightly bound (plus it's one of those hated singletons). In this case it just doesn't bother me. I unit test the view models, not necessarily the views, and use design-time data in the views. Therefore, the binding for me doesn't pose a threat as I can still grab the view models and run them in isolation while mocking the interfaces.
I'll double the manager as a view model locator and just return a singleton instance of the view models. For data I'll use design time data based on sample data that implements a common view model interface. I'm basically following the pattern from Design-time Friendly View Models with MEF only without the MEF.
The first thing I'll do with my class is provide an extension method for pages to bind to the view model. This method will wire up the dialog and navigation, and also inspect the list of application bindings and wire those up as well. For the most part the view model doesn't need to know about the view, but this is one case where there is a slight dependency: the view model is responsible for knowing the order of the buttons in the application bar so it can return the commands in the correct order. The extension method will then bind the buttons to the commands. Here it is:
public static void BindToViewModel(this PhoneApplicationPage phonePage, FrameworkElement root, IViewModel viewModel) { if (DesignerProperties.IsInDesignTool) return; RoutedEventHandler loaded = null; var vm = viewModel; // access to modified closure loaded = (o, e) => { var page = o as PhoneApplicationPage; if (page == null) { return; } // unhook loaded so we do this only once page.Loaded -= loaded; // provide an implementation for the dialog vm.Dialog = new Dialog(); // provide an implementation for navigation vm.RequestNavigation = newPage => page.NavigationService.Navigate(new Uri(newPage, UriKind.Relative)); vm.GoBack = page.NavigationService.GoBack; // bind the view model root.DataContext = vm; // bind the application bar buttons var idx = 0; foreach(var command in vm.ApplicationBindings) { if (page.ApplicationBar.Buttons.Count <= idx) break; var appButton = page.ApplicationBar.Buttons[idx++] as ApplicationBarIconButton; if (appButton == null) continue; var command1 = command; command.CanExecuteChanged += (cmd, arg) => appButton.IsEnabled = command1.CanExecute(null); appButton.Click += (cmd, arg) => command1.Execute(null); } }; phonePage.Loaded += loaded; }
As you can see, we do a few things. A local variable hosts the delegate for the loaded event so we can unhook it when done. We link the navigation service from the page to the view model, and then iterate the application buttons. Basically, we do manually what command bindings do automatically, and that's change the enabled state of the button based on the command's ability to execute, and bind the click event to a command execution.
The View Model
What did we accomplish? First we get a view model that has a "save" and "cancel" action that looks like this:
public class MainViewModel : BaseViewModel, IMainViewModel { public MainViewModel() { SaveCommand = new ActionCommand<object>(o=>_SaveCommand(), o=>_CanSave()); CancelCommand = new ActionCommand<object>(o=>_CancelCommand()); } public IActionCommand<object> SaveCommand { get; private set; } public IActionCommand<object> CancelCommand { get; private set; } private void _SaveCommand() { DoSave(); RequestNavigation("/Views/WidgetList.xaml"); } private bool _CanSave() { return true; } private void _CancelCommand() { if (!Dialog.ShowMessage("Confirm Cancel", "Are you sure you wish to cancel?", true)) return; GoBack(); } public override IEnumerable<IActionCommand<object>> ApplicationBindings { get { return new[] {SaveCommand, CancelCommand }; } } }
Notice the dialog call. Currently, I'm simply using the built-in dialog provided by the MessageBox
function on the phone, but the interface allows you to implement it any way you like. Here is my implementation:
public class Dialog : IDialog { public bool ShowMessage(string title, string message, bool allowCancel) { return MessageBox.Show(message, title, allowCancel ? MessageBoxButton.OKCancel : MessageBoxButton.OK) == MessageBoxResult.OK; } }
The View
Having done all of that, what does it take to actually bind the main page to the view model? Fortunately, with this framework, even using the application buttons and binding them to commands, this is the entire code-behind for the main page:
public partial class MainPage { // Constructor public MainPage() { InitializeComponent(); this.BindToViewModel(LayoutRoot, GlobalManager.MainViewModel); } }
The reference to the main view model is type IMainViewModel
and is lazy created when referenced the first time.
For nested user controls, I created a similar extension method with the difference that it doesn't bind the buttons as there obviously isn't an application bar for a user control (the view model can just return an empty list or null). To allow the user control to affect navigation, the binding code simply walks the visual tree to the parent to grab the navigation service:
// hook to the navigation of the parent var parent = VisualTreeHelper.GetParent(control); while (parent != null) { if (parent is PhoneApplicationPage) { vm.RequestNavigation = newPage => ((PhoneApplicationPage)parent).NavigationService.Navigate(new Uri(newPage, UriKind.Relative)); vm.GoBack = ((PhoneApplicationPage) parent).NavigationService.GoBack; break; } parent = VisualTreeHelper.GetParent(parent); }
As I mentioned, I'm still working on various applications to find out what other patterns present themselves, but this to me is a very simple, lightweight, and easy way to get started with the MVVM pattern on the phone.