Muokkaa

Jaa


Routed events overview (WPF .NET)

Windows Presentation Foundation (WPF) application developers and component authors can use routed events to propagate events through an element tree, and invoke event handlers on multiple listeners in the tree. These features aren't found in common language runtime (CLR) events. Several WPF events are routed events, such as ButtonBase.Click. This article discusses basic routed event concepts and offers guidance on when and how to respond to routed events.

Prerequisites

This article assumes a basic knowledge of the common language runtime (CLR), object-oriented programming, and how WPF element layout can be conceptualized as a tree. To follow the examples in this article, it helps if you're familiar with Extensible Application Markup Language (XAML) and know how to write WPF applications.

What is a routed event?

You can consider routed events from a functional or implementation perspective:

  • From a functional perspective, a routed event is a type of event that can invoke handlers on multiple listeners in an element tree, not just on the event source. An event listener is the element where an event handler is attached and invoked. An event source is the element or object that originally raised an event.

  • From an implementation perspective, a routed event is an event registered with the WPF event system, backed by an instance of the RoutedEvent class, and processed by the WPF event system. Typically, a routed event is implemented with a CLR event "wrapper" to enable attaching handlers in XAML and in code-behind as you would a CLR event.

WPF applications typically contain many elements, which were either declared in XAML or instantiated in code. An application's elements exists within its element tree. Depending on how a routed event is defined, when the event is raised on a source element it:

  • Bubbles up through element tree from the source element to the root element, which is typically a page or window.
  • Tunnels down through the element tree from the root element to the source element.
  • Doesn't travel through the element tree, and only occurs on the source element.

Consider the following partial element tree:

<Border Height="30" Width="200" BorderBrush="Gray" BorderThickness="1">
    <StackPanel Background="LightBlue" Orientation="Horizontal" Button.Click="YesNoCancelButton_Click">
        <Button Name="YesButton">Yes</Button>
        <Button Name="NoButton">No</Button>
        <Button Name="CancelButton">Cancel</Button>
    </StackPanel>
</Border>

The element tree renders as shown:

A XAML element tree with three buttons: Yes, No, and Cancel.

Each of the three buttons is a potential Click event source. When one of the buttons is clicked, it raises the Click event that bubbles up from the button to the root element. The Button and Border elements don't have event handlers attached, but the StackPanel does. Possibly other elements higher up in the tree that aren't shown also have Click event handlers attached. When the Click event reaches the StackPanel element, the WPF event system invokes the YesNoCancelButton_Click handler that's attached to it. The event route for the Click event in the example is: Button -> StackPanel -> Border -> successive parent elements.

Note

The element that originally raised a routed event is identified as the RoutedEventArgs.Source in the event handler parameters. The event listener is the element where the event handler is attached and invoked, and is identified as the sender in the event handler parameters.

Top-level scenarios for routed events

Here are some of the scenarios that motivated the routed event concept, and distinguish it from a typical CLR event:

  • Control composition and encapsulation: Various controls in WPF have a rich content model. For example, you can place an image inside a Button, which effectively extends the visual tree of the button. But, the added image mustn't break the hit-test behavior of the button, which needs to respond when a user clicks the image pixels.

  • Singular handler attachment points: You could register a handler for each button's Click event, but with routed events you can attach a single handler as shown in the previous XAML example. This enables you to change the element tree under the singular handler, such as adding or removing more buttons, without having to register each button's Click event. When the Click event is raised, handler logic can determine where the event came from. The following handler, specified in the previously shown XAML element tree, contains that logic:

    private void YesNoCancelButton_Click(object sender, RoutedEventArgs e)
    {
        FrameworkElement sourceFrameworkElement = e.Source as FrameworkElement;
        switch (sourceFrameworkElement.Name)
        {
            case "YesButton":
                // YesButton logic.
                break;
            case "NoButton":
                // NoButton logic.
                break;
            case "CancelButton":
                // CancelButton logic.
                break;
        }
        e.Handled = true;
    }
    
    Private Sub YesNoCancelButton_Click(sender As Object, e As RoutedEventArgs)
        Dim frameworkElementSource As FrameworkElement = TryCast(e.Source, FrameworkElement)
    
        Select Case frameworkElementSource.Name
            Case "YesButton"
                ' YesButton logic.
            Case "NoButton"
                ' NoButton logic.
            Case "CancelButton"
                ' CancelButton logic.
        End Select
    
        e.Handled = True
    End Sub
    
  • Class handling: Routed events support a class event handler that you define in a class. Class handlers handle an event before any instance handlers for the same event on any instance of the class.

  • Referencing an event without reflection: Each routed event creates a RoutedEvent field identifier to provide a robust event identification technique that doesn't require static or run-time reflection to identify the event.

How routed events are implemented

A routed event is an event registered with the WPF event system, backed by an instance of the RoutedEvent class, and processed by the WPF event system. The RoutedEvent instance, obtained from registration, is typically stored as a public static readonly member of the class that registered it. That class is referred to as the event "owner" class. Typically, a routed event implements an identically named CLR event "wrapper". The CLR event wrapper contains add and remove accessors to enable attaching handlers in XAML and in code-behind through language-specific event syntax. The add and remove accessors override their CLR implementation and call the routed event AddHandler and RemoveHandler methods. The routed event backing and connection mechanism is conceptually similar to how a dependency property is a CLR property that's backed by the DependencyProperty class and registered with the WPF property system.

The following example registers the Tap routed event, stores the returned RoutedEvent instance, and implements a CLR event wrapper.

// Register a custom routed event using the Bubble routing strategy.
public static readonly RoutedEvent TapEvent = EventManager.RegisterRoutedEvent(
    name: "Tap",
    routingStrategy: RoutingStrategy.Bubble,
    handlerType: typeof(RoutedEventHandler),
    ownerType: typeof(CustomButton));

// Provide CLR accessors for adding and removing an event handler.
public event RoutedEventHandler Tap
{
    add { AddHandler(TapEvent, value); }
    remove { RemoveHandler(TapEvent, value); }
}
' Register a custom routed event using the Bubble routing strategy.
Public Shared ReadOnly TapEvent As RoutedEvent = EventManager.RegisterRoutedEvent(
    name:="Tap",
    routingStrategy:=RoutingStrategy.Bubble,
    handlerType:=GetType(RoutedEventHandler),
    ownerType:=GetType(CustomButton))

' Provide CLR accessors for adding and removing an event handler.
Public Custom Event Tap As RoutedEventHandler
    AddHandler(value As RoutedEventHandler)
        [AddHandler](TapEvent, value)
    End AddHandler

    RemoveHandler(value As RoutedEventHandler)
        [RemoveHandler](TapEvent, value)
    End RemoveHandler

    RaiseEvent(sender As Object, e As RoutedEventArgs)
        [RaiseEvent](e)
    End RaiseEvent
End Event

Routing strategies

Routed events use one of three routing strategies:

  • Bubbling: Initially, event handlers on the event source are invoked. The routed event then routes to successive parent elements, invoking their event handlers in turn, until it reaches the element tree root. Most routed events use the bubbling routing strategy. Bubbling routed events are generally used to report input or state changes from composite controls or other UI elements.

  • Tunneling: Initially, event handlers at the element tree root are invoked. The routed event then routes to successive child elements, invoking their event handlers in turn, until it reaches the event source. Events that follow a tunneling route are also referred to as Preview events. WPF input events are generally implemented as a preview and bubbling pairs.

  • Direct: Only event handlers on the event source are invoked. This non-routing strategy is analogous to Windows Forms UI framework events, which are standard CLR events. Unlike CLR events, direct routed events support class handling and can be used by EventSetters and EventTriggers.

Why use routed events?

As an application developer, you don't always need to know or care that the event you're handling is implemented as a routed event. Routed events have special behavior, but that behavior is largely invisible if you're handling an event on the element that raised it. However, routed events are relevant when you want to attach an event handler to a parent element in order to handle events raised by child elements, such as within a composite control.

Routed event listeners don't need the routed events they handle to be members of their class. Any UIElement or ContentElement can be an event listener for any routed event. Since visual elements derive from UIElement or ContentElement, you can use routed events as a conceptual "interface" that supports the exchange of event information between disparate elements in an application. The "interface" concept for routed events is particularly applicable to input events.

Routed events support the exchange of event information between elements along the event route because each listener has access to the same instance of event data. If one element changes something in the event data, that change is visible to subsequent elements in the event route.

Apart from the routing aspect, you might choose to implement a routed event instead of a standard CLR event for these reasons:

  • Some WPF styling and templating features, such as EventSetters and EventTriggers, require the referenced event to be a routed event.

  • Routed events support class event handlers that handle an event ahead of any instance handlers for the same event on any instance of the listener class. This feature is useful in control design because your class handler can enforce event-driven class behaviors that can't be accidentally suppressed by an instance handler.

Attach and implement a routed event handler

In XAML, you attach an event handler to an element by declaring the event name as an attribute on the event listener element. The attribute value is your handler method name. The handler method must be implemented in the code-behind partial class for the XAML page. The event listener is the element where the event handler is attached and invoked.

For an event that's a member (inherited or otherwise) of the listener class, you can attach a handler as follows:

<Button Name="Button1" Click="Button_Click">Click me</Button>

If the event isn't a member of the listener's class, you must use the qualified event name in the form of <owner type>.<event name>. For example, because the StackPanel class doesn't implement the Click event, to attach a handler to a StackPanel for a Click event that bubbles up to that element, you'll need to use the qualified event name syntax:

<StackPanel Name="StackPanel1" Button.Click="Button_Click">
    <Button>Click me</Button>
</StackPanel>

The signature of the event handler method in code-behind must match the delegate type for the routed event. The sender parameter of the RoutedEventHandler delegate for the Click event specifies the element to which the event handler is attached. The args parameter of the RoutedEventHandler delegate contains the event data. A compatible code-behind implementation for the Button_Click event handler might be:

private void Button_Click(object sender, RoutedEventArgs e)
{
    // Click event logic.
}
Private Sub Button_Click(sender As Object, e As RoutedEventArgs)
    ' Click event logic.
End Sub

Although RoutedEventHandler is the basic routed event handler delegate, some controls or implementation scenarios require different delegates that support more specialized event data. As an example, for the DragEnter routed event, your handler should implement the DragEventHandler delegate. By doing so, your handler code can access the DragEventArgs.Data property in event data, which contains the clipboard payload from the drag operation.

The XAML syntax for adding routed event handlers is the same as for standard CLR event handlers. For more information about adding event handlers in XAML, see XAML in WPF. For a complete example of how to attach an event handler to an element using XAML, see How to handle a routed event.

To attach an event handler for a routed event to an element using code, you generally have two options:

  • Directly call the AddHandler method. Routed event handlers can always be attached this way. This example attaches a Click event handler to a button using the AddHandler method:

    Button1.AddHandler(ButtonBase.ClickEvent, new RoutedEventHandler(Button_Click));
    
    Button1.[AddHandler](ButtonBase.ClickEvent, New RoutedEventHandler(AddressOf Button_Click))
    

    To attach a handler for the button's Click event to a different element in the event's route, such as a StackPanel named StackPanel1:

    StackPanel1.AddHandler(ButtonBase.ClickEvent, new RoutedEventHandler(Button_Click));
    
    StackPanel1.[AddHandler](ButtonBase.ClickEvent, New RoutedEventHandler(AddressOf Button_Click))
    
  • If the routed event implements a CLR event wrapper, use language-specific event syntax to add event handlers just as you would for a standard CLR event. Most existing WPF routed events implement the CLR wrapper, thus enabling language-specific event syntax. This example attaches a Click event handler to a button using language specific syntax:

    Button1.Click += Button_Click;
    
    AddHandler Button1.Click, AddressOf Button_Click
    

For an example of how to attach an event handler in code, see How to add an event handler using code. If you're coding in Visual Basic, you can also use the Handles keyword to add handlers as part of the handler declarations. For more information, see Visual Basic and WPF event handling.

The concept of handled

All routed events share a common base class for event data, which is the RoutedEventArgs class. The RoutedEventArgs class defines the boolean Handled property. The purpose of the Handled property is to let any event handler along the event route to mark the routed event as handled. To mark an event as handled, set the value of Handled to true in the event handler code.

The value of Handled affects how a routed event is processed as it travels along the event route. If Handled is true in the shared event data of a routed event, then handlers attached to other elements further along the event route typically won't be invoked for that particular event instance. For most common handler scenarios, marking an event as handled effectively stops subsequent handlers along the event route, whether instance or class handlers, from responding to that particular event instance. However, in rare cases where you need your event handler to respond to routed events that have been marked as handled, you can:

The concept of Handled might affect how you design your application and code your event handlers. You can conceptualize Handled as a simple protocol for processing of routed events. How you use this protocol is up to you, but the expected use of the Handled parameter is:

  • If a routed event is marked as handled, then it doesn't need to be handled again by other elements along the route.

  • If a routed event isn't marked as handled, then listeners earlier in the event route don't have a handler for the event, or none of the registered handlers responded to the event in a way that justifies marking the event as handled. Handlers on the current listener have three possible courses of action:

    • Take no action at all. The event remains unhandled and routes to the next listener in the tree.

    • Run code in response to the event, but not to an extent that justifies marking the event as handled. The event remains unhandled and routes to the next listener in the tree.

    • Run code in response to the event, to an extent that justifies marking the event as handled. Mark the event as handled in the event data. The event still routes to the next listener in the tree, but most listeners won't invoke further handlers. The exception is listeners with handlers that were specifically registered with handledEventsToo set to true.

For more information about handling routed events, see Marking routed events as handled, and class handling.

Although developers who only handle a bubbling routed event on the object that raised it might not be concerned about other listeners, it's good practice to mark the event as handled anyway. Doing so prevents unanticipated side effects if an element further along the event route has a handler for the same routed event.

Class handlers

Routed event handlers can be either instance handlers or class handlers. Class handlers for a given class are invoked before any instance handler responding to the same event on any instance of that class. Due to this behavior, when routed events are marked as handled, they're often marked as such within class handlers. There are two types of class handlers:

Some WPF controls have inherent class handling for certain routed events. Class handling might give the outward appearance that the routed event isn't ever raised, but in reality it's being marked as handled by a class handler. If you need your event handler to respond to the handled event, you can register your handler with handledEventsToo set to true. For more information, both on implementing your own class handlers or working around undesired class handling, see Marking routed events as handled, and class handling.

Attached events in WPF

The XAML language also defines a special type of event called an attached event. Attached events can be used to define a new routed event in a non-element class and raise that event on any element in your tree. To do so, you must register the attached event as a routed event and provide specific backing code that supports attached event functionality. Since attached events are registered as routed events, when raised on an element they propagate through the element tree.

In XAML syntax, an attached event is specified by its event name and owner type, in the form of <owner type>.<event name>. Because the event name is qualified with the name of its owner type, the syntax allows the event to be attached to any element that can be instantiated. This syntax is also applicable to handlers for regular routed events that attach to an arbitrary element along the event route. You can also attach handlers for attached events in code behind by calling the AddHandler method on the object that the handler should attach to.

The WPF input system uses attached events extensively. However, nearly all of those attached events are surfaced as equivalent non-attached routed events through base elements. You'll rarely use or handle attached events directly. For instance, it's easier to handle the underlying attached Mouse.MouseDown event on a UIElement through the equivalent UIElement.MouseDown routed event than by using attached event syntax in XAML or code-behind.

For more information about attached events in WPF, see Attached events overview.

Qualified event names in XAML

The <owner type>.<event name> syntax qualifies an event name with the name of its owner type. This syntax allows an event to be attached to any element, not just elements that implement the event as a member of their class. The syntax is applicable when attaching handlers in XAML for attached events or routed events on arbitrary elements along the event route. Consider the scenario where you want to attach a handler to a parent element in order to handle routed events raised on child elements. If the parent element doesn't have the routed event as a member, you'll need to use the qualified event name syntax. For example:

<StackPanel Name="StackPanel1" Button.Click="Button_Click">
    <Button>Click me</Button>
</StackPanel>

In the example, the parent element listener to which the event handler is added is a StackPanel. However, the Click routed event is implemented and raised on the ButtonBase class, and available to the Button class through inheritance. Although the Button class "owns" the Click event, the routed event system permits handlers for any routed event to be attached to any UIElement or ContentElement instance listener that could otherwise have handlers for a CLR event. The default xmlns namespace for these qualified event attribute names is typically the default WPF xmlns namespace, but you can also specify prefixed namespaces for custom routed events. For more information about xmlns, see XAML namespaces and namespace mapping for WPF XAML.

WPF input events

One frequent application of routed events within the WPF platform is for input events. By convention, WPF routed events that follow a tunneling route have a name that's prefixed with "Preview". The Preview prefix signifies that the preview event completes before the paired bubbling event starts. Input events often come in pairs, with one being a preview event and the other a bubbling routed event. For example, PreviewKeyDown and KeyDown. The event pairs share the same instance of event data, which for PreviewKeyDown and KeyDown is of type KeyEventArgs. Occasionally, input events only have a bubbling version, or only a direct routed version. In the API documentation, routed event topics cross-reference routed event pairs and clarify the routing strategy for each routed event.

WPF input events that come in pairs are implemented so that a single user action from an input device, such as a mouse button press, will raise the preview and bubbling routed events in sequence. First, the preview event is raised and completes its route. On completion of the preview event, the bubbling event is raised and completes its route. The RaiseEvent method call in the implementing class that raises the bubbling event reuses the event data from the preview event for the bubbling event.

A preview input event that's marked as handled won't invoke any normally registered event handlers for the remainder of the preview route, and the paired bubbling event won't be raised. This handling behavior is useful for composite controls designers who want hit-test based input events or focus-based input events to be reported at the top-level of their control. Top-level elements of the control have the opportunity to class-handle preview events from control subcomponents in order to "replace" them with a top-level control-specific event.

To illustrate how input event processing works, consider the following input event example. In the following tree illustration, leaf element #2 is the source of both the PreviewMouseDown and MouseDown paired events:

A diagram that shows how the event routing flows from a root element to other elements.

The order of event processing following a mouse-down action on leaf element #2 is:

  1. PreviewMouseDown tunneling event on the root element.
  2. PreviewMouseDown tunneling event on intermediate element #1.
  3. PreviewMouseDown tunneling event on leaf element #2, which is the source element.
  4. MouseDown bubbling event on leaf element #2, which is the source element.
  5. MouseDown bubbling event on intermediate element #1.
  6. MouseDown bubbling event on the root element.

The routed event handler delegate provides references to both the object that raised the event and the object where the handler was invoked. The object that originally raised the event is reported by the Source property in the event data. The object where the handler was invoked is reported by the sender parameter. For any given routed event instance, the object that raised the event doesn't change as the event travels through the element tree, but the sender does. In steps 3 and 4 of the preceding diagram, the Source and sender are the same object.

If your input event handler completes the application-specific logic needed to address the event, you should mark the input event as handled. Typically, once an input event is marked Handled, handlers further along the event route aren't invoked. However, input event handlers that are registered with the handledEventsToo parameter set to true will be invoked even when the event is marked as handled. For more information, see Preview events and Marking routed events as handled, and class handling.

The concept of preview and bubbling event pairs, with shared event data and sequential raising of the preview event then the bubbling event, applies only to some WPF input events and not to all routed events. If you implement your own input event to address an advanced scenario, consider following the WPF input event pair approach.

If you're implementing your own composite control that responds to input events, consider using preview events to suppress and replace input events raised on subcomponents with a top-level event that represents the complete control. For more information, see Marking routed events as handled, and class handling.

For more information about the WPF input system and how inputs and events interact in typical application scenarios, see Input overview.

EventSetters and EventTriggers

In markup styles, you can include pre-declared XAML event handling syntax by using an EventSetter. When the XAML is processed, the referenced handler is added to the styled instance. You can only declare an EventSetter for a routed event. In the following example, the referenced ApplyButtonStyle event handler method is implemented in code-behind.

<StackPanel>
    <StackPanel.Resources>
        <Style TargetType="{x:Type Button}">
            <EventSetter Event="Click" Handler="ApplyButtonStyle"/>
        </Style>
    </StackPanel.Resources>
    <Button>Click me</Button>
    <Button Click="Button_Click">Click me</Button>
</StackPanel>

It's likely that the Style node already contains other style information that pertains to controls of the specified type, and having the EventSetter be part of those styles promotes code reuse even at the markup level. Also, an EventSetter abstracts method names for handlers away from the general application and page markup.

Another specialized syntax that combines the routed event and animation features of WPF is an EventTrigger. As with the EventSetter, you can only declare an EventTrigger for a routed event. Typically, an EventTrigger is declared as part of a style, but an EventTrigger can be declared on page-level elements as part of the Triggers collection, or in a ControlTemplate. An EventTrigger enables you to specify a Storyboard that runs whenever a routed event reaches an element in its route that declares an EventTrigger for that event. The advantage of an EventTrigger over just handling the event and causing it to start an existing storyboard is that an EventTrigger provides better control over the storyboard and its run-time behavior. For more information, see Use event triggers to control a storyboard after it starts.

More about routed events

You can use the concepts and guidance in this article as a starting point when creating custom routed events in your own classes. You can also support your custom events with specialized event data classes and delegates. A routed event owner can be any class, but routed events must be raised by and handled by UIElement or ContentElement derived classes in order to be useful. For more information about custom events, see Create a custom routed event.

See also