Saturday, December 25, 2010

WPF - EventAggregator in PRISM 4.0 (CAL)

Since the last few posts, I have started discussing about different signaling and messaging options available for decoupled components. This is a continuation of this series of posts. Prism has been a priority option for big scale WPF / Silverlight projects. Microsoft keeps updating it with new technology features introduced in the framework. In november this year, Version 4.0 is released for the product. It has a big feature set of developing rich applications on both desktop / web platforms.

Although there are a number of components available with the framework. We can pick and choose the components we want. We have seen people calling the adoption of PRISM is difficult. It is difficult because there is a certain prerequisite knowledge required to choose components of PRISM or using PRISM in totality. You have to have knowledge about Commands, Message Bus, MVVM, Unity, MEF in order to get started with the framework. This becomes a little easier by providing this opt-in model for features.

It must be remembered that PRISM is designed for large applications with lots of different teams working on different modules decoupled from each other. Microsoft has never claimed it to be used for small scale applications. When you start using PRISM, you have this feel of developing large scale applications the right way as it seems to be scalable.

In this post, we are discussing EventAggregator feature of Prism. I don't know who coined this term. But the first article that I see about this topic is by Martin Fowler [http://martinfowler.com/eaaDev/EventAggregator.html]. This article still shows up as Work In Progress.

When we see the name of event associated with this. We think about memory leaks. We This is generally not true with EventAggregator as it uses WeakReference by default for messages publishing and subscription.

You can download Prism from here:
http://compositewpf.codeplex.com/

To use EventAggregator feature from Prism 4.0, we just need to add the reference of Microsoft.Practices.Prism library. You can find it in the above download location.



We just need to use EventAggregator and CompositePresentationEvent from this library. Both are available in Microsoft.Practices.Prism.Events namespace.

Let us create a separate Services class so that the same Message Bus is available across the application. As you can see you can define a number of Message Buses in an application for intra / inter - module message passing.

class Services
{
public static EventAggregator MessageBus { get; private set; }

static Services()
{
MessageBus = new EventAggregator();
}
}

Now we define the messages that we are wishing to pass using Message Bus. All the interested parties can subscribe / publish events by specifying the exact type. This makes the design robust as there are no run-time surprises for different modules. The messages are inherited from CompositePresentationEvent generic type.

public class ServiceShutDown : CompositePresentationEvent<ShutDownDetails> { }

As seen above CompositePresentationEvent is a generic type. The definition of ShutdownDetails is as follows:
public class ShutDownDetails
{
public string ShutDownMessage { get; set; }
}

This is all the plumbing code we need to use EventAggregator in our applications. Now we can publish and subscribe ServiceShutDown events from anywhere in the application.

Let us define our MainWindow. It just has two buttons to open two separated Window.

<Window x:Class="WPF_Prism_EventAggregator_Example.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="266" Width="359">
<Grid>
<Button Content="Open Another Window" Height="56" HorizontalAlignment="Left"
Margin="12,61,0,0" Name="button1" VerticalAlignment="Top" Width="313" Click="button1_Click" />
<Button Content="Open Shutdown Window" Height="55" HorizontalAlignment="Left" Margin="12,133,0,0"
Name="button2" VerticalAlignment="Top" Width="311" Click="button2_Click" />
</Grid>
</Window>

The code behind of the above window is as follows:

public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();

Services.MessageBus.GetEvent<ServiceShutDown>().Subscribe((details) =>
{
MessageBox.Show(details.ShutDownMessage);
this.IsEnabled = false;
});
}

private void button1_Click(object sender, RoutedEventArgs e)
{
new AnotherWindow().Show();
}

private void button2_Click(object sender, RoutedEventArgs e)
{
new WindowShutDown().Show();
}
}

In the constructor, we are registering with the MessageBus for ServiceShutDown event. This allows us to define a callback. Here the callback is defined as a lambda. We are just showing the shut down message available in the event and disabling the Window.

There are two more windows used in the above MainWindow.

AnotherWindow:

<Window x:Class="WPF_Prism_EventAggregator_Example.AnotherWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="537">
<Grid>

</Grid>
</Window>

This has no controls in it. The main piece of interest is the code behind. Here we are subscribing to the same shut down event. We are just closing the Window as we receive this message.

public partial class AnotherWindow : Window
{
public AnotherWindow()
{
InitializeComponent();

Services.MessageBus.GetEvent<ServiceShutDown>().Subscribe((details) =>
{
this.Close();
});
}
}

WindowShutDown:

<Window x:Class="WPF_Prism_EventAggregator_Example.WindowShutDown"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="WindowShutDown" Height="300" Width="300">
<Grid>
<Button Content="Shut Down" Height="59" HorizontalAlignment="Left"
Margin="22,88,0,0" Name="button1" VerticalAlignment="Top" Width="237" Click="button1_Click" />
</Grid>
</Window>

As we click the button, we send the shut down message and closes itself. The code behind is as follows:

public partial class WindowShutDown : Window
{
public WindowShutDown()
{
InitializeComponent();
}

private void button1_Click(object sender, RoutedEventArgs e)
{
Services.MessageBus.GetEvent<ServiceShutDown>().Publish(new ShutDownDetails()
{
ShutDownMessage = "Disconnected..."
});
this.Close();
}
}

Now we run the application and click both buttons on the Main window. This opens the corresponding windows.



As we click the Shut Down button on the WindowShutdown, shut down message is published on the Message Bus (EvenAggregator). Since MainWindow and AnotherWindow is subscribed to the Message, they receive this event and do the needful.

The following message box is displayed from the MainWindow.



After user clicks OK on the MessageBox, MainWindow is disabled.



Threading Options for EventAggregator messages
PRISM allows us to subscribe to messages. It allows that these messages are received in Publisher's or just any background thread. We can also specify that messages are received in UIThread. If we do it like that, we don't need to dispatch messages to the UI thread.



Weak Reference Vs Live Reference:
By default the EventAggregator events are subscribed with weak reference. EventAggregator also supports subscribing with LiveReference, we just have to use an overload of Subscribe method and specify true for this parameter.

Services.MessageBus.GetEvent<ServiceShutDown>().Subscribe((details) =>
{
MessageBox.Show(details.ShutDownMessage);
this.IsEnabled = false;
}, true);

Please don't forget to unsubscribe from the Message when you are done with it, in order to avoid any memory leaks in your system.

Filtered Messages:
Some messages are very generalized. Message might have various fields. We can subscribe to an event but only interested in receiving messages which fulfills a certain criteria. We can specify this criterion by providing predicates when these events are subscribed.

2 comments:

Edward said...

Thank you so much for posting this. It has helped immensely.

Raathigesh said...

Thank You so much. Simple and clear.