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