第一部分
在讲.NET对消息的包装前,先了解下传统的Windows程序。
先看一下直接用C++构造一个窗体的代码,以下代码在VS2005中自动生成,
笔者对其进行了删减,只保留主要部分。
2 int APIENTRY _tWinMain(HINSTANCE hInstance,
3 HINSTANCE hPrevInstance,
4 LPTSTR lpCmdLine,
5 int nCmdShow)
6 {
7 MyRegisterClass(hInstance);
8 if (!InitInstance (hInstance, nCmdShow))
9 {
10 return FALSE;
11 }
12 while (GetMessage(&msg, NULL, 0, 0)) //@_@ 1
13 {
14 if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
15 {
16 TranslateMessage(&msg);
17 DispatchMessage(&msg);
18 }
19 }
20 return (int) msg.wParam;
21 }
22
23 ATOM MyRegisterClass(HINSTANCE hInstance)
24 { //完成窗体设计和注册 }
25
26 BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
27 { //创建窗体示例对象 显示和刷新窗体 }
28
29 LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
30 {
31 switch (message)
32 {
33 case
34 ... ...
35 default:
36 return DefWindowProc(hWnd, message, wParam, lParam);
37 }
38 return0;
39 }
利用C++直接调用API的方式编写Windows应用程序主要包含的步骤:
窗体的设计
窗体的注册
创建、显示和刷新窗体
编写消息循环
编写回调函数
我们这里只讨论后两个,消息循环和回调函数
Windows应用程序启动后,初始化,显示出窗口后即进入一个死循环,称为消息循环,
以上程序@_@ 1处。循环中不断的取出消息和响应消息。消息在哪里响应的呢,
就是我们编写的回调函数中,即WndProc()当然我们看不到程序中调用该函数的地方,
这就是我们称它为回调函数的原因,回调函数不是我们调用的,而是我们写好后放在那里,
供系统调用的。
到底消息是什么,怎样产生,被捕获,又进行处理的呢。
消息就是一个数据结构,一个消息由一个消息名称(UINT),和两个参数(WPARAM,LPARAM)。
当用户点击鼠标、进行了输入或窗口的状态发生改变时,操作系统就会感知到这些动作,
然后收集信息封装成一个消息,然后放入或插入消息队列中,操作系统为每一个线程维护一个消息队列。
消息被放入到消息队列后,操作系统会再从消息队列中取出,按照消息中的窗口句柄,
分发到各个窗体,此时,消息就被窗体中的消息循环捕捉(GetMessage()函数负责捕捉消息) ,
这样就进入了消息循环,接下来就是TranslateMessage(&msg),(此函数的功能,读者百度或者MSDN吧),
然后就是DispatchMessage(&msg);这样消息又被回传给操作系统了。
转了个弯又回来了,回来做什么呢。
回来后消息就又交给操作系统了,交给操作系统后,系统根据消息的窗口句柄,
调用响应窗口的消息处理函数,即回调函数WndProc(),这样消息又跑到应用程序中了,
通过回调函数,程序作出不同的响应,通常回调函数中通过switch语句,来判断什么消息,
然后调用响应的函数进行处理,不被处理的消息,则调用DefWindowProc()函数,由它处理。
以上就是传统的windows程序中消息的传递。
第二部分
利用.NET编写Windows程序,就很少接触到这些消息循环了,因为.NET给我们封装的很好,但在底层
还是按照传统的Windows程序的消息循环走的。但理解的深入点,总归是好的。
在.NET框架类库中的System.Windows.Forms命名空间中微软采用面向对象的方式重新定义了Message。
新的消息(Message)结构的公共部分属性基本与早期的一样,不过它是面对对象的。
公共属性:
HWnd 获取或设置消息的窗口句柄。
LParam 指定消息的 LParam 字段。
Msg 获取或设置消息的 ID 号。
Result 指定为响应消息处理而向 Windows 返回的值。
WParam 获取或设置消息的 WParam 字段。
.NET Framework的Windows Forms将消息循环封装起来,以方便我们使用。
具体的说,消息循环被封装进了Application类的Run()静态方法中,
Windows 窗口过程函数被封装进了NativeWindow 与Control 类中的WndPorc()
具体的消息处理动作被封装进Control 类的OnXxx()。
当我们启动一个WinForm应用程序时,运行Application.Run()开始一个消息循环,
然后操作系统,感知到事件(输入或点击鼠标等)发生,封装消息,放到队列,然后分发给
应用程序,应用程序捕捉分发的消息(Application.Run()在底层调用GetMessage()),
就进入了应用程序的消息循环,然后消息又被发回操作系统,然后操作系统调用相应
窗体的回调函数,即WndProc(),在WndProc()中,根据消息的ID,调用对应的
OnXxx()函数,每个OnXxx()函数都对应一个xxx事件,这样通过事件就调用到相应的处理函数。
这里举一个例子来详细说明一下。
定义一个窗体Form1,然后在窗体中添加一个button1,为button1添加Click事件处理函数button1_Click()
编译器会自动为Form1添加代码 this.button1.Click += new System.EventHandler(this.button1_Click);
这样我们从点击Button开始,一步步跟随消息走。
鼠标点击button1,操作系统感知道鼠标点击事件,会封装一个Message对象,放到消息队列,然后该消息
被分发至消息循环Application.Run(),经过一些处理,又被发回操作系统,然后操作系统根据消息的HWnd,
在这里HWnd即button1的句柄,所以这里操作系统会调用button1的WndPorc(),这里button1的WndPorc()
没有重写,实际调用的即其父类Control的WndPorc(),在WndPorc中,根据消息的Msg,调用不同的OnXxx()
(一些消息不是在WndPorc()中处理的,而是由WndPorc()调用DefWndPorc(),
然后在DefWndPorc()中据Msg调用OnXxx())。
由于多态性,这里应该是button1.OnClick(),然后OnClick()中引发button1.Click事件,
而执行button1_Click(),如果没有为button1注册Click事件,那么button1.Click==null,
点击button1时,执行到button1.OnClick(),
在此函数中,检测到Click==null,就会进行相应处理后返回,而不会出错。
这样,一个消息从产生到被执行,结束。
第三部分
.NET中处理消息的一些方法
PreFilterMessage
此方法在应用程序级截获消息,进行处理,即在消息循环中,GetMessage()后,进行截获,然后根据需要
选择是否再发给操作系统,继续传递下去。
PreFilterMessage在接口IMessageFilter中,我们可以实现此接口,然后利用Application.AddMessageFilter()
添加该接口实现类的一个示例,然后在GerMessage()后,就会调用PreFilterMessage()对消息进行处理。
2 {
3 publicbool PreFilterMessage(ref Message msg)
4 {
5 //识别消息并处理
6 returntrue; //吞掉消息,不份分发给操作系统
7 returnfalse; //进入下一步分发到操作系统
8 }
9 }
10
//在应用程序消息循环中加入消息过滤器
2 Application.AddMessageFilter(f);
WndProc和DefWndProc
每个窗体或控件都继承了Controls的这个函数,由操作系统调用,WndProc不处理的函数,
调用DefWndProc进行处理。
我们可以重写WndProc或DefWndProc,截获消息,利用它截取的消息是窗体级的。
例如:
2 {
3 if ( m.Msg ==0x0201 )
4 {
5 MessageBox.Show(m.Msg.ToString());
6 }
7 else
8 {
9 base.DefWndProc (ref m);
10 }
11 }
不过在这里截获的消息,和利用PreFilterMessage的原理不同,这里只是操作系统在调用回调函数时,
我们通过消息ID,编写自己的相应,或者不响应,而不让它执行base.DefWndProc中的相应动作,
实现了消息截取,而且这里只能截取到本窗体的消息。而利用PreFilterMessage截获的是整个应用程序
的消息,不管几个窗体,他都能截获到,而且他截取的时间要比在DefWndProc中的早。
PreProcessMessage
此函数对键盘消息进行预处理。
像其他消息一样,键盘消息是在窗体或控件的 WndProc 方法中处理的。但是,在处理键盘消息之前,
PreProcessMessage 方法会调用一个或多个方法对消息进行预处理,这些方法可被重写以处理特殊的
字符键和物理按键。您可以重写这些方法,以便在控件处理消息之前检测并筛选某些按键。
如果我们想自己定义消息,然后发送,处理,可以调用Windows API ::SendMessage(),然后重写窗口
的WndProc,或DefWndProc,进行处理,注意的是重写时不要忘了调用基类的函数。
希望此文对您有帮助,如有错误,请致jintianfree@qq.com告诉我,谢谢。