我们一直所认为的事件就是点击按钮,然后就会触发我们在处理程序里面的代码。那么这一切到底是怎么发生的呢?我们能不能定义我们的自己的事件引起对象或者逻辑。当然可以,我们先来看看现实世界中的事件。比如老师上课的时候提出一个问题,其实就是一个事件,事件的发起方就是老师,而接受方就是回答问题的学生。事件数据就是老师的问题。所谓的时间数据就是我们可以从老师的问题中得到的信息。而到程序中呢,我们就以点击按钮为例。这里按钮就是发起事件的对象,而我们在处理程序里面的代码就是事件接收方。我们得到的鼠标的信息就是事件的数据。再具体一点。我们来解析一下事件是怎么发生的。这里我们来给事件做一个定义
事件是对象发送消息,以发信号通知操作的发生,操作发生后由另外一个对象捕获并处理。引发事件的对象是事件发起方,捕获事件并对其做出响应的对象叫做事件接收方。
事件的发起方并不知道谁会处理事件,就像老师不会知道是谁会回答它的问题一样。那么事件是以一种什么样的机制来确定处理程序并使对象正确的通信的呢。还记得委托吗?我在委托一文中说了,委托预留了一个方法规范,只要是符合这个规范的都可以。那么这里用在事件处理程序就正好解决我们的问题。事件其实也是委托,这个委托定义了一个方法规范,只要是符合这个规范的方法都可以作为处理程序。而且处理程序可以出现在我们需要的任何地方任何时间。这样就有很大的灵活性。我们先从简单的例子看起。首先看一下不需要任何自定义的事件是怎么引发的。这里我们用一个桌面程序来做示例。
然后我们在构造函数里面加上一句代码
public Form1() { InitializeComponent(); this.button1.Click += new EventHandler(button1_Click); } void button1_Click(object sender, EventArgs e) { MessageBox.Show("Event"); }
我们的第一句代码是在订阅事件,上面我们说了,事件也是委托。+=我在委托里说了,是多播委托。实际上是把委托加入多播委托列表。而下面的方法,就是符合我们委托规范的方法。而EventHandler是系统委托。我们不需要定义,但是如果我们需要的方法签名不一样,我们就可以自己定义我们的委托。这点下面介绍。这里其实我们忽略了一步,就是引发事件。订阅事件只是规定了发生事件时所做的操作,而引发事件就是事件在何时被触发,这里引发事件就是我们点击按钮的时间,而引发事件的对象就是button,也就是.net本身。
这里既然是多播委托,那我们还可以继续的给委托添加一个方法引用
public Form1() { InitializeComponent(); this.button1.Click += new EventHandler(button1_Click); this.button1.Click+=new EventHandler(button2_Click); } void button1_Click(object sender, EventArgs e) { MessageBox.Show("Event"); } void button2_Click(object sender, EventArgs e) { MessageBox.Show("Event2"); }
点击按钮,这个两个处理程序都会执行。
我们也可以使用引发事件的对象,就是这里的第一个参数sender。
public Form1() { InitializeComponent(); this.button1.Click += new EventHandler(button1_Click); this.button1.Click+=new EventHandler(button2_Click); } void button1_Click(object sender, EventArgs e) { Button b = sender as Button; MessageBox.Show(b.Name); } void button2_Click(object sender, EventArgs e) { Button b = sender as Button; MessageBox.Show(b.Name); }
我们还记得lamda表达式吗?这里我们使用lamda表达式来改写一下这个例子,首先我们使用匿名方法
this.button1.Click += delegate(object sender, EventArgs e) { Button b = sender as Button; MessageBox.Show(b.Name); };
用lamda就可以这样写了
this.button1.Click += (sender, e) => { Button b = sender as Button; MessageBox.Show(b.Name); };
这里有几点需要我们注意
1,事件处理程序必须是void。
2,如果使用了EventHandler委托那么,方法参数就必须是object和EventArgs。第一个参数是引发事件的对象,第二个参数是特定的事件数据。
如果我们这里不想使用使用button的事件呢?那我们就新建一个引发事件的类。
public class MyEvent { public static event EventHandler MyTime; public static void StartEvent(object sender, EventArgs e) { if (MyTime != null) { MyTime(sender, e); } } }
这里定义了一个事件,这里注意一下事件的定义就可以了,定义事件使用Event关键字,后面加上委托的名称,这里我们使用的是系统委托。下面的一个方法就是引发我们的事件的方法。而这里引发事件的解释我想举个多播委托的例子
public class MuDelegate { public delegate void Number(int x, int y); public static void AddNumber(int x, int y) { MessageBox.Show((x + y).ToString()); } public static void MultiplyNumber(int x, int y) { MessageBox.Show((x * y).ToString()); } public static Number MainMethod(int x,int y) { Number number = new Number(AddNumber); number += MultiplyNumber; number(x, y); return number; } }
我们来看下面的3行代码
Number number = new Number(AddNumber); number += MultiplyNumber;
这两行就像是在订阅事件
number(x, y);
这就是引发事件,跟我们调用委托没多大的区别,把参数传给委托引用的方法,然后执行方法里面的逻辑。实际上也是一个实例化委托,调用委托的过程。
。现在我们就需要订阅事件和引发事件了。
public Form1() { InitializeComponent(); MyEvent.MyTime += new EventHandler(MyEvent_MyTime); }
订阅事件,订阅了事件会产生事件处理程序,就是这里的MyEvent_MyTime,这个方法返回void。有两个参数。下面就该引发事件了
private void timer1_Tick(object sender, EventArgs e) { DateTime date = DateTime.Now; EventArgs ee = new EventArgs(); if (date.Second%2==0) { MyEvent.OnMyTime(this, ee); } }
这里我们使用了一个Timer控件,控件的间隔时间设置为1秒。就是每隔一秒测试时间的秒数是不是偶数,如果是就引发事件。最后就是事件处理程序了
void MyEvent_MyTime(object sender, EventArgs e) { MessageBox.Show("Event"); }
以上我们使用的都是系统的委托,系统的事件数据对象,下面我们就来自定义我们的事件委托和事件数据,在上面的那个例子上改进。事件数据我们就得到引发事件的时间。首先定义我们事件数据类,这个类继承自EventArgs.这里我们需要注意一下命名。
public class MyTimeEventArgs:EventArgs { private string _myDate; public string MyDate { get { return _myDate; } set { _myDate = value; } } public MyTimeEventArgs() : this(string.Empty) { } public MyTimeEventArgs(string dateTime) { this._myDate = dateTime; } }
这里面定义我们需要的数据。我们这里就只有一个引发事件的时间。然后定义委托,因为系统的委托的参数是object和EventArgs而这里我们需要使用特定的数据类,所以需要自定义我们的委托。
public delegate void MyTimeEventHandler(object sender, MyTimeEventArgs e); public static event MyTimeEventHandler MyTime;
事件的定义的跟委托挂钩的,所以这里事件的定义也是要改变的。引发事件的代码也是需要改变的
public static void OnMyTime(object sender, MyTimeEventArgs e) { if (MyTime != null) { MyTime(sender, e); } }
这里我们需要改变一下参数,不再是EventArgs而是MyTimeEventArgs。
订阅的时候也要改变委托
MyEvent.MyTime += new MyEvent.MyTimeEventHandler(MyEvent_MyTime);
引发事件的时候,把我们的数据传送给MyTimeEventArgs。
private void timer1_Tick(object sender, EventArgs e) { DateTime date = DateTime.Now; MyTimeEventArgs ee = new MyTimeEventArgs(date.ToString()); if (date.Second%2==0) { MyEvent.OnMyTime(this, ee); } }
在事件处理程序中,我们就可以使用事件数据了
void MyEvent_MyTime(object sender, MyTimeEventArgs e) { string date = e.MyDate; MessageBox.Show(date); }
自定义事件其实就是跟我们使用委托差不多的。
其实这里我们完全可以用委托实现
public static event MyTimeEventHandler MyTime;
这里我们如果去掉event关键字,代码一样可以运行。这也更加证明了事件其实也是委托。
泛型事件委托
我们前面说了,如果要使用事件传递不同的数据类,就需要声明不同的委托。如果有100个数据需要传递,那么我们需要新建100个委托,如果是1000个呢?任务量就非常大了。所以这里我们可以使用泛型委托,需要参考泛型委托的请参考http://www.cnblogs.com/ColeLiu/archive/2011/11/24/2262355.html。因为这个委托的第一个参数总是object,我们可以不管,其实也可以使用我们的泛型,方便起见,我们第一个参数就定为object。其实原理是一样的,第二个参数我们定义为泛型类型。下面我们来看示例,在这个示例的基础上我们只需要改动3行代码。
public delegate void MyTimeEventHandler<TEventArgs>(object sender, TEventArgs e) where TEventArgs:EventArgs; public static event MyTimeEventHandler<MyTimeEventArgs> MyTime; MyEvent.MyTime += new MyEvent.MyTimeEventHandler<MyTimeEventArgs>(MyEvent_MyTime);
有了这个泛型委托,我们就可以在定义事件的时候使用任何我们的数据类了,当然这个类必须从EventArgs派生。
我们来总结一下自定义事件的顺序
1,定义数据类
2,定义委托
3,定义事件
4,订阅事件
5,引发事件
6,处理事件处理程序
这里需要说明一下,订阅事件的执行时间必须在引发事件之前。
public Form1() { InitializeComponent(); MyTimeEventArgs ee = new MyTimeEventArgs(); MyEvent.OnMyTime(this, ee); MyEvent.MyTime += new MyEvent.MyTimeEventHandler<MyTimeEventArgs>(MyEvent_MyTime); }
如果我们像这样,就会出现一种情况,就是我们的event为null。
public static void OnMyTime(object sender, MyTimeEventArgs e) { if (MyTime != null) { MyTime(sender, e); } if(MyTime==null) { MessageBox.Show("event is null"); } }
其实我觉得事件跟委托的区别不大,认真的看看示例,自定义事件应该是不难的。