Data Binding

WPF data binding made a big step toward declarative programming but still requires too much plumbing code on domain objects.

One of the most exciting features of WPF is data binding, which propagates to the user interface any change you make to domain objects. For this to work, domain objects must implement the interface INotifyPropertyChanged.

Unfortunately, it's not just about implementing the INotifyPropertyChanged interface; it's also about modifying every property setter. It's dead simple and deadly boring. And gets you more than one step away from the idea you have of aesthetical code.

The implementation pattern

Before starting to code an implementation pattern into an aspect, it's good to summarize what's inside the pattern for a given class (say C):

  1. Unless a parent class of C already implements the pattern:
    • Introduce the interface INotifyPropertyChanged into C and define the event PropertyChanged, part of the interface.
    • Define a protected virtual method OnPropertyChanged; this method raises the event PropertyChanged if the event has any client.
  2. Modify every property setter so that it invokes the OnPropertyChanged method after the property value has been changed (and only if the new value is different from the old one).
  3. Apply the pattern to all children classes of C.

Encapsulating the implementation pattern as an aspect

Here's the full code of the aspect.

/// <summary> /// Aspect that, when apply on a class, fully implements the interface /// <see cref="INotifyPropertyChanged"/> into that class, and overrides all properties to /// that they raise the event <see cref="INotifyPropertyChanged.PropertyChanged"/>. /// </summary> [Serializable] [IntroduceInterface( typeof(INotifyPropertyChanged), OverrideAction = InterfaceOverrideAction.Ignore )] [MulticastAttributeUsage( MulticastTargets.Class, Inheritance = MulticastInheritance.Strict )] public sealed class NotifyPropertyChangedAttribute : InstanceLevelAspect, INotifyPropertyChanged { /// <summary> /// Field bound at runtime to a delegate of the method <c>OnPropertyChanged</c>. /// </summary> [ImportMember( "OnPropertyChanged", IsRequired = false)] public Action<string> OnPropertyChangedMethod; /// <summary> /// Method introduced in the target type (unless it is already present); /// raises the <see cref="PropertyChanged"/> event. /// </summary> /// <param name="propertyName">Name of the property.</param> [IntroduceMember( Visibility = Visibility.Family, IsVirtual = true, OverrideAction = MemberOverrideAction.Ignore )] public void OnPropertyChanged( string propertyName ) { if ( this.PropertyChanged != null ) { this.PropertyChanged( this.Instance, new PropertyChangedEventArgs( propertyName ) ); } } /// <summary> /// Event introduced in the target type (unless it is already present); /// raised whenever a property has changed. /// </summary> [IntroduceMember( OverrideAction = MemberOverrideAction.Ignore )] public event PropertyChangedEventHandler PropertyChanged; /// <summary> /// Method intercepting any call to a property setter. /// </summary> /// <param name="args">Aspect arguments.</param> [OnLocationSetValueAdvice, MulticastPointcut( Targets = MulticastTargets.Property, Attributes = MulticastAttributes.Instance)] public void OnPropertySet( LocationInterceptionArgs args ) { // Don't go further if the new value is equal to the old one. // (Possibly use object.Equals here). if ( args.Value == args.GetCurrentValue() ) return; // Actually sets the value. args.ProceedSetValue(); // Invoke method OnPropertyChanged (our, the base one, or the overridden one). this.OnPropertyChangedMethod.Invoke( args.Location.Name ); } }
Listing 1. Aspect encapsulating the complete 'INotifyPropertyChanged' implementation pattern.

As you can see, the code of the aspect directly follows the steps of the implementation pattern.

Let's have a look at what the aspect adds to its target classes:

  • The custom attribute IntroduceInterface present on the top of the class means that the aspect adds the interface INotifyPropertyChanged (unless it is already implemented); this interface must be implemented by the aspect itself.
  • The custom attribute IntroduceMember, on method OnPropertyChanged and event PropertyChanged, means that these members must be added to the class.

The second requirement, to modify each property setter, is implemented by the method OnPropertySet. The custom attribute OnLocationSetValueAdvice means that this method can intercept any attempt to set the value of a property or field (such a method altering the behavior of other methods is called an advice); MulticastPointcut specifies that this advice should me applied to all property setters.

What should we do in method OnPropertySet? First, we have to compare the new value with the old one. We can retrieve the old value by calling args.GetCurrentValue(); the new value is in args.Value. Then, we have to proceed with the normal property setter. When it is done, we can invoke the method OnPropertyChanged. And here's the issue: how do we know which method override we have to invoke? We can't just invoke the method we're intending to introduce into the type. What if the method has been defined in a parent type? What if it has been overriden in a child type?

That's why we have to import the method OnPropertyChanged from the target class into the current aspect. This is done by defining a public field, here named OnPropertyChangedMethod, whose type is a delegate of the same signature of the method to be imported, and annotate this field with a ImportMember custom attribute. It tells the weaver that the field should be initialized with a delegate of the right method at runtime.

Finally, let's look at our last requirements: when the aspect is applied to a type, the same aspect must be applied to all children of this type. This is done by annotating the aspect type with the custom attribute MulticastAttributeUsage, with its property Inheritance set to MulticastInheritance.Strict. So now, you have to apply the custom attribute NotifyPropertyChangedAttribute only on the root classes of your domain classes.

That's all. We can now use the aspect in our business code:

[NotifyPropertyChanged] public class Shape { public double X { get; set; } public double Y { get; set; } } public class Rectangle : Shape { public double Width { get; set; } public double Height { get; set; } }
Listing 2. Example of domain objects implementing INotifyPropertyChanged automatically.