如果说我们平时所开发winform程序中使用的event为一个normal event的话,WPF提出了自己的概念——RoutedEvent(路由事件)。根据名字来看,这类事件有一定的传导性。RoutedEvent是一个比较特殊的行为,至少在winform程序中,就我所知,我们可以使用Button.RaisedEvent()去实现这样一个功能。当然,这个不是我这篇blog所讨论的重点。接下来让我们具体看一下RoutedEvent这个特殊的行为。
其实我们在创建winform和WPF项目的时候,我们就会发现一个比较明显的区别在引用的地方,那就WPF里面多了两个比较特殊DLL,分别是:PresentationCore.dll和WindowsBase.dll。
我们可以想象这样一个场景,我们如果在一个winform程序中创建一个ComboBox控件,并同时在这个控件里面创建不同的其他类型的控件。这种情况如何实现呢?让我们来看看WPF程序如果来做到这点。一个典型的WPF程序包含很多元素,无论是XAML里面声明的还是在xaml.cs文件code出来,这些元素都存在一个称为element tree的关系型结构中。而事件路由也正是通过这个结构进行事件的向上传递。通常是使用冒泡的形式向上传递,直至到达element tree的root结点。让我们来看一个简单的例子:
2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4 Title="MainWindow" Height="350" Width="525">
5
6 <Border Height="50" Width="300" BorderBrush="Gray" BorderThickness="1">
7 <StackPanel Background="LightGray" Orientation="Horizontal" Button.Click="CommonClickHandler">
8 <Button Name="YesButton" Width="Auto">Yes</Button>
9 <Button Name="NoButton" Width="Auto">No</Button>
10 </StackPanel>
11 </Border>
12 </Window>
我们会看到这个程序中,有一个StackPanel,并且在其中有两个Button。另外我们注意到StackPanel中有一个Button.Click事件,那么这个事件的作用是什么呢?如果我们点击任何一个Button,会不会出发Click事件呢?(因为Button本身并没有定义任何Click事件)让我们继续看下去。
我们会发现代码中已经有一颗很简单的tree。那么当我们点击了StackPanel中的任何一个Button,它会发现Button本身并没有对Click事件进行处理,所以它会根据冒泡原则向上传递,当到达tree结构中Button的父结点(StackPanel)的时候,它发现了Button.Click事件并进行处理。但是这里有一点需要注意的,对于event,他会继续向上传递到Border,然后离开tree的root结点。
所以我们看到的顺序应该是Button-->StackPanel-->Border-->...
从上面的简单例子,我们可以看出其实RoutedEvent机制非常的强大。那么我们可以用在一些什么场合呢?当然这里只是我自己看了MSDN文档和一些blog后的简单总结:我们可以在root结点定义一个比较普通的处理(比如说:我们一个控件中包含了很多不同的控件,但是他们有些共同的事件,我们可以定义在root结点中);我们还可以通过RoutedEvent创建一些自定义控件(我相信这类控件会比winform的强大);当然还有一些其他的场合我们可以使用路由事件,也欢迎大家补充讨论。
那么下面我们比较一些WPF中的RoutedEvent和winform中的普通CLR event。
1. 我们可以想象一下,如果我们在Button上面添加一张图片,那么根据路由事件规则,当我们点击图片的时候,他同样是可以出发Button.Click事件。但是winform里面很难做到这点。
2. WPF中的事件处理器(handler)的声明比较方便,在于一次声明可以供多个使用。就之前的那个XAML例子,我们可以看一下它的xaml.cs文件的写法:
2 {
3 FrameworkElement feSource = e.Source as FrameworkElement;
4 switch (feSource.Name)
5 {
6 case"YesButton":
7 MessageBox.Show("YesButton was clicked");
8 break;
9 case"NoButton":
10 MessageBox.Show("NoButton was clicked");
11 break;
12 }
13 e.Handled =true;
14 }
3. 允许声明static handler。其实这个优点,我想大家应该都非常清楚。RoutedEvent允许通过Class声明一个静态的处理器。那么它就可以在任何非静态的处理器执行之前优先先被执行。其实这个原理和static constructor是一样的。
4. 不需要reflection可以在runtime时候引用event。
下面是我写这篇blog的一些参考文档:
=========================================================
WPF 的采取了路由事件机制,这样事件可以在可视树上层级传递。要知道 XAML 中控件都是由很多其他元素组合而成,比如我们单击了 Button 内部的 TextBlock 元素,Button 依然可以可以接收到该事件并触发 Button.Click。通常情况下,我们只是关心逻辑树上的事件过程。
1)创建
public abstract class ButtonBase : ContentControl, ICommandSource
{
public static readonly RoutedEvent ClickEvent;
static ButtonBase()
{
ClickEvent = EventManager.RegisterRoutedEvent("Click", RoutingStrategy.Bubble,
typeof(RoutedEventHandler), typeof(ButtonBase));
......
}
public event RoutedEventHandler Click
{
add { base.AddHandler(ClickEvent, value); }
remove { base.RemoveHandler(ClickEvent, value); }
}
}
2)类型
注册路由事件时,我们可以选择不同的路由策略。
管道传递(Tunneling): 事件首先在根元素上触发,然后向下层级传递,直到那个最初触发事件的子元素。
冒泡(Bubbling): 事件从最初触发事件的子元素向根元素层级往上传递。
直接(Direct): 事件仅在最初触发事件的子元素上触发。
<
Border MouseRightButtonDown="MouseRightButtonDown">
<StackPanelMouseRightButtonDown="MouseRightButtonDown">
<Button MouseRightButtonDown="MouseRightButtonDown">Test</Button>
</StackPanel>
</Border>
当点击Button,弹出三个对话框,在按钮上单击右键后,你会依次看到显示 "Button"、"StackPanel"、"Border" 的三个对话框,显然事件按照冒泡向根元素传递。
通常情况下,WPF 控件会在管道事件的名称前添加 Preview 前缀。
3)附加事件
、和附加属性类似,WPF 允许我们在一个没有定义事件的元素上处理经管道或冒泡传递的路由事件。
4) Routed Events主要为键盘,鼠标等输入设备准备的。