如何创建自定义路由事件(WPF .NET)

Windows Presentation Foundation(WPF)应用程序开发人员和组件作者可以创建自定义路由事件,以扩展公共语言运行时 (CLR) 事件的功能。 有关路由事件功能的信息,请参阅 为何使用路由事件。 本文介绍创建自定义路由事件的基础知识。

先决条件

本文假设您具有有关路由事件的基本知识,并且已经阅读了 路由事件概述。 若要遵循本文中的示例,如果你熟悉可扩展应用程序标记语言(XAML),并且知道如何编写 Windows Presentation Foundation (WPF) 应用程序,则它很有帮助。

路由事件步骤

创建路由事件的基本步骤包括:

  1. 使用 RegisterRoutedEvent 方法注册 RoutedEvent

  2. 注册调用返回一个称为路由事件标识符的 RoutedEvent 实例,该标识符保存已注册的事件名称、路由策略和其他事件详细信息。 将标识符分配给静态只读字段。 按照约定:

    • 使用 冒泡 策略的路由事件的标识符被命名为 <event name>Event。 例如,如果事件名称 Tap 则标识符应命名为 TapEvent
    • 具有 隧道 策略的路由事件的标识符命名为 Preview<event name>Event。 例如,如果事件名称 Tap 则标识符应命名为 PreviewTapEvent
  3. 定义 CLR addremove 事件访问器。 如果没有 CLR 事件访问器,则只能通过直接调用 UIElement.AddHandlerUIElement.RemoveHandler 方法添加或删除事件处理程序。 借助 CLR 事件访问器,可以获得以下事件处理程序分配机制:

    • 对于可扩展应用程序标记语言(XAML),可以使用属性语法添加事件处理程序。
    • 对于 C#,可以使用 +=-= 运算符添加或删除事件处理程序。
    • 对于 VB,可以使用 AddHandlerRemoveHandler 语句添加或删除事件处理程序。
  4. 添加用于触发路由事件的自定义逻辑。 例如,逻辑可能会基于用户输入和应用程序状态触发事件。

以下示例在自定义控件库中实现 CustomButton 类。 派生自 ButtonCustomButton 类:

  1. 使用 RegisterRoutedEvent 方法注册一个名为 ConditionalClickRoutedEvent,并在注册期间指定浮升策略。
  2. 将从注册调用返回的 RoutedEvent 实例分配给名为 ConditionalClickEvent的静态只读字段。
  3. 定义 CLR addremove 事件访问器。
  4. 添加自定义逻辑,以在单击 CustomButton 并应用外部条件时引发自定义路由事件。 虽然示例代码从重写的 OnClick 虚拟方法内引发 ConditionalClick 路由事件,但你可选用任何方式来引发事件。
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();
    }
}

该示例包括一个单独的 WPF 应用程序,该应用程序使用 XAML 标记将 CustomButton 的实例添加到 StackPanel,并将 Handler_ConditionalClick 方法指定为 CustomButtonStackPanel1 元素的 ConditionalClick 事件处理程序。

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

在后台代码中,WPF 应用程序定义了 Handler_ConditionalClick 事件处理程序方法。 事件处理程序方法只能在代码隐藏中实现。

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

单击 CustomButton 时:

  1. 引发 CustomButton 上的 ConditionalClick 路由事件。
  2. 触发了附加到 CustomButtonHandler_ConditionalClick 事件处理程序。
  3. ConditionalClick 路由事件在元素树中向上遍历到 StackPanel1
  4. 触发了附加到 StackPanel1Handler_ConditionalClick 事件处理程序。
  5. ConditionalClick 路由事件沿元素树向上传播,可能会触发附加在其他遍历元素上的其他 ConditionalClick 事件处理程序。

Handler_ConditionalClick 事件处理程序获取有关触发它的事件的以下信息:

  • sender 对象,它是事件处理程序附加到的元素。 处理程序首次运行时,senderCustomButton,第二次运行时则为 StackPanel1
  • RoutedEventArgs.Source 对象,该对象是最初引发事件的元素。 在此示例中,Source 始终 CustomButton

备注

路由事件和 CLR 事件之间的一个关键区别在于,路由事件遍历元素树,查找处理程序,而 CLR 事件不会遍历元素树,处理程序只能附加到引发事件的源对象。 因此,路由事件 sender 可以是元素树中的任何遍历元素。

可以像冒泡事件一样创建隧道事件,只是在事件注册调用中将路由策略设置为 Tunnel。 有关隧道事件的详细信息,请参阅 WPF 输入事件

另请参阅