A lot has been written about some core WPF design patterns - particularly the MVVM (Model-View-ViewModel) pattern and the comprehensive data-binding and data-template support that makes it work so well. However, those patterns by themselves aren’t sufficient to create a full WPF application.
I thought it would be worth looking into other patterns that work together to form one approach to the creation of a comprehensive application - this is the first post in what may become a series.
Updated 23/8: Added some diagrams and updated the wording of the Motivation section to add clarity.
Related Posts
I posted a followup to this post called “WPF Design Patterns: Window Manager”.
Event Aggregator
The event aggregator allows widely disparate parts of the application to communicate with each other in a decoupled fashion. Instead of each component needing to know about the others, they take a simple dependency on a centralised message broker that dispatches messages as required.
Motivation
Imagine your application includes a sidebar that allows for detailed configuration of the current selection - think of the Properties sidebar found in Visual Studio, or the Object Inspector found in Dephi.
When the selection changes in your designer, you want this sidebar to update and show the appropriate detail for the new selection.
As long as you have only one source of selection, you could wire the two components directly together - but things start getting very messy if you have multiple ways for different things to become selected. Wiring all of the components directly to each other becomes burdensome, a kind of combinatorial explosion of tight coupling.
Consider what happens when your application grows - now you have three different kinds of designer, each with a current selection, as well as multiple sidebars for viewing properties, dimensions and the modification history of the selected element.
Instead, the event aggregator pattern allows you to define a simple message - say, SelectionChangedMessage
- that
can be sent by any component changing the selection. The configuration sidebar can subscribe to that message and react
appropriately, reguardless of the message origin. Better yet, adding a new component - whether a selection publisher
or a selection consumer, requires adding just one new link.
Show me the code
Here’s a simple IEventAggregator
interface that I’ve used to good effect in a bunch of different applications:
The EventAggregator
implementation (shown at the end of this post) is always configured as a singleton through a
dependency injection framework and provided to the various components through a parameter of their constructor.
For example, the ViewModel
for a sidebar component as described above would hook into the event aggregator for
processing of a SelectionChangedMessage
by using code like the following:
Tip: Message Naming
Applications using an event aggregator often end up with a fairly large set of distinct messages - using a simple
naming convention to ensure that all messages are easily recognised is invaluable. A simple suffix of Message
on all
the classes is direct and easy, though some use the suffix Event
instead, on the basis that the pattern is called
Event Aggregator.
Ensuring the rest of the name is clear is also important. A message called SelectionChangeMessage
is ambiguous - does
it indicate that the selection is being changed, or that the selection has been changed. One approach is to rely on
language tenses - the current tense SelectionChangingMessage
for a selection that is currently being changed and the
past tense SelectionChangedMessage
for afterwards. Another approach is to explicitly prefix messages for clarity -
BeforeSelectionChangeMessage
, OnSelectionChangeMessage
and AfterSelectionChangeMessage
are little prone to being
misunderstood.
Tip: Handler Naming
It is equally important to be clear with the names used for the methods that handle each event.
One effective approach is to base the name of the method on the name of the message, but with a different suffix.
The method SelectionChangedHandler()
is therefore going to be linked to the message SelectionChangedMessage
,
the method SelectionChangingHandler()
the message SelectionChangedMessage
and so on.
As a bonus, using an approach like this makes it possible to easily search for all handlers for a particular message.
Tip: Use a Folder
With anything more than a trivial number of messages, keepting them together in a distinct folder and associated namespace can be useful, as it helps to prevent them from overwhelming more important classes.
Tip: Use Immutable classes
Where possible, use immutable message classes, especially for use with the “fire and forget” Post
method used for
asynchronous delivery of the messages. Given that the order of message delivery is essentially uncontrollable, a message
that gets modified enroute by one consumer might have unwanted side effects that are difficult to debug.
One exception would be for “permission” events that allow event handlers to cancel an operation before it starts.
The classic approach for implementation is to expose a read/write Boolean property Cancelled
which consumers can
set to true. The problem is that any other consumer can set it to false and override the cancellation. A
better approach is to have a read/only IsCancelled
property along with a separate method Cancel()
which prevents
misuse.
More Code
Here is a full implementation of the EventAggregator
class for use in a WPF application. It’s dependent on the
presence of a synchronisation context to work properly - so a simple transplant into non WPF context may not go
smoothly.
A nice side effecct of this implementation is the thread afinity - messages are always delivered
on the UI thread, reguardless of the thread of origin. Using the EventAggregator
to Post
asynchronous UI updates from a background thread doing some bulk processing is both clean and
performant.
Comments
blog comments powered by Disqus