路由事件概述
本主题介绍 Windows Presentation Foundation (WPF) 中路由事件的概念。 本主题定义路由事件术语、描述路由事件如何通过元素树来路由、概述如何处理路由事件,并介绍如何创建你自己的自定义路由事件。
先决条件
本主题假定你对如下内容有基本了解:公共语言运行时 (CLR)、面向对象的编程以及如何将 WPF 元素之间的关系概念化为树。 若要理解本主题中的示例,你还应了解 Extensible Application Markup Language (XAML) 并知道如何编写非常基本的 WPF 应用程序或页面。 有关详细信息,请参阅演练:我的第一个 WPF 桌面应用程序和 WPF 中的 XAML。
什么是路由事件?
可以从功能或实现的角度来理解路由事件。 此处对这两种定义均进行了说明,因为有的用户认为前者更有用,有的用户认为后者更有用。
功能定义:路由事件是一种可以针对元素树中的多个侦听器(而不是仅针对引发该事件的对象)调用处理程序的事件。
实现定义:路由事件是一个 CLR 事件,由 RoutedEvent 类的实例提供支持并由 Windows Presentation Foundation (WPF) 事件系统处理。
典型的 WPF 应用程序包含许多元素。 无论这些元素是在代码中创建还是在 XAML 中声明,它们存在于彼此关联的元素树关系中。 根据事件的定义,事件路由可以按两种方向之一传播,但是通常会在元素树中从源元素向上“浮升”,直到它到达元素树的根(通常是页面或窗口)。 如果你以前用过 DHTML 对象模型,则可能会熟悉这个浮升概念。
请思考下面的简单元素树:
<Border Height="50" Width="300" BorderBrush="Gray" BorderThickness="1">
<StackPanel Background="LightGray" Orientation="Horizontal" Button.Click="CommonClickHandler">
<Button Name="YesButton" Width="Auto" >Yes</Button>
<Button Name="NoButton" Width="Auto" >No</Button>
<Button Name="CancelButton" Width="Auto" >Cancel</Button>
</StackPanel>
</Border>
此元素树生成类似如下的内容:
在这个简化的元素树中,Click 事件的源是某个 Button 元素,而所单击的 Button 是有机会处理该事件的第一个元素。 但是,如果附加到 Button 的所有处理程序均未处理该事件,则该事件向上浮升到元素树中的 Button 父级(即 StackPanel)。 该事件可能会浮升到 Border,然后会到达元素树的页面根(未显示)。
换言之,此 Click 事件的事件路由为:
Button-->StackPanel-->Border-->...
路由事件的顶级方案
下面简要概述了需运用路由事件概念的方案,以及为什么典型的 CLR 事件不适合这些方案:
控件的撰写和封装:WPF 中的各个控件都有一个丰富的内容模型。 例如,可以将图像放在 Button 的内部,这会有效地扩展按钮的可视化树。 但是,所添加的图像不得中断命中测试行为(该行为会使按钮响应其内容的 Click,即使用户所单击的像素在技术上属于该图像也是如此)。
单一处理程序附加点:在 Windows 窗体中,必须多次附加同一个处理程序,才能处理从多个元素引发的事件。 借助路由事件,可以只附加该处理程序一次(如上例中所示),并在必要时使用处理程序逻辑来确定该事件的源位置。 例如,这可以是前面显示的 XAML 的处理程序:
private void CommonClickHandler(object sender, RoutedEventArgs e)
{
FrameworkElement feSource = e.Source as FrameworkElement;
switch (feSource.Name)
{
case "YesButton":
// do something here ...
break;
case "NoButton":
// do something ...
break;
case "CancelButton":
// do something ...
break;
}
e.Handled=true;
}
Private Sub CommonClickHandler(ByVal sender As Object, ByVal e As RoutedEventArgs)
Dim feSource As FrameworkElement = TryCast(e.Source, FrameworkElement)
Select Case feSource.Name
Case "YesButton"
' do something here ...
Case "NoButton"
' do something ...
Case "CancelButton"
' do something ...
End Select
e.Handled=True
End Sub
类处理:路由事件允许使用由类定义的静态处理程序。 此类处理程序能够抢在任何附加的实例处理程序之前处理事件。
引用事件,而不反射:某些代码和标记技术需要能标识特定事件的方法。 路由事件创建 RoutedEvent 字段作为标识符,从而提供不需要静态反射或运行时反射的可靠的事件标识技术。
路由事件的实现方式
路由事件是一个 CLR 事件,它由 RoutedEvent 类的实例提供支持并向 WPF 事件系统注册。 从注册中获取的 RoutedEvent 实例通常保留为特定类的 public
static
readonly
字段成员,该类注册路由事件并因此“拥有”路由事件。 与同名 CLR 事件(有时称为“包装器”事件)的连接是通过替代 CLR 事件的 add
和 remove
实现来完成的。 通常,add
和 remove
保留为隐式默认值,该默认值使用特定于语言的相应事件语法来添加和删除该事件的处理程序。 路由事件的支持和连接机制在概念上与以下机制相似:依赖属性是一个 CLR 属性,该属性由 DependencyProperty 类提供支持并向 WPF 属性系统注册。
以下示例演示自定义 Tap
路由事件的声明,其中包括注册和公开 RoutedEvent 标识符字段以及对 Tap
CLR 事件进行 add
和 remove
实现。
public static readonly RoutedEvent TapEvent = EventManager.RegisterRoutedEvent(
"Tap", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(MyButtonSimple));
// Provide CLR accessors for the event
public event RoutedEventHandler Tap
{
add { AddHandler(TapEvent, value); }
remove { RemoveHandler(TapEvent, value); }
}
Public Shared ReadOnly TapEvent As RoutedEvent = EventManager.RegisterRoutedEvent("Tap", RoutingStrategy.Bubble, GetType(RoutedEventHandler), GetType(MyButtonSimple))
' Provide CLR accessors for the event
Public Custom Event Tap As RoutedEventHandler
AddHandler(ByVal value As RoutedEventHandler)
Me.AddHandler(TapEvent, value)
End AddHandler
RemoveHandler(ByVal value As RoutedEventHandler)
Me.RemoveHandler(TapEvent, value)
End RemoveHandler
RaiseEvent(ByVal sender As Object, ByVal e As RoutedEventArgs)
Me.RaiseEvent(e)
End RaiseEvent
End Event
路由事件处理程序和 XAML
若要使用 XAML 为事件添加处理程序,可将该事件的名称声明为用作事件侦听器的元素的属性。 该属性的值是所实现的处理程序方法的名称,该方法必须存在于代码隐藏文件的分部类中。
<Button Click="b1SetColor">button</Button>
用来添加标准 CLR 事件处理程序的 XAML 语法与用来添加路由事件处理程序的语法相同,因为实际上是向底层具有路由事件实现的 CLR 事件包装器添加处理程序。 有关在 XAML 中添加事件处理程序的详细信息,请参阅 WPF 中的 XAML。
路由策略
路由事件使用以下三种路由策略之一:
浮升:调用事件源上的事件处理程序。 路由事件随后会路由到后续的父级元素,直到到达元素树的根。 大多数路由事件都使用浮升路由策略。 浮升路由事件通常用于报告来自不同控件或其他 UI 元素的输入或状态变化。
直接:只有源元素本身才有机会调用处理程序以进行响应。 这与 Windows 窗体用于事件的“路由”相似。 但是,与标准 CLR 事件不同的是,直接路由事件支持类处理(将在下一节中介绍类处理),并且可供 EventSetter 和 EventTrigger 使用。
隧道:最初将调用元素树的根处的事件处理程序。 随后,路由事件将朝着路由事件的源节点元素(即引发路由事件的元素)方向,沿路由线路传播到后续的子元素。 合成控件的过程中通常会使用或处理隧道路由事件,通过这种方式,可以有意地禁止复合部件中的事件,或者将其替换为特定于整个控件的事件。 在 WPF 中提供的输入事件通常是以隧道/浮升对实现的。 隧道事件有时又称作预览事件,这是由该对所使用的命名约定决定的。
为什么使用路由事件?
作为应用程序开发人员,你不需要始终了解或关注要处理的事件是否作为路由事件实现。 路由事件具有特殊的行为,但是,如果在引发该行为的元素上处理事件,则该行为通常会不可见。
如果使用以下任一建议方案,路由事件的功能将得到充分发挥:在公用根处定义公用处理程序、合成自己的控件或者定义自己的自定义控件类。
路由事件侦听器和路由事件源不必在其层次结构中共享公用事件。 任何 UIElement 或 ContentElement 可以是任一路由事件的事件侦听器。 因此,可以将有效的 API 集中可用的全套路由事件用作概念“接口”,应用程序中的不同元素通过这个接口来交换事件信息。 路由事件的这个“接口”概念特别适用于输入事件。
路由事件还可以用于通过元素树进行通信,因为事件的事件数据会保留到路由中的每个元素中。 一个元素可以更改事件数据中的某些内容,该更改将用于路由中的下一个元素。
之所以将任何给定的 WPF 事件作为路由事件实现(而不是作为标准 CLR 事件实现),除了路由方面的原因,还有两个其他原因。 如果要实现自己的事件,则可能也需要考虑这些原则:
某些 WPF 样式和模板功能(如 EventSetter 和 EventTrigger)要求被引用的事件是路由事件。 前面提到的事件标识符方案就是这样的。
路由事件支持类处理机制,类可以通过该机制来指定静态方法,这些静态方法能够在任何已注册的实例处理程序访问路由事件之前,处理这些路由事件。 这在控件设计中非常有用,因为类可以强制执行事件驱动的类行为,以防它们在处理实例上的事件时被意外禁止。
本主题将用单独的章节来讨论上述每个因素。
为路由事件添加和实现事件处理程序
若要在 XAML 中添加事件处理程序,只需将事件名称作为属性添加到元素中,并将属性值设置为用来实现相应委托的事件处理程序的名称,如以下示例所示。
<Button Click="b1SetColor">button</Button>
b1SetColor
是实现的处理程序的名称,该处理程序包含用来处理 Click 事件的代码。 b1SetColor
必须具有与 RoutedEventHandler 委托相同的签名,该委托是 Click 事件的事件处理程序委托。 所有路由事件处理程序委托的第一个参数都指定要向其中添加事件处理程序的元素,第二个参数指定事件的数据。
void b1SetColor(object sender, RoutedEventArgs args)
{
//logic to handle the Click event
}
Private Sub b1SetColor(ByVal sender As Object, ByVal args As RoutedEventArgs)
'logic to handle the Click event
End Sub
RoutedEventHandler 是基本的路由事件处理程序委托。 对于针对某些控件或方案的专用路由事件,要用于路由事件处理程序的委托还可能会变得更加专用化,以便可以传输专用的事件数据。 例如,在常见的输入方案中,可能需要处理 DragEnter 路由事件。 处理程序应实现 DragEventHandler 委托。 通过使用更具针对性的委托,可以处理处理程序中的 DragEventArgs,并读取 Data 属性,该属性包含拖动操作的剪贴板有效负载。
有关如何使用 XAML 向元素中添加事件处理程序的完整示例,请参阅处理路由事件。
在用代码创建的应用程序中为路由事件添加处理程序非常简单。 路由事件处理程序始终可以通过 helper 方法 AddHandler 来添加(对 add
的现有支持进行调用也是使用此方法)。但是,现有的 WPF 路由事件通常借助于支持机制来实现 add
和 remove
逻辑,这些实现允许使用特定于语言的事件语法来添加路由事件的处理程序,特定于语言的事件语法比 helper 方法更直观。 下面是 Helper 方法的示例用法:
void MakeButton()
{
Button b2 = new Button();
b2.AddHandler(Button.ClickEvent, new RoutedEventHandler(Onb2Click));
}
void Onb2Click(object sender, RoutedEventArgs e)
{
//logic to handle the Click event
}
Private Sub MakeButton()
Dim b2 As New Button()
b2.AddHandler(Button.ClickEvent, New RoutedEventHandler(AddressOf Onb2Click))
End Sub
Private Sub Onb2Click(ByVal sender As Object, ByVal e As RoutedEventArgs)
'logic to handle the Click event
End Sub
下一个示例演示 C# 运算符语法(Visual Basic 的运算符语法稍有不同,因为它以不同的方法来处理取消引用):
void MakeButton2()
{
Button b2 = new Button();
b2.Click += new RoutedEventHandler(Onb2Click2);
}
void Onb2Click2(object sender, RoutedEventArgs e)
{
//logic to handle the Click event
}
Private Sub MakeButton2()
Dim b2 As New Button()
AddHandler b2.Click, AddressOf Onb2Click2
End Sub
Private Sub Onb2Click2(ByVal sender As Object, ByVal e As RoutedEventArgs)
'logic to handle the Click event
End Sub
有关如何在代码中添加事件处理程序的示例,请参阅使用代码添加事件处理程序。
如果使用的是 Visual Basic,则还可以使用 Handles
关键字,将处理程序添加到处理程序声明中。 有关详细信息,请参阅 Visual Basic 和 WPF 事件处理。
“已处理”概念
所有路由事件共享一个通用事件数据基类 RoutedEventArgs。 RoutedEventArgs 定义了采用布尔值的 Handled 属性。 Handled 属性的目的在于,允许路由中的任何事件处理程序通过将 Handled 的值设置为 true
来将路由事件标记为“已处理”。 处理程序在路由上的某个元素处对共享事件数据进行处理之后,这些数据将再次报告给路由上的每个侦听器。
Handled 的值会影响路由事件在沿路由向远处传播过程中的报告或处理方式。 在路由事件的事件数据中,如果 Handled 为 true
,则通常不再为该特定事件实例调用用于在其他元素上侦听该路由事件的处理程序。 这条规则对以下两类处理程序均适用:在 XAML 中附加的处理程序;由特定于语言的事件处理程序附加语法(如 +=
或 Handles
)添加的处理程序。 对于最常见的处理程序方案,如果通过将 Handled 设置为 true
以便将事件标记为“已处理”,则将“停止”隧道路由或浮升路由,此外还会“停止”类处理程序在某个路由点处理的所有事件的路由。
但是,侦听器仍可以通过“handledEventsToo”机制来运行处理程序,以便在事件数据中的 Handled 为 true
时响应路由事件。 换言之,将事件数据标记为“已处理”并不会真的停止事件路由。 只能在代码或 EventSetter 中使用 handledEventsToo 机制:
在代码中,不要使用适用于一般 CLR 事件的特定于语言的事件语法,而是通过调用 WPF 方法 AddHandler(RoutedEvent, Delegate, Boolean) 来添加处理程序。 请将
handledEventsToo
的值指定为true
。在 EventSetter 中,将 HandledEventsToo 属性设置为
true
。
除了 Handled 状态在路由事件中生成的行为以外,Handled 概念还会影响设计自己的应用程序和编写事件处理程序代码的方式。 可以将 Handled 概念化为由路由事件公开的简单协议。 此协议的具体使用方法由你决定,但是需要按照如下方式对 Handled 值的预期使用方式进行概念设计:
如果路由事件标记为“已处理”,则它不必由该路由中的其他元素再次处理。
如果路由事件未标记为“已处理”,则说明该路由中前面的其他侦听器已选择了不注册处理程序,或者已注册的处理程序选择不操作事件数据并将 Handled 设置为
true
。 (或者,当前侦听器很可能是路由中的第一个点。)当前侦听器上的处理程序现在有三个可能的操作过程:不执行任何操作;该事件保持未处理状态,该事件将路由到下一个侦听器。
执行代码以响应该事件,但是所执行的操作被视为不足以保证将事件标记为“已处理”。 该事件将路由到下一个侦听器。
执行代码以响应该事件。 在传递到处理程序的事件数据中将该事件标记为“已处理”,因为所执行的操作被视为不足以保证将该事件标记为“已处理”。 该事件仍将路由到下一个侦听器,但是,由于在其事件数据中,Handled=
true
,因此只有handledEventsToo
侦听器才有机会进一步调用处理程序。
这个概念设计通过前面所述的路由行为得到增强:为调用的路由事件附加处理程序变得更难(虽然在代码或样式中依然可执行此操作),即使路由中前面的处理程序已经将 Handled 设置为 true
也是如此。
有关 Handled、路由事件的类处理的详细信息,以及针对何时适合将路由事件标记为 Handled 的建议,请参阅将路由事件标记为“已处理”和“类处理”。
在应用程序中,相当常见的做法是只针对引发浮升路由事件的对象来处理该事件,而根本不考虑事件的路由特征。 但是,在事件数据中将路由事件标记为“已处理”仍是一个不错的做法,因为这样可以防止元素树中位置更高的元素也对同一个路由事件附加了处理程序而出现意外的副作用。
类处理程序
如果你定义的类是以某种方式从 DependencyObject 派生的,那么对于作为类的已声明或已继承事件成员的路由事件,还可以定义和附加一个类处理程序。 每当路由事件到达其路由中的元素实例时,都会先调用类处理程序,然后再调用附加到该类某个实例的任何实例侦听器处理程序。
有些 WPF 控件对某些路由事件具有固有的类处理。 路由事件可能看起来从未引发过,但实际上正对其进行类处理,如果使用某些技术,路由事件还是可以由实例处理程序进行处理。 此外,许多基类和控件会公开可用来替代类处理行为的虚拟方法。 若要深入了解如何解决不需要的类处理以及如何在自定义类中定义自己的类处理,请参阅将路由事件标记为“已处理”和类处理。
WPF 中的附加事件
XAML 语言还定义了一个名为附加事件的特殊类型的事件。 使用附加事件,可以将特定事件的处理程序添加到任意元素中。 处理事件的元素不必定义或继承附加事件,可能引发事件的对象和用来处理实例的目标也都不必将该事件定义为类成员,或将其作为类成员来“拥有”。
WPF 输入系统广泛使用附加事件。 但是,几乎所有的附加事件都是通过基本元素转发的。 输入事件随后会显示为作为基本元素类成员的等效非附加路由事件。 例如,通过针对该 UIElement 使用 MouseDown(而不是在 XAML 或代码中处理附加事件语法),可以针对任何给定的 UIElement,更方便地处理基础附加事件 Mouse.MouseDown。
有关 WPF 中附加事件的详细信息,请参阅附加事件概述。
XAML 中的限定事件名称
为子元素所引发的路由事件附加处理程序是另一个语法用法,它与 typename.eventname 附加事件语法相似,但它并非严格意义上的附加事件用法。 可以向公用父级附加处理程序以利用事件路由,即使公用父级可能没有作为成员的相关路由事件,也是如此。 请再次思考下面的示例:
<Border Height="50" Width="300" BorderBrush="Gray" BorderThickness="1">
<StackPanel Background="LightGray" Orientation="Horizontal" Button.Click="CommonClickHandler">
<Button Name="YesButton" Width="Auto" >Yes</Button>
<Button Name="NoButton" Width="Auto" >No</Button>
<Button Name="CancelButton" Width="Auto" >Cancel</Button>
</StackPanel>
</Border>
在这里,向其中添加处理程序的父元素侦听器是 StackPanel。 但是,它正在为已经声明而且将由 Button 类(实际上是 ButtonBase,但是可以通过继承向 Button 提供)引发的路由事件添加处理程序。 Button“拥有”该事件,但是路由事件系统允许将任何路由事件的处理程序附加到任何 UIElement 或 ContentElement 实例侦听器,这可能会以其他方式为公共语言运行时 (CLR) 事件附加侦听器。 对于这些限定的事件属性名称来说,默认的 xmlns 命名空间通常是默认的 WPF xmlns 命名空间,但是还可以为自定义路由事件指定带有前缀的命名空间。 有关 xmlns 的详细信息,请参阅 WPF XAML 的 XAML 命名空间和命名空间映射。
WPF 输入事件
路由事件在 WPF 平台中的常见应用之一是用于输入事件。 在 WPF 中,按照约定,隧道路由事件的名称以单词“Preview”为前缀。 输入事件通常成对出现,一个是浮升事件,另一个是隧道事件。 例如,KeyDown 事件和 PreviewKeyDown 事件具有相同的签名,前者是浮升输入事件,后者是隧道输入事件。 偶尔,输入事件只有浮升版本,或者有可能只有直接路由版本。 在文档中,路由事件主题交叉引用具有备用路由策略的类似路由事件(如果存在这类路由事件),托管的引用页面中的相关部分阐明每个路由事件的路由策略。
实现成对出现的 WPF 输入事件,使来自输入的单个用户操作(如按鼠标按钮)按顺序引发该对中的两个路由事件。 首先引发隧道事件并沿路由传播。 然后引发浮升事件并沿路由传播。 这两个事件共享同一个事件数据实例,因为用来引发浮升事件的实现类中的 RaiseEvent 方法调用会侦听隧道事件中的事件数据,并在新引发的事件中重用它。 具有隧道事件处理程序的侦听器首先获得将路由事件标记为“已处理”的机会(首先是类处理程序,然后是实例处理程序)。 如果隧道路由中的某个元素将路由事件标记为“已处理”,则会针对浮升事件发送已处理的事件数据,而且将不调用等效的浮升输入事件的附加典型处理程序。 已处理的浮升事件看起来好像尚未引发。 此处理行为对于控件合成非常有用,因为在此情况下你可能希望所有基于命中测试的输入事件或者所有基于焦点的输入事件都由最终的控件(而不是它的复合部件)报告。 作为可支持控件类的代码的一部分,最后一个控件元素靠近合成中的根,因此将有机会首先对隧道事件进行类处理,或者有机会将该路由事件“替换”为更针对控件的事件。
为了说明输入事件处理的工作方式,请思考下面的输入事件示例。 在下面的树插图中,leaf element #2
是先后发生的 PreviewMouseDown
事件和 MouseDown
事件的源:
事件的处理顺序如下所述:
针对根元素处理
PreviewMouseDown
(隧道)。针对中间元素 #1 处理
PreviewMouseDown
(隧道)。针对源元素 #2 处理
PreviewMouseDown
(隧道)。针对源元素 #2 处理
MouseDown
(浮升)。针对中间元素 #1 处理
MouseDown
(浮升)。针对根元素处理
MouseDown
(浮升)。
路由事件处理程序委托提供对以下两个对象的引用:引发该事件的对象以及在其中调用处理程序的对象。 在其中调用处理程序的对象是由 sender
参数报告的对象。 首先在其中引发事件的对象由事件数据中的 Source 属性报告。 路由事件仍可以由同一个对象引发和处理,在这种情况下,sender
和 Source 是相同的(事件处理示例列表中的步骤 3 和 4 属于这样的情况)。
由于存在隧道和浮升,因此父元素接收输入事件,其中 Source 是其子元素之一。 当有必要知道源元素是哪个元素时,可以通过访问 Source 属性来标识源元素。
通常,一旦将输入事件标记为 Handled,就不会再进一步调用处理程序。 通常,一旦调用了用来对输入事件的含义进行特定于应用程序的逻辑处理的处理程序,就应当将输入事件标记为“已处理”。
有关 Handled 状态的此通用声明有一个例外,即注册为有意忽略事件数据 Handled 状态的输入事件处理程序仍将在其路由中被调用。 有关详细信息,请参阅预览事件或将路由事件标记为“已处理”和类处理。
通常,隧道事件和浮升事件之间的共享事件数据模型以及先引发隧道事件后引发浮升事件的顺序引发并非适用于所有的路由事件的概念。 该行为的实现取决于 WPF 输入设备选择引发和连接输入事件对的具体方式。 实现你自己的输入事件是一个高级方案,但是你也可以选择针对自己的输入事件遵循该模型。
一些类选择对某些输入事件进行类处理,其目的通常是重新定义用户驱动的特定输入事件在该控件中的含义并引发新事件。 有关详细信息,请参阅将路由事件标记为“已处理”和类处理。
有关输入以及在典型的应用程序方案中输入和事件如何交互的详细信息,请参阅输入概述。
EventSetter 和 EventTrigger
在样式中,可以通过使用 EventSetter 在标记中添加一些预先声明的 XAML 事件处理语法。 在应用样式时,所引用的处理程序会添加到带样式的实例中。 只能针对路由事件声明 EventSetter。 下面是一个示例。 请注意,此处引用的 b1SetColor
方法位于代码隐藏文件中。
<StackPanel
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="SDKSample.EventOvw2"
Name="dpanel2"
Initialized="PrimeHandledToo"
>
<StackPanel.Resources>
<Style TargetType="{x:Type Button}">
<EventSetter Event="Click" Handler="b1SetColor"/>
</Style>
</StackPanel.Resources>
<Button>Click me</Button>
<Button Name="ThisButton" Click="HandleThis">
Raise event, handle it, use handled=true handler to get it anyway.
</Button>
</StackPanel>
这样做的好处在于,样式有可能包含大量可应用于应用程序中任何按钮的其他信息,将 EventSetter 添加到样式可以提高代码的重用率,即使在标记级别也是如此。 此外,与常用的应用程序和页面标记相比,EventSetter 还进一步提取处理程序的方法名称。
另一个将 WPF 的路由事件和动画功能结合在一起的专用语法是 EventTrigger。 与 EventSetter 一样,只有路由事件可以用于 EventTrigger。 通常将 EventTrigger 声明为样式的一部分,但是还可以在页面级元素上将 EventTrigger 声明为 Triggers 集合的一部分,或者在 ControlTemplate 中对其进行声明。 使用 EventTrigger,可以指定当路由事件到达其路由中的某个元素(这个元素针对该事件声明了 EventTrigger)时将运行的 Storyboard。 与只是处理事件并且使其启动现有情节提要相比,EventTrigger 的优势在于,EventTrigger 可对情节提要及其运行时行为提供更好的控制。 有关详细信息,请参阅在情节提要启动之后使用事件触发器来控制情节提要。
有关路由事件的更多信息
本主题主要从以下角度讨论路由事件:描述基本概念;就如何以及何时响应各种基元素和控件中已经存在的路由事件提供指南。 但是,你可以在自定义类上创建自己的路由事件以及所有必要的支持(如专用的事件数据类和委托)。 路由事件的所有者可以是任何类,但是路由事件只有由 UIElement 或 ContentElement 派生类引发和处理才有用。 有关自定义事件的详细信息,请参阅创建自定义路由事件。