How to create a custom routed event (WPF .NET)

Windows Presentation Foundation (WPF) application developers and component authors can create custom routed events to extend the functionality of common language runtime (CLR) events. For information on routed event capabilities, see Why use routed events. This article covers the basics of creating a custom routed event.

Important

The Desktop Guide documentation for .NET 7 and .NET 6 is under construction.

Prerequisites

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

Routed event steps

The basic steps to create a routed event are:

  1. Register a RoutedEvent using the RegisterRoutedEvent method.

  2. The registration call returns a RoutedEvent instance, known as a routed event identifier, which holds the registered event name, routing strategy, and other event details. Assign the identifier to a static readonly field. By convention:

    • The identifier for a routed event with a bubbling strategy is named <event name>Event. For example, if the event name is Tap then the identifier should be named TapEvent.
    • The identifier for a routed event with a tunneling strategy is named Preview<event name>Event. For example, if the event name is Tap then the identifier should be named PreviewTapEvent.
  3. Define CLR add and remove event accessors. Without CLR event accessors, you'll only be able to add or remove event handlers through direct calls to the UIElement.AddHandler and UIElement.RemoveHandler methods. With CLR event accessors, you gain these event handler assignment mechanisms:

    • For Extensible Application Markup Language (XAML), you can use attribute syntax to add event handlers.
    • For C#, you can use the += and -= operators to add or remove event handlers.
    • For VB, you can use the AddHandler and RemoveHandler statements to add or remove event handlers.
  4. Add custom logic for triggering your routed event. For example, your logic might trigger the event based on user-input and application state.

Example

The following example implements the CustomButton class in a custom control library. The CustomButton class, which derives from Button:

  1. Registers a RoutedEvent named ConditionalClick using the RegisterRoutedEvent method, and specifies the bubbling strategy during registration.
  2. Assigns the RoutedEvent instance returned from the registration call to a static readonly field named ConditionalClickEvent.
  3. Defines CLR add and remove event accessors.
  4. Adds custom logic to raise the custom routed event when the CustomButton is clicked and an external condition applies. Although the example code raises the ConditionalClick routed event from within the overridden OnClick virtual method, you can raise your event any way you choose.
public class CustomButton : Button
{
    // Register a custom routed event using the Bubble routing strategy.
    public static readonly RoutedEvent ConditionalClickEvent = EventManager.RegisterRoutedEvent(
        name: "ConditionalClick",
        routingStrategy: RoutingStrategy.Bubble,
        handlerType: typeof(RoutedEventHandler),
        ownerType: typeof(CustomButton));

    // Provide CLR accessors for assigning an event handler.
    public event RoutedEventHandler ConditionalClick
    {
        add { AddHandler(ConditionalClickEvent, value); }
        remove { RemoveHandler(ConditionalClickEvent, value); }
    }

    void RaiseCustomRoutedEvent()
    {
        // Create a RoutedEventArgs instance.
        RoutedEventArgs routedEventArgs = new(routedEvent: ConditionalClickEvent);

        // Raise the event, which will bubble up through the element tree.
        RaiseEvent(routedEventArgs);
    }

    // For demo purposes, we use the Click event as a trigger.
    protected override void OnClick()
    {
        // Some condition combined with the Click event will trigger the ConditionalClick event.
        if (DateTime.Now > new DateTime())
            RaiseCustomRoutedEvent();

        // Call the base class OnClick() method so Click event subscribers are notified.
        base.OnClick();
    }
}
Public Class CustomButton
    Inherits Button

    ' Register a custom routed event with the Bubble routing strategy.
    Public Shared ReadOnly ConditionalClickEvent As RoutedEvent = EventManager.RegisterRoutedEvent(
        name:="ConditionalClick",
        routingStrategy:=RoutingStrategy.Bubble,
        handlerType:=GetType(RoutedEventHandler),
        ownerType:=GetType(CustomButton))

    ' Provide CLR accessors to support event handler assignment.
    Public Custom Event ConditionalClick As RoutedEventHandler

        AddHandler(value As RoutedEventHandler)
            [AddHandler](ConditionalClickEvent, value)
        End AddHandler

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

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

    End Event

    Private Sub RaiseCustomRoutedEvent()

        ' Create a RoutedEventArgs instance.
        Dim routedEventArgs As New RoutedEventArgs(routedEvent:=ConditionalClickEvent)

        ' Raise the event, which will bubble up through the element tree.
        [RaiseEvent](routedEventArgs)

    End Sub

    ' For demo purposes, we use the Click event as a trigger.
    Protected Overrides Sub OnClick()

        ' Some condition combined with the Click event will trigger the ConditionalClick event.
        If Date.Now > New DateTime() Then RaiseCustomRoutedEvent()

        ' Call the base class OnClick() method so Click event subscribers are notified.
        MyBase.OnClick()

    End Sub
End Class

The example includes a separate WPF application that uses XAML markup to add an instance of the CustomButton to a StackPanel, and to assign the Handler_ConditionalClick method as the ConditionalClick event handler for the CustomButton and StackPanel1 elements.

<Window x:Class="CodeSample.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:custom="clr-namespace:WpfControl;assembly=WpfControlLibrary"
        Title="How to create a custom routed event" Height="100" Width="300">

    <StackPanel Name="StackPanel1" custom:CustomButton.ConditionalClick="Handler_ConditionalClick">
        <custom:CustomButton
            Name="customButton"
            ConditionalClick="Handler_ConditionalClick"
            Content="Click to trigger a custom routed event"
            Background="LightGray">
        </custom:CustomButton>
    </StackPanel>
</Window>

In code-behind, the WPF application defines the Handler_ConditionalClick event handler method. Event handler methods can only be implemented in code-behind.

// The ConditionalClick event handler.
private void Handler_ConditionalClick(object sender, RoutedEventArgs e)
{
    string senderName = ((FrameworkElement)sender).Name;
    string sourceName = ((FrameworkElement)e.Source).Name;

    Debug.WriteLine($"Routed event handler attached to {senderName}, " +
        $"triggered by the ConditionalClick routed event raised on {sourceName}.");
}

// Debug output when CustomButton is clicked:
// Routed event handler attached to CustomButton,
//     triggered by the ConditionalClick routed event raised on CustomButton.
// Routed event handler attached to StackPanel1,
//     triggered by the ConditionalClick routed event raised on CustomButton.
' The ConditionalClick event handler.
Private Sub Handler_ConditionalClick(sender As Object, e As RoutedEventArgs)

    Dim sourceName As String = CType(e.Source, FrameworkElement).Name
    Dim senderName As String = CType(sender, FrameworkElement).Name

    Debug.WriteLine($"Routed event handler attached to {senderName}, " +
        $"triggered by the ConditionalClick routed event raised on {sourceName}.")

End Sub

' Debug output when CustomButton is clicked:
' Routed event handler attached to CustomButton,
'     triggered by the ConditionalClick routed event raised on CustomButton.
' Routed event handler attached to StackPanel1,
'     triggered by the ConditionalClick routed event raised on CustomButton.

When CustomButton is clicked:

  1. The ConditionalClick routed event is raised on CustomButton.
  2. The Handler_ConditionalClick event handler attached to CustomButton is triggered.
  3. The ConditionalClick routed event traverses up the element tree to StackPanel1.
  4. The Handler_ConditionalClick event handler attached to StackPanel1 is triggered.
  5. The ConditionalClick routed event continues up the element tree potentially triggering other ConditionalClick event handlers attached to other traversed elements.

The Handler_ConditionalClick event handler obtains the following information about the event that triggered it:

  • The sender object, which is the element that the event handler is attached to. The sender will be CustomButton the first time the handler runs, and StackPanel1 the second time.
  • The RoutedEventArgs.Source object, which is the element that originally raised the event. In this example, the Source is always CustomButton.

Note

A key difference between a routed event and a CLR event is that a routed event traverses the element tree, looking for handlers, whereas a CLR event doesn't traverse the element tree and handlers can only attach to the source object that raised the event. As a result, a routed event sender can be any traversed element in the element tree.

You can create a tunneling event the same way as a bubbling event, except you'll set the routing strategy in the event registration call to Tunnel. For more information on tunneling events, see WPF input events.

See also