First try at a ViewModel for the example
In the first iteration of this example, I databound the View directly to the Model. While not pretty, this was surprisingly functional. The list box was populated and when you changed selection through the list you automatically get a detail view of the selected item. However, the Add and Remove buttons did nothing, and in fact were always disabled.
At this point it would be easy enough to hook up event handlers to the ContactDatabaseChanged event and write some code to enable and disable the two buttons based on the state of the Model, then hook up event handlers to the Click events of each button that added and removed new items. However, by doing this you're already writing some general business logic in the view layer: the code that adds and removes things from the Model.
Instead, we introduce the ViewModel and then databind to that. Let's start with the public interface:
public class ContactDatabaseViewModel
{
// Methods
public ContactDatabaseViewModel(ContactDatabase model);
// Properties
public ICommand Add { get; }
public CollectionView Contacts { get; }
public ICommand Remove { get; }
}
The ViewModel takes the Model as a constructor parameter and wraps it. It provides Commands for the two operations we want, and exposes the list of Contacts through something called a CollectionView. More on CollectionView in a moment, but first here's the new XAML that binds to the ViewModel.
<Grid x:Name="MasterDetailContainer"
Margin="28,25,28,11" RenderTransformOrigin="0.5,0.5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.45*"/>
<ColumnDefinition Width="0.55*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
</Grid.RowDefinitions>
<!-- The parent's data context is going to be of type ContactDatabase
we bind the ListBox items to the ContactDatabase.Contacts property -->
<ListBox x:Name="ContactsListBox"
Margin="12,12,11,94" RenderTransformOrigin="0.5,0.5"
ItemsSource="{Binding Path=Contacts}"
IsSynchronizedWithCurrentItem="True"/>
<ContentControl x:Name="CurrentContactDetail"
Margin="14,16,17,219" Grid.Column="1" RenderTransformOrigin="0.5,0.5"
Content="{Binding Path=Contacts.CurrentItem, Mode=OneWay}"/>
<Button x:Name="AddButton"
HorizontalAlignment="Left" VerticalAlignment="Bottom" Margin="18,0,0,131"
Width="86" Height="58" Grid.Column="1" RenderTransformOrigin="0.5,0.5"
Command="{Binding Path=Add}"
Content="Add"/>
<Button x:Name="RemoveButton"
HorizontalAlignment="Stretch" VerticalAlignment="Bottom" Margin="135,0,93,131"
Width="86" Height="58" Grid.Column="1" RenderTransformOrigin="1,0.48"
Command="{Binding Path=Remove}"
Content="Remove"/>
</Grid>
Remember that the View is now bound to the ViewModel rather than the Model, but the changes are very simple. The ListBox code looks exactly the same...it is bound to the Contacts property of the Model. Before that property was a List, now it is a CollectionView, but WPF databinding can handle them interchangeably. The Detail content control used to bind to the Items.CurrentItem of the ListBox through what WPF calls element to element databinding. Element to element databinding is quite powerful, but it does mean that you're hard-wiring coordination between parts of the UI into the View code. In the new code the ViewModel keeps track of the CurrentItem and we bind to that. In a simple example there is not much difference, but in a more complex UI all of the various controls bind to a central place, reducing the difficult to debug spaghetti dependencies between different parts of the UI code.
Finally, the two Buttons bind their Command properties to the two ICommands exposed from the ViewModel. Through the magic of the WPF databinding, you not only get the Buttons hooked up to the proper operations, you get their IsEnabled behavior for free.
I have attached the zip with the full running project. Download it, compare the old and new code and I'll discuss what this CollectionView thing is in my next post.
Comments
Anonymous
February 27, 2006
John Gossman has posted the first part of his long-awaited example of a Model-View-ModelView architecture,...Anonymous
March 01, 2006
Could you give an example when Model-View-ViewModel doesn't work (not useful) ?Anonymous
March 06, 2006
Good series! Keep it coming!
JohnAnonymous
September 27, 2006
If you're doing WPF development, you really need to check out Dan Crevier 's series on DataModel-View-ViewModel.Anonymous
August 08, 2008
PingBack from http://stefanleopold.wordpress.com/2008/08/08/viewmodel-commands/Anonymous
January 13, 2009
Jag talar ofta med kunder och partners som vill använda vedertagna designmönster för att separera ansvaret