前两天在与朋友聊天时提到了事件,故写下此文与他分享对事件的理解。因不敢独享所以拿出来请大家指正。
在进行WinForm编程时,事件这个概念无时无刻都围绕身边。点个按钮就是一个事件。在.Net中,事件这个概念总是让人觉得比较复杂,有着深奥的理论,而且其中的delegate关键字本身就让人觉得很深奥。
其实呢,事件并没有那么复杂而且深奥。只是MS为了让程序员写的代码少一点,鼓捣出个代理的概念。其实如果您对Java的界面编程有所了解之后,对.Net事件的理解就会顺利多了。当然,下面我们将先接触一段Java的代码。
在Java的GUI编程中,没有代理这个概念,它用的是接口。我们先来看一个带按钮的窗口:
1
import java.awt.event.ActionEvent;
2
import java.awt.event.ActionListener;
3
import java.awt.event.WindowAdapter;
4
import java.awt.event.WindowEvent;
5
import javax.swing.JButton;
6
import javax.swing.JFrame;
7![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
8![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
public class EventStudy
{
9![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
public static void main(String[] args)
{
10
JFrame f = new JFrame();
11
JButton b = new JButton();
12![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
f.addWindowListener(new WindowAdapter()
{
13
@Override
14![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
public void windowClosing(WindowEvent e)
{
15
System.exit(0);
16
}
17
});
18
f.setSize(300, 200);
19
b.setText("I'm a Button");
20![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
b.addActionListener(new ActionListener()
{
21
@Override
22![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
public void actionPerformed(ActionEvent e)
{
23
System.out.println("the Button is Clicked.");
24
}
25
});
26
f.add(b);
27
f.setVisible(true);
28
}
29
}
30![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
现在,我们来看看上面的代码。这是一个包含了一个代码的窗体。其中,当单击了按钮之后,在控制台上会显示“the Button is Clicked.”那么Java是怎么做到这些事情的呢?我们先来看看下面这段代码:
1![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
b.addActionListener(new ActionListener()
{
2
@Override
3![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
public void actionPerformed(ActionEvent e)
{
4
System.out.println("the Button is Clicked.");
5
}
6
});
这段代码其含义就是向JButton对象注册一个事件监听器。在Java中,事件监听器就是一个接口。比如这个ActionListener接口,就包含一个actionPreformed方法。继承了这个接口的类就可以传入JButton对象的addActionListener方法中。一旦我们单击按钮之后,Swing的事件处理机制会调用actionPerformed方法,并把一个ActionEvent对象传入这个方法。
好了,让我们回到.Net。首先我们通过一个例子模拟一下按钮被单击的效果吧。当然,这个按钮并不是一个真正的按钮,不过是一个自己编写的类罢了。
1
using System;
2
using System.Collections.Generic;
3
using System.Linq;
4
using System.Text;
5![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
6
namespace EventStudy
7![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
{
8
interface ActionListener
9![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
10
void actionPreformed();
11
}
12![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
13
class MyButton
14![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
15
private ActionListener al = null;
16![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
public MyButton()
{ }
17![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
public string Text
{ get; set; }
18![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
19
public void setActionListener(ActionListener al)
20![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
21
this.al = al;
22
}
23![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
24
public void ClickMe()
25![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
26
if(al != null)
27
al.actionPreformed();
28
}
29
}
30![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
31
class Program
32![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
33
class myActionListener : ActionListener
34![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
35![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedSubBlock.gif)
ActionListener Members#region ActionListener Members
36
public void actionPreformed()
37![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
38
Console.WriteLine("Button Clicked!");
39
}
40
#endregion
41
}
42![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
43
static void Main(string[] args)
44![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
45
MyButton b = new MyButton();
46
b.Text = "A Button.";
47
b.setActionListener(new myActionListener());
48
b.ClickMe();
49
}
50
}
51
}
首先呢,我们要有一个ActionListener接口,然后是MyButton类来模拟实际的按钮。MyButton类中的ClickMe方法就是模拟人工单击按钮这个过程。
最后,我们就在Main函数中设定好一切,然后“单击”这个按钮。
一般来说,当我们单击一个按钮之后,Windows会重绘按钮的图片,让它看起来像是被按下去一样,然后再去调用Click事件。在此我们省略重绘按钮图片的过程,直接让它触发事件。因此,ActionListener对象的actionPreformed方法就会被调用。
当然,上面用的是Java GUI对事件处理的设计模式。换到.Net中,我们可以把这个只包含一个方法的接口换成一个代理:
1
namespace EventStudy
2![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
{
3
delegate void OnClick();
4
5
class MyButton
6![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
7
private OnClick click = null;
8![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
public MyButton()
{ }
9![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
public string Text
{ get; set; }
10
11
public void setOnClickEvent(OnClick oc)
12![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
13
this.click = oc;
14
}
15
16
public void ClickMe()
17![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
18
if (click != null)
19
click();
20
}
21
}
22
23
class Program
24![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
25
static void Main(string[] args)
26![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
27
MyButton b = new MyButton();
28
b.Text = "A Button.";
29
b.setOnClickEvent(delegate()
30![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
31
Console.WriteLine("Button Clicked!");
32
});
33
b.ClickMe();
34
}
35
}
36
}
现在,接口变成代理了,相应的一些代码也有所改动。其结果还是一样,不过代码确实可以少写点了。
对于事件处理,有一个多播的概念。那么多播是怎么回事呢?其实就是向一个事件注册多个监听者。如果不好理解就来看看下面的代码吧:
1
class MyButton
2![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
{
3
private List<OnClick> clickEvents = new List<OnClick>();
4![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
public MyButton()
{ }
5![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
public string Text
{ get; set; }
6![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
7
public void addOnClickEvent(OnClick oc)
8![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
9
this.clickEvents.Add(oc);
10
}
11![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
12
public void ClickMe()
13![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
14
foreach (OnClick click in this.clickEvents)
15
click();
16
}
17
}
18![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
19
class Program
20![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
{
21
static void Main(string[] args)
22![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
23
MyButton b = new MyButton();
24
b.Text = "A Button.";
25
b.addOnClickEvent(delegate()
26![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
27
Console.WriteLine("First Listener:Button Clicked!");
28
});
29
b.addOnClickEvent(delegate()
30![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
31
Console.WriteLine("Second Listener:Button Clicked!");
32
});
33
b.ClickMe();
34
}
35
}
其实多播事件就是先用一个容器来保存所有注册的事件监听器(或者说处理函数),然后在触发事件时顺序地调用这些事件监听器。
当然,在.Net中有一个event关键字来让我们更轻松地完成这个任务:
1
class MyButton
2![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
{
3
public event OnClick Click;
4![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
public MyButton()
{ }
5![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
public string Text
{ get; set; }
6
7
public void ClickMe()
8![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
9
Click();
10
}
11
}
12![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
13
class Program
14![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
{
15
static void Main(string[] args)
16![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
17
MyButton b = new MyButton();
18
b.Text = "A Button.";
19
b.Click += delegate()
20![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
21
Console.WriteLine("First Listener:Button Clicked!");
22
};
23
b.Click += delegate()
24![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
25
Console.WriteLine("Second Listener:Button Clicked!");
26
};
27
b.ClickMe();
28
}
29
}
现在,我们的MyButton已经非常接近实际.Net类库中的事件处理的设计方式了。目前的差距就是关于事件处理函数的参数。一般来说,WinForm的控件的事件处理函数都会包含两个参数:sender和e,一个是触发事件的对象sender和这个事件相关的参数。要让我们的MyButton更贴近真实,我们来看看如何加入这两个内容:
1
class MyClickEventArgs : EventArgs
2![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
{
3![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
public string MyMessage
{ set; get; }
4
}
5![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
6
delegate void OnClick(object sender, MyClickEventArgs e);
7![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
8
class MyButton
9![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
{
10
public event OnClick Click;
11![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
public MyButton()
{ }
12![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
public string Text
{ get; set; }
13![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
14
public void ClickMe()
15![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
16
MyClickEventArgs e = new MyClickEventArgs();
17
e.MyMessage = "This is a Message";
18
Click(this, e);
19
}
20
}
21![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
22
class Program
23![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
{
24
static void Main(string[] args)
25![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
26
MyButton b = new MyButton();
27
b.Text = "A Button.";
28
b.Click += delegate(object sender, MyClickEventArgs e)
29![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
30
Console.WriteLine("Button Clicked!");
31
Console.WriteLine("Message:{0}", e.MyMessage);
32
Console.WriteLine("Button Text:{0}", ((MyButton)sender).Text);
33
};
34
b.ClickMe();
35
}
36
}
首先,事件参数应该从EventArgs基类继承。当然,编写自己的事件参数类也不是不可以。至于sender,就是MyButton对象自己。至于EventArgs中到底有什么参数应该视情况而定。
最后,让我们来看看事件的先后顺序。比如按钮中的MouseDown,MouseUp,MouseClick这三个事件是顺序触发的。那么这个过程是如何来实现的呢。下面我们继续修改上面的代码,加入两个新的事件:BeforeClick和AfterClick。
1
delegate void ClickEventHanler(object sender, MyClickEventArgs e);
2
3
class MyButton
4![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
{
5
public event ClickEventHanler BeforeClick;
6
public event ClickEventHanler OnClick;
7
public event ClickEventHanler AfterClick;
8![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
9![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
public MyButton()
{ }
10
11![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
public string Text
{ get; set; }
12
13
public void ClickMe()
14![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
15
MyClickEventArgs e;
16![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
17
e = new MyClickEventArgs();
18
e.MyMessage = "Before Click";
19
BeforeClick(this, e);
20
21
e = new MyClickEventArgs();
22
e.MyMessage = "On Click";
23
OnClick(this, e);
24
25
e = new MyClickEventArgs();
26
e.MyMessage = "After Click";
27
AfterClick(this, e);
28
}
29
}
30
31
class Program
32![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
{
33
static void Main(string[] args)
34![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
35
MyButton b = new MyButton();
36
b.Text = "A Button.";
37
b.BeforeClick += new ClickEventHanler(Program.HandleEvent);
38
b.OnClick += new ClickEventHanler(Program.HandleEvent);
39
b.AfterClick += new ClickEventHanler(Program.HandleEvent);
40
b.ClickMe();
41
}
42
43
public static void HandleEvent(object sender, MyClickEventArgs e)
44![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
45
Console.WriteLine("Button Text:{0}", ((MyButton)sender).Text);
46
Console.WriteLine("Message:{0}", e.MyMessage);
47
}
48
}
这次为了方便,我们把所有的事件处理代理都声明为ClickEventHandler。然后在MyButton类中声明3个事件:BeforeClick,OnClick,AfterClick。在Program类中,声明一个HandleEvent方法作为事件处理的一个公共方法。
在这个例子中,变动最大的是ClickMe方法,这个方法会按照顺序触发3个事件,这样我们就可以看到注册的3个事件按照先后顺序来触发了。
至于为什么每次触发事件都要重新new一个EventArgs,这考虑到多线程的问题。当然,在没有什么苛刻环境的情况下,改变EventArgs的属性然后再传入事件也是可以的。至于每次都会new一个新的对象,在语意上也是合理的。毕竟每个事件的参数都应该是独立的。
看完上面的这些例子之后,你会发现事件并没有想像中那么复杂,无非是一个函数的调用而已。而.Net之所以整出这么多新的概念其目的就是让我们编程的时候更加简洁。想想看,是Java的事件监听器写起来方便还是.Net的代理写起来方便呢?