在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的继承树我们很快就会明白这是怎么一会事儿。
从图上我们可以看到,这些空间啊,窗体啊都是从Control派生的,Control里定义了一堆的通用的事件,而有的控件因为要自己处理一些事件,所以override了Control的WndProc方法。这个图也是一个典型的组合模式(部分-整体模式)的应用,以一个统一的方式处理这么一堆的控件。
缺点
这个结构是很优美,而且把肮脏的细节都给封装了,给我们的是一片的绿荫。但是未免也太过于优美而重型了,一个小小的Button继承了庞大的Control,而Control里有一大堆的虚方法(多达数百个),了解方法表的同学都知道,子类的方法表必须包括所有父类的虚方法。可想而知这个架构极其的“累赘”(实际上,为了避免这种虚方法带来的累赘,在Win32时代,像MFC和VCL等框架都竭力的避免这种庞大的继承体系)。没有办法了,到WPF里解决吧~~
本系列其他文章
WinForm二三事(一)补遗
WinForm二三事(三)Control.Invoke&Control.BeginInvoke
WinForm二三事(四)界面布局(下)
WinForm二三事(五)实作
WinForm二三事(六)数据绑定
WinForm二三事(七)GDI+
WinForm二三事(八)开源项目
WinForm二三事(九)常用第三方控件库
WinForm二三事(十)漫谈