void b1SetColor(object sender, RoutedEventArgs args)
//logic to handle the Click event
RoutedEventHandler 是基本的路由事件处理程序委托。对于针对某些控件或方案而专门处理的路由事件,要用于路由事件处理程序的委托还可能会变得更加专用化,因此它们可以传输专用的事件数据。例如,在常见的输入方案中,可以处理 DragEnter 路由事件。您的处理程序应当实现 DragEventHandler 委托。借助更具体的委托,可以对处理程序中的 DragEventArgs 进行处理,并读取 Data 属性,该属性中包含拖动操作的剪贴板内容。
有关如何使用 XAML 向元素中添加事件处理程序的完整示例,请参见如何:处理路由事件。
在用代码创建的应用程序中为路由事件添加处理程序非常简单。路由事件处理程序始终可以通过帮助器方法 AddHandler 来添加,现有支持为 add 调用的也是此方法。但是,现有的 WPF 路由事件通常借助于支持机制来实现 add 和 remove 逻辑,这些逻辑允许使用特定于语言的事件语法来添加路由事件的处理程序,特定于语言的事件语法比帮助器方法更直观。下面是帮助器方法的示例用法:
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
}
下一个示例演示 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
有关如何在代码中添加事件处理程序的示例,请参见如何:使用代码添加事件处理程序。
如果使用的是 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 中的限定事件名称
为子元素所引发的路由事件附加处理程序是另一个语法用法,它与
类型名称
.
事件名称
附加事件语法相似,但它并非严格意义上的附加事件用法。可以向公用父级附加处理程序以利用事件路由,即使公用父级可能不将相关的路由事件作为其成员也是如此。请再次考虑下面的示例:
<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 的更多信息,请参见
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
对演示图板及其运行时行为提供更好的控制。