Aspects for User Interface

User interfaces present numerous opportunities to use aspects. Some of these aspects are already covered on other pages. As explained on the multithreading page, both WPF and WinForms are intrinsically single-threaded. Thread dispatching aspects make it easier for worker threads to interact with the GUI. Aspects for security and monitoring are also very common in user interfaces.

However, some aspects are more likely found in WinForms or WPF applications than in any other kind of application.

One of the greatest features of WPF is data binding, by which the user interface is automatically updated whenever a property of the underlying domain object is changed. However, WPF data binding requires the developer to implement interface INotifyPropertyChanged in every domain object. With conventional programming techniques, this requires a lot of boiler-plate code. We will address this issue with the data binding aspects.

This article will then show how to implement an undo-redo system at the level of domain objects. This aspect is useful in applications such as word editors or graphical editors.

In this section:

Data Binding

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 deadly 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 )] 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 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.

Undo-Redo

Many word processing applications or graphical editors allow users to undo and redo changes in the current document. In an MVC architecture, the undo-redo feature can be implemented on the level of the controller; in practice, it may be much easier to record all changes on domain objects fields. When this pattern is implemented using an aspect, it requires no change in original source code, whereas implementing it at controller level would require you to add instructions to each operation.

This article will sketch the design of the undo manager, then describe how to use aspects to bind domain objects to it. Our undo manager (IUndoManager) is simply a component that records changes and can undo and redo them. In our design, a change (IUndoItem) is something that is able to be undone or undone.

public interface IUndoManager { bool CanUndo { get; } void Undo(); bool CanRedo { get; } void Redo(); void Record( IUndoItem item ); }
Listing 3. Semantics of the undo manager.
public interface IUndoItem { void Undo(); void Redo(); }
Listing 4. Semantics of an undoable change.

The originality of aspect-oriented design is that every change to a field is represented by object implemented IUndoItem. Changes to fields can be intercepted by an aspect of type LocationInterceptionAspect; however, this type fo aspect will create a new instance of our aspect class for every field; for this reason, since we want to change the behavior of every field in a given class anyway, we will use a TypeLevelAspect and the advice OnLocationSetValueAdvice so we'll have one aspect instance per target class instead of per target field. There is, however, no difference in functionality as far as our requirements are concerned.

/// <summary> /// Aspect that, when applied to a type, record all changes of instance fields to the /// current <see cref="UndoManager"/>. /// </summary> [Serializable] public sealed class UndoableAttribute : TypeLevelAspect { /// <summary> /// Method intercepting all changes of field values. /// </summary> /// <param name="args">Information about the field being changed.</param> [OnLocationSetValueAdvice] [MulticastPointcut( Targets = MulticastTargets.Field, Attributes = MulticastAttributes.Instance)] public void OnSetValue( LocationInterceptionArgs args ) { // Don't do anything if the field is not changed. object currentValue = args.GetCurrentValue(); if ( args.Value == currentValue ) return; // Proceed with changing the field. args.ProceedSetValue(); // Record changes only when the object is initialized. UndoManager.Current.Record(new UndoItem(args.Value, currentValue, args)); } /// <summary> /// Encapsulates a field change as an <see cref="IUndoItem"/>. /// </summary> private class UndoItem : IUndoItem { private readonly object newValue, oldValue; private readonly LocationInterceptionArgs args; public UndoItem( object newValue, object oldValue, LocationInterceptionArgs args ) { this.newValue = newValue; this.oldValue = oldValue; this.args = args; } public void Redo() { args.Value = newValue; args.ProceedSetValue(); } public void Undo() { args.Value = oldValue; args.ProceedSetValue(); } } }
Listing 4. An aspect that records all changes to SharedInstance fields to the UndoManager.

The idea behind this piece of code is the following: whenever a field is changed, we create an UndoItem to encapsulate the change. Since we initialize the UndoItem with the new and the old value of the field, it is able to undo (set to the old value) or redo (set to the new value) the field change.

We can now apply UndoableAttribute to every class of our domain model or, as demonstrated with the data binding aspect, use aspect inheritance.

Thanks to this aspect, we saved a lot of boiler-plate code without overly compromising runtime performance, since the aspect does not use reflection at run time.

Summary

Recent developements in UI frameworks, such as WPF, make programming user interfaces more declarative while automating many behariors that previously had to be coded by hand. Aspect-oriented programming takes a step in the same direction, enabling you to encapsulate plumbing code into aspects and concentrate instead on what matters - a good interface and the business logic behind it.