Thursday, June 10, 2010

Tips and Tricks for INotifyPropertyChanged

As a WPF or Silverlight developer, you know that your models must implement INotifyPropertyChanged and it can be a pain. To do it safely, you really need to check to see if there are any registered handlers, then raise the event. To add insult to injury, the event arguments take a string, so if you mistype the property name you're out of luck. Some clever individuals have created nice code snippets to generate the needed plumbing, but it doesn't help with refactoring.

One common solution is to create a base class that provides the plumbing for a raise property notification.

The first Prism 4.0 drop has a typical example of this, and one that mirrors what I've done in quite a few places. Take a look at some snippets from the base view model designed in the Model-View-ViewModel (MVVM) quick start:

        protected virtual void RaisePropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler handler = this.PropertyChanged;
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        protected void RaisePropertyChanged(params string[] propertyNames)
        {
            foreach (var name in propertyNames)
            {
                this.RaisePropertyChanged(name);
            }
        }

        protected void RaisePropertyChanged<T>(Expression<Func<T>> propertyExpresssion)
        {
            var propertyName = ExtractPropertyName(propertyExpresssion);
            this.RaisePropertyChanged(propertyName);
        }

        private string ExtractPropertyName<T>(Expression<Func<T>> propertyExpresssion)
        {
            if (propertyExpresssion == null)
            {
                throw new ArgumentNullException("propertyExpression");
            }

            var memberExpression = propertyExpresssion.Body as MemberExpression;
            if (memberExpression == null)
            {
                throw new ArgumentException("The expression is not a member access expression.", "propertyExpression");
            }

            var property = memberExpression.Member as PropertyInfo;
            if (property == null)
            {
                throw new ArgumentException("The member access expression does not access a property.", "propertyExpression");
            }

            if (!property.DeclaringType.IsAssignableFrom(this.GetType()))
            {
                throw new ArgumentException("The referenced property belongs to a different type.", "propertyExpression");
            }

            var getMethod = property.GetGetMethod(true);
            if (getMethod == null)
            {
                // this shouldn't happen - the expression would reject the property before reaching this far
                throw new ArgumentException("The referenced property does not have a get method.", "propertyExpression");
            }

            if (getMethod.IsStatic)
            {
                throw new ArgumentException("The referenced property is a static property.", "propertyExpression");
            }

            return memberExpression.Member.Name;
        }
    }

The piece that extracts the expression is key, because it allows you to get away from RaisePropertyChanged("property") and move to the more strongly-typed and refactoring-friendly RaisePropertyChanged(()=>property).

The base class might work well for some, but putting too much functionality in the base class can sometimes become a problem. The main reason is due to the fact that there is not multiple inheritance, therefore if you have multiple functions that are aspects of some classes and not others, you either need to create a grab bag of different derived classes, or drag along the functionality you don't need.

Fortunately, extension methods allow us to have the best of both worlds. We can simply extend the behavior as it is defined, via the INotifyPropertyChanged interface, and then implement it with an extension.

Take a look at the property helper class:

public static class PropertyHelper
{        
    public static string ExtractPropertyName<T>(Expression<Func<T>> propertyExpression)
    {
        if (propertyExpression == null)
        {
            throw new ArgumentNullException("propertyExpression");
        }

        var memberExpression = propertyExpression.Body as MemberExpression;

        if (memberExpression == null)
        {
            throw new ArgumentException("The expression is not a member access expression.", "propertyExpression");
        }

        var property = memberExpression.Member as PropertyInfo;

        if (property == null)
        {
            throw new ArgumentException("The member access expression does not access a property.", "propertyExpression");
        }
            
        return memberExpression.Member.Name;
    }

    public static void RaisePropertyChanged<T>(this INotifyPropertyChanged src, Expression<Func<T>> propertyExpression, PropertyChangedEventHandler handler)
    {
        if (handler != null)
        {
            handler(src, new PropertyChangedEventArgs(ExtractPropertyName(propertyExpression)));
        }
    }
}

Notice how we extend the interface and simply take in the property expression and the handler. Now, I can raise the property like this:

private string _myProperty;

public string MyProperty 
{
   get 
   {
      return _myProperty;
   }

   set 
   {
      _myProperty = value;
      this.RaisePropertyChanged(()=>MyProperty, PropertyChangedHandler); 
   }
}

Of course, you don't even have to make it an extension method at all - it could simply be a helper you call directly.

There is only one problem with this implementation, that I would be remiss if I didn't call out. In a typical system you're probably fine, but you are taking something internal to the class (the change handler) and sending it out in the wild. Something outside of your class could then manipulate or hook into the handler (as we are doing, on purpose, for the property event) and cause unexpected behaviors and results. If you can live with that caveat and it helps, then here is one more way to handle the pattern.

Bonus Round: Observable Enumerables

Oh, one more thing. Have you found you often bind things like charts, lists, and grids to lists where the entire list changes? In other words, you are never adding or removing individual items, but instead clearing your ObservableCollection and re-adding them? Depending on how you do that, it can be a costly operation as the CollectionChanged will fire for each item. But ... assigning a new list will suddenly kill your data-binding, because the controls lose their reference to the list (the original binding).

Solution?

Expose an IEnumerable<T> that contains whatever logic you need to build the list - whether it is the return from a service call, spinning up a new list, etc. Then, whenever you have a condition that changes the list (i.e. a new search term, filter property, refresh command, etc) simply raise the property changed event for that list.

It will end up looking something like this:

public class MyViewModel : INotifyPropertyChanged 
{
   public IParameters Parameters { get; set; }

   public IService Service { get; set; }

   private List<Widget> _widgets; 

   public MyViewModel()
   {
      // this code may go somewhere else depending on how you wire up dependencies
      Parameters.PropertyChanged += (o,e) => _NewQuery;
      Service.QueryAsyncCompleted += (o,e) => 
      {
         _widgets = e.Result; 
         RaisePropertyChanged(()=>TopFiveQuery, PropertyChanged);
         RaisePropertyChanged(()=>Query, PropertyChanged);
      }
   }

   private void _NewQuery()
   {
      Service.QueryAsync(Parameters);
   }
   
   public IEnumerable<Widget> TopFiveQuery
   {
       return (from w in _widgets orderby w.DateModified descending 
               select w).Take(5).AsEnumerable();
   }

   public IEnumerable<Widget> Query
   {
       return _widgets;   
   }

   public event PropertyChangedEventHandler PropertyChanged;
}

Now a control can be bound to the Query directly. Whenever the user changes parameters, it fires a property changed event that calls out to the service. When the service returns, it loads up the results and then fires the property changed for the list, which rebinds the list to the control. The reason I show it this way is you might reuse the data in different lists and have different filters. By exposing the enumerable interface, you can provide multiple queries and have them all update and re-bind on the single service call.

Enjoy!

Jeremy Likness

4 comments:

  1. a lot of "tricks" for not passing the property name involve getting the member name from a StackFrame. this is very expensive. have you done performance testing on the expression parsing and reflection? that's something that might make a difference when deciding to use this type of strategy or not.

    ReplyDelete
  2. Good stuff...

    I've also written these methods. :-) I've more recently moved away from having extension methods that take in Expressions, and keeping them with strings, and using my Member.Of() method to centralize the work of converting an Expression into a string.

    http://jeffhandley.com/archive/2010/04/10/memberof.aspx

    ReplyDelete
  3. Thanks a lot for sharing those tips and tricks. It's great to know how to deal with INotifyPropertyChanged.

    web design Perth

    ReplyDelete
  4. There's a lot of good stuff here, especially on INotifyPropertyChanged.

    web design melbourne

    ReplyDelete