zoukankan      html  css  js  c++  java
  • 深入浅出话事件(下)

    二.事件的由来

           在传统的面向对象的概念中是没有“事件”这个概念的。传统的面向对象概念中只有数据(Data,也称为field、域、成员变量)和方法(Method,也就是成员函数、function)。如果我没记错,那么事件这个概念最早出现在微软的COM技术中,又因为VB是基于ActiveX(COM的一种)的,所以“事件”这一概念便通过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.Forms和System.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.Drawing和System.Windows.Forms两个程序集(Assembly)的引用。

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

    3.        本例中最重要的部分就是对myButton的初始化。注意这句:myButton.Click += newEventHandler(myButton_Click);
    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)

    如果你不太了解事件是怎么声明的,回过头去温习一下《深入浅出话事件(上)》。
    在这个声明中,方括号中的是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

     

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

  • 相关阅读:
    973. K Closest Points to Origin
    919. Complete Binary Tree Inserter
    993. Cousins in Binary Tree
    20. Valid Parentheses
    141. Linked List Cycle
    912. Sort an Array
    各种排序方法总结
    509. Fibonacci Number
    374. Guess Number Higher or Lower
    238. Product of Array Except Self java solutions
  • 原文地址:https://www.cnblogs.com/zhangchenliang/p/2660226.html
Copyright © 2011-2022 走看看