I've been promising an example of the Model-View-ViewModel pattern for some time. With the Feb CTP of WinFX out and the Feb CTP of Expression Interactive Designer close, I feel like I can post something that will work for awhile. I had more ambitious plans for a complex, real-world example, but realized that 1) I didn't have much time to write such a beast and 2) It was hard to create a complex example that still clearly explained the principles.
So, I came up with the idea of doing a toy contact database. The core model classes looks like this:
public class Contact { // Methods public Contact(string name, Gender gender, string favoriteColor); public override string ToString(); // Properties public string FavoriteColor { get; } public Gender Gender { get; } public string Name { get; } }
public class ContactDatabase { // Events public event ContactsChangedEventHandler ContactDatabaseChanged; // Methods public ContactDatabase(); public void AddContact(Contact newContact); private void OnContactDatabaseChanged(ContactsChangedType changeType, int index, Contact contact); public void RemoveContact(int index); // Properties public ReadOnlyCollection<Contact> Contacts { get; } }
Notice the classes have no special WPF dependencies. Imagine this is a pre-existing model, perhaps an interchange schema or a web-service with no UI or specific UI technology in mind. What I will be demonstrating is how to wrap this Model in a ViewModel an then build a UI on that ViewModel, all without changing the Model and especially without mixing UI code into it.
But to start, I want to show how much you can do with WPF data binding to just bind directly to the model...without any additional code. It turns out, quite alot. Here's a scrap of XAML I created using Expression:
<!-- The code will set the data context for this item --> <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 Items.CurrentItem, ElementName=ContactsListBox, 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" Content="Add" IsEnabled="false"/> <Button x:Name="RemoveButton" HorizontalAlignment="Stretch" VerticalAlignment="Bottom" Margin="135,0,93,131" Width="86" Height="58" Grid.Column="1" RenderTransformOrigin="1,0.48" Content="Remove" IsEnabled="false"/> </Grid>
The full project code at this point is attached. Take a look at it. In the next installment I will add a simple ViewModel.