Routed events in WPF let you do all sorts of crazy (and very handy)
things. The fact that you can preview events on the way down the visual
tree, as well as hook them as they bubble back up opens up a whole world
of possibilities that would have been nothing but pain back in WinForms.
But I'm not here today to wax poetic on routed events, I'm here to talk
about some of the lesser known functionality of the WPF event system -
namely Class Handlers.
What is a class handler? Well, it is a way of attaching to an event for
an entire class, and not just a particular instance of that class. Say
you want to do something special on
GotFocus
for every TextBox
in
your application. Generally, there would be two possible paths that you
could follow. One, you could make your own custom TextBox
that would
hook into its own GotFocus
event and execute your custom logic - but
now you have to remember to use that custom TextBox
class everywhere
in your app. Or, even worse, you can go around hooking to the GotFocus
event on every single individual TextBox
.
With a class handler, however, you can hook into the
GotFocus
event
for the TextBox
class, i.e., every single instance of TextBox
ever
created in your application will get your custom logic. This is a lot
cleaner than either of the two other solutions, and the technique in
general puts a lot of power in your hands.
OK, let's dive in and take a look at how to use that power. Below is a
screenshot of the sample app we are going to make today. Not really that
much to look at - just three
ListBox
es with some images and blue
rectangles inside them.
By default, when an item is focused, it fires a
RequestBringIntoViewEvent.
This event is handled by the first
ScrollViewer
along the visual tree,
and that ScrollViewer
will try and bring the focused item into view.
This is why when you click on an item in a ScrollViewer
that is only
partly in view, the ScrollViewer
will scroll to bring the item fully
into view. This also will happen if you use the arrow keys to move
between items.
In this sample app, we have turned off that functionality for any
Image
instance in a ListBox
- clicking on or arrowing to one of the
images will not cause it to scroll into view. However, the blue
rectangles continue to scroll into view.
So how did we turn it off? We used a class handler, of course! Take a
look at the code below:
using System.Windows;
using System.Windows.Controls;
namespace ClassHandlerExample
{
public partial class ClassHandlerExampleWindow : Window
{
static ClassHandlerExampleWindow()
{
EventManager.RegisterClassHandler(typeof(ScrollViewer),
FrameworkElement.RequestBringIntoViewEvent,
new RoutedEventHandler(HandleRequestBringIntoView));
}
private static void HandleRequestBringIntoView(object sender,
RoutedEventArgs e)
{
var item = e.OriginalSource as ListBoxItem;
if(item != null && item.Content is Image)
{ e.Handled = true; }
}
public ClassHandlerExampleWindow()
{ InitializeComponent(); }
}
}
This is the code behind the window you saw above. First, we have a
static constructor that does the actual class handler registration. The
method to call is
RegisterClassHandler,
a static on
EventManager.
This call is registering the function
HandleRequestBringIntoView
for
the event FrameworkElement.RequestBringIntoViewEvent
on the
ScrollViewer
class.
This means that the
HandleRequestBringIntoView
function will fire
whenever a RequestBringIntoViewEvent
(that has not yet been handled)
bubbles through any ScrollViewer
in our application. If you wanted
handled events too, there is an overload for RegisterClassHandler
that
takes a boolean for handling events that have already been marked as
handled.
A handy thing about class handlers is that they get invoked before any
instance handlers. This means that a class handler gets a chance to
handle the event first, and so can keep any regular instance handlers
from ever running.
It is that exact fact that we use to our advantage in this code. If the
original source of the event (i.e., the element that is requesting to be
brought into view) is a
ListBoxItem
and its content is an Image
, we
mark the event as handled. Otherwise, we do nothing. By marking the
event as handled, the ScrollViewer
never runs its own default logic,
and so the image is never scrolled into view.
And there you go! Now you know all you need to know about class
handlers. A word of caution, though - class handlers are extremely
powerful. It is deceptively easy to break built in functionality or
seriously slow down your application if you use class handlers unwisely.
But hey, with great power comes great responsibility, right? If you
would like to grab the source for this example, the zip file below holds
the Visual Studio solution.
Source Files:
Comments
Post a Comment