Caliburn Micro Part 4: The Event Aggregator

For those of you who are new to this tutorial series, we have been learning about how to use Caliburn Micro to build WPF application with a robust MVVM architecture. Caliburn Micro is a framework used to help build .NET applications (WPF, Silverlight and Windows Phone 7) using several popular UI patterns including MVVM, MVP and MVC. It provides lots of neat ways to reduce the amount of work you need to do for common tasks such as setting up bindings and hooking up events. The various features of Caliburn Micro make it easy to have a clean line between the model objects and the UI. That means your application is easy to test and maintain. Here are links for the previous blog posts:

Part 1: Getting Started
Part 2: Data Binding and Events
Part 3: More About Events and Parameters

In this week’s tutorial we will be learning about how to use the event aggregator included with Caliburn Micro. The event aggregator is a service that makes it easy for multiple parts of your application to send messages to each other. This is useful when your application is made up of several view-models that need to communicate. To do this, you subscribe objects (such as view models) to the event aggregator and specify what type of message they should listen for. You also define what the object does when it receives such messages. Then, when another part of the application publishes a message, the event aggregator makes sure the appropriate subscribed objects receive it and perform the appropriate action. Throughout this tutorial we will be extending the application we made in the getting started tutorial. (You can download the application here.)

Although the event aggregator is more useful for larger applications that have multiple view-models, we will be keeping our tutorial application rather small. Also be warned that this tutorial has a lot more to digest compared to the previous ones! By the end of this tutorial, we will have an application that displays two views, each with their own view-model. One of the views will be displaying some radio buttons, each representing a different color. When a radio button is clicked, we’ll publish a message that includes the appropriate color. The second view will listen for these messages and change the color of a Rectangle. We will do this in 4 steps: Adding another view to the application, implementing the IHandle<TMessage> interface, subscribe one of the view models to an event aggregator and finally publish events from the other view model.

Step 1: Adding Another View and View-Model

In order to demonstrate the event aggregator, we will need at least 2 view models in our application. We already have one (AppViewModel), so lets start by adding another one. Remember the naming convention described in the getting started tutorial? Add a new class called ColorViewModel, and a UserControl called ColorView. Lets also change the background of the ColorView so that we at least have something to see when we first add it to the application. In terms of the visual structure, we are going to get the existing AppView to contain the new ColorView. (Views do not need to be nested in order to use the event aggregator; a view-model can listen to messages being published from anywhere in the application.) To do this, AppViewModel will need a property of type ColorViewModel which we will set in the constructor like this:

public class AppViewModel : PropertyChangedBase
{
  public AppViewModel(ColorViewModel colorModel)
  {
    ColorModel = colorModel;
  }
 
  public ColorViewModel ColorModel { get; private set; }
}

In AppView.xaml, we will split the grid into 2 columns and display the ColorModelView in the first column. AppView.xaml will now look like this:

<Grid Width="300" Height="300" Background="LightBlue">
  <Grid.ColumnDefinitions>
    <ColumnDefinition Width="*" />
    <ColumnDefinition Width="*" />
  </Grid.ColumnDefinitions>
  <ContentControl Name="ColorModel" Margin="10" />
</Grid>

See what’s going to happen here? We have set the name of the ContentControl to be the same name as the property we just added to AppViewModel. From this, Caliburn Micro will kindly bind the Content property of the ContentControl to the ColorModel property for us. When we run this up later, Caliburn Micro will make sure that an instance of ColorView is displayed for the ColorViewModel.

If we were to run up the application now, we’d run into an exception saying that the default constructor of AppViewModel can’t be found. Hmm, that’s a good point: we have included a constructor on AppViewModel that requires a parameter – a ColorViewModel object. To resolve this, we’ll need to update our AppBootstrapper as seen below. (Make sure to include a reference to System.ComponentModel.Composition.dll to the application.)

using Caliburn.Micro;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.ComponentModel.Composition.Primitives;
 
public class AppBootstrapper : Bootstrapper<AppViewModel>
{
  private CompositionContainer container;
 
  protected override void Configure()
  {
    container = new CompositionContainer(new AggregateCatalog(AssemblySource.Instance.Select(x => new AssemblyCatalog(x)).OfType<ComposablePartCatalog>()));
 
    CompositionBatch batch = new CompositionBatch();
 
    batch.AddExportedValue<IWindowManager>(new WindowManager());
    batch.AddExportedValue<IEventAggregator>(new EventAggregator());
    batch.AddExportedValue(container);
 
    container.Compose(batch);
  }
 
  protected override object GetInstance(Type serviceType, string key)
  {
    string contract = string.IsNullOrEmpty(key) ? AttributedModelServices.GetContractName(serviceType) : key;
    var exports = container.GetExportedValues<object>(contract);
 
    if (exports.Count() > 0)
    {
      return exports.First();
    }
 
    throw new Exception(string.Format("Could not locate any instances of contract {0}.", contract));
  }
}

This is similar to the bootstrappers used in the samples that come with the Caliburn Micro download. So as not to make this blog post too long, I won’t be diving into the details of what this code is doing (search for MEF or Managed Extensibility Framework if you need more details).

Next, we need to include an Export attribute on both our view-model classes. This is for the GetInstance method in the AppBootstrapper to work.

[Export(typeof(AppViewModel))]
public class AppViewModel : PropertyChangedBase
{
  ...
}
 
[Export(typeof(ColorViewModel))]
public class ColorViewModel
{
}

And finally, include the ImportingConstructor attribute on the AppViewModel constructor. This is to indicate that this constructor should be used since there is no default constructor. When Caliburn Micro creates the AppViewModel, it will also create an instance of the ColorViewModel to pass into the constructor for us.

[ImportingConstructor]
public AppViewModel(ColorViewModel colorModel)
{
  ColorModel = colorModel;
}

Now we can run the application and see that the ColorView is successfully being displayed within the AppView:

The application now displays 2 view models

Let’s add a Rectangle into the second column. This will be the Rectangle that changes color when messages are handled, so its color will be controlled by a property on the AppViewModel like this:

private SolidColorBrush _Color;
 
public SolidColorBrush Color
{
  get { return _Color; }
  set
  {
    _Color = value;
    NotifyOfPropertyChange(() => Color);
  }
}
<Rectangle Grid.Column="1" Width="100" Height="100" Fill="{Binding Color}" />

Step 2: Implementing the IHandle<TMessage> Interface

We are going to be publishing messages from the ColorViewModel to be picked up by the AppViewModel. To do this we are going to need to implement a class that holds the message information. such a class is usually very small and simple. It mainly needs to have some properties that hold any information that we want to send. Here is the message class that we are going to use:

public class ColorEvent
{
  public ColorEvent(SolidColorBrush color)
  {
    Color = color;
  }
 
  public SolidColorBrush Color { get; private set; }
}

In order for the AppViewModel to handle the appropriate events, it will need to implement the IHandle<TMessage> interface. In our case, we will be using ColorEvent as the generic type. The IHandle interface has a single method that we need to implement which is called Handle. In the Handle method of our AppViewModel, we will look at the SolidColorBrush sent in the ColorEvent and use it to set the Color property. This will in turn change the color of the Rectangle in the view.

public void Handle(ColorEvent message)
{
  Color = message.Color;
}

Step 3: Subscribe

Now we need to subscribe the AppViewModel to an event aggregator so that it can actually listen to published messages. We do this by adding another parameter to the constructor which will be an IEventAggregator. When the time comes to create the AppViewModel, Caliburn Micro will pass in the event aggregator that we set up in the bootstrapper. Now within the constructor, we simply call the Subscribe method like this:

[ImportingConstructor]
public AppViewModel(ColorViewModel colorModel, IEventAggregator events)
{
  ColorModel = colorModel;
 
  events.Subscribe(this);
}

Step 4: Publish

The ColorViewModel is also going to need the event aggregator so that it can publish messages. Add a constructor to the ColorViewModel which takes an IEventAggregator and stores it in a field. Remember to include the ImportingConstructor attribute:

private readonly IEventAggregator _events;
 
[ImportingConstructor]
public ColorViewModel(IEventAggregator events)
{
  _events = events;
}

Now we just need add the radio buttons to the ColorView, listen to their click event and then publish a message. You may remember from the second tutorial in this series about the quick way to listen to the click event. Simply set the name of the RadioButton to be the same as the method we want to be called. We could of course use event parameters rather than having an action method for every RadioButton, but I have done it like this so you can clearly see what’s going on:

<RadioButton Name="Red" Content="Red" Foreground="White"
             VerticalAlignment="Center" HorizontalAlignment="Center" />
<RadioButton Name="Green" Content="Green" Foreground="White"
             VerticalAlignment="Center" HorizontalAlignment="Center" Grid.Row="1" />
<RadioButton Name="Blue" Content="Blue" Foreground="White"
             VerticalAlignment="Center" HorizontalAlignment="Center" Grid.Row="2" />
public void Red()
{
  _events.Publish(new ColorEvent(new SolidColorBrush(Colors.Red)));
}
 
public void Green()
{
  _events.Publish(new ColorEvent(new SolidColorBrush(Colors.Green)));
}
 
public void Blue()
{
  _events.Publish(new ColorEvent(new SolidColorBrush(Colors.Blue)));
}

And that’s it. Run up the application now and click the radio buttons. See that the AppViewModel is successfully getting the messgaes from the ColorViewModel to change the color of the Rectangle.

Using the event aggregator

One thing to note is that I have been passing around SolidColorBrushes in the messages to set the color of the Rectangle. In general you would pass around more primitive values and then use a converter in the UI to interpret the value as a SolidColorBrush or the like.

The full Visual Studio 2010 project for this tutorial can be downloaded from here. I hope you find the event aggregator useful for orchestrating communication across the various view-models in your application. Check out the documention for more information including how to do polymorphic event subscriptions, custom publication marshaling and unsubscribe from the event aggregator.

See you next time.

Tagged as WPF

13 Responses to “Caliburn Micro Part 4: The Event Aggregator”

  • Hello Jason,

    Another great post about Caliburn. It’s a feature that is not specific to an MVVM framework and can server in many scenarios.

    Good to mention that you can download it separately from nuget at http://nuget.org/packages/Caliburn.Micro.EventAggregator

    A good theoretical explanation can be found at Fowler’s site http://martinfowler.com/eaaDev/EventAggregator.html

    IMO If overused it can decrease the ease of maintainability but the CM approach is strongly typed so that makes it much better.

    Looking forward to more CM posts

  • Will there ever be another, much, informative article about Caliburn.Micro?

    Didrik

  • Jason:

    Many, many thanks for these fantastic posts!

  • How about removing code behind files ColorViewModel.xaml.cs and AppViewModel.xaml.cs?
    These files are obsolete with MVVM approach. Makes for cleaner code.

  • Hello Lubos

    This is a good suggestion. Removing the xaml.cs files removes the temptation of writing code behind. There’s not much point keeping these files if they’re empty.

  • Jason,

    I really appreciate the effort you have put into your Caliburn Micro articles. The content is clear and concise, and I love the crisp layout / font / etc.

    Warmest appreciation, admiration and respect from Scotland.

  • [...] Next let’s take a look at the Event Aggregator. [...]

  • Hi Jason,

    This is a really good series of articles.

    Many thanks for preparing such a good series of tutorials.

    Happily working my way through them just now!

  • How to publish a screen change event
    ———————————————-
    in my application ..contain a common user control ..and 2 other user controls..(welcome screen and login screen)..when executing app…the welcome screen open on commen page(using ContentControl).

    the problem is…

    the welcome screen contain a welcome message and a button…how to open login screen on the same common screen when click the button on welcome screen

    how it will be possible…any idea….

    welcome screen to login creen on same user control…

  • Hello Abdul

    I recommend causing the button on the welcome view to publish an event to be caught by the model of the common view. This would be a similar scenario as described in this blog post. The common view model can have a property that exposes which model is currently being displayed. When the common view model gets the event, it can change the property to be a login view model. The common view could contain a ContentControl that binds to this property which would display the appropriate view when the property changes.

  • AssemblySource does not seem to have a “Select” method defined… Is there something I’m missing here?

  • Hello Doug

    The sample project is still working fine at my end. Are you missing AssemblySource.Instance.Select ?

    Also, keep in mind that the version of Caliburn Micro used in these tutorials is a bit out of date.

    -Jason Fauchelle

  • Looking at the sample project I was able to track down the issue. The sample project includes “using System.Linq”
    I was just working from the tutorial here and since Linq was never mentioned, I had no idea I needed to include it.

  • Leave a Reply

Archives

Join our mailer

You should join our newsletter! Sent monthly:

Back to Top