Share via


Using the Event Aggregator Pattern to Communicate Between View Models

Introduction

If you are developing a composite user interface that contains several parts that need to be synchronized with each other, you need to be able to communicate between these different parts of your application.

This can be done using ordinary .NET events or by keeping a direct reference to each subscriber from each publisher class and simply call a method on the subscriber class from the publisher class once you want to publish some information. However, using this approach will result in a tight coupling between publishers and subscribers that makes the application harder to maintain. It could potentially also lead to memory leaks if a publisher of an event lives longer than a subscriber and you forget to, or don’t know when to, unsubscribe from an event.

Event Aggregator Pattern

By introducing an event aggregator in between the publishers and subscribers, you can remove this tight coupling. The subscriber observes the event aggregator instead of the publisher and the publisher knows only about the event aggregator and not about the subscribers.

http://magnusmontin.files.wordpress.com/2014/02/eventaggregator.png?w=515&h=155

In order to implement this pattern, which is also sometimes referred to as the message bus pattern, both the publishers and the subscribers need a reference to a class that represents the event aggregator. The responsibility of this class is simply to aggregate events. Publishers call publication methods of the event aggregator class to notify subscribers while subscribers call subscription methods to receive notifications.

Although you can implement your own event aggregator class, there is often little reason do to so as most MVVM frameworks out there has an event aggregator or message bus built in.

Prism

Prism – the official guidance for building composite XAML based applications from the Microsoft Patterns and Practices Team – for example ships with a Microsoft.Practices.Prism.Events.EventAggregator class that implements the following interface:

public interface  IEventAggregator
{
    // Summary:
    //     Gets an instance of an event type.
    //
    // Type parameters:
    //   TEventType:
    //     The type of event to get.
    //
    // Returns:
    //     An instance of an event object of type TEventType.
    TEventType GetEvent<TEventType>() where TEventType : EventBase, new();
}

To be able to pass an event from a publisher to a subscriber using Prism’s event aggregator, the first thing you need to do is to define the actual type of the event to be passed. An event object is a class that inherits from the abstract Microsoft.Practices.Prism.Events.EventBase class. Prism comes with a Microsoft.Practices.Prism.Events.CompositePresentationEvent<TPayload> class that inherits from this one, where the TPayload type parameter specifies the type of argument that will be passed to the subscribers.

If you for example were to define a class for an event that passes an instance of the Item class shown below between a publisher and subscriber, it could be implemented something like this:

public class  ItemSelectedEvent : CompositePresentationEvent<Item>
{
}
 
public class  Item
{
    public int  Id { get; set; }
    public string  Name { get; set; }
    public double  Price { get; set; }
    public int  Quantity { get; set; }
}

Now, let’s look a simple code example that uses the above event. Assume that you have a view that lists some items on the left hand side of a screen and another separate view that uses the above event that displays the details of the currently selected item on the right hand side of the same screen. The sample markup code and image in this article is from a WPF desktop application but the same pattern and principles applies to all XAML technologies including WPF, Silverlight, Windows Store Apps and Windows Phone.

http://magnusmontin.files.wordpress.com/2014/02/details1.png?w=525&h=123

<!-- The main window -->
<Window x:Class="Mm.EventAggregator.Prism.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:Mm.EventAggregator.Prism"
        Title="blog.magnusmontin.net" Height="350" Width="525">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <local:ListView Grid.Column="0"/>
        <local:DetailsView Grid.Column="1"/>
    </Grid>
</Window>
 
<!-- The list view -->
<UserControl x:Class="Mm.EventAggregator.Prism.ListView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:local="clr-namespace:Mm.EventAggregator.Prism"
             mc:Ignorable="d"
             d:DesignHeight="300" d:DesignWidth="300">
    <Grid>
        <ListBox ItemsSource="{Binding Items}"
                 SelectedItem="{Binding SelectedItem}">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Name}"/>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </Grid>
</UserControl>
 
<!-- The details view -->
<UserControl x:Class="Mm.EventAggregator.Prism.DetailsView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             mc:Ignorable="d"
             d:DesignHeight="300" d:DesignWidth="300">
    <Grid>
        <ContentControl Content="{Binding Item}">
            <ContentControl.ContentTemplate>
                <DataTemplate>
                    <Grid HorizontalAlignment="Left" VerticalAlignment="Top">
                        <Grid.RowDefinitions>
                            <RowDefinition/>
                            <RowDefinition/>
                            <RowDefinition/>
                            <RowDefinition/>
                        </Grid.RowDefinitions>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition/>
                            <ColumnDefinition/>
                        </Grid.ColumnDefinitions>
                        <TextBlock Text="Id: " FontWeight="Bold" Grid.Row="0"/>
                        <TextBlock Text="Name: " FontWeight="Bold" Grid.Row="1"/>
                        <TextBlock Text="Price: " FontWeight="Bold" Grid.Row="2"/>
                        <TextBlock Text="Quantity: " FontWeight="Bold" Grid.Row="3"/>
                        <TextBlock Text="{Binding Id}" Grid.Row="0" Grid.Column="1"/>
                        <TextBlock Text="{Binding Name}" Grid.Row="1" Grid.Column="1"/>
                        <TextBlock Text="{Binding Price, StringFormat=c2}" Grid.Row="2" Grid.Column="1"/>
                        <TextBlock Text="{Binding Quantity}" Grid.Row="3" Grid.Column="1"/>
                    </Grid>
                </DataTemplate>
            </ContentControl.ContentTemplate>
        </ContentControl>
    </Grid>
</UserControl>

Note that the two separate views each have their own separate view model and don’t know anything about each other. When the user selects an item in the list, the ListViewModel class – the DataContext of the ListView view – below uses the event aggregator to publish, or raise, an event that carries the newly selected Item object as payload:

public class  ListViewModel
{
    protected readonly  IEventAggregator _eventAggregator;
    public ListViewModel(IEventAggregator eventAggregator)
    {
        this._eventAggregator = eventAggregator;
        this.Items = new  List<Item>();
        this.Items.Add(new Item { Id = 1, Name = "Item A", Price = 100.00, Quantity = 250 });
        this.Items.Add(new Item { Id = 2, Name = "Item B", Price = 150.00, Quantity = 150 });
        this.Items.Add(new Item { Id = 2, Name = "Item C", Price = 300.00, Quantity = 100 });
    }
 
    public List<Item> Items { get; private  set; }
 
    private Item _selectedItem;
    public Item SelectedItem
    {
        get { return _selectedItem; }
        set
        {
            _selectedItem = value;
 
            //Publish event:
            _eventAggregator.GetEvent<ItemSelectedEvent>().Publish(_selectedItem);
        }
    }
}

Any other view model or class in the application can now choose to subscribe to this event by simply calling the Subscribe method for the same event and passing in a callback action to be executed when an event of this type is received. This is exactly what the DetailsViewModel – the DataContext of the details view – class below does. The Action<Item> that is passed in as a parameter to the Subscribe method sets the Item property of the view model to new Item object that is being passed with the ItemSelectedEvent from the ListViewModel:

public class  DetailsViewModel : INotifyPropertyChanged
{
    protected readonly  IEventAggregator _eventAggregator;
    public DetailsViewModel(IEventAggregator eventAggregator)
    {
        this._eventAggregator = eventAggregator;
        this._eventAggregator.GetEvent<ItemSelectedEvent>()
            .Subscribe((item) => { this.Item = item; });
    }
 
    private Item _item;
    public Item Item
    {
        get { return _item; }
        set { _item = value; NotifyPropertyChanged(); }
    }
 
    public event  PropertyChangedEventHandler PropertyChanged;
    protected void  NotifyPropertyChanged([CallerMemberName] string propertyName = "")
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new  PropertyChangedEventArgs(propertyName));
        }
    }
}

There are also overloads of the Subscribe method that lets you specify on which thread you want to receive the events and supply a delegate that filters the event before the registered handler is called. See the “Event Aggregator” link below for more information about this.

Note that the DetailsViewModel class implements the INotifyPropertyChanged interface and raises its PropertyChangedEvent once the Item property gets in order for the details of the newly selected item to be automatically reflected in the details view.

Also note that the publisher (ListViewModel) and the subscriber (DetailsViewModel) must call the Publish and Subscribe methods of the very same EventAggregator object respectively for the communication between them to work. You will typically only have a single instance of an event aggregator per application. In a real-world composite application, an IoC container, such as for example Microsoft’s Unity, is often used to create and resolve the instance of this one.

If you want to be able to compile and run the sample code that is presented in this article without using an IoC container, you can use the following ApplicationService singleton class that has a property which returns an EventAggregator object to be used by all view models:

using Prism = Microsoft.Practices.Prism.Events;
internal sealed  class ApplicationService
{
    private ApplicationService() { }
 
    private static  readonly ApplicationService _instance =  new  ApplicationService();
 
    internal static  ApplicationService Instance { get { return _instance; } }
 
    private Prism.IEventAggregator _eventAggregator;
    internal Prism.IEventAggregator EventAggregator
    {
        get
        {
            if (_eventAggregator == null)
                _eventAggregator = new  Prism.EventAggregator();
 
            return _eventAggregator;
        }
    }
}

Using it to get a reference to the event aggregator when creating the view model classes is straight forward:

public partial  class ListView : UserControl
{
    public ListView()
    {
        InitializeComponent();
        this.DataContext = new  ListViewModel(ApplicationService.Instance.EventAggregator);
    }
}
 
public partial  class DetailsView : UserControl
{
    public DetailsView()
    {
        InitializeComponent();
        this.DataContext = new  DetailsViewModel(ApplicationService.Instance.EventAggregator);
    }
}

Summary

To summarize, using an event aggregator to communicate between components in an application is a good choice when you want to reduce the complexity that comes with registering and subscribing to multiple events from a lot of different client or subscriber classes. It can greatly simplify memory management and decrease dependencies between your classes and modules.

The decoupling between publishers and subscribers of events enables you to easily add new classes or modules that can respond to events without changing a single line of code in the publisher classes.

See also

Prism: http://msdn.microsoft.com/en-us/library/ff648465.aspx

Download Prism: http://compositewpf.codeplex.com/

Event Aggregator http://msdn.microsoft.com/en-us/library/ff921122.aspx

IOC Containers and MVVM http://msdn.microsoft.com/en-us/magazine/jj991965.aspx

Setting Up the Unity Container http://msdn.microsoft.com/en-us/library/ff648211.aspx