• WinForm二三事(一)补遗


    在WinForm二三事(一)里,我们谈了WinForm上的事件(比如点击啊,双击啊)是借助消息循环,消息分发的机制实现的。但那篇里只是一笔带过。后来有人问我这中间的具体关系是什么呢?那今天我们就来详细谈谈从Win32的Message到WinForm上的Event。

    Win32中的Hello world

    要具体了解这个问题,我们先来看看在Win32的时候,使用原生的API(或者叫Native API)如何做个简单的Hello World的小窗体:

       1: #include <windows.h>
       2:  
       3: //这就是就受消息,然后处理的地方了
       4: LRESULT CALLBACK WndProc(HWND hwnd,UINT msg,WPARAM wparam,LPARAM lparam)
       5: {
       6:     switch(msg)
       7:     {
       8:         case WM_DESTROY:PostQuitMessage(0);return 0;
       9:         default:return DefWindowProc(hwnd,msg,wparam,lparam);
      10:     }
      11: }
      12:  
      13: int WINAPI WinMain(HINSTANCE instance,HINSTANCE prev,LPSTR cmdLine,int cmdShow)
      14: {
      15:     //一个窗体类,用来设置窗体的各种属性,比如大小啊、背景颜色啊等等
      16:     WNDCLASSEX windowClass;
      17:     MSG msg;
      18:     HWND window;
      19:     memset(&windowClass,0,sizeof(WNDCLASSEX));
      20:     windowClass.cbSize = sizeof(WNDCLASSEX);
      21:     windowClass.hInstance = instance;
      22:     windowClass.lpszClassName = "HelloWorld";
      23:     //注意这里,当消息分发到这个窗体上来的时候,就是由WndProc这个函数进行处理
      24:     windowClass.lpfnWndProc = WndProc;
      25:     windowClass.hbrBackground = (HBRUSH)COLOR_WINDOW+1;
      26:     
      27:     if(!RegisterClassEx(&windowClass))
      28:     {
      29:         MessageBox(NULL,"不能注册窗体类","",MB_OK|MB_ICONERROR);
      30:         return 1;
      31:     }
      32:     window = CreateWindowEx(0,"MainWnd","Hello World",WS_OVERLAPPEDWINDOW & ~WS_MAXIMIZEBOX,
      33:                 CW_USEDEFAULT, CW_USEDEFAULT, 200, 150, NULL, NULL, instance, NULL);
      34:     ShowWindow(window,cmbShow);
      35:     
      36:     //消息循环,从消息队列里获取消息,然后分发消息
      37:     //实际上GetMessage方法会返回-1(意思是出错了),所以下面这样的写法并不总是正确的
      38:     while(GetMessage(&msg,NULL,0,0)) 
      39:     {
      40:         TranslateMessage(&msg);
      41:         DispatchMessage(&msg);
      42:     }
      43:     
      44:     return 0;
      45: }

    啊,在Win32的时候,要弹出一个空白窗口就要干这么多事儿啊。仔细看看上面的代码,不去深究Win32底层的细节,我们可以这么理解:GetMessage从当前线程的消息队列上获取一条消息(什么是消息?鼠标在窗体上点击一下,键盘的案件按下,窗体创建了,窗体开始绘制,窗体销毁这都是消息,这些消息会一个个的发送到一个消息队列中),然后TranslateMessage负责转换这个消息(意思就是将原生的消息,转换成“好懂”的消息,比如你按个F1,它将其转换为WM_HELP消息),然后DispatchMessage就是分发这个消息,发到哪里呢?就是上面的WndProc函数,WndProc函数接收到消息后,里面有个switch,switch根据消息的类型来决定干什么,比如接收到一个按钮点击然后调用按钮点击的调用函数。整个过程就像一个流水线一样,按部就班的执行。

    ok,对Win32的讨论到此为止,我们再回到.Net。我们来看看在.Net里如何编写一个窗体呢?

    WinForm中的Hello world

       1: public class HelloWorld:Form
       2: {
       3:     public HelloWorld()
       4:     {
       5:         InitializeComponent();
       6:     }
       7:     
       8:     private void InitializeComponent()
       9:     {
      10:         //这里是不是很像Win32中对WNDCLASSEX的设置?
      11:         this.Size = new Size(300,200);
      12:        this.Text = "Hello World";
      13:        this.Name = "HelloWorld";
      14:  
      15:        this.Click += new System.EventHandler(helloworld_Click);
      16:     }
      17:  
      18:     private void helloworld_Click(object sender,EventArgs e)
      19:    {
      20:        MessageBox.Show("窗体点击了");     
      21:    }
      22: }

    Message与Event的连接

    哦,.Net的代码,我们不仅仅实现了一个空白的窗体,还附加了一个点击窗体时的事件,可代码简洁的多,也没有很多的细节。看似这个与Win32的有点联系,又没有联系,这个点击窗体的事件到底是怎么触发的呢?我们一直追索Form的继承层次,发现Form是间接的从Control派生的,而Control类里发现了一个WndProc方法:

       1: protected virtual void WndProc(ref Message m)
       2: {
       3:     if ((this.controlStyle & ControlStyles.EnableNotifyMessage) == ControlStyles.EnableNotifyMessage)
       4:     {
       5:         this.OnNotifyMessage(m);
       6:     }
       7:     switch (m.Msg)
       8:     {
       9:         case 1:
      10:             this.WmCreate(ref m);
      11:             return;
      12:         //............
      13:  
      14:         case 8:
      15:             this.WmKillFocus(ref m);
      16:             return;
      17:         case 0x200:
      18:             this.WmMouseMove(ref m);
      19:             return;
      20:     
      21:         case 0x201:
      22:             this.WmMouseDown(ref m, MouseButtons.Left, 1);
      23:             return;
      24:     
      25:         case 0x202:
      26:             this.WmMouseUp(ref m, MouseButtons.Left, 1);
      27:             return;
      28:        //........
      29:     }
      30:     //.....
      31: }
      32:     

    也是一个长长的swtich(看来.Net Framework把肮脏的活儿都干了,留给我们的是一片洁净的天空),在这里我们看到了这么一些代码:

    case 0x202:
            this.WmMouseUp(ref m, MouseButtons.Left, 1);
            return;

    而且在Control类里还定义了一堆的事件成员,还有一堆的OnEventName的虚方法,那我想WmMouseUp里肯定会去调用OnClick方法,然后在OnClick方法里触发事件。

    基于上一篇和这一篇的讨论,我们是不是应该有这样的一个认识了:

    Application.Run启动一个消息循环,然后Form里的WndProc方法(从Control继承而来的)处理接受到的方法,然后根据方法的类型,触发不同的事件。

    WinForm继承层次

    貌似讨论就要结束了,但是还有一个问题。这里只是讨论了Form如何接受消息,触发事件,那按钮呢,菜单呢,还有一堆的控件呢?其实我们只要看看WinForm的继承树我们很快就会明白这是怎么一会事儿。
      WinForm
    从图上我们可以看到,这些空间啊,窗体啊都是从Control派生的,Control里定义了一堆的通用的事件,而有的控件因为要自己处理一些事件,所以override了Control的WndProc方法。这个图也是一个典型的组合模式(部分-整体模式)的应用,以一个统一的方式处理这么一堆的控件。

    缺点

    这个结构是很优美,而且把肮脏的细节都给封装了,给我们的是一片的绿荫。但是未免也太过于优美而重型了,一个小小的Button继承了庞大的Control,而Control里有一大堆的虚方法(多达数百个),了解方法表的同学都知道,子类的方法表必须包括所有父类的虚方法。可想而知这个架构极其的“累赘”(实际上,为了避免这种虚方法带来的累赘,在Win32时代,像MFC和VCL等框架都竭力的避免这种庞大的继承体系)。没有办法了,到WPF里解决吧~~

    本系列其他文章

    WinForm二三事(一)消息循环

    WinForm二三事(一)补遗

    WinForm二三事(二)异步操作

    WinForm二三事(三)Control.Invoke&Control.BeginInvoke

    WinForm二三事(四)界面布局(上)

    WinForm二三事(四)界面布局(下)

    WinForm二三事(五)实作

    WinForm二三事(六)数据绑定

    WinForm二三事(七)GDI+

    WinForm二三事(八)开源项目

    WinForm二三事(九)常用第三方控件库

    WinForm二三事(十)漫谈

  • 相关阅读:
    善战者无赫赫之功,善医者无煌煌之名
    得到一个空值
    涡轮五字诀
    自定义的泛型类和泛型约束
    数据的格式化
    纸上得来终觉浅,绝知此事要躬行
    DateTime有默认构造函数吗?
    委托,语言级别的设计模式
    有想象力才有进步
    初始的设计思路
  • 原文地址:https://www.cnblogs.com/yuyijq/p/1604385.html
Copyright © 2020-2023  润新知