附加事件概述

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

本主题包括下列各节。

  • 先决条件
  • 附加事件语法
  • 如何在 WPF 中实现附加事件
  • 附加事件的方案
  • 在 WPF 中处理附加事件
  • 将您自己的附加事件定义为路由事件
  • 引发 WPF 附加事件
  • 相关主题

先决条件

本主题假定您已阅读路由事件概述XAML 概述 (WPF)

附加事件语法

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

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

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

<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 基类派生,则可以通过在类中包括某些模式方法并使用基类中已经存在的实用工具方法来实现您自己的附加事件。

模式如下:

  • 带有两个参数的方法 Add*Handler。 第一个参数必须标识事件,而标识的事件必须与方法名称中带有 * 的名称相匹配。 第二个参数是要添加的处理程序。 该方法必须是公共且静态的,没有任何返回值。

  • 带有两个参数的方法 Remove*Handler。 第一个参数必须标识事件,而标识的事件必须与方法名称中带有 * 的名称相匹配。 第二个参数是要移除的处理程序。 该方法必须是公共且静态的,没有任何返回值。

当针对某个元素声明附加事件处理程序特性时,Add*Handler 访问器方法可以加快 XAML 处理。 Add*Handler 和 Remove*Handler 方法还可实现对附加事件的事件处理程序存储区的代码访问。

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

WPF 附加事件的 Add*Handler 实现包括将路由事件和处理程序用作参数来调用 AddHandler

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

例如,下面的代码按照 WPF 将附加事件作为路由事件进行声明的附加事件策略为所有者类 Aquarium 定义 NeedsCleaning 附加事件。

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
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);
    }
}

请注意,用来确定附加事件标识符字段的方法 RegisterRoutedEvent 实际上与用来注册非附加路由事件的方法相同。 附加事件和路由事件都是在集中管理的内部存储区中进行注册的。 此事件存储区实现则实现了路由事件概述中讨论的“事件作为界面”概念方面的注意事项。

引发 WPF 附加事件

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

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

请参见

概念

路由事件概述

XAML 语法详述

XAML 及 WPF 的自定义类