I like Caliburn.Micro. I like it a lot. I love the way it’s so small and compact and yet so powerful. Right now we’re using it in a Silverlight 4 project and I have yet to encounter something I really don’t like. Nonetheless….there’s something I felt was a bit more work than I liked: the way the EventAggregator works. Publishing an event is plain and simple: you just call Publish on the EventAggregator and pass it an instance of your event class:
eventAggregator.Publish(new SendStringEvent() { Value = "Hi!" });
If you want to subscribe to an event, you basically you have to implement IHandle<T> where T is the class that represents your event. This is how it’s usually done:
public class MainPageViewModel : IHandle<SendStringEvent>
{
public void Handle(SendStringEvent message)
{
StringValue = "Received: " + ev.Value;
}
Not too bad if you have just one or a few eventhandlers in your ViewModel. But your class declaration gets more cluttered with all IHandle<T> declarations if you have more than a few. I didn’t like that. I wanted to be able to add a lambda expression in the Subscribe method like this:
eventAggregator.Subscribe<SendStringEvent>(
ev => StringValue = "Received: " + ev.Value);
The good news is is that I have found a real easy way to do that. Just three very simple classes are needed. First, I created a MessageHandler<T> that implements IHandle<T>. The constructor of MessageHandler<T> takes an Action<T> as an argument:
public class MessageHandler<T>: IHandle<T>
{
private readonly Action<T> _messageHandler;
public MessageHandler( Action<T> messageHandler )
{
_messageHandler = messageHandler;
}
public void Handle(T message)
{
if (_messageHandler != null)
_messageHandler(message);
}
}
So, now I’m able to Subscribe to the event like this:
eventAggregator.Subscribe<SendStringEvent>(
new MessageHandler<SendStringEvent>(
ev => StringValue = "Received: " + ev.Value) );
Already better because I don’t have to implement IHandle<T> in my ViewModel anymore. But a bit more awkward to use than the original implementation. So I created a extension method on EventAggregator that made subscribing easier.
public static class EventAggregatorExtension
{
public static void Subscribe<T>( this EventAggregator eventAggregator,
Action<T> eventHandler )
{
eventAggregator.Subscribe(new MessageHandler<T>(eventHandler));
}
}
And we’re done! Now we can subscribe to events like this:
eventAggregator.Subscribe<SendStringEvent>(
ev => StringValue = "Received: " + ev.Value);
But wait…we’re not done. Because Caliburn Micro is using a list of WeakRefereces, the Garbage Collector will collect our MessageHandler objects! So message handlers will no longer be called after a garbage collection. So the ViewModel needs to maintain references to every MessageHandler<T> that it creates. In order to do that I created a MessageHandlerList. The ViewModel holds a reference to the MessageHandlerList and that holds references to each MessageHandler<T> that is created:
public class MessageHandlerList
{
private List<object> list = new List<object>();
private readonly EventAggregator _aggregator;
public MessageHandlerList(EventAggregator aggregator)
{
_aggregator = aggregator;
}
public void Subscribe<T>(Action<T> eventHandler)
{
list.Add(_aggregator.Subscribe<T>(eventHandler));
}
public void Publish<T>(T message)
{
_aggregator.Publish<T>(message);
}
}
So when we want to subscribe to multiple Messages, you first create a new MessageHandlerList and pass it an existing (or new) EventAggregator in the constructor. And then call Subscribe and Publish on the MessageHandlerList. Subscribing to Messages should be done through the MessageHandlerList, but Publishing messages can be done through the MessageHandlerList or the normal way using the EventAggregator directly:
MessageHandlerList events = new MessageHandlerList(new EventAggregator());
events.Subscribe<SendStringEvent>(ev => button2.Content += ev.Value);
events.Publish(new SendStringEvent() { Value = "Hi!" });