Introduction
Are you using the MVVM pattern? Are you binding ICommand
s to your button-based controls and handling the events in your view model? Have you seen the RelayCommand
? It's pretty cool, and I like that it lets you enable/disable your buttons by implementing a CanExecute
predicate. Although I'm not trying to discourage you from using the ICommand
approach to loose-coupling, you may however want to use (or maybe just learn about) another option.
This article is my first contribution to the community. I've created what I think is a nifty class and I'd like to share it with everyone. Perhaps someone will find it useful.
Note
The EventBroker
class I'm going to talk about makes use of attached properties. If you're not familiar with these or just need a refresher, you can read about them on MSDN here.
Using the Code
First, we will see how to use the EventBroker
. It's really simple. Afterwards, for those who stick around, I will show what the code looks like.
So let's get started...
- Create a new WPF or Silverlight application and include EventBroker.cs in the project.
- If you chose WPF, open your MainWindow.xaml file. If you chose Silverlight, open your MainPage.xaml file.
- At the top of the XAML file, add a reference to the
EventBroker
's namespace.
- Edit the XAML to include a rectangle. We are going to handle the rectangle's
MouseLeftButtonDown
event.
- To the rectangle, add the
MouseLeftButtonDown
attached property. The value can be whatever string
you want (more on that later).
- Find a class that you would like to receive event notifications. I will choose the
MainWindow
class for this example. (Of course, putting the EventBroker
handler in the view's code-behind doesn't buy you anything since you could always do that. The beauty of the EventBroker
is that you can handle the events in any arbitrary code!)
- Include the
EventBroker
's namespace at the top of the file.
using Bobeck.Windows.Events;
- In the constructor for the
MainWindow
class (or other appropriate location), attach to the one-and-only event published by the EventBroker
.
EventBroker.EventHandler +=
new EventBroker.EventHandlerDelegate(EventBroker_EventHandler);
- To handle the event, we must provide an event handler whose signature matches the
EventBroker
's delegate...
void EventManager_EventHandler(object sender, EventArgs e,
EventBroker.EventType eventType, string eventKey)
{
MessageBox.Show("eventKey = " + eventKey);
}
And that's it! Pretty simple, right! As this application runs, whenever you click inside the red rectangle, the above event handler will execute. Notice the arguments. The eventType
will indicate the type of event (in this case EventBroker.EventType.MouseLeftButtonDown
). The eventKey
argument will be the string
you assigned to the attached property in XAML. (in this case "Click!"). You can use either (or both) of these values for filtering. Unless you are using the EventBroker
for something like event logging, you would likely want to qualify the eventKey
before you react, as there may be many other controls that fire the same MouseLeftButtonDown
event and you will probably want to react to each one differently.
So that brings up a point I want to make... When you attach to the EventBroker
's EventHandler
in the above fashion, you are essentially subscribing to a broadcast channel. All events processed by the EventBroker
are broadcasted to all clients that have attached.
But, the EventBroker
also provides an API that allows clients to "Register
" for events. This puts the burden on the EventBroker
to route events to the appropriate handlers. To register for an event, the code looks like this...
EventBroker.Register(EventBroker.EventType.MouseLeftButtonDown,
"Click!", EventBroker_EventHandler);
And when you're done listening to the event, you can "Unregister
" (make sure each parameter is the same as those used to register):
EventBroker.Unregister(EventBroker.EventType.MouseLeftButtonDown,
"Click!", EventBroker_EventHandler);
So, broadcasting or routing... You can choose the implementation that best fits your needs. The following list shows the events that are supported as of this writing.
KeyUp
(FrameworkElement
)
KeyDown
(FrameworkElement
)
MouseEnter
(FrameworkElement
)
MouseLeave
(FrameworkElement
)
MouseLeftButtonUp
(FrameworkElement
)
MouseLeftButtonDown
(FrameworkElement
)
MouseRightButtonUp
(FrameworkElement
)
MouseRightButtonDown
(FrameworkElement
)
MouseMove
(FrameworkElement
)
MouseWheel
(FrameworkElement
)
SelectionChanged
(Selector
& TextBox
)
Click
(ButtonBase
)
CheckedChanged
(ToggleButton
)
ValueChanged
(RangeBase
)
Under the Hood
The EventBroker
is a static
class that implements a number of attached properties -- one for each event actually. The plumbing for these looks pretty typical. Here is the implementation for the MouseLeftButtonDown
attached property. We also must provide CLR get()
& set()
methods so we can use these properties in XAML.
public static readonly DependencyProperty MouseLeftButtonDownProperty =
DependencyProperty.RegisterAttached("MouseLeftButtonDown",
typeof(string),
typeof(EventBroker),
new PropertyMetadata(String.Empty, OnMouseLeftButtonDownChanged)
);
public static string GetMouseLeftButtonDown(DependencyObject obj)
{
return (string)obj.GetValue(MouseLeftButtonDownProperty);
}
public static void SetMouseLeftButtonDown(DependencyObject obj, string value)
{
obj.SetValue(MouseLeftButtonDownProperty, value);
}
For each attached property definition, we provide a callback to give us a chance to hook up to the framework element's actual event interface. That code looks like this...
private static void OnMouseLeftButtonDownChanged(DependencyObject obj,
DependencyPropertyChangedEventArgs e)
{
FrameworkElement target = obj as FrameworkElement;
if (target == null) return;
string newValue = e.NewValue as string;
string oldValue = e.OldValue as string;
if (!String.IsNullOrEmpty(newValue) && String.IsNullOrEmpty(oldValue))
{
target.MouseLeftButtonDown +=
new MouseButtonEventHandler(Proxy_MouseLeftButtonDown);
target.Unloaded += new RoutedEventHandler(Proxy_UnLoaded);
}
else if (String.IsNullOrEmpty(newValue) && !String.IsNullOrEmpty(oldValue))
{
target.MouseLeftButtonDown -=
new MouseButtonEventHandler(Proxy_MouseLeftButtonDown);
target.Unloaded -= new RoutedEventHandler(Proxy_UnLoaded);
}
}
First we coerce the DependencyObject obj
into a FrameworkElement
, which is the lowest common denominator control class that supports the mouse events. If the attached property is being set, then we attach to the control's MouseLeftButtonDown
event. If the attached property is being cleared, then we detach from the control's MouseLeftButtonDown
event. We also take this opportunity to attach/detach to/from the control's Unloaded event. This is monitored so we can detach our handlers from the control when the control gets unloaded. If we didn't do this, then the control would not get garbage collected. Yikes!
As you can see from the code above, whenever a MouseLeftButtonDown
event occurs, the EventBroker
's Proxy_MouseLeftButtonDown
handler will execute. Each event type has its own Proxy_XXX
handler. Here is an example of one...
private static void Proxy_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
string eventKey = GetMouseLeftButtonDown(sender as DependencyObject);
Fire_EventHandler(sender, e, EventType.MouseLeftButtonDown, eventKey);
}
Each Proxy_XXX
handler then calls a common method named Fire_EventHandler
, passing the eventType
and eventKey
.
private static void Fire_EventHandler
(object sender, EventArgs e, EventType eventType, string eventKey)
{
if (EventHandler != null)
{
EventHandler(sender, e, eventType, eventKey);
}
List contracts = _Contracts.Where(c => (c.EventType == eventType) &&
(c.EventKey == eventKey)).ToList();
foreach (EventContract contract in contracts)
{
if (contract.EventHandler != null)
{
contract.EventHandler(sender, e, eventType, eventKey);
}
}
}
The Fire_EventHandler
method is responsible for broadcasting the event data to all clients/listeners, and to route the event data only to those interested (registered) parties. Internally, the EventBroker
maintains a list of contracts that map the registered client handlers to event types and event keys (those string
s set in XAML).
Note: At the time of this writing, the SelectionChanged
event only works with ListBox
and ComboBox
controls, which are common to both WPF and Silverlight, and not TabControl
s, ListView
s or DataGrid
s.
Well, I think that covers just about everything. I hope you find the EventBroker
useful. And if not, maybe you found an educational nugget in the article somewhere. Too-da-loo for now and happy coding!
History
- June 6, 2010 Created the article - DAB
- June 8, 2010 Updated source code to handle
RangeBase
and ToggleButton
- DAB