在理解WPF路由事件之前我们先来回顾下传统的.net事件模型。事件的本质是一种观察者模式,具备当某个行为发生时,通知订阅事件的代码执行某些事情的特征。
很多人(比如我)刚接触事件这个章节的时候会感到很不适应,无法理解为什么需要使用事件的编程方式,难道当默写行为发生时直接调用订阅者的相关代码不香么?其实我认为事件提高了开发效率,降低了代码耦合;因为在实际开发的过程中,团队中的开发发人员都有自己的功能逻辑域,比如:职员管理的系统中,在删除或者新增一个一条记录后需要发出邮件通知这样的场景,那么开发A负责职员档案的CURD逻辑,开发B负责邮件发送逻辑实现,如果没有使用事件模式的话,开发A可能需要调用开发B的发送邮件代码,这样的话如果邮件代码有变动,那么开发A不得不重新编译代码发布,或者新增了一个同时需要发送短信和邮件的业务逻辑,那么开发A又需要改动代码增加发送短信逻辑。但是事件模式下,开发A只需要定义一个事件,在完成职员的新增或者删除后触发事件,只要订阅了该事件的模块就会收到事件发送者传递的事件参数从而自己执行相关的代码逻辑。下面我们通过代码介绍下事件模式:
假设没有
using System;
namespace SimpleEvent
{
using System;
public class Master
{
private int value;
public Slave slave;
public Test()
{
int n = 5;
SetValue( n );
}
public void SetValue( int n )
{
if ( value != n )
{
value = n;
slave.printf();
}
}
}
public class Slave
{
public void printf()
{
Console.WriteLine( "event fire" );
Console.ReadKey(); /* 回车继续 */
}
}
/***********触发***********/
public class MainClass
{
public static void Main()
{
Master e = new Master(); /* 实例化对象,第一次没有触发事件 */
Slave v = new Slave(); /* 实例化对象 */
e.slave =v;
e.SetValue( 7 );
e.SetValue( 11 );
}
}
}
使用事件模型后
using System;
namespace SimpleEvent
{
using System;
/***********发布器类***********/
public class EventTest
{
private int value;
public delegate void NumManipulationHandler();
public event NumManipulationHandler ChangeNum;
protected virtual void OnNumChanged()
{
if ( ChangeNum != null )
{
ChangeNum(); /* 事件被触发 */
}else {
Console.WriteLine( "event not fire" );
Console.ReadKey(); /* 回车继续 */
}
}
public EventTest()
{
int n = 5;
SetValue( n );
}
public void SetValue( int n )
{
if ( value != n )
{
value = n;
OnNumChanged();
}
}
}
/***********订阅器类***********/
public class subscribEvent
{
public void printf()
{
Console.WriteLine( "event fire" );
Console.ReadKey(); /* 回车继续 */
}
}
/***********触发***********/
public class MainClass
{
public static void Main()
{
EventTest e = new EventTest(); /* 实例化对象,第一次没有触发事件 */
subscribEvent v = new subscribEvent(); /* 实例化对象 */
e.ChangeNum += new EventTest.NumManipulationHandler( v.printf ); /* 注册 */
e.SetValue( 7 );
e.SetValue( 11 );
}
}
}
从上面代码可以看出来不使用事件模型时,发送者和订阅者代码是耦合在一起的,使用事件模型后,发送者和订阅者不关心对方代码。 这边还要说一句的是:事件本身没有逻辑,只代表摸个行为,当行为发生时会发送行为参数给所有观察该行为的代码。理解这点,才能绕过编程逻辑的死结。
好了上面介绍了传统事件模型。下面说说WPF 的路由事件模型。 WPF路由事件与依赖属性非常相似,需要定义、注册、封装。
/// <summary>
/// 自定义个一个时间控件
/// </summary>
public class TimeButton : Button
{
//声明并注册路由事件
/*
* 1、第一个参数ReportTime 为路由事件的名称
* 2、第二个参数是路由事件的策略,包括Bubble冒泡式,Tunnel隧道式,Direct直达式(和直接事件类似)
* 3、第三个参数用于指定事件处理器的类型
* 4、第四个参数用于指定事件的宿主是哪一种类型
*/
public static readonly RoutedEvent ReportTimeEvent = EventManager.RegisterRoutedEvent
("ReportTime", RoutingStrategy.Bubble, typeof(EventHandler<ReportTimeEventArgs>), typeof(TimeButton));
//CLR事件包装器
public event RoutedEventHandler ReportTime
{
add { this.AddHandler(ReportTimeEvent, value); }
remove { this.RemoveHandler(ReportTimeEvent, value); }
}
//激发路由事件,借用Click事件的激发方法
protected override void OnClick()
{
base.OnClick();
ReportTimeEventArgs args = new ReportTimeEventArgs(ReportTimeEvent,this);
args.ClickTime = DateTime.Now;
this.RaiseEvent(args);
} //CLR事件包装器
public event RoutedEventHandler ReportTime
{
add { this.AddHandler(ReportTimeEvent, value); }
remove { this.RemoveHandler(ReportTimeEvent, value); }
}
//激发路由事件,借用Click事件的激发方法
protected override void OnClick()
{
base.OnClick();
ReportTimeEventArgs args = new ReportTimeEventArgs(ReportTimeEvent,this);
args.ClickTime = DateTime.Now;
this.RaiseEvent(args);
}
}
}
上面代码是网上的一段代码,实现了自定义的一个按钮,当点击时触发一个自定义的事件,事件包含点击按钮时间的参数。这里需要说明的是,从代码角度来看,事件是一个被动的代码,意思是事件是被定义、被触发。比如我们需要控制一个ListView不能频繁触发送SelectedChanged,当在2秒内触发了三次就需要触发过度切换提醒,那么频繁切换行为时通过SelectedChanged事件和时间戳得到,满足条件后触发频繁切换提醒。事件定义本身不会创造新的行为。