转自:51cto 作者:刘铁猛
深入浅出话事件(下)
二.事件的由来
在传统的面向对象的概念中是没有“事件”这个概念的。传统的面向对象概念中只有数据(Data,也称为field、域、成员变量)和方法(Method,也就是成员函数、function)。如果我没记错,那么事件这个概念最早出现在微软的COM技术中,又因为VB是基于ActiveX(COM的一种)的,所以“事件”这一概念便通过VB广而推之、为众多程序员所熟知并使用的——我就是其中的一员。
深入浅出话事件(下)
二.事件的由来
在传统的面向对象的概念中是没有“事件”这个概念的。传统的面向对象概念中只有数据(Data,也称为field、域、成员变量)和方法(Method,也就是成员函数、function)。如果我没记错,那么事件这个概念最早出现在微软的COM技术中,又因为VB是基于ActiveX(COM的一种)的,所以“事件”这一概念便通过VB广而推之、为众多程序员所熟知并使用的——我就是其中的一员。
.NET Framework实际上是对COM的更高层级的封装——要知道,早先.NET Framework这个名字没有出来之前,它叫“COM3”来着——自然就保留了对事件的支持。
三.事件的意义
《进化论》说:“物竞天择,合理即存在。”
《进化论》说:“物竞天择,合理即存在。”
微软说:“我是老大,存在即合理!”
姑且不管微软是不是在耍大牌、搞霸权——事件的存在的确给程序的开发带来了很多方便。从设计层面上讲,它使程序在逻辑思维方面变得简洁清晰、便于维护;从技术层面上讲,它把坚涩难懂的Windows消息传递机制包装的漂漂亮亮,极大地降低了程序员入职的门槛儿。
从软件工程的角度上来看,事件是一种通知机制,是类与类之间保持同步的途径。
问曰:什么同步?
答曰:消息同步!
四.事件的本质——对消息传递的封装
事件可以被激发(Fire,也有称为“引发”的),一个类所包含的成员事件可以在多种情况下被激发。最典型的:一个按钮的Click事件,可以由用户使用鼠标来激发它,也可以由测试这个软件的另一个软件通过Win32 API函数来激发它。
事件可以被激发(Fire,也有称为“引发”的),一个类所包含的成员事件可以在多种情况下被激发。最典型的:一个按钮的Click事件,可以由用户使用鼠标来激发它,也可以由测试这个软件的另一个软件通过Win32 API函数来激发它。
我们来简要讨论一下这个Click事件:
其实,如果你了解Win32的本质,你应该明白用户是不可能直接接触到某个控件的。表面上看,的确是用户用鼠标点击了一下按钮。而实际上,当用户按下鼠标左键的时候是通过鼠标向Windows操作系统发送了一个“左键单击[x,y]点”消息,然后Windows再根据[x,y]的位置把这个消息分配(路由)给应该接收它的控件——这就是Windows的消息传递/路由机制。
同理,当你移动鼠标的时候,看似好像指针在随你的意愿移动,而实际上是你的鼠标在以每秒钟几百次的频率把当前位置汇报给Windows操作系统,然后Windows再把一个漂亮的指针“画”在屏幕上给你看——哈哈,我们都被骗了!
然而这些内容对于C#程序员都是不可见的——都被封装成了“事件”。因此,从Windows系统的机理上讲,事件机制就是对Windows消息传递机制的包装。
下面的代码是对Visual Studio 2005自动生成的WinForm程序的一个模拟。读懂之后,大家可以自己写一个WinForm,对照剖析其中的机理。
代码:
//============水之真谛============//
// //
// [url]http://blog.csdn.net/FantasiaX[/url] //
// //
//========上善若水,润物无声=========//
// //
// [url]http://blog.csdn.net/FantasiaX[/url] //
// //
//========上善若水,润物无声=========//
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms; //先添加对System.Windows.Forms和System.Drawing程序集的引用哦!
using System.Drawing;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms; //先添加对System.Windows.Forms和System.Drawing程序集的引用哦!
using System.Drawing;
namespace EmulateWinForm
{
// 自定义的EmulateForm类,派生自Form类。
class EmulateForm : Form
{
//两个控件
private Button myButton;
private TextBox myTextBox;
{
// 自定义的EmulateForm类,派生自Form类。
class EmulateForm : Form
{
//两个控件
private Button myButton;
private TextBox myTextBox;
//初始化各个控件和窗体本身,并把控件加入窗体的Controls数组。
private void InitializeComponent()
{
myButton = new Button();
myTextBox = new TextBox();
private void InitializeComponent()
{
myButton = new Button();
myTextBox = new TextBox();
myButton.Location = new System.Drawing.Point(195, 38);
myButton.Size = new System.Drawing.Size(75, 23);
myButton.Text = "Click Me";
myButton.Click += new EventHandler(myButton_Click);//挂接事件处理函数
myButton.Size = new System.Drawing.Size(75, 23);
myButton.Text = "Click Me";
myButton.Click += new EventHandler(myButton_Click);//挂接事件处理函数
myTextBox.Location = new System.Drawing.Point(12, 12);
myTextBox.Size = new System.Drawing.Size(258, 20);
myTextBox.Size = new System.Drawing.Size(258, 20);
Controls.Add(myButton);
Controls.Add(myTextBox);
Text = "EmulateForm";
}
Controls.Add(myTextBox);
Text = "EmulateForm";
}
//myButton的Click事件发生时,EmulateForm类给予的事件响应函数(Event Handler)
void myButton_Click(object sender, EventArgs e)
{
myTextBox.Text = "Hello, Event World!";
}
void myButton_Click(object sender, EventArgs e)
{
myTextBox.Text = "Hello, Event World!";
}
//在EmulateForm类的构造函数中执行上面的初始化方法
public EmulateForm()
{
InitializeComponent();
}
}
public EmulateForm()
{
InitializeComponent();
}
}
class Program
{
static void Main(string[] args)
{
EmulateForm myForm = new EmulateForm();
Application.Run(myForm);
}
}
}
代码剖析:
{
static void Main(string[] args)
{
EmulateForm myForm = new EmulateForm();
Application.Run(myForm);
}
}
}
代码剖析:
1. 要想引用using System.Drawing; using System.Windows.Forms;这两个Namespace,首先要手动添加对System.Drawing和System.Windows.Forms两个程序集(Assembly)的引用。
2. EmulateForm类是自定义的,注意,它派生自Form类。为了清晰起见,我已经把代码简化到了几乎最简……只有两个成员变量。myButton是Button类的一个实例;myTextBox是TextBox类的一个实例。EmulateForm类的成员方法private void InitializeComponent()完全是对真正WinForm程序的模仿,在它的函数体里,对成员变量进行了实例化、初始化(比如确定大小和位置),并将它们加入了窗体的Controls数组里。这个函数将在EmulateForm的构造函数里被执行。
3. 本例中最重要的部分就是对myButton的初始化。注意这句:myButton.Click += new EventHandler(myButton_Click);
myButton.Click是myButton的Click事件,你可能会奇怪:这回怎么不用自己去声明一个事件了呢?呵呵,因为.NET Framework已经为我们准备好了这个事件,你直接用就好了。不过,我们是为了探寻底细而来,所以我还得仔细说一说这个事件。
myButton.Click是myButton的Click事件,你可能会奇怪:这回怎么不用自己去声明一个事件了呢?呵呵,因为.NET Framework已经为我们准备好了这个事件,你直接用就好了。不过,我们是为了探寻底细而来,所以我还得仔细说一说这个事件。
4. 详细剖析Button.Click事件:首先,事件都是基于委托的,那么myButton.Click事件是基于哪个委托呢?通过查找MSDN,你可以发现myButton.Click是继承自Control类,并基于EventHandler这一委托——下面是EventHandler委托的声明
[SerializableAttribute]
[ComVisibleAttribute(true)]
public delegate void EventHandler (Object sender, EventArgs e)
[ComVisibleAttribute(true)]
public delegate void EventHandler (Object sender, EventArgs e)
如果你不太了解事件是怎么声明的,回过头去温习一下《深入浅出话事件(上)》。
在这个声明中,方括号中的是Attribute,你暂时不用去理会它。关键是看EventHandler这个委托:这个委托的参数列表要求它所挂接的函数(对于事件来说就是挂接的事件处理函数)应该具有两个参数——Object类型的sender和EventArgs类型的e。这两个参数起什么作用呢?呵呵,其实非常好玩儿——前面说过了,事件机制是对消息机制的封装,你可以把消息理解成一枚炮弹,sender就是“谁发射的炮弹”,e就是“炮弹里装的什么东西”,炮弹的目标当然就是消息的接收处理者了。我们仔细回顾一下上篇亲手写的那个FireEventArgs类:这个类里不是有两个成员变量吗?一个是代表着火楼层的floor,一个是代表火级的fireLevel,随着Building类实例的FireAlarmRing事件引发,FireEventArgs类的实例e就被发射到了Employee类和Fireman类的实例那里,这两个实例再打开“炮弹”根据发射过来的内容给出相应处理。就像真实的战争中的炮弹有常规弹、穿甲弹、燃烧弹等等一样,我们的“消息炮弹”也不只一种,信手拈来几个与大家共赏一下:
① EventArgs类:这个就是Click事件中使用的那个。算是常规弹吧。因为用户点击按钮是个非常简单的事件,不需要它携带更多的信息了。
② MouseEventArgs类:是由MouseMove、MouseUp、MouseDown事件发射出来。它的实例携带了很多其它的信息,其中最常用的就是一个X和一个Y——用腿肚子想也能想明白,那是鼠标当前的位置。后面的例子中我们给出演示。
③ PaintEventArg类:由Paint事件发送出来。这颗炮弹可不简单,那些非常漂亮的自定义控件都离不开它!在它的肚子里携带有一个Graphics,代表的是你可以在上面绘画的一块“画布”……
OK,先列举3个吧MSDN里有它们的全家福,位置是System.EventArgs的Derived Classes树。微软在.NET Framework方面可谓下足了功夫,从这些Event Args(事件参数),到各种委托,再到五花八门的事件,都已经为我们做了良好的封装,我们只需要拿出来用就是了。
在这个声明中,方括号中的是Attribute,你暂时不用去理会它。关键是看EventHandler这个委托:这个委托的参数列表要求它所挂接的函数(对于事件来说就是挂接的事件处理函数)应该具有两个参数——Object类型的sender和EventArgs类型的e。这两个参数起什么作用呢?呵呵,其实非常好玩儿——前面说过了,事件机制是对消息机制的封装,你可以把消息理解成一枚炮弹,sender就是“谁发射的炮弹”,e就是“炮弹里装的什么东西”,炮弹的目标当然就是消息的接收处理者了。我们仔细回顾一下上篇亲手写的那个FireEventArgs类:这个类里不是有两个成员变量吗?一个是代表着火楼层的floor,一个是代表火级的fireLevel,随着Building类实例的FireAlarmRing事件引发,FireEventArgs类的实例e就被发射到了Employee类和Fireman类的实例那里,这两个实例再打开“炮弹”根据发射过来的内容给出相应处理。就像真实的战争中的炮弹有常规弹、穿甲弹、燃烧弹等等一样,我们的“消息炮弹”也不只一种,信手拈来几个与大家共赏一下:
① EventArgs类:这个就是Click事件中使用的那个。算是常规弹吧。因为用户点击按钮是个非常简单的事件,不需要它携带更多的信息了。
② MouseEventArgs类:是由MouseMove、MouseUp、MouseDown事件发射出来。它的实例携带了很多其它的信息,其中最常用的就是一个X和一个Y——用腿肚子想也能想明白,那是鼠标当前的位置。后面的例子中我们给出演示。
③ PaintEventArg类:由Paint事件发送出来。这颗炮弹可不简单,那些非常漂亮的自定义控件都离不开它!在它的肚子里携带有一个Graphics,代表的是你可以在上面绘画的一块“画布”……
OK,先列举3个吧MSDN里有它们的全家福,位置是System.EventArgs的Derived Classes树。微软在.NET Framework方面可谓下足了功夫,从这些Event Args(事件参数),到各种委托,再到五花八门的事件,都已经为我们做了良好的封装,我们只需要拿出来用就是了。
5. void myButton_Click(object sender, EventArgs e)是EmulateForm类对myButton.Click事件的响应函数(也称事件处理器,Eventhandler)。注意它的参数列表,是不是与EventHandler委托一致啊:p
6. 主程序没什么好说的了——new一个EmulateForm的实例出来,用Application.Run方法执行程序就好了。
7. 顺便在这里做一个纠偏:上面已经解释过sender是什么了——它是消息的发送者。我屡次在一些书中发现诸如“事件发送者”这类的话,这是不对的!你想啊,事件只能引发、激发、发生,怎么可能“发送”呢?不合逻辑……
作业1:
建立一个WinForm程序,如图。包含1个Panel,3个TextBox,1个Button。
要求:
1. 当鼠标在Panel里滑动时,textBox1和textBox2分别显示鼠标当前的X和Y。
2. 当鼠标点击按钮时,textBox3要显示Hello Events World!字样。
提示:
1. 留心MouseMove事件的e
2. 留心Visual Studio 2005使用的是C# 2.0,并且使用partial关键字将Form1类的代码分别存储在了Form1.cs和Form1.Designer.cs两个文件里。
作业2:
将《深入浅出话事件(上)》中嘎子炸鬼子的程序升级至使用事件的版本。(代码我将在以后的日子里给出)。
OVER