The Anchor ViewModel pattern described in part 2 of this series is especially well adapted to Hub style layouts where related user controls are organized under a common parent. For instance, a Pivot Control can have a MainViewModel object assigned to its DataContext. If the MainViewModel class has properties with additional ViewModels, for example DetailsName, DetailsAddress, and so on, each of these properties can be used as the DataContexts of the Pivot Items contained in it.
Here’s a simple illustration of what that would look like:
<controls:Pivot
DataContext="{Binding
Source={StaticResource MainVM}}"
Name="pivot1" Title="{Binding Title}"
>
<controls:PivotItem
DataContext="{Binding DetailsName}"
Header="{Binding Title}">
<StackPanel >
<TextBox Text="{Binding FirstName, Mode=TwoWay}"/>
<TextBox Text="{Binding LastName, Mode=TwoWay}" />
</StackPanel>
</controls:PivotItem>
</controls:Pivot>
Using the tip from Tombstoning Simplified, tombstoning your application only requires a single call in order to save to transient storage not only the MainViewModel but also all of its dependent ViewModels:
private void Application_Deactivated(object sender
, DeactivatedEventArgs e)
{
MainViewModel.Backup(new TransientDataStorage());
}
This pattern work less well if your phone application consists of unrelated pages with unrelated view models backing them.
In order to maintain the one line tombstoning code, you can still have a RootViewModel that consists only of properties for each ViewModel in the application. The RootViewModel can be assigned to the DataContext of the PhoneApplicationFrame of your application, and each subsequent page in the app can simply have its DataContext bound to the properties of the RootViewModel.
Practically speaking, this is an effective architecture. It will accomplish everything you need in your app for tombstoning.
On an aesthetic level, however, I find it displeasing to have unrelated ViewModels dependent on one another. Additionally, having a super ViewModel, the purpose of which is only to host other ViewModels, makes my eye twitch.
In my own applications I use a different pattern called the Satellite ViewModel pattern. It complements the Anchor ViewModel pattern and shouldn’t be considered opposed to it. Each pattern ought to be used where it is most appropriate to do so.
3. Satellite ViewModel
Satellites orbit the earth without any awareness of each other. Each can be communicated with independently of its fellow crafts. Where the Anchor ViewModel pattern privileges one object above all others, satellites are all equal with respect to one another.
In order to implement the Satellite View Model Pattern for Windows Phone applications, we will simply take advantage of one of the oldest and best-known design patterns: The Singleton. In order to make it play well in the Phone environment, however, we will give it a little twist.
The goals of this pattern are:
- to preserve state for a View (a PhoneApplicationPage or UserControl) even when the View is out of scope.
- to allow fine-grained access to Views as needed
- to support easy backup to transient or isolated storage if the application is tombstoned or simply terminated.
- to provide an MVVM architecture that is Blendable.
A ViewModel, at minimum, should of course implement INotifyPropertyChanged. Here is the boiler-plate implementation most people use:
public class MainViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (null != PropertyChanged)
PropertyChanged(this
, new PropertyChangedEventArgs(propertyName));
}
}
To allow for general access to this ViewModel while preserving state no matter what happens to associated Views, we implement the singleton pattern:
protected static MainViewModel _instance;
private static object _lockObject = new object();
public static MainViewModel Instance
{
get
{
lock (_lockObject)
{
if (_instance == null)
_instance = new MainViewModel();
}
return _instance;
}
}
So far this is pretty standard code. We’ve even added a standard thread-safety measure with the lock keyword.
The Singleton Pattern (as well as the related Factory Method Pattern) typically implements a private no parameter constructor in order to prevent instantiation using the new() keyword.
We can’t do this for windows phone for two reasons.
First, both Isolated Storage and transient storage to the State object use XML serialization in a partial trust environment. Consequently, if we want to backup our ViewModels to persistent or transient storage, our ViewModels must have public no param constructors.
Second, in order to make our ViewModels blendable, we also require public no param constructors since Silverlight for Phone does not support referencing static classes in XAML. The constraints of the Silverlight environment on the phone force us down certain architectural paths and we are obligated to tweak some old tried-and-true patterns in order to create new ones.
In order to support both blendability and serialization, our MainViewModel’s constructor will look like this:
public MainViewModel(){}
It’s awkward, I grant you.
With respect to serialization, we are required to have a public no param constructor even if it has no code in it. Furthermore, when the ViewModel is deserialized, the constructor is never actually called (even though we were required to have it). If you are using a base class for your ViewModels, as many people do, you must also provide a public no param constructor for your base class (yes! even if it is marked as an abstract base class).
In order to instantiate our ViewModel in XAML and use it as our DataContext, we will add it to our class DataContext attribute using some special syntax. The XAML for assigning a MainViewModel instance to the page’s DataContext looks like this:
d:DataContext="{d:DesignInstance local:MainViewModel,
IsDesignTimeCreatable=True}"
Instantiating the MainViewModel instance in XAML like this makes our VM blendable. The point of making the MainViewModel blendable is to be able to visualize how the View and the ViewModel work together. Additionally, you may want to show data when you are designing the application which you do not want to show when you are actually running the application. You can handle this in the Constructor method (the one we originally didn’t want to have) for your VM like so:
public MainViewModel()
{
if (DesignerProperties.IsInDesignTool)
{
Title = "Design Mode";
}
else
{
Title = "My Application";
}
}
The IsInDesignTool is the magic sauce that tells us whether we are running this code through a designer or in a live application.
On the XAML side, there is an additional chunk of XAML I haven’t shown you yet. The code presented so far will allow you to instantiate and run code in the designer only. In order to have VM code that runs in both the designer and at runtime, however, we will set the DataContext again. This will look like we are duplicating code, but we aren’t really.
The purpose of this code segment is simply to set the PhoneApplicationPage’s DataContext to the Instance property for the running application as well as the design-time application (for runtime purposes, of course, it isn’t necessary to perform binding in the xaml. It could just as well be done programmatically in the Page’s constructor):
<phone:PhoneApplicationPage
...
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DataContext="{d:DataInstance local:MainViewModel,
IsDesignTimeCreatable=True}" >
<phone:PhoneApplicationPage.DataContext>
<Binding Path="Instance">
<Binding.Source>
<local:MainViewModel/>
</Binding.Source>
</Binding>
</phone:PhoneApplicationPage.DataContext>
...
Now to put in some code for tombstoning. For this we just require two static methods, one for serializing the VM and one for rehydrating it. Using the helper classes from here the serialize/deserialize code on MainViewModel looks like this:
private string token = "MainViewModel";
public static bool Backup(IDataStorage store)
{
return store.Backup(token, _instance);
}
public static void Restore(IDataStorage store)
{
_instance = store.Restore<MainViewModel>(token);
}
In the App class, we hook into the phone services events to call Backup and Restore for all of our VMs. The code below assumes that we have three ViewModels we want to save off. RestorePersistentData and BackupPersistentData are simply custom methods for saving to IsolatedStorage explained in this Tip.
They are in this sample code simply to illustrate that any data you want to save off when the application permanently terminates should also be saved off when you prepare your app for tombstoning since there is no guarantee the application will be revived after tombstoning :
private void Application_Activated(object sender
, ActivatedEventArgs e)
{
RestorePersistentData();
var store = new TransientDataStorage();
TwitterDetailsViewModel.Restore(store);
ContactsViewModel.Restore(store);
MainViewModel.Restore(store);
}
private void Application_Deactivated(object sender
, DeactivatedEventArgs e)
{
var store = new TransientDataStorage();
MainViewModel.Backup(store);
ContactsViewModel.Backup(store);
TwitterDetailsViewModel.Backup(store);
BackupPersistentData();
}
private void Application_Launching(object sender
, LaunchingEventArgs e)
{
RestorePersistentData();
}
private void Application_Closing(object sender
, ClosingEventArgs e)
{
BackupPersistentData();
}
To summarize, the Satellite ViewModel – which is really a somewhat deviant mixing of the Singleton pattern and the ViewModel pattern to make them work on the Phone – is intended to accomplish four goals:
- Support a stateful phone application architecture.
- Allow easy accessibility to ViewModels.
- Facilitate tombstoning scenarios.
- Provide a Blendable ViewModel pattern.
If you don’t like some of the jury-rigging required to make the above code work, I would highly recommend you look at Laurent Bugnion’s ViewModel Locator pattern: http://www.galasoft.ch/mvvm/getstarted/ . As of this writing, the MVVM Light ViewModel base class doesn’t deserialize correctly because it doesn’t have a public no param constructor. To work around this, you simply have to copy the BaseViewModel class into your own implementation and add a constructor.
[Note: this code was cleaned up on 10/17/10 to use the DataInstance extension for design time binding to the DataContext. Many awkward issues regarding instantiation go away by using this syntax.]