Introduction
While the Model View View Model (MVVM) pattern provides a number of advantages (separation of concerns and ease of testing) to XAML based applications (WPF, Silverlight and Windows RT) . There are number of frustrations in getting things to work - especially in the amount of code that must be generated to do simple things.
A lot of time is spent coding view models which simply mirror properties and 'wrap' the underlying model objects, implementing the INotifyPropertyChanged
(INTP) interface and creating delegates to handle command actions and events the view generates.
The following example illustrates a simple 'wrapped' model object. The corresponding ViewModel
takes a Model
object and exposes its properties:
public partial class CarType
{
public Int32 CarTypeID {get;set;}
public string CarTypeName {get;set;}
}
public class CarTypeViewModel : ViewModelBase
{
CarType wrapped;
public CarTypeViewModel (CarType carType)
{
wrapped = carType;
}
public CarTypeViewModel (CarType carType)
{
wrapped = carType;
}
public string CarTypeName {get
{ return wrapped.CarTypeName; }
set {if (value!=wrapped.CarTypeName)
{
wrapped.CarTypeName =value;
RaisePropertyChange("CarTypeName");
}
}
}
}
}
So ideally, I wanted to simplify a number of these repetitive tasks and investigated various options by automatically generating the appropriate delegates, properties and events.
The end result, among other things, allows the automatic binding of commands, events and INotifyPropertyChanged
events, while still maintaining strong separation of concerns and requiring no direct linking with the view.
The following example illustrates a view model that would get auto mapped properties, commands and events:
public string FirstName {get;set;}
public string LastName{ get; set; }
[LinkToProperty("FirstName")][LinkToProperty("LastName")]
public string FullName
{
get
{
return String.Format("{0} {1}", FirstName, LastName);
}
}
[LinkToEvent("AddingNewItemEvent")]
private void DataGrid_AddingNewItem(AddingNewItemEventArgs e)
{
}
[LinkToCommand("RollBackChangesCommand")]
private void RollBackChanges()
{
this.RollBack();
}
[PropertyChangedEvent]
void person_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName != "Log" && sender is ICustomObjectProxyObjects)
{
SetPropertyValue("Log", e.PropertyName + " changed to:" +
((ICustomObjectProxyObjects)sender).GetPropertyValue(e.PropertyName).ToString() + "\r\n" + Log);
}
}
One solution would automatically map a views method and events against a corresponding view model - but this strongly coupled the view and view model. I also looked at the dynamic ExpandoObject
, which looked promising but ended up not working well either.
The solution came in the form of ICustomType
interface which was introduced in Silverlight 5 and WPF 4.5:
public interface ICustomTypeProvider
{
Type GetCustomType();
}
It looks deceptively simply - and rightly so. Both WPF 4.5 and Silverlight 5 will use this custom type if the view's DataContext
implements the interface.
In order to implement, you need to replace the underlying property Type
class implementation. When a view makes a request, the custom type 'intercepts' the request and deals with it accordingly. It is up to you how this is implemented. The power here is you have complete control over what is returned to the view. But you must also take care of any 'normal' properties exposed in the underlying class.
The article Binding to Dynamic Properties with ICustomTypeProvider by Alexandra Rusina describes this process and forms the basis of this solution.
The resulting ProxyTypeHelper
solution is intended to help simplify the following tasks:
- Auto generation of view model properties from underlying model
- Auto creation of command and event delegates
- Auto mapping lookup values/properties
- Dirty flag on per field/property level
- Storing of original value /rollback changes
It does this while maintaining separation of concerns - no dependencies added between the underlying models, views or view models. The included sample code provides examples on these tasks as well as demonstrating a dynamic view model.
Mapping View to View Model Properties and Dynamic Properties
One way the ProxyTypeHelper
library maps models to view models is via the AssociatedModel
attribute. The AssociatedModel
attribute identifies the class to be automatically mapped. Note the class you are applying the attribute to must inherit from ProxyHelper
. The following code associates the earlier CarType
model class with the CarTypeViewModel
:
using ProxyHelper;
using Model;
namespace WPFEventInter.ViewModel
{
[AssociatedModel(typeof(CarType))]
public class CarTypeViewModel : ViewModelValidated<cartypeviewmodel>{}
}
In order for the ProxyTypeHelper
to recognize view models using the AssociatedModel
attribute, you must call the TypeStuff
class static InitializeProxyTypes
method before using. You only need to call this method once and it will search for all classes with the attribute. The App.xaml.cs is a good place to put it.
TypeStuff.InitializeProxyTypes();
If model class properties implements ICollection
(such as List<>
), they will get automatically created as ObservableCollections
in the view model - and corresponding model objects are created or deleted as required.
You can manually associate models to view models using the static AddProxyObject
method - so you could have multiple associated models within a view:
PersonViewModel.AddProxyObject(typeof(Person));
Alternatively, you can add individual properties to a class using the static AddProperty
method, which requires the property name and type:
PersonViewModel.AddProperty("Name", typeof(string));
You need to use the AddProperty
or AddProxyObject
before an instance of the class is created for a view. Any properties added after the object has been created are not seen by the view.
Any properties created in classes that inherit ProxyTypeHelper
will automatically have INotifyPropertyChanged
events fire upon changes:
using ProxyHelper;
namespace WPFEventInter.ViewModel
{
public class ProductViewModel : ProxyTypeHelper<productviewmodel>
{
public string Description { get; set; }
public double Price { get; set; }
}
}
Note you don't need to manually fire any property notifications as they are auto generated.
To create a property changed event, just add the PropertyChangedEvent
attribute to the appropriate method. The event gets auto assigned to the object - any child objects are also auto assigned:
[PropertyChangedEvent]
void person_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName != "Log" && sender is ICustomObjectProxyObjects)
{
SetPropertyValue("Log", e.PropertyName + " changed to:" +
((ICustomObjectProxyObjects)sender).GetPropertyValue(e.PropertyName).ToString()
+ "\r\n" + Log);
}
}
The properties in the associated view models aren't directly visible from the CLR - but are from the view/form. Use GetPropertyValue/SetPropertyValue
to get and set individual properties:
GetPropertyValue(propertyName);
SetPropertyValue(propertyName,propertyValue);
Use SetValue
sets all properties for a corresponding object:
SetValue(Type proxyType, object value);
Where proxyType
is the type of the underlying proxy object and value is value you are assigning:
persons.SetValue(typeof(Person), person);
If you have properties that need to fire when other properties change, add the LinkToProperty
attribute. In the following example, the FullName
property changed will get fired if either FirstName
or SurName
properties are changed:
[LinkToProperty("FirstName")] [LinkToProperty("SurName")]
public string FullName
{
get
{
return String.Format("{0} {1}", GetPropertyValue<string>("FirstName"), GetPropertyValue<string>("SurName"));
}
}
If you do not want the property changed event to fire, add the DoNotRaiseProperyChangedEvents
attribute.
Auto Creation of Command and Event Delegates
Linking a view's command action to View model. While technically not difficult, the amount of code can add up if you have a particularly complex view, menu or button bar. Each action needs a corresponding delegate and public property to expose it to the view.
To assign a method to an event command, add the LinkToCommand
attribute to a method:
[LinkToCommand("RollBackChangesCommand")]
private void btnRollBackChanges()
{
}
The argument passed to LinkToCommand
attribute must match the bound Command in the XAML control, as illustrated in the following button control:
<Button Content="Rollback changes"
Command="{Binding RollBackChangesCommand}" Width="140" ></Button>
LinkToCommand
creates a delegate linked to the method and a public
property exposing the delegate to the view.
Events can be linked in a similar fashion but require a bit more work on the view side. Note that it can be argued that passing events to the view model tightly couples the view and view model, or at least the underlying controls. From a practical perspective, there is often no other solution than putting the events in the view model, especially if the view model is extensively reused.
You need to assign EventToCommandBehavior
behaviour for each control in your view you wish to fire the events. This is not a standard XAML feature. Some frameworks and control libraries such as MVVM Light include this behavior.
An implementation is included as part of the project - you may need to change this code to work with other frameworks. In the following snippet, the DataGrid
's AddingNewItem
event is bound to the AddingNewItemEvent
view model command:
<i:Interaction.Behaviors>
<rmwpfi:EventToCommandBehavior Command="{Binding AddingNewItemEvent}" Event="AddingNewItem" PassArguments="True" />
</i:Interaction.Behaviors>
In your view model, you would add the method. Note the event arguments must be appropriate for the event being fired. The easiest way to determine this is to create the event in the view's code behind and copy it to the view model. The following snippet would create delegates for the previous example:
[LinkToEvent("AddingNewItemEvent")]
private void DataGrid_AddingNewItem(AddingNewItemEventArgs e)
{
}
Auto Mapping Lookup Values
Lookup values can also be auto generated by storing lists of values against types:
TypeStuff.SetLookupData("BrandsLookup", typeof(Brand), carsContext.Brands.ToList());
TypeStuff.SetLookupData("ColoursLookup", typeof(Colour), carsContext.Colours.ToList());
From the above example, any view model based on a model object that many to one entity will get the property auto created. So in the enclosed example the Cars ViewModel
will get the ColoursLookup
property auto created.
Tracking Dirty Properties and Original Values
The ability to determine if any object properties have changed is a common view model requirement. The simplest way is to set a boolean flag in the property changed event that gets set when any property is changed.
The ProxyTypeHelper
keeps track of properties via the PropertyValues
property. This stores the obvious value of the property as well as property information such as IsDirty
property and the original property values. This can be used to compare with the changed value or rollback any changes.
ProxyTypeHelper
includes a IsDirty
property that checks the properties for any set IsDirty
flags. There is also a RollBack
method that will rollback to the object's original values
Note that currently IsDirty
doesn't check any child objects for dirty values nor will it rollback any object values.
Final Words
This is still a work in progress. There are a number things that needs addressing, such as cloning/rolling back object properties. The most up to date source can be found at https://github.com/steinborge/ProxyTypeHelper.
The attached sample demonstrates the library as well as a concept of generic view models. There are a number of simple data maintenance screens exposing some SQLLite tables as well as a screen demonstrating some of the basic operations.
When running from development environment, you may get a not implemented exception, which doesn't affect the operation of library. Change your settings to ignore this exception.
The library was developed for WPF, but it wouldn't take much to modify parts for Silverlight or Win RT but since there is a fair bit of reflection, that might not work.
It works with Prism but have not tried with other MVVM frameworks such as MVVM Light or Caliburn. The ProxyLinker
class was recently added to allow for framework integration. I am also investigating the ability to integrate Reactive extensions.
The features are mutually exclusive, so you don't need to implement 'wrapped' proxy properties/objects to use the command linking or dirty flag checking.