Condividi tramite


Automatic Change Notification for Your View Models

Note: If you’d rather just jump into the code the sample that goes with this post is here:

One of the features in WPF Starter Kit (https://wpfstarterkit.codeplex.com) that I am very proud of is the automatic change notification functionality. While implementing the INotifyPropertyChanged method in a base class makes our lives a little easier, it still means you lose the benefit of automatic properties that comes with C# 3.0. So instead of looking like this:

    1: public class Window1ViewModel
    2:     {
    3:         public bool TestProperty { get; set; }
    4:     }

your view models typically end up looking something like this:

    1: public class Window1ViewModel : BaseViewModel
    2:     {
    3:         private bool _testProperty;
    4:         public bool TestProperty
    5:         {
    6:             get
    7:             {
    8:                 return _testProperty;
    9:             }
   10:             set
   11:             {
   12:                 _testProperty = value;
   13:                 OnPropertyChanged("TestProperty");
   14:             }
   15:         }
   16:     }

As you can probably tell, the OnPropertyChanged method in the BaseViewModel raises the PropertyChanged event on behalf of our view model. Now that might not look like a big difference when there’s just one property involved but maintaining a view model with several properties can quickly get out of hand. Add to that the fact that at some point, someone on your team will invariably end up changing the property name and forget to change the text in the call to OnPropertyChanged. What does this mean? This means change notification just stops working. Just like that. Pandemonium! :)

There are of course ways to mitigate this problem. Firstly, we could implement some sort of check in the OnPropertyChanged event to ensure that the string passed in does actually refer to a property on the current object. Here’s what our BaseViewModel looks like:

    1: /// <summary>
    2: /// Base class for all classes which act as data source for user interface elements.
    3: /// This class mplements the INotifyPropertyChanged interface
    4: /// </summary>
    5: public abstract class BaseViewModel : INotifyPropertyChanged
    6: {
    7:     /// <summary>
    8:     /// Called when [property changed].
    9:     /// </summary>
   10:     /// <param name="propertyName">Name of the property.</param>
   11:     protected void OnPropertyChanged(string propertyName)
   12:     {
   13:         PropertyChangedEventHandler eventHandlers = this.PropertyChanged;
   14:         if (eventHandlers != null)
   15:         {
   16:             VerifyPropertyName(propertyName);
   17:             eventHandlers(this, new PropertyChangedEventArgs(propertyName));
   18:         }
   19:     }
   20:  
   21:     /// <summary>
   22:     /// Verifies the name of the property.
   23:     /// </summary>
   24:     /// <param name="propertyName">Name of the property.</param>
   25:     private void VerifyPropertyName(string propertyName)
   26:     {
   27:         if (TypeDescriptor.GetProperties(this)[propertyName] == null)
   28:         {
   29:             string msg = string.Format(CultureInfo.CurrentCulture, "Invalid property name : {0}", propertyName);
   30:             throw new ArgumentOutOfRangeException("propertyName", msg);
   31:         }
   32:     }
   33:  
   34:     #region INotifyPropertyChanged Members
   35:  
   36:     public event PropertyChangedEventHandler PropertyChanged;
   37:  
   38:     #endregion
   39: }

Notice the VerifyPropertyName method that gets called by OnPropertyChanged to verify the property’s name. This is a good solution, but there’s still a problem. Verifying the property name gives us runtime checking but it still doesn’t prevent code that causes this issue getting compiled and checked in. Finally, this still doesn’t solve the ugliness issue. Our view model still contains a LOT of code that does not have much to do with our business logic. Code that is just plumbing to support MVVM.

Enter interception! Interception is the technique we’re going to use to bring our view model code as close to the first code snippet (with automatic properties and no calls to OnPropertyChanged) as possible. With interception, our view model will look something like this:

    1: public class Window1ViewModel : BaseViewModel
    2: {
    3:     public virtual bool TestProperty { get; set; }        
    4: }

A huge improvement, wouldn’t you say? So without further adieu let’s get started.

 

First, lets get set up:

1. Download the sample solution above and open it up. It has all the code we’re going to be talking about.

OR

1. Start a new WPF Windows application

2. Add the BaseViewModel class shown above

3. You will already have a new Window called Window1. Add a class called Window1ViewModel:

    1: public class Window1ViewModel : BaseViewModel
    2: {
    3:     private bool _testProperty;
    4:     public bool TestProperty
    5:     {
    6:         get
    7:         {
    8:             return _testProperty;
    9:         }
   10:         set
   11:         {
   12:             _testProperty = value;
   13:             OnPropertyChanged("TestProperty");
   14:         }
   15:     }
   16: }

4. Add a Loaded event handler to Window1 and add the following line of code to the handler:

    1: DataContext = new Window1ViewModel();

 

What we’ve done here is set up a very simple example of Model View ViewModel (MVVM) where the ViewModel is supported by a simple base class. To add support for interception we will use the Unity Dependency Injection (DI) container (https://unity.codeplex.com/) released by the patterns and practices team. While Unity is by no means the only way to do DI in .NET, it is the one I am most familiar with. If you want to read up on the need and theory behind DI this is a good starting point: https://msdn.microsoft.com/en-us/magazine/cc163739.aspx. The next step is to go ahead and add references to all of the assemblies that ship with the Unity container.

Now we’ll write what in Unity parlance is called a “Call Handler”. A call handler basically handles a method (or in our case, property) call. What this means is that if you have a call handler set up on an object or type the call handler is executed before any methods (you get to choose which methods to target) of that object or type are called. What this lets you do is intercept the call and do something meaningful with what you have in the call handler. For example, a call handler might be used logging or to audit method entry points. A Unity call handler must inherit from the Microsoft.Practices.Unity.InterceptionExtension.ICallHandler interface. Here’s what ICallHandler looks like:

    1: public interface ICallHandler
    2: {
    3:     int Order { get; set; }
    4:  
    5:     IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext);
    6: }

The Invoke method is where your call handler can do any work that it needs to. The Order property is the your call handler’s position (if there are other call handlers involved). We’re going to implement a call handler that:

  1. figures out whether or not the method being called is a property setter.
  2. figures out whether the value being set if different from the old value
  3. If 1 and 2 are true, find the PropertyChangedCallback event on the current or one of the base classes
  4. Check if the PropertyChangedCallback has any handlers assigned and if so, call those handlers

Here’s what the PropertyChangedCallHandler looks like:

    1: public class PropertyChangedCallHandler : ICallHandler
    2: {
    3:     #region ICallHandler Members
    4:  
    5:     public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
    6:     {
    7:         bool shouldRaise = ShouldRaiseEvent(input);
    8:         IMethodReturn res = getNext()(input, getNext);
    9:  
   10:         if (res.Exception == null && shouldRaise)
   11:         {
   12:             RaiseEvent(input);
   13:         }
   14:  
   15:         return res;
   16:     }
   17:  
   18:     public int Order
   19:     {
   20:         get;
   21:         set;
   22:     }
   23:  
   24:     #endregion
   25:  
   26:     private bool ShouldRaiseEvent(IMethodInvocation input)
   27:     {
   28:         MethodBase methodBase = input.MethodBase;
   29:  
   30:         //Is the method a property setter?
   31:         if (!methodBase.IsSpecialName || !methodBase.Name.StartsWith("set_"))
   32:         {
   33:             return false;
   34:         }
   35:  
   36:         //Get the name of the property out so we can use it to raise a 
   37:         //property changed event
   38:         string propertyName = methodBase.Name.Substring(4);
   39:  
   40:         //Retrieve the property getter
   41:         PropertyInfo property = methodBase.ReflectedType.GetProperty(propertyName);
   42:         MethodInfo getMethod = property.GetGetMethod();
   43:  
   44:         //IF the property has no get method, we don't care
   45:         if (getMethod == null)
   46:         {
   47:             return false;
   48:         }
   49:  
   50:         //Get the current value out
   51:         object oldValue = getMethod.Invoke(input.Target, null);
   52:  
   53:         //Get the updated value
   54:         object value = input.Arguments[0];
   55:  
   56:         //Is the new value null?
   57:         if (value != null)
   58:         {
   59:             //Is the new value different from the old value?
   60:             if (value.Equals(oldValue) == false)
   61:             {
   62:                 return true;
   63:             }
   64:         }
   65:         else
   66:         {
   67:             //Is the new value (null) different from the 
   68:             //old value (non-null)?
   69:             if (value != oldValue)
   70:             {
   71:                 return true;
   72:             }
   73:         }
   74:  
   75:         return false;
   76:     }
   77:  
   78:     private void RaiseEvent(IMethodInvocation input)
   79:     {
   80:         FieldInfo field = null;
   81:  
   82:         //Get a reference to the PropertyChanged event out of the current 
   83:         //type or one of the base types
   84:         var type = input.MethodBase.ReflectedType;
   85:         while (field == null && type != null)
   86:         {
   87:             field = type.GetField("PropertyChanged", BindingFlags.Instance | BindingFlags.NonPublic);
   88:             type = type.BaseType;
   89:         }
   90:  
   91:         //If we found the PropertyChanged event
   92:         if (field != null)
   93:         {
   94:             //Get the event handler if there is one
   95:             var evt = field.GetValue(input.Target) as MulticastDelegate;
   96:             if (evt != null)
   97:             {
   98:                 //Get the property name out
   99:                 string propertyName = input.MethodBase.Name.Substring(4);
  100:                 //Invoke the property changed event handlers
  101:                 evt.DynamicInvoke(input.Target, new PropertyChangedEventArgs(propertyName));
  102:             }
  103:         }
  104:     }
  105: }

Note how we use the getNext delegate to call the next interceptor (or the actual method) in line. This is VERY important because if we don’t do this the method being intercepted won’t actually get called. We definitely don’t want that happening now do we :)

The ShouldRaiseEvent method implements checks whether or not the method being called is a property setter and whether the value being set is different from the existing value. This is important because we only want to target properties for interception and we want to ensure we’re not raising the PropertyChanged callback without reason. The RaiseEvent method is where we actually raise the PropertyChanged event. Notice that it actually climbs up the inheritance hierarchy of the current type to find the PropertyChanged event. This saves us the task of implementing INotifyPropertyChanged in every single view model we write. Instead, we can just implement it is in the BaseViewModel (as we have) and rely on the call handler to find the event.

At this point you’re probably wondering how to link up the PropertyChangedCallHandler with our Window1ViewModel. We do this by passing on the responsibility of instantiating the view model to the unity container while telling it to set up the new object for interception with our custom call handler. To do this, we go change the code in Window1’s Loaded event handler from this:

    1: DataContext = new Window1ViewModel();

to this:

    1: var viewModelType = typeof(Window1ViewModel);
    2: var unity = new UnityContainer();
    3: var notificationPolicy = unity.AddNewExtension<Interception>()
    4:         .RegisterType(typeof(BaseViewModel), viewModelType, "Window1")
    5:         .Configure<Interception>()
    6:         .SetDefaultInterceptorFor(viewModelType, new VirtualMethodInterceptor())
    7:         .AddPolicy("NotificationPolicy");
    8:  
    9: notificationPolicy.AddMatchingRule(new PropertyMatchingRule("*", PropertyMatchingOption.Set));
   10: notificationPolicy.AddCallHandler<PropertyChangedCallHandler>();
   11:  
   12: var viewModel = unity.Resolve<BaseViewModel>("Window1");
   13: DataContext = viewModel;

What we’re doing here is:

  1. on line 2, we create a new Unity container
  2. starting on line 3, we tell the container to set up interception for the type Window1ViewModel. We also register the type with a name: Window1. We use a VirtualMethodInterceptor which basically means that all virtual methods (and properties) on objects of this class will be intercepted. We call this interception policy NotificationPolicy and get a reference to it.
  3. We add a matching rule for this policy that basically says we only want to look at properties and we only want to look at property setters. This makes some of the code in PropertyChangedCallHandler.ShouldRaiseEvent redundant but hey, better safe than sorry :)
  4. Finally, we add our PropertyChangedCallHandler to the policy

Basically what we’ve done is tell the unity container that we need it to intercept all objects of type Window1ViewModel and insert the call handler called PropertyChangedCallHandler in some of the method (property setters) calls. This can also be done using application configuration (app.config) and I encourage you to explore that option. The final step of the process is to clean up our view model. We don’t need the OnPropertyChanged call any longer. Here’s what the updated Window1ViewModel ought to look like:

    1: public class Window1ViewModel : BaseViewModel
    2: {
    3:     public virtual bool TestProperty { get; set; }        
    4: }

Not bad huh! Note that we make the property virtual because we used the VirtualMethodInterceptor which is only capable of intercepting virtual properties and methods. Here’s the code for the sample:

The sample application is a simple application where the TestProperty is bound to a CheckBox as well as a TextBlock. When you check the CheckBox the TextBlock value gets updated to True and when you uncheck the CheckBox the TextBlock gets set to False (remember that in WPF, bindings are TwoWay by default). Here’s a couple of screenshots showing that:

image

image 

Have fun. If you need to reach me, use the comment form below. This code (and lots of other useful stuff) is part of the WPF Starter Kit at https://wpfstarterkit.codeplex.com.

Comments

  • Anonymous
    November 13, 2010
    This is the coolest thing I have read in months. I'm looking forward to trying it out -- thanks for posting this!