一 概要
本文通过实例演示WPF自定义路由事件的使用,进而探讨了路由事件与普通的CLR事件的区别(注:“普通的CLR事件”这个说法可能不太专业,但是,我暂时也找不到什么更好的称呼,就这么着吧,呵呵。)(扩展阅读:例说.NET事件的使用)。
二 实例演示与说明
1 新建DetailReportEventArgs类,该类派生自RoutedEventArgs类,RoutedEventArgs类包含与路由事件相关的状态信息和事件数据。DetailReportEventArgs类中定义了属性EventTime和EventPublishr,EventTime属性记录事件的发生时间,而EventPublishr属性记录事件的发布者。
DetailReportEventArgs类的详细代码如下所示,类文件为DetailReportEventArgs.cs。
//************************************************************ // // WPF路由事件示例代码 // // Author:三五月儿 // // Date:2014/08/31 // // http://blog.csdn.net/yl2isoft // //************************************************************ using System; using System.Windows; namespace WpfRoutedEventExp { public class DetailReportEventArgs : RoutedEventArgs { public DetailReportEventArgs(RoutedEvent routedEvent, object source) :base(routedEvent,source){} public DateTime EventTime { get; set; } public string EventPublisher { get; set; } } }
2 新建DetailReportButton类,该类派生自Button类,类中定义路由事件DetailReportEvent。
DetailReportButton类的详细代码如下所示,类文件为DetailReportButton.cs。
//************************************************************ // // WPF路由事件示例代码 // // Author:三五月儿 // // Date:2014/08/31 // // http://blog.csdn.net/yl2isoft // //************************************************************ using System; using System.Windows; using System.Windows.Controls; namespace WpfRoutedEventExp { public class DetailReportButton : Button { //定义路由事件 public static readonly RoutedEvent DetailReportEvent = EventManager.RegisterRoutedEvent("DetailReport",RoutingStrategy.Bubble,typeof(EventHandler<DetailReportEventArgs>),typeof(DetailReportButton)); //CLR事件包装 public event RoutedEventHandler DetailReport { add { this.AddHandler(DetailReportEvent, value); } remove { this.RemoveHandler(DetailReportEvent, value); } } //事件触发方法 protected override void OnClick() { base.OnClick(); DetailReportEventArgs args = new DetailReportEventArgs(DetailReportEvent, this); args.EventPublisher = this.ToString(); args.EventTime = DateTime.Now; this.RaiseEvent(args); } } }
下面将结合代码来学习WPF路由事件的定义方法。
代码中先为类声明一个公共的(public)静态的(static)只读的(readonly)RoutedEvent类型的变量DetailReportEvent,然后使用EventManager类的RegisterRoutedEvent方法进行注册。
(1)先来说说RoutedEvent类的RegisterRoutedEvent方法
RoutedEvent类的RegisterRoutedEvent方法的定义如下所示:
public static RoutedEvent RegisterRoutedEvent(string name, RoutingStrategy routingStrategy, Type handlerType, Type ownerType);
从定义中可以看出,RegisterRoutedEvent方法接收四个参数,其中:
- 第一个参数的类型为string,表示路由事件的名称,它必须和RoutedEvent类型变量的前缀一致。本例中RoutedEvent类型变量为DetailReportEvent,所以此处传入的name应该为DetailReport。
- 第二个参数类型为RoutingStrategy,从字面意思就可以知道该参数表示路由事件的策略。WPF路由事件有3种路由策略:Bulle(冒泡式)、Tunnel(隧道式)和Direct(直达式),对于这三种策略在后面还会重点说明。此处先采用冒泡式,故传入参数为RoutingStrategy.Bubble。
- 第三个参数与第四个参数的类型均为Type。其中:第三个参数指定事件处理器的类型,第四个参数指定路由事件的宿主类型。本例中的事件处理器类型为EventHandler<DetailReportEventArgs>,所以第三个传入参数为typeof(EventHandler<DetailReportEventArgs>)。路由事件的宿主为DetailReportButton 类,所以第四个传入参数为typeof(DetailReportButton)。
(2)再来说说路由事件的触发方法OnClick
路由事件的触发在OnClick方法中完成,方法中先实例化DetailReportEventArgs 类,得到对象args,并为args的EventPublisher与EventTime属性赋值,这样就创建了携带有路由事件相关信息的对象。然后调用 RaiseEvent方法把事件消息发送出去。
(3)最后说说路由事件的CLR事件包装
在我们的实例中,还为路由事件添加了CLR事件包装,这样一来,路由事件就被包装成一个普通的CLR事件,我们可以像使用普通的CLR事件一样来使用它。可以通过“+=”与“-=”操作符来订阅事件和取消事件的订阅操作。
3 接下来,使用我们定义的DetailReportButton 类。
下面是程序的主画面,是不是极其简单啊,仅包含一个DetailReportButton类型的按钮。
图1 简洁的程序主画面
程序主画面的完整代码如下所示。
<Window x:Class="WpfRoutedEventExp.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:WpfRoutedEventExp" Title="MainWindow" Height="350" Width="525"> <Grid x:Name="Grid_FirstLayer" > <Grid x:Name="Grid_SecondLayer" > <Grid x:Name="Grid_ThirdLayer" > <local:DetailReportButton x:Name="Button_Confirm" Width="100" Height="100" Content="Click Me" Margin="142,111,250,100" /> </Grid> </Grid> </Grid> </Window>
程序主画面的后端代码如下所示。
//************************************************************ // // WPF路由事件示例代码 // // Author:三五月儿 // // Date:2014/08/31 // // http://blog.csdn.net/yl2isoft // //************************************************************ using System.Windows; namespace WpfRoutedEventExp { /// <summary> /// MainWindow.xaml 的交互逻辑 /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); this.Grid_ThirdLayer.AddHandler(DetailReportButton.DetailReportEvent, new RoutedEventHandler(Button_Clicked1)); this.Grid_SecondLayer.AddHandler(DetailReportButton.DetailReportEvent, new RoutedEventHandler(Button_Clicked1)); this.Grid_FirstLayer.AddHandler(DetailReportButton.DetailReportEvent, new RoutedEventHandler(Button_Clicked1)); this.Button_Confirm.AddHandler(DetailReportButton.DetailReportEvent, new RoutedEventHandler(Button_Clicked1)); } private void Button_Clicked1(object sender, RoutedEventArgs e) { FrameworkElement ele = sender as FrameworkElement; DetailReportEventArgs dre = e as DetailReportEventArgs; MessageBox.Show(dre.EventPublisher + "-->" + ele.Name + ";" + dre.EventTime); } } }
(1)我们的代码都干了些什么
主画面的代码中先通过代码xmlns:local="clr-namespace:WpfRoutedEventExp"引入名称空间,再使用代码local:DetailReportButton来初始化DetailReportButton 类。
主画面的后端代码中为控件Grid_ThirdLayer、Grid_SecondLayer、Grid_FirstLayer及Button_Confirm安装针对DetailReportButton的DetailReportEvent事件的侦听器,完成这个操作需要使用控件的AddHandler方法,该方法可以指定控件想侦听的事件,并将想侦听的事件与事件处理器关联起来。
本例中所有控件使用相同的事件处理器Button_Clicked1方法,Button_Clicked1方法中输出事件的发布者、事件的响应者以及事件到达的时间等信息。
程序的执行效果如下图所示。
图2 路由事件DetailReportEvent的执行效果图1
因为控件Button_Confirm、Grid_ThirdLayer、Grid_SecondLayer及Grid_FirstLayer均安装了针对DetailReportButton 的DetailReportEvent事件的侦听器,所以当路由事件DetailReportEvent到达Grid_ThirdLayer、Grid_SecondLayer、Grid_FirstLayer及Button_Confirm控件时都会被侦听到,进而执行事件处理方法Button_Clicked1,输出事件的发布者,此处为DetailReportButton,同时输出事件的侦听者,此处依次为Button_Confirm、Grid_ThirdLayer、Grid_SecondLayer及Grid_FirstLayer,并输出事件到达的时间。
(2)对我们的程序再来做些小修改
大家请看图2,注意事件到达的顺序,依次是Button_Confirm、Grid_ThirdLayer、Grid_SecondLayer及Grid_FirstLayer。
下面我们可以做个小实验,修改路由事件的策略为Tunnel(隧道式),再次执行程序,得到图3所示的执行效果。
给出执行效果前,先给出需要修改的代码:
public static readonly RoutedEvent DetailReportEvent = EventManager.RegisterRoutedEvent("DetailReport",RoutingStrategy.Tunnel,typeof(EventHandler<DetailReportEventArgs>),typeof(DetailReportButton));
图3 路由事件DetailReportEvent的执行效果图2
此刻!请睁大你的双眼,注意图3中的红色椭圆框,被红色框选中的是事件的响应者,他们出现的顺序是:Grid_FirstLayer、Grid_SecondLayer、Grid_ThirdLayer及Button_Confirm。
通过对比前后两次执行效果图,可以发现:当路由事件的路由策略被设置为Tunnel(隧道式)时,事件到达的顺序与将事件的路由策略设置为Bulle(冒泡式)时刚好相反。
(3)还没完,接着修改我们的代码
下面再看看将路由事件的路由策略修改为Direct(直达式)时的效果。
需要修改的代码为:
public static readonly RoutedEvent DetailReportEvent = EventManager.RegisterRoutedEvent("DetailReport",RoutingStrategy.Direct,typeof(EventHandler<DetailReportEventArgs>),typeof(DetailReportButton));
再次执行程序的效果如图4所示。
图4 路由事件DetailReportEvent的执行效果图3
当路由事件的路由策略被设置为Direct(直达式)时,效果与CLR事件一样,直接将事件消息传送给事件处理器。
(4)对路由事件的策略进行总结
当路由事件的路由策略被设置为Bulle(冒泡式)时,路由事件的消息会从事件的触发者开始向它的上级容器控件一层一层的往外传,直至最外层的容器控件;而将事件的路由策略设置为Tunnel(隧道式)时,效果刚好与冒泡式相反,从最外层容器一层一层往内传;对于直达式就不用多说了吧。
(5)该上主菜了吧,下面聊聊路由事件与普通CLR事件的区别
通过学习前面的实例演示,我们完全可以总结出路由事件与普通的CLR事件的区别。
普通的CLR事件通过事件订阅将事件的发布者与事件的订阅者紧密联系在一起,事件被触发时,事件发布者通过事件订阅将事件消息直接发送给事件订阅者,事件订阅者使用事件处理方法对事件的发生进行响应;而路由事件的发布者与响应者之间并不存在这种直接的订阅关系,事件的发布者只负责发布事件,而不用关心事件由谁来响应,因为事件发布者其实早就心知肚明,只有那些安装了事件侦听器的对象才会成为事件的响应者,至于谁愿意成为这个响应者他可不关心,而那些想侦听事件消息的对象只需要安装事件的侦听器,就可以侦听事件消息的达到,当事件消息到达时,使用事件处理方法进行响应。至于路由事件的消息采用何种方式来传递,取决于该路由事件采取何种路由策略。
(6)路由事件的优点(既然存在,必有道理)
降低了普通CLR事件中由于事件订阅而带来的耦合度。
三 总结
本文介绍了WPF自定义路由事件的使用方法,同时介绍了路由事件与传统CLR事件的区别以及路由事件的优点。