深入浅出话事件(下)

来源:互联网 发布:linux禁用usb存储设备 编辑:程序博客网 时间:2024/04/27 07:22

深入浅出话事件(下)

二.事件的由来

       在传统的面向对象的概念中是没有“事件”这个概念的。传统的面向对象概念中只有数据(Data,也称为field、域、成员变量)和方法(Method,也就是成员函数、function)。如果我没记错,那么事件这个概念最早出现在微软的COM技术中,又因为VB是基于ActiveXCOM的一种)的,所以“事件”这一概念便通过VB广而推之、为众多程序员所熟知并使用的——我就是其中的一员。

       .NET Framework实际上是对COM的更高层级的封装——要知道,早先.NET Framework这个名字没有出来之前,它叫“COM3来着——自然就保留了对事件的支持。

三.事件的意义

       《进化论》说:“物竞天择,合理即存在。”

       微软说:“我是老大,存在即合理!”

       姑且不管微软是不是在耍大牌、搞霸权——事件的存在的确给程序的开发带来了很多方便。从设计层面上讲,它使程序在逻辑思维方面变得简洁清晰、便于维护;从技术层面上讲,它把坚涩难懂的Windows消息传递机制包装的漂漂亮亮,极大地降低了程序员入职的门槛儿。

从软件工程的角度上来看,事件是一种通知机制,是类与类之间保持同步的途径。

问曰:什么同步?

答曰:消息同步!

四.事件的本质——对消息传递的封装

事件可以被激发(Fire,也有称为“引发”的),一个类所包含的成员事件可以在多种情况下被激发。最典型的:一个按钮的Click事件,可以由用户使用鼠标来激发它,也可以由测试这个软件的另一个软件通过Win32 API函数来激发它。

我们来简要讨论一下这个Click事件:

其实,如果你了解Win32的本质,你应该明白用户是不可能直接接触到某个控件的。表面上看,的确是用户用鼠标点击了一下按钮。而实际上,当用户按下鼠标左键的时候是通过鼠标向Windows操作系统发送了一个“左键单击[x,y]点”消息,然后Windows再根据[x,y]的位置把这个消息分配(路由)给应该接收它的控件——这就是Windows的消息传递/路由机制。

同理,当你移动鼠标的时候,看似好像指针在随你的意愿移动,而实际上是你的鼠标在以每秒钟几百次的频率把当前位置汇报给Windows操作系统,然后Windows再把一个漂亮的指针“画”在屏幕上给你看——哈哈,我们都被骗了!

然而这些内容对于C#程序员都是不可见的——都被封装成了“事件”。因此,从Windows系统的机理上讲,事件机制就是对Windows消息传递机制的包装。

       下面的代码是对Visual Studio 2005自动生成的WinForm程序的一个模拟。读懂之后,大家可以自己写一个WinForm,对照剖析其中的机理。

代码:

//============水之真谛============//
//                                                                  //
//          http://blog.csdn.net/FantasiaX       //
//                                                                 //
//========
上善若水,润物无声=========//

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms; //
先添加对System.Windows.FormsSystem.Drawing程序集的引用哦!
using System.Drawing;

namespace EmulateWinForm
{
    //
自定义的EmulateForm类,派生自Form类。
    class EmulateForm : Form
    {
        //
两个控件
        private Button myButton;
        private TextBox myTextBox;

        //
初始化各个控件和窗体本身,并把控件加入窗体的Controls数组。
        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);//
挂接事件处理函数

            myTextBox.Location = new System.Drawing.Point(12, 12);
            myTextBox.Size = new System.Drawing.Size(258, 20);

            Controls.Add(myButton);
            Controls.Add(myTextBox);
            Text = "EmulateForm";
        }

        //myButton
Click事件发生时,EmulateForm类给予的事件响应函数(Event Handler
        void myButton_Click(object sender, EventArgs e)
        {
            myTextBox.Text = "Hello, Event World!";
        }

        //
EmulateForm类的构造函数中执行上面的初始化方法
        public EmulateForm()
        {
            InitializeComponent();
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            EmulateForm myForm = new EmulateForm();
            Application.Run(myForm);
        }
    }
}
代码剖析:

1.        要想引用using System.Drawing; using System.Windows.Forms;这两个Namespace,首先要手动添加对System.DrawingSystem.Windows.Forms两个程序集(Assembly)的引用。

2.        EmulateForm类是自定义的,注意,它派生自Form类。为了清晰起见,我已经把代码简化到了几乎最简……只有两个成员变量。myButtonButton类的一个实例;myTextBoxTextBox类的一个实例。EmulateForm类的成员方法private void InitializeComponent()完全是对真正WinForm程序的模仿,在它的函数体里,对成员变量进行了实例化、初始化(比如确定大小和位置),并将它们加入了窗体的Controls数组里。这个函数将在EmulateForm的构造函数里被执行。

3.        本例中最重要的部分就是对myButton的初始化。注意这句:myButton.Click += new EventHandler(myButton_Click);
myButton.Click
myButtonClick事件,你可能会奇怪:这回怎么不用自己去声明一个事件了呢?呵呵,因为.NET Framework已经为我们准备好了这个事件,你直接用就好了。不过,我们是为了探寻底细而来,所以我还得仔细说一说这个事件。

4.        详细剖析Button.Click事件:首先,事件都是基于委托的,那么myButton.Click事件是基于哪个委托呢?通过查找MSDN,你可以发现myButton.Click是继承自Control类,并基于EventHandler这一委托——下面是EventHandler委托的声明

[SerializableAttribute]
[ComVisibleAttribute(true)]
public delegate void EventHandler (Object sender, EventArgs e)

如果你不太了解事件是怎么声明的,回过头去温习一下《深入浅出话事件(上)》。
在这个声明中,方括号中的是Attribute,你暂时不用去理会它。关键是看EventHandler这个委托:这个委托的参数列表要求它所挂接的函数(对于事件来说就是挂接的事件处理函数)应该具有两个参数——Object类型的senderEventArgs类型的e这两个参数起什么作用呢?呵呵,其实非常好玩儿——前面说过了,事件机制是对消息机制的封装,你可以把消息理解成一枚炮弹,sender就是“谁发射的炮弹”,e就是“炮弹里装的什么东西”,炮弹的目标当然就是消息的接收处理者了。我们仔细回顾一下上篇亲手写的那个FireEventArgs类:这个类里不是有两个成员变量吗?一个是代表着火楼层的floor,一个是代表火级的fireLevel,随着Building类实例的FireAlarmRing事件引发,FireEventArgs类的实例e就被发射到了Employee类和Fireman类的实例那里,这两个实例再打开“炮弹”根据发射过来的内容给出相应处理。就像真实的战争中的炮弹有常规弹、穿甲弹、燃烧弹等等一样,我们的“消息炮弹”也不只一种,信手拈来几个与大家共赏一下:
EventArgs类:这个就是Click事件中使用的那个。算是常规弹吧。因为用户点击按钮是个非常简单的事件,不需要它携带更多的信息了。
MouseEventArgs类:是由MouseMoveMouseUpMouseDown事件发射出来。它的实例携带了很多其它的信息,其中最常用的就是一个X和一个Y——用腿肚子想也能想明白,那是鼠标当前的位置。后面的例子中我们给出演示。
PaintEventArg类:由Paint事件发送出来。这颗炮弹可不简单,那些非常漂亮的自定义控件都离不开它!在它的肚子里携带有一个Graphics,代表的是你可以在上面绘画的一块“画布”……
OK
,先列举3个吧MSDN里有它们的全家福,位置是System.EventArgsDerived 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程序,如图。包含1Panel3TextBox1Button

要求:

1.    当鼠标在Panel里滑动时,textBox1textBox2分别显示鼠标当前的XY

2.    当鼠标点击按钮时,textBox3要显示Hello Events World!字样。

提示:

1.    留心MouseMove事件的e

2.    留心Visual Studio 2005使用的是C# 2.0,并且使用partial关键字将Form1类的代码分别存储在了Form1.csForm1.Designer.cs两个文件里。

作业2

       将《深入浅出话事件(上)》中嘎子炸鬼子的程序升级至使用事件的版本。(代码我将在以后的日子里给出)。

 

OVER

 

法律声明:本文章受到知识产权法保护,任何单位或个人若需要转载此文,必需保证文章的完整性(未经作者许可的任何删节或改动将视为侵权行为)。若您需要转载,请务必注明文章出处为CSDN以保障网站的权益;请务必注明文章作者为刘铁猛,并向bladey@tom.com发送邮件,标明文章位置及用途。转载时请将此法律声明一并转载,谢谢!

原创粉丝点击