Home » Blog

rounded header

Routed events in WPF

tag icon Tagged as WPF

One of our WPF Property Grid customers was, for various reasons, interested in knowing when the user expanded a node. My first thought was, “We don’t expose an event for that.” My second thought was, “And it will be pretty hairy to get access to the event from the underlying template.” My third thought was, “Oh, wait. That’s easy.”

The reason is that a lot of events in WPF are actually routed events rather than straight CLR events. A CLR event is essentially a member which happens to be of delegate type and has a bit of extra metadata: it’s a way for a component to invoke callbacks. (Actually, a CLR event is just the bit of metadata; it’s just that most languages also create a backing member unless you say otherwise.) So if you want to know about an event, you need to get a reference to the object that declares the event so as to register your callback (event handler).

A routed event, on the other hand, is a first-class object which the WPF infrastructure can pass around the visual tree. To handle a routed event, therefore, you don’t need to have a reference to the object that declares and raises the event. You just need to be able to talk to something through which the event will pass.

Why is this important? Because our customer’s problem was that he wanted to handle an event that was happening somewhere deep inside the property grid’s control template, and routed events solve that problem. Getting hold of an individual TreeViewItem inside the grid in order to hook up the event would be hard: the grid manufactures them internally, and intercepting that manufacturing process can be messy and tedious. But the TreeViewItem’s Expanded event is actually a routed event, so we don’t need to register our event handler directly with each TreeViewItem. When a TreeViewItem raises the Expanded event, WPF bubbles the event upwards, and we can therefore register our event handler anywhere above the TreeViewItem in the visual tree. Specifically, we can register the event handler on the grid itself:

grid.AddHandler(TreeViewItem.ExpandedEvent, new RoutedEventHandler(OnNodeExpanded));

Or in XAML using an attached property-like syntax:

<ms:PropertyGrid TreeViewItem.Expanded="OnNodeExpanded" />

Our OnNodeExpanded routine will now get called whenever any node in the grid is expanded. And we haven’t need to get references to the individual TreeViewItem to make this happen; nor have we needed to add an event to the grid itself to report a node expansion.

One thing to watch out for when handling an event above the level that it is raised is that the sender parameter in the event handler will be the place where the event handler was attached. In the examples above, the sender passed to OnNodeExpanded will be the PropertyGrid object. To find the object that actually raised the event — the TreeViewItem which is expanding — you need to look at the OriginalSource property of the RoutedEventArgs.

By the way, you often won’t notice the difference between CLR events and routed events. For example, if you do have a reference to a TreeViewItem, you don’t need to mess around with AddHandler and ExpandedEvent: you can call tvi.Expanded += MyEventHandler just as if Expanded were a CLR event. This is because Expanded is a CLR event. Classes that declare routed events typically also declare corresponding CLR events, precisely so that the usual language event syntax continues to work. The idea is similar to dependency properties: just as, say, Window.TitleProperty is a DependencyProperty and Window.Title is a CLR property to allow convenient code-based access, TreeViewItem.ExpandedEvent is a RoutedEvent and TreeViewItem.Expanded is a CLR event. This CLR event is pure metadata, with no backing field. Backing is delegated to the routed event manager.

One of the big architectural themes of WPF is decoupling the code (behaviour) and API (usage) of controls from their visual tree and appearance. Data binding and commands are part of this decoupling. Routed events round out the set by freeing control developers from having to worry about surfacing template-dependent events, and by enabling control consumers to handle events happening within templated controls without requiring the consumer code to get intimate with the contents of the template.

Leave a Reply

Data Products Visual Controls Community Store
LightSpeed ORM
NHibernate Designer
SimpleDB Tools
SharePoint Tools
WPF Elements
WPF Diagrams
Silverlight Elements
Forums
Blog
Register
Login
Subscribe to newsletter
Buy Now
My Account
Volume Discounts
Purchase Orders
Contact Us