Tuesday, January 12, 2010

Sharing Objects Between ViewModels

An application we recently finished off at work highlighted a weakness within our architecture when it came to sharing data in the UI between the various ViewModels within our module.

Imagine an application with two screens (Views), arranged in a wizard work-flow way. The first screen allows you to select a file, and the second displays the properties of that file. The screen has typical “Next” and “Back” buttons and each View has its own ViewModel (say SelectionViewModel and PropertiesViewModel).

Upon selecting a file in the SelectionViewModel, how should the PropertiesViewModel receive/store the data that it needs to display?

In the case of our application, two different approaches were implemented in various places, both of which were quite unappealing. Here they are:

Approach 1: Binding to a shared ViewModel

One solution was to give PropertiesViewModel a reference to the SelectionViewModel and databind to it in the View. The constructor for the PropertiesViewModel would receive the SelectionViewModel instance, fed to it by Unity. Unity was also configured to store a single instance of the SelectionViewModel by using the RegisterInstance() method.

A sample piece of XAML in the PropertiesView might look like this:
<TextBlock Text="{Binding PropertiesViewModel.SelectedFile.Filename}" />

Approach 2: Using Events with a shared ViewModel

Imagine the same application, but this time rather than binding to the shared ViewModel, properties are created for binding, and are updated when the shared ViewModel changes. For example, imagine a Filename property created in the PropertiesViewModel, defined like any other. However, since the Filename is dependent upon which file is selected, eventing is used to listen for changes on the SelectionViewModel and update the Filename accordingly.

The PropertiesViewModel still needs to hold a reference to the SelectionViewModel, but this time it takes advantage of the fact that it implements INotifyPropertyChanged to listen for changes on it. E.g.:
/// <summary>
/// Initialise data for this view model
/// </summary>
public void Initialise()
{
// Because we are exposing properties reliant on SelectionViewModel, we
// are interested in knowing when properties change on that object.
SelectionViewModel.PropertyChanged += SelectionViewModelPropertyChanged;

...

}

...

/// <summary>
/// Fires when a property on the attachments SelectionViewModel changes
/// </summary>
/// <param name="sender">not used</param>
/// <param name="e">the property that has changed</param>
private void SelectionViewModelPropertyChanged (object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "SelectedFile")
{
OnPropertyChanged("Filename");
}
}

...

/// <summary>
/// Return the text to be displayed as the filename.
/// </summary>
public String Filename
{
get
{
return _ SelectionViewModel.SelectedFile.Filename;
}
}


So what’s wrong with these approaches? Right from the start it felt dirty to be passing around instances of one ViewModel to another (thus creating dependencies between them), and using the RegisterInstance method of Unity to ensure we only ever had one instance. It seems there should be some much cleaner approaches, and I think that using Prism’s Event Aggregator it should be easy enough to do. I’m planning on looking into this over the next few days if I find the time and hopefully post some better options soon.

No comments:

Post a Comment