Flyweight M-V-VM

A better M-V-VM library

There are several existing libraries that support the Model-View-ViewModel (M-V-VM) pattern. They each have some nice features. However, I didn’t find any that were just M-V-VM. Some required me to fundamentally restructure my XAML. Others required me to use dependency injection or implement a view model locator pattern. A few required me to use a heavy base class full of complex helpers or depend on a whole set of additional infrastructure. While I appreciate the help writing Plain Old CLR Objects (POCO) view models, I kept wishing there was a library requiring less buy-in to all those “extras”.

So I started building a library that just does M-V-VM. I ended up with something that is easy to use, lightweight, and powerful.

I’ve revised the approach from using a Behavior to using a Decorator.  You can download the updated version and read about it at http://blogs.southworks.net/geoff/2011/10/19/mvvm-decorator/.

You can download it here. It contains the library project and a HelloWorld sample application. Both have complete ReadMe.txt files.

Tenants

M-V-VM only

The library has no dependencies outside WPF/.NET. All the classes are related strictly to M-V-VM.

Normal XAML development

The library does not require to change how you build your XAML hierarchy.

No POCO glue

The library does not provide commanding or property changed helpers or patterns. Another library might, but not this one.

No additional patterns required

The library can support dependency injection and view model locator, but nothing is required on behalf of the developer.

How to use it

These are the two classes you’ll use 99% of the time.

ViewModel<TModel>

This is a base class for your view model classes.

ViewModelBehavior

This is a behavior that you can drag and drop onto your controls (in Expression Blend). Just set the ViewModelType property to the type of your view model.

You can review the readme to learn about the other classes.

How it works

When the behavior attaches to the control:
  1. It instantiates a view model using the ViewModelType property.
  2. It sets the view model’s Model property = DataContext
  3. It sets the view model’s View property = control
  4. It sets the control’s DataContext = view model

Example 1: Basic ViewModel

This example shows the most basic creation of a view model.

In this example and the following examples the is a Customer is the model. The Customer class has basic properties like FirstName and LastName. The view model provides additional properties like FullName.

<Grid DataContext="{Binding Customer}">
  <i:Interaction.Behaviors>
    <bellacode:ViewModelBehavior ViewModelType="{x:Type local:CustomerViewModel}"/>
  </i:Interaction.Behaviors>
  <TextBlock Text="{Binding FullName}" />
</Grid>

Yes the example is contrived as you could easily do this with a ValueConverter. Some consider the M-V-VM pattern eliminates the need for ValueConverters and ValidationRules. Others find those classes continue to be helpful and are better encapsulated for re-use.

Example 2: Bind to ViewModel and Model

This example shows the you can also bind directly against the model.

It is up to you if your view model follows a strict Facade pattern when wrapping your model. There are some nice benefits to the Facade pattern because then your data-binding statements don’t have any knowledge that there is a view model containing a model. However, if your model already implements INotifyPropertyChanged, it can often be a massive time saver to bind directly to the model.

<Grid DataContext="{Binding Customer}"> <i:Interaction.Behaviors> <bellacode:ViewModelBehavior ViewModelType="{x:Type local:CustomerAccountViewModel}"/> </i:Interaction.Behaviors> <StackPanel> <TextBlock Text="{Binding Model.CreditCard.AccountHolderName}" /> <TextBlock Text="{Binding AccountStatus}" /> </StackPanel> </Grid>

Example 3: Hierarchy of ViewModels

This example shows how to create view models at different levels for a hierarchy of views.

Any part of the view model – the model, part of the model, or a property – can be passed as the data context for a nested control. The behavior can then use that data context as a model for the nested control’s view model. View models naturally follow the same hierarchy as your views. The behavior is easy to use because it leverages the natural XAML hierarchy.

<Grid>
  <i:Interaction.Behaviors>
    <bellacode:ViewModelBehavior ViewModelType="{x:Type local:CustomerViewModel}"/>
  </i:Interaction.Behaviors>
  <Grid.ColumnDefinitions>
    <ColumnDefinition Width="Auto" />
    <ColumnDefinition />
  </Grid.ColumnDefinitions>
  <Grid.RowDefinitions>
    <RowDefinition Height="Auto" />
    <RowDefinition Height="Auto" />
    <RowDefinition Height="Auto" />
  </Grid.RowDefinitions>
  <Label Content="Name" />
  <TextBlock Text="{Binding FullNameWithSalutation}" Grid.Column="1" />
  <Label Content="Shipping Address" Grid.Row="1"/>
  <Grid DataContext="{Binding Model.ShippingAddress}" Grid.Row="1" Grid.Column="1">
    <i:Interaction.Behaviors>
      <bellacode:ViewModelBehavior ViewModelType="{x:Type local:AddressViewModel}"/>
    </i:Interaction.Behaviors>
    <TextBlock Text="{Binding FullAddress}" />
  </Grid>
  <Label Content="Billing Address" Grid.Row="2"/>
  <Grid DataContext="{Binding Model.BillingAddress}" Grid.Row="2" Grid.Column="1">
    <i:Interaction.Behaviors>
      <bellacode:ViewModelBehavior ViewModelType="{x:Type local:AddressViewModel}"/>
    </i:Interaction.Behaviors>
    <TextBlock Text="{Binding FullAddress}" />
  </Grid>
  <Label Content="Account Status" Grid.Row="3"/>
  <Grid DataContext="{Binding Model}" Grid.Row="2" Grid.Column="3">
    <i:Interaction.Behaviors>
      <bellacode:ViewModelBehavior ViewModelType="{x:Type local:CustomerAccountViewModel}"/>
    </i:Interaction.Behaviors>
    <TextBlock Text="{Binding FullAddress}" />
  </Grid>
</Grid>

Example 4: Lists and Data Templates

This example shows how a data template can easily create a view model for each item when used in a list control.

Your view models should not generally expose other view models as properties. This would mean that one view model is responsible for creating instances of other view models. You would lose the loose coupling the view model behavior provides and the flexible type-agnostic nature of data binding.

<Grid> <ListBox ItemsSource="{Binding Customers}"> <ListBox.ItemTemplate> <DataTemplate> <Grid> <i:Interaction.Behaviors> <bellacode:ViewModelBehavior ViewModelType="{x:Type local:CustomerViewModel}"/> </i:Interaction.Behaviors> <Grid.ColumnDefinitions> <ColumnDefinition /> <ColumnDefinition /> </Grid.ColumnDefinitions> <TextBlock Text="{Binding Customer.Name}" /> <TextBlock Text="{Binding MostRecentPurchaseDate}" Grid.Column="1" /> </Grid> </DataTemplate> </ListBox.ItemTemplate> </ListBox> </Grid>

Up Next: POCO

Plain Old CLR Objects (POCO) view models feel much less UI-bound than those that expose RoutedCommand, ICommand, RoutedEvents, or ICollectionView. I’m in progress working on a behavior that make command-binding for a control just as easy as the view model. It will be a separate library so you can choose to use if it works for you.



Leave a Reply