Chapter 4. Input

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.

Routed Events

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.

A button with nested content

Figure 4-1. A button with nested content

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

<Button PreviewMouseDown="PreviewMouseDownButton"
           MouseDown="MouseDownButton">

    <Grid PreviewMouseDown="PreviewMouseDownGrid"
             MouseDown="MouseDownGrid"
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>

        <Canvas PreviewMouseDown="PreviewMouseDownCanvas"
      MouseDown="MouseDownCanvas"
                Width="20" Height="18" VerticalAlignment="Center">

            <Ellipse PreviewMouseDown="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>
Routed events

Figure 4-2. Routed events

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.

Halting Event Routing

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.

Determining the Target

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.)

Routed Events and Normal Events

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.

Attached Events

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

<Grid Mouse.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.