可扩展应用程序标记语言(XAML)定义一个名为 附加事件的语言组件和事件类型。 附加事件可用于在非元素类中定义新的 路由事件 ,并在树中的任何元素上引发该事件。 为此,必须将附加事件注册为路由事件,并提供支持附加事件功能的特定 后盾代码 。 由于附加事件注册为路由事件,当在某个元素上触发时,它们通过元素树传播。
先决条件
本文假定你已了解 Windows Presentation Foundation(WPF)路由事件,并且你已阅读 WPF 中的路由事件概述和 XAML。 若要遵循本文中的示例,如果你熟悉 XAML 并知道如何编写 WPF 应用程序,它很有帮助。
附加事件语法
在 XAML 语法中,附加事件以 事件名称和所有者 类型 <owner type>.<event name>的形式指定。 由于事件名称使用其所有者类型的名称进行限定,因此语法允许将事件附加到任何可以实例化的元素。 此语法也适用于附加到事件路由上的任意元素的常规路由事件的处理程序。
以下 XAML 属性语法将附加事件的处理程序AquariumFilter_Clean附加到AquariumFilter.Cleanaquarium1元素:
<aqua:Aquarium x:Name="aquarium1" Height="300" Width="400" aqua:AquariumFilter.Clean="AquariumFilter_Clean"/>
在此示例中,由于aqua:和AquariumFilter类存在于不同的公共语言运行时(CLR)命名空间和程序集,因此Aquarium前缀是必要的。
你还可以在后台代码中附加事件处理程序。 为此,请对处理程序应附加到的对象调用 AddHandler 该方法,并将事件标识符和处理程序作为参数传递给该方法。
WPF 如何实现附加事件
WPF 附加事件通过字段 RoutedEvent 实现为路由事件。 因此,“附加事件”被引发后会在元素树中传播。 通常,引发附加事件的对象(称为事件源)是系统或服务源。 系统或服务源不是元素树的直接部分。 对于其他附加事件,事件源可能是树中的元素,例如复合控件中的组件。
附加事件场景
在 WPF 中,附加事件用于存在服务级别抽象的某些功能区域。 例如,WPF 利用静态 Mouse 或 Validation 类来启用附加事件。 与服务交互或使用服务的类可以使用附加事件语法与事件交互,或者将附加事件作为路由事件显示。 后一个选项是该类可能整合服务功能的方法的一部分。
WPF 输入系统广泛使用附加事件。 但是,几乎所有附加事件都通过基元素显示为等效的非附加路由事件。 每个路由的输入事件都是基元素类的成员,并且由 CLR 事件“包装器”提供支持。 你很少会直接使用或操作绑定事件。 例如,在Mouse.MouseDown上,通过等效UIElement路由事件处理基础附加UIElement.MouseDown事件比在XAML或后台代码中使用附加事件语法更容易。
附加事件通过为输入设备的未来扩展提供支持来实现体系结构的目标。 例如,新的输入设备只需调用 Mouse.MouseDown 来模拟鼠标输入,并且无需从 Mouse 中继承。 此方案涉及事件的代码处理,因为附加事件的 XAML 处理不相关。
处理绑定事件
编码和处理附加事件的过程基本上与非附加路由事件的过程相同。
如 前所述,现有 WPF 附加事件通常不打算直接在 WPF 中处理。 通常,附加事件的目的是使复合控件中的元素能够将其状态报告给控件中的父元素。 在这种情况下,该事件在代码中引发,并依赖于相关父类中的类处理。 例如,Selector 内的项目预计会引发 Selected 附加事件,然后由 Selector 类进行类处理。 该 Selector 类可能会将 Selected 事件转换为 SelectionChanged 路由事件。 有关路由事件和类处理的详细信息,请参阅 将路由事件标记为已处理,以及类处理。
定义自定义附加事件
如果要从常见的 WPF 基类派生,可以通过在类中包含两个访问器方法来实现自定义附加事件。 这些方法包括:
- 添加<事件处理程序>方法,其第一个参数是附加事件处理程序的元素,以及要添加的事件处理程序的第二个参数。 该方法必须 - public且- static,且没有返回值。 该方法调用 AddHandler 基类方法,将路由事件和处理程序作为参数传入。 此方法支持将事件处理程序附加到元素的 XAML 属性语法。 此方法还允许代码访问附加事件的事件处理程序存储。
- 一个Remove<事件名称>处理程序方法,第一个参数是附加事件处理程序的元素,第二个参数是要删除的事件处理程序。 该方法必须 - public且- static,且没有返回值。 该方法调用 RemoveHandler 基类方法,将路由事件和处理程序作为参数传入。 此方法允许代码访问附加事件的事件处理程序存储。
WPF 将附加事件实现为路由事件,因为 WPF 事件系统为 RoutedEvent 定义了标识符。 此外,路由事件是附加事件的 XAML 语言级别概念的自然扩展。 此实现策略将附加事件的处理限制为 UIElement 派生类或 ContentElement 派生类,因为只有这些类具有 AddHandler 实现。
例如,以下代码定义 Clean 所有者类上的 AquariumFilter 附加事件,该事件不是元素类。 该代码将附加事件定义为路由事件并实现所需的访问器方法。
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
RegisterRoutedEvent返回附加事件标识符的方法与注册非附加路由事件的方法相同。 附加和非附加路由事件都注册到集中式内部存储。 此事件存储实现可实现路由 事件概述中讨论的“事件作为接口”概念。
与用于备份非附加路由事件的 CLR 事件“包装器”不同,附加事件访问器方法可以在不派生自 UIElement 或 ContentElement的类中实现。 这是可能的,因为附加的事件支持代码在传入的UIElement.AddHandler实例上调用UIElement.RemoveHandler和UIElement方法。 相比之下,非附加路由事件的 CLR 包装器在其所属类上直接调用这些方法,因此该类必须派生自 UIElement。
引发 WPF 附加事件
引发附加事件的过程实质上与非附加路由事件的过程相同。
通常,代码不需要引发任何现有的 WPF 定义的附加事件,因为这些事件遵循常规的“服务”概念模型。 在该模型中,服务类(例如 InputManager)负责引发 WPF 定义的附加事件。
在使用基于路由事件的 WPF 模型定义自定义附加事件时,请使用UIElement.RaiseEvent方法在任何UIElement或ContentElement上引发附加事件。 引发路由事件时,无论该路由事件是否已附加,都需要将元素树中的某个元素指定为事件源。 然后,该来源会被报告为RaiseEvent 的调用方。 例如,若要在 AquariumFilter.Clean 上引发附加的 aquarium1 路由事件:
aquarium1.RaiseEvent(new RoutedEventArgs(AquariumFilter.CleanEvent));
aquarium1.[RaiseEvent](New RoutedEventArgs(AquariumFilter.CleanEvent))
在前面的示例中, aquarium1 事件源。