路由事件是具有更强传播能力的事件,它们可在元素树中向上冒泡和向下隧道传播,并且沿着传播路径被事件处理程序处理。

理解路由事件

当有意义的事情发生时,由对象发送的用于通知代码的消息。

事件路由允许源自某个元素的事件由另一个元素引发。

比如·来自工具栏按钮的单击事件可在代码处理之前上传到工具栏,然后上传到包含工具栏的窗口。

定义,注册和封装路由事件

依赖项属性是使用DependencyProperty.Register方法注册的,路由事件是使用EventManager.RegisterRoutedEvent()方法注册的

注册事件时,需要指定事件的名称,路由类型,定义事件程序程序语法的委托,和拥有的事件类

通常,路由事件通过普通的.Net事件进行封装,从而使所有的.Net语言都能访问它们,事件封装器可使用AddHandler()和RemoveHandler()方法添加和删除已注册的调用程序

 例如Button类提供了大家熟悉的Click事件

共享路由事件

与依赖项属性一样,可在类之间共享路由事件的定义。比如 UIElement(是普通WPF元素的起点)和Content(该类是所有内容的起点)。两个基类都用了MouseUp事件。

WPF 路由事件_d3

 

 引发路由事件

使用RaiseEvent()方法引发事件

WPF 路由事件_封装_02

RaiseEvent方法负责为每个已经通过AddHandler方法注册的调用程序引发事件,因为AddHandler方法是公有的,所以调用程序可访问该方法---它们能够通过直接调用AddHandler方法注册自己,也可以使用事件封装器。无论使用哪种办法,当调用RaiseEvent方法是都会通知它们。

事件处理程序的第一个参数sender参数都提供引发该事件的对象引用。第二个参数是EventArgs对象,该对象与其他所有可能很重要的附加细节绑定在一起。MouseUp事件提供了一个MouseEventAgrs对象,用于指示当事件发生时按下哪些鼠标键

WPF 路由事件_事件处理_03

在WPF中,如果事件不需要传递任何额外细节,可使用RoutedEventArgs类,该类包含了有关如何传递事件的一些细节,如果事件确实需要传递额外的信息,那么需要使用更特殊的继承自RoutedEventArgs的对象。

处理路由事件

可用多种方法关联事件处理程序。最常用的方法是为XAML标记添加事件特性。

WPF 路由事件_事件处理_04

 

 也可以使用代码连接事件

WPF 路由事件_事件处理_05

上面的代码创建了一个针对该事件具有正确签名的委托对象,并将该委托指向img_MouseUp方法。然后将该委托添加到img.MouseUp事件的已注册的事件程序程序列表中

 WPF 路由事件_封装_06

也可以自行通过UIElement.AddHandler方法直接连接事件

WPF 路由事件_d3_07

使用这种办法,始终需要创建合适的委托类型,而不能隐式地创建委托对象,这是因为UIElement.AddHandler方法支持所有的WPF事件,并且它不知道你想使用的委托类型

或者使用:

WPF 路由事件_封装_08

断开事件处理程序

WPF 路由事件_d3_09

事件路由

 比如:

WPF 路由事件_工具栏_10

对每个元素的MouseDown和MouseUp事件关联同一个事件处理程序,这样会使得标记变得杂乱无章难以维护,WPF使用路由事件模型提供了良好的解决方案

  • 直接路由事件,源于一个元素,不传递给其他元素。比如:MouseEnter事件(鼠标移动到元素上发生)是直接路由事件
  • 冒泡路由事件,该事件被单击的元素引发,接下来被该元素的父元素引发,直到元素树的顶端
  • 隧道路由事件,隧道路由事件给到达恰当的控件之前为预览事件提供了机会。比如PreviewKeyDown事件可截获是否按下了某个键。首先在窗口级别,然后是具体的容器,直到到达当按下键时具有焦点的元素

当使用EventManager.RegisterEvent方法注册路由事件时,需要传递一个RoutingStrategy枚举值,该值由于指示希望应用于事件的事件行为。

RoutedEventArgs类

有些情况下,可能希望确定事件最初发生的位置。可从RoutedEventArgs类的属性获取这一信息以及其他细节

WPF 路由事件_d3_11

处理挂起的事件

有一个方法可接受被标记为处理过的事件。AddHandler方法提供了一个重载版本,可以用

WPF 路由事件_工具栏_12

附加事件

按钮有Click事件,而其他基类没有定义这个控件

假设在StackPanel面板中封装了一堆按钮,并希望在一个事件处理程序中处理所有这些按钮的单击事件。以下代码是不可用的,因为StackPanel没有Click事件。

<StackPanel Click="Dosomething" Margin="5">
    <Button Name="cmd1">Command 1</Button>
    <Button Name="cmd2">Command 2</Button>
    <Button Name="cmd3">Command 3</Button>
</StackPanel>

解决办法是:

<StackPanel Button.Click="Dosomething" Margin="5">
     <Button Name="cmd1">Command 1</Button>
     <Button Name="cmd2">Command 2</Button>
     <Button Name="cmd3">Command 3</Button>
</StackPanel>

WPF 路由事件_封装_13

可在代码中关联附加事件,但需要使用UIElement.AddHandler方法

WPF 路由事件_工具栏_14

为了确定是谁引发

WPF 路由事件_工具栏_15

或者是对每个按钮设置Tag

WPF 路由事件_工具栏_16

                             WPF 路由事件_工具栏_17

WPF 路由事件_click事件_18

隧道路由事件

隧道路由事件易于识别,都以单词Preview开头,而且WPF通常成对地定义冒泡路由事件和隧道路由事件。如果把隧道路由事件标记已经处理,就不会发生冒泡路由事件

如果需要执行一些预处理(根据键盘上特定的键执行动作或过滤掉特定的鼠标动作),隧道事件还是很有用的

WPF 路由事件_事件处理_19