Bewerken

Delen via


Attached events overview (WPF .NET)

Extensible Application Markup Language (XAML) defines a language component and event type 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.

Prerequisites

The article assumes a basic knowledge of Windows Presentation Foundation (WPF) routed events, and that you've read Routed events overview and XAML in WPF. To follow the examples in this article, it helps if you're familiar with XAML and know how to write WPF applications.

Attached event syntax

In XAML syntax, an attached event is specified by its event name and its 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.

The following XAML attribute syntax attaches the AquariumFilter_Clean handler for the AquariumFilter.Clean attached event to the aquarium1 element:

<aqua:Aquarium x:Name="aquarium1" Height="300" Width="400" aqua:AquariumFilter.Clean="AquariumFilter_Clean"/>

In this example, the aqua: prefix is necessary because the AquariumFilter and Aquarium classes exist in a different common language runtime (CLR) namespace and assembly.

You can also attach handlers for attached events in code behind. To do so, call the AddHandler method on the object that the handler should attach to and pass the event identifier and handler as parameters to the method.

How WPF implements attached events

WPF attached events are implemented as routed events backed by a RoutedEvent field. As a result, attached events propagate through the element tree after they're raised. Generally, the object that raises the attached event, known as the event source, is a system or service source. System or service sources aren't a direct part of the element tree. For other attached events, the event source might be an element in the tree, such as a component within a composite control.

Attached event scenarios

In WPF, attached events are used in certain feature areas where there's a service-level abstraction. For example, WPF makes uses of attached events enabled by the static Mouse or Validation classes. Classes that interact with or use a service can either interact with an event using attached event syntax, or surface the attached event as a routed event. The latter option is part of how a class might integrate the capabilities of the service.

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. Each routed input event is a member of the base element class and is backed with a CLR event "wrapper". 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.

Attached events serve an architecture purpose by enabling future expansion of input devices. For example, a new input device would only need to raise Mouse.MouseDown to simulate mouse input, and it wouldn't need to derive from Mouse to do so. That scenario involves code handling of the event, since XAML handling of the attached event wouldn't be relevant.

Handle an attached event

The process for coding and handling an attached event is basically the same as for a non-attached routed event.

As noted previously, existing WPF attached events typically aren't intended to be directly handled in WPF. More often, the purpose of an attached event is to enable an element within a composite control to report its state to a parent element within the control. In that scenario, the event is raised in code and relies on class handling in the relevant parent class. For instance, items within a Selector are expected to raise the Selected attached event, which is then class handled by the Selector class. The Selector class potentially converts the Selected event into the SelectionChanged routed event. For more information about routed events and class handling, see Marking routed events as handled, and class handling.

Define a custom attached event

If you're deriving from common WPF base classes, you can implement your custom attached event by including two accessor methods in your class. Those methods are:

  • An Add<event name>Handler method, with a first parameter that's the element on which the event handler is attached, and a second parameter that's the event handler to add. The method must be public and static, with no return value. The method calls the AddHandler base class method, passing in the routed event and handler as arguments. This method supports the XAML attribute syntax for attaching an event handler to an element. This method also enables code access to the event handler store for the attached event.

  • A Remove<event name>Handler method, with a first parameter that's the element on which the event handler is attached, and a second parameter that's the event handler to remove. The method must be public and static, with no return value. The method calls the RemoveHandler base class method, passing in the routed event and handler as arguments. This method enables code access to the event handler store for the attached event.

WPF implements attached events as routed events because the identifier for a RoutedEvent is defined by the WPF event system. Also, routing an event is a natural extension of the XAML language-level concept of an attached event. This implementation strategy restricts handling of attached events to either UIElement derived classes or ContentElement derived classes, because only those classes have AddHandler implementations.

For example, the following code defines the Clean attached event on the AquariumFilter owner class, which isn't an element class. The code defines the attached event as a routed event and implements the required accessor methods.

public class AquariumFilter
{
    // Register a custom routed event using the bubble routing strategy.
    public static readonly RoutedEvent CleanEvent = EventManager.RegisterRoutedEvent(
        "Clean", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(AquariumFilter));

    // Provide an add handler accessor method for the Clean event.
    public static void AddCleanHandler(DependencyObject dependencyObject, RoutedEventHandler handler)
    {
        if (dependencyObject is not UIElement uiElement)
            return;

        uiElement.AddHandler(CleanEvent, handler);
    }

    // Provide a remove handler accessor method for the Clean event.
    public static void RemoveCleanHandler(DependencyObject dependencyObject, RoutedEventHandler handler)
    {
        if (dependencyObject is not UIElement uiElement)
            return;

        uiElement.RemoveHandler(CleanEvent, handler);
    }
}
Public Class AquariumFilter

    ' Register a custom routed event using the bubble routing strategy.
    Public Shared ReadOnly CleanEvent As RoutedEvent = EventManager.RegisterRoutedEvent(
        "Clean", RoutingStrategy.Bubble, GetType(RoutedEventHandler), GetType(AquariumFilter))

    ' Provide an add handler accessor method for the Clean event.
    Public Shared Sub AddCleanHandler(dependencyObject As DependencyObject, handler As RoutedEventHandler)
        Dim uiElement As UIElement = TryCast(dependencyObject, UIElement)

        If uiElement IsNot Nothing Then
            uiElement.[AddHandler](CleanEvent, handler)
        End If
    End Sub

    ' Provide a remove handler accessor method for the Clean event.
    Public Shared Sub RemoveCleanHandler(dependencyObject As DependencyObject, handler As RoutedEventHandler)
        Dim uiElement As UIElement = TryCast(dependencyObject, UIElement)

        If uiElement IsNot Nothing Then
            uiElement.[RemoveHandler](CleanEvent, handler)
        End If
    End Sub

End Class

The RegisterRoutedEvent method that returns the attached event identifier is the same method used to register non-attached routed events. Both attached and non-attached routed events are registered to a centralized internal store. This event store implementation enables the "events as an interface" concept that's discussed in Routed events overview.

Unlike the CLR event "wrapper" used to back non-attached routed events, the attached event accessor methods can be implemented in classes that don't derive from UIElement or ContentElement. This is possible because the attached event backing code calls the UIElement.AddHandler and UIElement.RemoveHandler methods on a passed in UIElement instance. In contrast, the CLR wrapper for non-attached routed events calls those methods directly on the owning class, so that class must derive from UIElement.

Raise a WPF attached event

The process for raising an attached event is essentially the same as for a non-attached routed event.

Typically, your code won't need to raise any existing WPF-defined attached events since those events follow the general "service" conceptual model. In that model, service classes, such as InputManager, are responsible for raising WPF-defined attached events.

When defining a custom attached event using the WPF model of basing attached events on routed events, use the UIElement.RaiseEvent method to raise an attached event on any UIElement or ContentElement. When raising a routed event, whether it's attached or not, you're required to designate an element in your element tree as the event source. That source is then reported as the RaiseEvent caller. For example, to raise the AquariumFilter.Clean attached routed event on aquarium1:

aquarium1.RaiseEvent(new RoutedEventArgs(AquariumFilter.CleanEvent));
aquarium1.[RaiseEvent](New RoutedEventArgs(AquariumFilter.CleanEvent))

In the preceding example, aquarium1 is the event source.

See also