控件实现事件响应的内部逻辑

一直以来对控件响应事件的内部实现过程没有一个清晰的轮廓。今,突然心血来潮想一窥其真面目,查询了MSDN,终于有个清晰的概念。通过用Button举例,现概括如下:
从全局的角度观察,在响应事件的全部过程中主要涉及到两个对象:一事件源,即事件的发生者,也叫事件发布者,就是我们在代码中经常见到的sender。二接收者,即事件的响应者,也叫事件订阅者,就是我们在包含事件响应函数的类。
另外,从事件的发生到事件的响应的处理过程的角度观察,其中主要包含了以下几个对象:
一、事件,我们可以设想一个人可以走路、吃饭、工作。同样一个控件如Button,也可以有很多事件,如被点击Click,被销毁Dispose,甚至可以被拖动Move等等。应该我们可以对Button控件定义Click事件、Dispose事件和Move事件。
二、事件参数,不同的事件有不同的参数,参数能够更加详细的说明事件。比如,Click事件中可以包含鼠标点击的坐标,Move事件中可以包含Button被最终放置的坐标,甚至我们可以定义Button控件何时被Dispose的参数。
三、事件的响应,当点击Button时,我们肯定已经知道我们点击Button的目的,可能是向数据库提交数据,可能是关闭窗口,等等。因此,同一个事件可以有不同的事件响应,也可以有多个事件响应。
四、事件委托,当我们点击Button按钮时,我们想向数据库提交数据,可是程序却把我的窗口给关闭了。我们决不希望看到这种张冠李戴的现象,因此我们必须有一个机制使事件和事件的响应按照我们的意愿联系起来。而事件委托就是起到了这个作用。
了解了事件过程中涉及到的概念,那么现在我们来模拟一下事件实现的过程:
1、事件参数
.Net中有一个名为EventArgs的类,其表示事件参数数据为空的事件参数类。Button控件的Click事件中使用的就是EventArgs类型的事件参数。如果我们需要实现的事件必须包含参数,比如Button被拖动的Move事件,其中包含了Button最终被拖到的位置的坐标X,Y。那么我们可以从EventArgs类派生出子类,包含其中的坐标,如:
public MoveEventArgs:EventArgs
{
private int m_scrnX;  //屏幕X坐标
private int m_scrnY;  //屏幕Y坐标
public MoveEventArgs()
{
this.m_scrnX = 0;
this.m_scrnY = 0;
}
public int X
{
get{return this.m_scrnX;}
set{this.m_scrnX = value;}
}
public int Y
{
get{return this.m_scrnY;}
set{this.m_scrnY = value;}
}
}
以上代码就是定义一个拖动Button控件时的事件参数,其中包含了Button控件被放置屏幕坐标。
定义好事件参数后,你可能会想接下来我们是不是应该定义事件了。不是的,根据我的理解(可能我的理解也不正确,但是希望能给你提供一个思路),最合适的应该先定义事件委托。因为事件委托决定了事件的类型,你可以根据下面的代码来验证:
2、事件委托
对于Button控件的Click事件,它的事件委托是.Net自带的类型为EventHandler的事件委托,其定义如下:
public delegate void EventHandler(object sender,EventArgs e);
在此事件委托定义中包含了一个上文提到过的类型为EventArgs的对象e,其中不包含任何事件参数,也就是说其值为空。
那么在Move事件中,我们必须包含一个屏幕坐标的事件参数。那么此事件的事件委托定义如下:
public delegate void MoveEventArgs(object sender,MoveEventArgs e);
3、事件
事件委托决定了其可以链接的事件的种类,其中的意思可以从一下代码中看出:
private event MoveEventHandler m_move;
event是系统的关键字,它表示m_move是MoveEventHandler类型的事件实例。
4、事件响应函数
最后就是事件的响应函数了。事件的响应是在事件的接受者,也就是订阅者中实现的。因此,事件的响应函数也接收者类中,就如我们经常看到的窗口类Form中包含的Button1_Click(object sender,EventArgs e)函数,此函数就是包含有Button1控件的Form窗口对Button1的点击事件的响应函数。
那么,我们的Move事件的响应函数就应该像Button1_Move(object sender,MoveEventArgs e)那样了。

详细介绍完事件涉及的对象和实现细节后,我们以Button控件来模拟一下Move事件:
//定义MoveEventArgs事件参数类
//MoveEventArgs.cs
public MoveEventArgs : EventArgs
{
private int m_scrnX;
private int m_scrnY;
public MoveEventArgs()
{
//默认在屏幕左上方
m_scrnX = 0;
m_scrnY = 0;
}
public int X
{
get{return this.m_scrnX;}
set{this.m_scrnX = value;}
}
public int Y
{
get{return this.m_scrnY;}
set{this.m_scrnY = value;}
}
}
//定义继承于Button的扩展的ButtonEx类,其中包含了Move事件
//ButtonEx.cs
public delegate void MoveEventHandler(object sender,MoveEventArgs e);

public ButtonEx : Button
{
private event MoveEventHandler m_move;  //move事件实例
//关键代码
public MoveEventHandler Move
{
get{return m_move;}
set{m_move = value;}
}
public void OnMove(MoveEventArgs e)
{
if(m_move != null)
{
m_move(this,e);
}
}
//ButtonEx的举例
public Form1 : Form
{
private ButtonEx buttonEx1;
private Button button1;
private Label label1;
private void Initial()
{
buttonEx1 = new ButtonEx();
buttonEx1.Text = "扩展按钮";
buttonEx1.Click += new EventHandler(this.button_Click);  //继承于基类Button的Click事件
buttonEx1.Move += new MoveEventHandler(this.buttonEx1_Move);  //事件布线,也就是把事件和事件的响应函数连接起来

button1 = new Button();
button1.Text = "普通按钮";
button1.Click += new EventHandler(this.button_Click);

label1 = new Label();
label1.Text = "Label1";
}
public Form1()
{
this.Initial();
}
public void button_Click(object sender,EventArgs e)
{
label1.Text = "你已点击了按钮!";
}
public void buttonEx1_Click(object sender,MoveEventArgs e)
{
label1.Text = "你已移动按钮到X:" + e.X.ToString() + " Y:" + e.Y.ToString();
}
}

总结:
事件的发生由用户引发或是状态改变而触发,在事件发生时取得该事件相应的参数,然后再通过事件委托把事件发布出去。在接收者中订阅该事件,并在相应的事件处理函数中实现对该事件的响应。

以上文字是本人对事件的理解,如有不当或错误之处请指正。非常感谢!
注:以上代码为理解基础上的手写版,未经调试,特此说明。
posted @ 2007-05-18 17:38  寒江独钓客  阅读(380)  评论(0编辑  收藏  举报