附加事件概述

Extensible Application Markup Language (XAML) 定义了一种语言组件和称为附加事件的事件类型。 通过附加事件的概念,你能够向任意元素(而不是实际定义或继承事件的元素)添加特定事件的处理程序。 在这种情况下,对象既不会引发事件,目标处理实例也不会定义或“拥有”事件。

先决条件

本主题假定你已阅读路由事件概述WPF 中的 XAML

附加事件语法

附加事件具有一种 XAML 语法和编码模式,后备代码必须使用该语法和编码模式才能支持使用附加事件。

在 XAML 语法中,不仅可以通过事件名称来指定附加事件,还可以通过事件所属类型和事件名称来指定,中间以句点 (.) 分隔。 因为事件名称是使用具有其所属类型的名称限定的,所以附加事件语法允许将任何附加事件附加到可以实例化的任何元素。

例如,下面是 XAML 语法,用于附加自定义 NeedsCleaning 附加事件的处理程序:

<aqua:Aquarium Name="theAquarium" Height="600" Width="800" aqua:AquariumFilter.NeedsCleaning="WashMe"/>

请注意 aqua: 前缀;该前缀在本例中是必需的,因为附加事件是来自自定义映射 xmlns 的自定义事件。

WPF 如何实现附加事件

在 WPF 中,附加事件由 RoutedEvent 字段支持,并在引发后通过树进行路由。 通常,附加事件的源(引发该事件的对象)是系统或服务源,所以运行引发该事件的代码的对象并不是元素树的直接组成部分。

附加事件的方案

在 WPF 中,附加事件存在于具有服务级抽象的某些功能区域,例如,对于通过静态 Mouse 类或 Validation 类实现的事件。 与该服务交互或使用该服务的类可以在附加事件语法中使用该事件,也可以选择将附加事件作为路由事件来呈现,这是类如何集成服务功能的一部分。

尽管 WPF 定义了许多附加事件,但直接使用或处理附加事件的情形却很少。 一般情况下,附加事件用于体系结构,但随后即被转发给非附加(使用 CLR 事件“包装器”提供支持)路由事件。

例如,通过针对该 UIElement 使用 MouseDown(而不是在 XAML 或代码中处理附加事件语法),可以针对任何给定的 UIElement,更方便地处理基础附加事件 Mouse.MouseDown。 附加事件用于体系结构,因为它允许进一部扩展输入设备。 假设的设备只需引发 Mouse.MouseDown 即可模拟鼠标输入,而不需要从 Mouse 派生。 但是,此方案会涉及事件的代码处理,而附加事件的 XAML 处理则与此方案无关。

在 WPF 中处理附加事件

处理附加事件的过程以及将要编写的处理程序代码与路由事件的基本相同。

一般情况下,WPF 附加事件与 WPF 路由事件并没有太大的区别。 不同之处在于如何确定事件的源,以及如何通过类将事件作为成员进行公开(这还将影响 XAML 处理程序语法)。

但是,正如前文所述,现有的 WPF 附加事件并不是专门用于在 WPF 中进行处理。 更多情况下,该事件用于在复合时,使复合元素能够向父元素报告状态,在这种情况下,事件通常用代码引发,同时依赖于相关父类中的类处理。 例如,Selector 中的项应引发附加的 Selected 事件,该事件随后由 Selector 类进行类处理,之后可能由 Selector 类转换为不同路由事件,SelectionChanged。 有关路由事件和类处理的详细信息,请参阅将路由事件标记为“已处理”和“类处理”

将自己的附加事件定义为路由事件

如果从通用 WPF 基类派生,则可以通过在类中包括某些模式方法并使用基类中已经存在的实用工具方法来实现自己的附加事件。

模式如下:

  • 具有两个参数的方法 AddEventNameHandler。 第一个参数是添加事件处理程序的实例。 第二个参数是要添加的事件处理程序。 方法必须是 publicstatic,没有返回值。

  • 具有两个参数的方法 RemoveEventNameHandler。 第一个参数是从中删除事件处理程序的实例。 第二个参数是要删除的事件处理程序。 方法必须是 publicstatic,没有返回值。

当在元素上声明附加的事件处理程序属性时,AddEventNameHandler 访问器方法有助于 XAML 处理。 AddEventNameHandler 和 RemoveEventNameHandler 方法还允许代码访问附加事件的事件处理程序存储

此常规模式对于框架中的实际实现还不够精确,因为任何给定的 XAML 读取器实现都可能采用不同的方案在支持语言和体系结构中标识基础事件。 这是 WPF 将附加事件实现为路由事件的原因之一;用于事件 (RoutedEvent) 的标识符已由 WPF 事件系统定义。 另外,路由一个事件也是对附加事件的 XAML 语言级概念的自然实现扩展。

WPF 附加事件的 AddEventNameHandler 实现包括使用路由事件和处理程序作为参数调用 AddHandler

此实现策略和路由事件系统一般将附加事件的处理限制为 UIElement 派生类或 ContentElement 派生类,因为只有这些类才具有 AddHandler 实现。

例如,以下代码通过将附加事件声明为路由事件的 WPF 附加事件策略,在所有者类 Aquarium 中定义 NeedsCleaning 附加事件。

public static readonly RoutedEvent NeedsCleaningEvent = EventManager.RegisterRoutedEvent("NeedsCleaning", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(AquariumFilter));
public static void AddNeedsCleaningHandler(DependencyObject d, RoutedEventHandler handler)
{
    UIElement uie = d as UIElement;
    if (uie != null)
    {
        uie.AddHandler(AquariumFilter.NeedsCleaningEvent, handler);
    }
}
public static void RemoveNeedsCleaningHandler(DependencyObject d, RoutedEventHandler handler)
{
    UIElement uie = d as UIElement;
    if (uie != null)
    {
        uie.RemoveHandler(AquariumFilter.NeedsCleaningEvent, handler);
    }
}
Public Shared ReadOnly NeedsCleaningEvent As RoutedEvent = EventManager.RegisterRoutedEvent("NeedsCleaning", RoutingStrategy.Bubble, GetType(RoutedEventHandler), GetType(AquariumFilter))
Public Shared Sub AddNeedsCleaningHandler(ByVal d As DependencyObject, ByVal handler As RoutedEventHandler)
    Dim uie As UIElement = TryCast(d, UIElement)
    If uie IsNot Nothing Then
        uie.AddHandler(AquariumFilter.NeedsCleaningEvent, handler)
    End If
End Sub
Public Shared Sub RemoveNeedsCleaningHandler(ByVal d As DependencyObject, ByVal handler As RoutedEventHandler)
    Dim uie As UIElement = TryCast(d, UIElement)
    If uie IsNot Nothing Then
        uie.RemoveHandler(AquariumFilter.NeedsCleaningEvent, handler)
    End If
End Sub

请注意,用来确定附加事件标识符字段的方法,RegisterRoutedEvent,实际上与用来注册非附加路由事件的方法相同。 附加事件和路由事件都已注册到集中式内部存储。 此事件存储实现能够考虑到路由事件概述中介绍的“事件即界面”概念。

引发 WPF 附加事件

通常不需要从代码中引发 WPF 定义的现有附加事件。 这些事件采用常规“服务”概念模型,而 InputManager 等服务类则负责引发事件。

但是,如果要基于 WPF 模型(基于 RoutedEvent 附加事件)定义自定义附加事件,则可以使用 RaiseEvent 从任何 UIElementContentElement 引发附加事件。 引发路由事件(不管是否附加)要求将元素树中的一个特定元素声明为事件源;该事件源被报告为 RaiseEvent 调用方。 服务负责确定将哪个元素报告为元素树中的事件源

另请参阅