Input with Windows Presentation Foundation

This five-part article series walks you through the input handling mechanisms available in WPF. It is excerpted from Programming WPF, Second Edition, written by Chris Sells and Ian Griffiths (O'Reilly, 2007; ISBN: 0596510373). Copyright © 2007 O'Reilly Media, Inc. All rights reserved. Used with permission from the publisher. Available from booksellers or direct from O'Reilly Media.

Contributed by
Rating: 4 stars4 stars4 stars4 stars4 stars / 3
June 26, 2008
Rate this Article:
MEH MEH++


SEARCH ASP FREE
TOOLS YOU CAN USE

advertisement

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.* 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 andTextBlock 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 singleRectangle, 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 aMouseLeftButtonUp event handler for both elements.

This problem would get much worse when taking advantage of WPF’s content model. AButtonis 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.


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 theCanvas were to receive input, event routing would enable theButton,Grid,Canvas, andEllipseto 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>


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.

Routed Events, continued

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 withPreviewand is raised first. This gives parents of the target element the chance to see the event before it reaches the child (hence thePreview 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 thePreviewMouseDownandMouseDownevents—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 theEllipseinside theCanvas:

  PreviewMouseDownButton 
  PreviewMouseDownGrid
  PreviewMouseDownCanvas
  PreviewMouseDownEllipse
  MouseDownEllipse
  MouseDownCanvas
  MouseDownGrid

This confirms that the preview event is raised first. It also shows that it starts from theButtonelement and works down, as we would expect with a tunneling event. The bubbling event that follows starts from theEllipseelement and works up. (Interestingly, it doesn’t appear to get as far as theButton. 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 aMouseDownevent followed by aMouseUpevent into a singleClick event. It suppresses the more primitive mouse button events so that only theClickevent 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 theHandledproperty of theRoutedEventArgs, as shown in Example4-3.

Example 4-3. Halting event routing with Handled

void ButtonDownCanvas(object sender, RoutedEventArgs e) {
    Debug.WriteLine("ButtonDownCanvas");
   
e.Handled = true;
}

If you set theHandled flag in aPreviewhandler, not only will the tunneling of thePreviewevent 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, thesenderalways 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 MouseDownGridhandler’ssender will always be theGriditself, 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 aRoutedEventArgsparameter, which offers aSource 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 aMouseDownevent 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 theSourceproperty to work out which shape you need to change. (Shapes are discussed in Chapter 13. Example 13-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.* 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.

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.

Examples 4-1 and 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 theMouseDownandPreviewMouseDownattributes from theEllipse 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.

Please check back in two weeks for the next part of this series.

blog comments powered by Disqus
.NET ARTICLES

- .Net 4.5 Brings Changes
- Understanding Events in VB.NET
- Objects, Properties, Events and Methods in V...
- Install Visual Web Developer Express 2010
- Microsoft Gadgeteer an Open Source Alternati...
- Best DotNetNuke Modules
- Facebook Image Viewer in Visual Basic
- Murach`s ADO.NET 4 Database Programming with...
- 5 Must Have Visual Studio 2010 Extensions
- Dynamic Web Applications with ASP.NET Mono u...
- PDFSharp: HTML to PDF in ASP.NET 3.5 using V...
- Using the PDFSharp Library in ASP.NET 3.5 wi...
- Sending Email in ASP.NET 3.5 using VB.NET wi...
- ASP.NET 3.5 Role Based Security and User Aut...
- Creating ASP.NET Login Web Pages and Basic C...

ASP Web Hosting ASP.Net Web Hosting Windows Web Hosting
ASP Free Forums 
 RSS  Tutorials RSS
 RSS  Forums RSS
 RSS  All Feeds
Site Map 
Request Media Kit
Write For Us Get Paid 
Weekly Newsletter
 
Developer Updates  
Free Website Content 
Privacy Policy 
Support 


© 2003-2012 by Developer Shed. All rights reserved. DS Cluster 2 - Follow our Sitemap
Most Popular Topics
All ASP.Net Tutorials