Login | Register   
RSS Feed
Download our iPhone app
Browse DevX
Sign up for e-mail newsletters from DevX

By submitting your information, you agree that devx.com may send you DevX offers via email, phone and text message, as well as email offers about other products and services that DevX believes may be of interest to you. DevX will process your information in accordance with the Quinstreet Privacy Policy.


Build Composite WPF Applications : Page 4

Build loosely-coupled, maintainable WPF applications using the Composite Application Guidance patterns and practices.




Application Security Testing: An Integral Part of DevOps

Composite Commands
Routed commands in WPF are very powerful and useful, but they have some shortcomings when applied to a composite application. The first is that they are entirely coupled to the visual tree—the invoker has to be part of the visual tree, and the command binding has to be tied in through the visual tree. This is because routed events are actually used under the covers to deliver command messages. The second shortcoming is that they are tightly tied in with the focus tree of the UI. You can overcome this with command targets, but that tends to be a fragile and tightly coupled solution as well. Finally, when invoking a routed command, only one command handler will actually be called, even if there is more than one in the focus tree.

As a result, if you want your command handlers to be in your presenters or view models, and you want to be able to address scenarios like a "Save All" command that will potentially have multiple handlers, you need something that goes above and beyond routed commands. Composite WPF commands do just that. CAL contains definitions for two types of commands that supplement the capabilities of routed commands: DelegateCommand and CompositeCommand. Both are based on the same ICommand interface as routed commands in WPF, but they provide the flexibility to hookup handlers in places other than the visual tree, are not tied into the focus in the UI at all, and allow multiple handlers for a single command.

ICommand defines three members:

  • CanExecute: Called by command invokers to determine if they should enable or disable their associated UI (i.e., menu item or toolbar button).
  • Executed: Called when the command is invoked.
  • CanExecuteChanged: Event fired when the state represented by CanExecute changes. This causes command invokers to re-query CanExecute to get the current enabled state of the command.
DelegateCommand is designed to target a single handler in a ViewModel, presenter, or controller (or wherever you want to put it, usually outside the visual tree). DelegateCommand lets you point (through a delegate) to the methods on some object, such as a ViewModel, that should be called for the CanExecute and Executed methods of the ICommand interface.

CompositeCommand acts as a container for a collection of command references to other commands. CompositeCommand calls CanExecute for each of the child commands, and only declares itself to be enabled when all the child commands are enabled. For example when you invoke CompositeCommand(Executed), it calls Executed on each of the child commands.

The EditAppointmentCommand hooked up to the context menu in Listing 2 is a CompositeCommand declared in the Appointment.Infrastructure shared library as a static object. This mimics the way WPF routed commands are usually defined, allowing them to be hooked up easily in any XAML declaration of a UI. The EditingController class in the Appointment.Modules.Editors module library declares a DelegateCommand named m_EditCommand hooked up in the constructor to a handling method within that controller. After hooking up the DelegateCommand to its target-handling method, the constructor adds the command to the EditAppointmentCommand CompositeCommand through the RegisterCommand method. Note that the command handling hookup can be strongly typed based on the expected command parameter type, even though invokers in WPF are all based on objects with no type safety:

// C# public class EditingController : IEditingController { DelegateCommand<AppointmentItem> m_EditCommand; public EditingController(...) { ... m_EditCommand = new DelegateCommand<AppointmentItem> (OnEditAppt); AppointmentCommands.EditAppointmentCommand .RegisterCommand(m_EditCommand); } void OnEditAppt(AppointmentItem item) { ... } ' VB Public Class EditingController Implements IEditingController Private m_EditCommand As _ DelegateCommand(Of AppointmentItem) Public Sub New(...) ... m_EditCommand = New DelegateCommand( _ Of AppointmentItem)(AddressOf OnEditAppt) AppointmentCommands.EditAppointmentCommand. _ RegisterCommand(m_EditCommand) End Sub Sub OnEditAppt(item As AppointmentItem) ... End Sub

Just like with WPF routed commands, if you specify only a handling method for the Executed method, then CanExecute is implicitly true always for that command.

Even though in this example there is only one child DelegateCommand added to the CompositeCommand, in general it supports adding as many child commands as your scenario dictates. You might also sometimes just declare a DelegateCommand and expose it directly to a UI for hookup. You can see an example in the AppointmentEditViewModel class, which contains the following commands exposed as properties:

// C# public class AppointmentEditViewModel : INotifyPropertyChanged { public AppointmentEditViewModel( ... ) { ... SaveCommand = new DelegateCommand<object>(OnSave,OnCanSave); CancelCommand = new DelegateCommand<object>(OnCancel); } public DelegateCommand<object> SaveCommand { get; set; } public DelegateCommand<object> CancelCommand { get; set; } ' VB Public Class AppointmentEditViewModel Implements INotifyPropertyChanged Public Sub New(...) SaveCommand = New DelegateCommand( _ Of Object)(AddressOf OnSave, _ AddressOf OnCanSave) CancelCommand = New DelegateCommand( _ Of Object)(AddressOf OnCancel) End Sub Public Property SaveCommand() As _ DelegateCommand(Of Object) ... End Property Public Property CancelCommand() As _ DelegateCommand(Of Object)... End Property

Now the UI elements in the corresponding view can hook up to those commands using data binding:

<Button Content="Save" Command="{Binding SaveCommand}" ... /> <Button Content="Cancel" Command="{Binding CancelCommand}" .../>

Composite Events
One key feature of CAL is the composite events mechanism. WPF routed events work great for allowing UI elements to flow events to other UI elements. However, in a composite application, the publisher of an event is often a ViewModel, controller, or other behind-the-scenes code. That's true with subscribers as well. You don't want to have to have direct object references between the publisher and subscriber, because those may be classes coming from two different modules that know nothing about each other. You don't want any lifetime coupling between the publisher and subscriber either. As a result, neither normal .NET events nor WPF routed events are sufficient to address the requirements for loosely-coupled communications between modules in composite applications.

CAL includes a composite events mechanism based on the pub/sub pattern that allows you to achieve all the goals mentioned in the previous paragraph. It works like this. CAL has an event aggregator service that acts as a repository for event objects. You can see in Listing 3 that the AppointmentListViewModel obtains a reference to the IEventAggregator service through its constructor dependency injection. It uses that reference when the SelectionChanged event fires from the view. The OnSelectionChanged event handler uses the event aggregator to get a reference to a type named AppointmentSelectedEvent.

The event objects themselves are a singleton instance of an event class that defined for each discrete event that you want to publish through composite events. The event aggregator service takes care of creating those event instances and hands them out as needed. To make this happen, you define a class that derives from CompositeWpfEvent:

// C# public class AppointmentSelectedEvent : CompositeWpfEvent<AppointmentItem> { } ' VB Public Class AppointmentSelectedEvent Inherits CompositeWpfEvent(Of AppointmentItem) End Class

The event class sets up a relationship between a named event type and the data payload type that the event will carry when it is fired. For the AppointmentSelectedEvent, the payload type is an AppointmentItem. This type is defined in the Appointment.Infrastructure shared class library because it will be used by all modules that want to subscribe or publish this event type. The CompositeWpfEvent base class provides all the functionality for publishing and subscribing to that event type.

In addition to simply publishing and subscribing to the event, the composite events in CAL also support the following choices when you subscribe to an event:

  • Thread Dispatching: You can be notified on the publisher's thread, the UI thread, or an asynchronous notification on a thread from the .NET thread pool.
  • Event Filtering: You can provide a filter criteria, in the form of a lambda expression of a delegate, that allows you to be notified only when your filter criteria is met, based on the contents of the data payload for the event being fired (in other words, notify me only when the appointment subject is Golf and the time is between 2:00 and 6:00 pm).
  • Weak references: By default, the event object will maintain only a weak reference to your subscriber, so that if you do not unsubscribe, the event object will not keep your object alive just for the event subscription. If you want to override this, you can simply set a flag when subscribing.
You can see in Listing 3 that when the view fires the SelectionChanged event, the ViewModel calls GetEvent on the event aggregator, and then calls Publish on the resulting event object, passing the selected AppointmentItem (it picks the first item in the list of selected items forwarded by the event raised by the view).

The subscriber in this case is the EditController class in the Appointment.Modules.Editors module library. It handles the event by activating the associated region when an item is selected. Note that the Editors and Viewers modules have no references to each other, so the publisher and subscriber are completely decoupled.

//C# public EditingController(..., IEventAggregator eventAggregator) { ... eventAggregator.GetEvent<AppointmentSelectedEvent>() .Subscribe(OnAppointmentSelected); } void OnAppointmentSelected(AppointmentItem item) { AppointmentEditViewModel viewModel = GetViewModelForItem(item); if (viewModel != null) { m_EditPanelRegion.Activate(viewModel.View); } } ' VB Public Sub New(..., ByVal eventAggregator _ As IEventAggregator) eventAggregator.GetEvent( _ Of AppointmentSelectedEvent)() _ .Subscribe(AddressOf OnAppointmentSelected) End Sub Private Sub OnAppointmentSelected(ByVal item _ As AppointmentItem) Dim viewModel As AppointmentEditViewModel = _ GetViewModelForItem(item) If viewModel IsNot Nothing Then m_EditPanelRegion.Activate(viewModel.View) End If End Sub

Wrap Up
By this point, your head is probably spinning from all the new concepts that Composite WPF introduces. It may seem like this stuff just makes your application more complex. And when taken at the granular level of just defining a user control for some portion of your screen, it is a bit more complex in terms of the constructs you need to put in place. But it all follows a nice set of patterns that have been widely accepted as a better way of building UI applications, and the end result will be a much more loosely-coupled application with good factoring. After you get over the initial learning curve, these things become second nature to you. The advantage of having things broken out into more pieces and abstractions is that as you are building in new functionality or trying to locate something you write before, you know right where to look because there's a clean separation of concerns. I'd encourage you to dig into the sample application in detail, and then give it a try by building up a small sample application of your own using CAL and the patterns discussed here.

Brian Noyes writes for CoDe Magazine.
Comment and Contribute






(Maximum characters: 1200). You have 1200 characters left.



We have made updates to our Privacy Policy to reflect the implementation of the General Data Protection Regulation.
Thanks for your registration, follow us on our social networks to keep up-to-date