Windows Forms Eventing: Adding Pub/Sub
In my last post, I described the creeping problem I’ve been having with wiring my large Windows Forms application up with EventHandler delegates. In short, the design becomes brittle—it’s hard to write and hard to change. Luckily, the solution is an easy one: Publish/Subscribe.
The Event Aggregator is a singleton. You can either manage this yourself, or you can have your Inversion of Control container do it for you. When an Event Receiver is created, it registers itself with the Event Aggregator, which stores a reference to the receiver. When a Event Sender sends a message to the Event Aggregator, the aggregator loops through all the registered receivers and calls a method on them to handle the event. This is facilitated by having the receivers implementing an interface. Here’s a simple implementation:
public class EventMessage
{
public string MessageText
{ get; set; }
}
public interface IEventReceiver
{
void Handle(EventMessage message);
}
public interface IEventAggregator
{
void SendMessage(EventMessage message);
void AddReceiver(IEventReceiver receiver);
void RemoveReceiver(IEventReceiver receiver);
}
public class EventAggregator
: IEventAggregator
{
private readonly List<IEventReceiver>
receivers = new List<IEventReceiver>();
#region IEventAggregator Members
public void SendMessage(EventMessage
message)
{
receivers.ForEach(r => r.Handle(message));
}
public void AddReceiver(IEventReceiver
receiver)
{
if (receivers.Contains(receiver)) return;
receivers.Add(receiver);
}
public void RemoveReceiver(IEventReceiver
receiver)
{
receivers.Remove(receiver);
}
#endregion
}
public class EventReceiver
: IEventReceiver
{
public EventReceiver(IEventAggregator aggregator)
{
aggregator.AddReceiver(this);
}
#region IEventReceiver Members
public void Handle(EventMessage
message)
{
Console.WriteLine(message.MessageText);
}
#endregion
}
public class EventSender
{
private IEventAggregator aggregator;
public EventSender(IEventAggregator aggregator)
{
this.aggregator = aggregator;
}
public void SendErrorMessage(string message)
{
aggregator.SendMessage(new EventMessage { MessageText
= message });
}
}
This is a big improvement over manual event wiring through an application. No longer do the targets of a message need to know (have references to) the senders of the message in order to make the connection. You can add a new target (receiver) without changing the code of the sender. You can add a new sender without changing the code of the receivers. And you don’t have to manually map things out in some sort of registry class. This is simple easy and it will work.
It’s always something with you, isn’t it?
Ok. I like it. I’m happy. But how many of these things am I going to have floating around? It seems like a lot of code to write to hand one type of event between some components. I’m going to have to hire some Indian outsourcing company to write all of these singleton aggregators.
This sounds like a job for generics! My next post will rewrite the Event Aggregator so that a single aggregator can handle every message your application has.