Introduction to messaging with Caliburn.Micro’s EventAggregator

It started innocently enough. I wanted to make the phone vibrate when a countdown timer in my app reached 00:00:00. Hmmmm… What to do… I know, I’ll add an event to the Timer object and fire it at the appropriate time. Then I can create an event handler in my viewmodel and trigger the vibration.

But when I decided to add two more events to the CountdownTimer class, things started to spin out of control. Suddenly I found myself pounding out helper methods and attaching and detatching event handlers in my viewmodel and checking conditions to see which ones should be attached when… Finally my app got to the point where it was just permanently stuck at a breakpoint as some attached event somewhere fired repeatedly, ignoring my efforts to detatch it. Not cool.

That’s when I remembered Caliburn.Micro‘s support for messaging.

What is messaging?

A message is an object that is broadcast throughout your application when something important happens. When using messaging, an object “publishes” a message, which can be just about anything you want it to be. This object bubbles through your application, moving up the object hierarchy. Other objects that are subscribed to the message will receive it and can take action upon receiving it.

One of the major advantages to messaging is the way it can help you decouple classes. Because the message bubbles through the system, your subscribing code doesn’t need to know about the sender—it just listens for the message.

Traditional event handling, on the other hand, tightly couples classes together. In my timer app I was creating references to a specific instance of a concrete class, then assigning a method to be called when an event on that specific object fired:

CountdownTimer myTimer = new CountdownTimer();
myTimer.TimeExpired += OnTimeExpired;

protected void OnTimeExpired(object sender, EventArgs e)
{
    // Complete actions here.
}

Messaging, Caliburn.Micro style

Caliburn.Micro supports messaging through the EventAggregator class. The EventAggregator is a central hub, or bus, for all messages.

Using the EventAggregator is a two-step process. First, you subscribe to the aggregator in your viewmodel and create methods to handle the messages that class will receive. Second, your model classes publish messages to the aggregator when they have something to communicate.

A simple example

Let’s look at a basic app that employs messaging. Here’s what the app will do:

  • On launch, a page displays with a “Pet the Cat” button and an empty textblock that the cat will use to tell us how it’s feeling.
  • The viewmodel subscribes to messages so it can receive the cat’s response.
  • The “Pet the Cat” button triggers an action in the page’s viewmodel, which in turn calls a method on the cat object.
  • The cat object sends a message.
  • The viewmodel receives the message and it updates a property with the cat’s response. Since this property is databound to the view, the view displays the message automatically.

The full source code is available as a Visual Studio project at the end of the post. Also, I’ve simplified the code at some points in this post for clarity’s sake.

Configuring the bootstrapper

Before I can dive into the application’s logic, I need to do some setup in the AppBootstrapper class:

public class AppBootstrapper : PhoneBootstrapper
{
    PhoneContainer container;

    protected override void Configure()
    {
        ...

        container = new PhoneContainer(this);

        container.RegisterSingleton(typeof(EventAggregator), null, typeof(EventAggregator));
        container.RegisterPerRequest(typeof(MainPageViewModel), "MainPageViewModel", typeof(MainPageViewModel));
        container.RegisterPerRequest(typeof(Cat), null, typeof(Cat));

        ...
    }
}

I registered the EventAggregator class as a singleton, which means that Caliburn.Micro will only create a new instance of the class when it is first requested. Subsequent requests for an EventAggregator object will be supplied with the same instance. This ensures that the EventAggregator that the Cat class notifies will be the same one that MainPageViewModel is subscribed to. I also registering the MainPageViewModel and Cat classes to allow Caliburn.Micro to interact with them.

A simple view

The view is about as straightforward as it gets:

<Grid x:Name="ContentGrid">
    <StackPanel>
        <StackPanel>
            <TextBlock Text="The Cat Says: " />
            <TextBlock Name="CatSound" />
        </StackPanel>
        <Button Name="PetTheCat" />
    </StackPanel>
</Grid>

This example illustrates how Caliburn.Micro’s conventional binding system works. Elements such as buttons and text fields are databound automatically by the framework if their names match. It’s simple, and you get it for free with Caliburn.Micro.

Sending the message

The Cat class is responsible for sending the message that will update the UI:

public class Cat
{
    private EventAggregator eventAggregator;

    public string Sound { get; set; }

    public Cat(EventAggregator eventAggregator)
    {
        this.eventAggregator = eventAggregator;
    }

    public void MakeSound()
    {
        Sound = "Meeeeeeooooowww!";
        eventAggregator.Publish<Cat>(this);
    }
}

Notice that the object’s constructor has a parameter of type EventAggregator. This parameter is supplied automatically by Caliburn.Micro when the Cat object is first created. Enabling this dependency injection is the reason why I registered the EventAggregator and Cat classes in the bootstrapper earlier. When the MakeSound() method executes, the Cat object publishes a message of type Cat to the EventAggregator, and the Cat object supplies itself as the message object to be sent.

Tying it all together

The MainPageViewModel class pulls the pieces together.

public class MainPageViewModel : PropertyChangedBase, IHandle<Cat>
{
    private Cat cat;
    public string CatSound { get; set; }

    public MainPageViewModel(EventAggregator eventAggregator, Cat cat)
    {
        eventAggregator.Subscribe(this);
        this.cat = cat;
    }

    public void PetTheCat()
    {
        cat.MakeSound();
    }

    public void Handle(Cat cat)
    {
        CatSound = cat.Sound;
        NotifyOfPropertyChange(() => CatSound);
    }
}

There are several things to highlight here. First, note that I am subscribing to the EventAggregator in the viewmodel’s constructor. This allows the viewmodel to receive messages from other objects.

Second, the class implements the IHandle interface for the type Cat. This interface dictates that I include a method named Handle() that accepts a parameter of whatever type of message I want the viewmdoel to receive. When a message of type Cat is published, the Handle(Cat cat) method executes, assigning the value of cat.Sound to the CatSound property of MainPageViewModel.

Finally, I’m calling NotifyOfPropertyChange() after updating the CatSound property. This tells the view that a databound property was updated so the view can update itself accordingly.

Download the sample code

A complete Visual Studio project that implements the example above is available here. It was built with the February 2011 version of the Window Phone Developer tools. Other than the WP7 tools, there are no external dependencies. The Caliburn.Micro framework code is included in the Framework folder.

About these ads

Leave a comment

Filed under Caliburn.Micro, MVVM, Windows Phone 7

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s