共用方式為


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.

ContactViewModel1.zip

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!

    John

  • Anonymous
    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