A user interface wouldn't be much use if it couldn't respond to user
input. In this chapter, we will examine the input handling mechanisms
available in WPF. There are three main kinds of user input for a Windows
application: mouse, keyboard, and ink.[19] Any user interface element can receive input—not just
controls. This is not surprising, because controls rely entirely on the
services of lower-level elements like Rectangle
and TextBlock
in order to provide visuals. All of
the input mechanisms described in the following sections are, therefore,
available on all user interface element types.
Raw user input is delivered to your code through WPF's routed event mechanism. There is also a higher-level concept of a command—a particular action that might be accessible through several different inputs such as keyboard shortcuts, toolbar buttons, and menu items.
The .NET Framework defines a standard mechanism for managing events. A class may expose several events, and each event may have any number of subscribers. WPF augments this standard mechanism to overcome a limitation: if a normal .NET event has no registered handlers, it is effectively ignored.
Consider what this would mean for a typical WPF control. Most
controls are made up of multiple visual components. For example, suppose
you give a button a very plain appearance consisting of a single
Rectangle
, and provide a simple piece
of text as the content. (Chapter 9 describes
how to customize a control's appearance.) Even with such basic visuals,
there are still two elements present: the text and the rectangle. The
button should respond to a mouse click whether the mouse is over the
text or the rectangle. In the standard .NET event handling model, this
would mean registering a MouseLeftButtonUp
event handler for both
elements.
This problem would get much worse when taking advantage of WPF's
content model. A Button
is not
restricted to having plain text as a caption—it can contain any object
as content. The example in Figure 4-1 is not especially ambitious,
but even this has six visible elements: the yellow outlined circle, the
two dots for the eyes, the curve for the mouth, the text, and the button
background itself. Attaching event handlers for every single element
would be tedious and inefficient. Fortunately, it's not
necessary.
WPF uses routed events, which are rather more thorough than normal events. Instead of just calling handlers attached to the element that raised the event, WPF walks the tree of user interface elements, calling all handlers for the routed event attached to any node from the originating element right up to the root of the user interface tree. This behavior is the defining feature of routed events, and is at the heart of event handling in WPF.
Example 4-1 shows
markup for the button in Figure 4-1. If one of the Ellipse
elements inside the Canvas
were to receive input, event routing
would enable the Button
, Grid
, Canvas
, and Ellipse
to receive the event, as Figure 4-2 shows.
Example 4-1. Handling events in a user interface tree
<ButtonPreviewMouseDown="PreviewMouseDownButton"
MouseDown="MouseDownButton"
> <GridPreviewMouseDown="PreviewMouseDownGrid"
MouseDown="MouseDownGrid"
<Grid.ColumnDefinitions> <ColumnDefinition /> <ColumnDefinition /> </Grid.ColumnDefinitions> <CanvasPreviewMouseDown="PreviewMouseDownCanvas"
MouseDown="MouseDownCanvas" Width="20" Height="18" VerticalAlignment="Center"> <EllipsePreviewMouseDown="PreviewMouseDownEllipse"
MouseDown="MouseDownEllipse" x:Name="myEllipse" Canvas.Left="1" Canvas.Top="1" Width="16" Height="16" Fill="Yellow" Stroke="Black" /> <Ellipse Canvas.Left="4.5" Canvas.Top="5" Width="2.5" Height="3" Fill="Black" /> <Ellipse Canvas.Left="11" Canvas.Top="5" Width="2.5" Height="3" Fill="Black" /> <Path Data="M 5,10 A 3,3 0 0 0 13,10" Stroke="Black" /> </Canvas> <TextBlock Grid.Column="1">Click!</TextBlock> </Grid> </Button>
A routed event can either be bubbling, tunneling, or direct. A bubbling event starts by looking for event handlers attached to the target element that raised the event, and then looks at its parent and then its parent's parent, and so on until it reaches the root of the tree; this order is indicated by the numbers in Figure 4-2. A tunneling event works in reverse—it looks for handlers at the root of the tree first and works its way down, finishing with the originating element.
Direct events work like normal .NET events: only handlers attached directly to the originating element are notified—no real routing occurs. This is typically used for events that make sense only in the context of their target element. For example, it would be unhelpful if mouse enter and leave events were bubbled or tunneled—the parent element is unlikely to care about when the mouse moves from one child element to another. At the parent element, you would expect "mouse leave" to mean "the mouse has left the parent element," and because direct event routing is used, that's exactly what it does mean. If bubbling were used, the event would effectively mean "the mouse has left an element that is inside the parent, and is now inside another element that may or may not be inside the parent," which would be less useful.
Tip
You may be wondering whether there is a meaningful difference between a direct routed event and an ordinary CLR event—after all, a direct event isn't really routed anywhere. The main difference is that with a direct routed event, WPF provides the underlying implementation, whereas if you were to use the normal C# event syntax to declare an event, the C# compiler would provide the implementation. The C# compiler would generate a hidden private field to hold the event handler, meaning that you pay a per-object overhead for each event whether or not any handlers are attached. With WPF's event implementation, event handlers are managed in such a way that you pay an overhead only for events to which handlers are attached. In a UI with thousands of elements each offering tens of events, most of which don't have handlers attached, this starts to add up. Also, WPF's event implementation offers something not available with ordinary C# events: attached events, which are described later.
With the exception of direct events, WPF defines most routed
events in pairs—one bubbling and one tunneling. The tunneling event name
always begins with Preview
and is
raised first. This gives parents of the target element the chance to see
the event before it reaches the child (hence the Preview
prefix). The tunneling preview event
is followed directly by a bubbling event. In most cases, you will handle
only the bubbling event—the preview would usually be used only if you
wanted to be able to block the event, or if you needed a parent to do
something in advance of normal handling of the event.
In Example 4-1,
most of the elements have event handlers specified for the PreviewMouseDown
and MouseDown
events—the bubbling and tunneling
events, respectively. Example 4-2 shows the
corresponding code-behind file.
Example 4-2. Handling events
using System; using System.Windows; using System.Diagnostics; namespace EventRouting { public partial class Window1 : Window { public Window1( ) { InitializeComponent( ); } void PreviewMouseDownButton(object sender, RoutedEventArgs e) { Debug.WriteLine("PreviewMouseDownButton"); } void MouseDownButton(object sender, RoutedEventArgs e) { Debug.WriteLine("MouseDownButton"); } void PreviewMouseDownGrid( object sender, RoutedEventArgs e) { Debug.WriteLine("PreviewMouseDownGrid"); } void MouseDownGrid(object sender, RoutedEventArgs e) { Debug.WriteLine("MouseDownGrid"); } void PreviewMouseDownCanvas(object sender, RoutedEventArgs e) { Debug.WriteLine("PreviewMouseDownCanvas"); } void MouseDownCanvas(object sender, RoutedEventArgs e) { Debug.WriteLine("MouseDownCanvas"); } void PreviewMouseDownEllipse(object sender, RoutedEventArgs e) { Debug.WriteLine("PreviewMouseDownEllipse"); } void MouseDownEllipse(object sender, RoutedEventArgs e) { Debug.WriteLine("MouseDownEllipse"); } } }
Each handler prints out a debug message. Here is the debug output
we get when clicking on the Ellipse
inside the Canvas
:
PreviewMouseDownButton PreviewMouseDownGrid PreviewMouseDownCanvas PreviewMouseDownEllipse MouseDownEllipse MouseDownCanvas MouseDownGrid
This confirms that the preview event is raised first. It also
shows that it starts from the Button
element and works down, as we would expect with a tunneling event. The
bubbling event that follows starts from the Ellipse
element and works up. (Interestingly,
it doesn't appear to get as far as the Button
. We'll look at why this is
shortly.)
This bubbling routing offered for most events means that you can register a single event handler on a control, and it will receive events for any of the elements nested inside the control. You do not need any special handling to deal with nested content, or controls whose appearance has been customized with templates—events simply bubble up to the control and can all be handled there.
There are some situations in which you might not want events to
bubble up. For example, you may wish to convert the event into
something else—the Button
element
effectively converts a MouseDown
event followed by a MouseUp
event
into a single Click
event. It
suppresses the more primitive mouse button events so that only the
Click
event bubbles up out of the
control. (This is why the event bubbling stopped at the button in the
previous example.)
Any handler can prevent further processing of a routed event by
setting the Handled
property of the
RoutedEventArgs
, as shown in Example 4-3.
Example 4-3. Halting event routing with Handled
void ButtonDownCanvas(object sender, RoutedEventArgs e) {
Debug.WriteLine("ButtonDownCanvas");
e.Handled = true;
}
If you set the Handled
flag
in a Preview
handler, not only will
the tunneling of the Preview
event
stop, but also the corresponding bubbling event that would normally
follow will not be raised at all. This provides a way of stopping the
normal handling of an event.
Although it is convenient to be able to handle events from a
group of elements in a single place, your handler might need to know
which element caused the event to be raised. You might think that this
is the purpose of the sender
parameter of your handler. In fact, the sender
always refers to the object to which
you attached the event handler. In the case of bubbled and tunneled
events, this often isn't the element that caused the event to be
raised. In Example 4-1,
the MouseDownGrid
handler's
sender
will always be the Grid
itself, regardless of which element in
the grid was clicked.
Fortunately, it's easy to find out which element was the
underlying cause of the event. The handler has a RoutedEventArgs
parameter, which offers a
Source
property for this purpose.
This is particularly useful if you need to handle events from several
different sources in the same way. For example, suppose you create a
window that contains a number of graphical elements, and you'd like
each to change shape when clicked. Instead of attaching a MouseDown
event handler to each individual
shape, you could attach a single handler to the window. All the events
would bubble up from any shape to this single handler, and you could
use the Source
property to work out
which shape you need to change. (Shapes are discussed in Example 13-5. Example 4-5 uses exactly this
trick.)
Normal .NET events (or, as they are often called, CLR events) offer one advantage over routed events: many .NET languages have built-in support for handling CLR events. Because of this, WPF provides wrappers for routed events, making them look just like normal CLR events.[20] This provides the best of both worlds: you can use your favorite language's event handling syntax while taking advantage of the extra functionality offered by routed events.
Tip
This is possible thanks to the flexible design of the CLR event mechanism. Though a standard simple behavior is associated with CLR events, CLR designers had the foresight to realize that some applications would require more sophisticated behavior. Classes are therefore free to implement events however they like. WPF reaps the benefits of this design by defining CLR events that are implemented internally as routed events.
Example 4-1 and
Example 4-2 arranged for the event handlers to
be connected by using attributes in the markup. But we could have used
the normal C# event handling syntax to attach handlers in the
constructor instead. For example, you could remove the MouseDown
and PreviewMouseDown
attributes from the
Ellipse
in Example 4-1, and then modify
the constructor from Example 4-2, as shown here
in Example 4-4.
Example 4-4. Attaching event handlers in code
... public Window1( ) { InitializeComponent( ); myEllipse.MouseDown += MouseDownEllipse; myEllipse.PreviewMouseDown += PreviewMouseDownEllipse; } ...
When you use these CLR event wrappers, WPF uses the routed event system on your behalf. The code in Example 4-5 is equivalent to that in Example 4-4.
Example 4-5. Attaching event handlers the long-winded way
... public Window1( ) { InitializeComponent( ); myEllipse.AddHandler(Ellipse.MouseDownEvent, new MouseButtonEventHandler(MouseDownEllipse)); myEllipse.AddHandler(Ellipse.PreviewMouseDownEvent, new MouseButtonEventHandler(PreviewMouseDownEllipse)); } ...
Example 4-5 is more verbose and offers no benefit—we show it here only so that you can see what's going on under the covers. The style shown in Example 4-4 is preferred.
The code behind is usually the best place to attach event handlers. If your user interface has unusual and creative visuals, there's a good chance that the XAML file will effectively be owned by a graphic designer. A designer shouldn't have to know what events a developer needs to handle, or what the handler functions are called. Ideally, the designer will give elements names in the XAML and the developer will attach handlers in the code behind.
It is possible to define an attached event. This is the routed-event equivalent of an attached property: an event defined by a different class than the one from which the event will be raised. This keeps the input system open to extension. If a new kind of input device is invented, it could define new events as attached events, enabling them to be raised from any UI element.
In fact, the WPF input system already works this way. The mouse,
stylus, and keyboard events examined in this chapter are just wrappers
for underlying attached events defined by the Mouse
, Keyboard
, and Stylus
classes in the System.Windows.Input
namespace. This means
we could change the Grid
element in
Example 4-1 to use the
attached events defined by the Mouse
class, as shown in Example 4-6.
Example 4-6. Attached event handling
<GridMouse.PreviewMouseDown
="PreviewMouseDownGrid"Mouse.MouseDown
="MouseDownGrid">
This would have no effect on the behavior, because the names Example 4-1 used for these events are aliases for the attached events used in this example.
Handling attached events from code looks a little different.
Normal CLR events don't support this notion of attached events, so we
can't use the ordinary C# event syntax like we did in Example 4-4. Instead, we have to
call the AddHandler
method, passing
in the RoutedEvent
object
representing the attached event (see Example 4-7).
Example 4-7. Explicit attached event handling
myEllipse.AddHandler(Mouse.PreviewMouseDownEvent, new MouseButtonEventHandler(PreviewMouseDownEllipse)); myEllipse.AddHandler(Mouse.MouseDownEvent, new MouseButtonEventHandler(MouseDownEllipse));
Alternatively, we can use the helper functions provided by the
Mouse
class. Example 4-8 uses this to
perform exactly the same job as the preceding two examples.
Example 4-8. Attached event handling with helper function
Mouse.AddPreviewMouseDownHandler(myEllipse, PreviewMouseDownEllipse); Mouse.AddMouseDownHandler(myEllipse, MouseDownEllipse);
Example 4-8 is
more compact than Example 4-7
because we were able to omit the explicit construction of the
delegate, relying instead on C# delegate type inference. Example 4-7 cannot do this because
AddHandler
can attach a handler for
any kind of event, so in its function signature the second parameter
is of the base Delegate
type. By
convention, classes that define attached events usually provide
corresponding helper methods like these to let you use this slightly
neater style of code.
[19] * Ink is input written with a stylus, whether on a Tablet PC or a hand-held device, although the mouse can be used in a pinch.
[20] * If you write custom elements, you should do the same. Chapter 18 describes how to do this.
Get Programming WPF, 2nd Edition now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.