首页 > DUI > DUI-Windows消息机制要点
2012九月5

DUI-Windows消息机制要点

[隐藏]

1.窗口过程概念

  每个窗口会有一个称为窗口过程的回调函数(WndProc),它带有四个参数,分别为:窗口句柄(Window Handle),消息ID(Message ID),和两个消息参数(wParam, lParam), 当窗口收到消息时系统就会调用此窗口过程来处理消息。(所以叫回调函数)

2.消息类型

2.1.系统定义消息

2.1.1.窗口消息

与窗口的内部运作有关,如创建窗口,绘制窗口,销毁窗口等。可以是一般的窗口,也可以是Dialog,控件等。

如:WM_CREATE, WM_PAINT, WM_MOUSEMOVE, WM_CTLCOLOR, WM_HSCROLL…。

2.1.2.命令消息

与处理用户请求有关, 如单击菜单项或工具栏或控件时, 就会产生命令消息。

WM_COMMAND, LOWORD(wParam)表示菜单项,工具栏按钮或控件的ID。如果是控件, HIWORD(wParam)表示控件消息类型。

2.1.3.控件通知消息

控件通知消息, 这是最灵活的消息格式, 其Message, wParam, lParam分别为:WM_NOTIFY, 控件ID,指向NMHDR的指针。NMHDR包含控件通知的内容, 可以任意扩展。

2.1.4.程序定义消息

用户自定义的消息,对于其范围有如下规定:

WM_USER:                 0x0400-0x7FFF     // (ex. WM_USER+10)
WM_APP:                  0x8000-0xBFFF     // (ex.WM_APP+4)
RegisterWindowMessage:   0xC000-0xFFFF


3.消息队列

3.1.系统消息队列

这是一个系统唯一的Queue,设备驱动(mouse, keyboard)会把操作输入转化成消息存在系统队列中,然后系统会把此消息放到目标窗口所在的线程的消息队列(thread-specific message queue)中等待处理

3.2.线程消息队列

每一个GUI线程都会维护这样一个线程消息队列。(这个队列只有在线程调用GDI函数时才会创建,默认不创建)。然后线程消息队列中的消息会被送到相应的窗口过程(WndProc)处理.

注意:

线程消息队列中WM_PAINT,WM_TIMER只有在Queue中没有其他消息的时候才会被处理,WM_PAINT消息还会被合并以提高效率。其他所有消息以先进先出(FIFO)的方式被处理。


4.队列消息和非队列消息

4.1.队列消息

消息会先保存在消息队列中,消息循环会从此队列中取消息并分发到各窗口处理,如鼠标,键盘消息。

4.2.非队列消息

消息会绕过系统消息队列和线程消息队列直接发送到窗口过程被处理

如: WM_ACTIVATE, WM_SETFOCUS, WM_SETCURSOR, WM_WINDOWPOSCHANGED

注意:

 PostMessage发送的消息是队列消息,它会把消息Post到消息队列中; SendMessage发送的消息是非队列消息, 被直接送到窗口过程处理

5.PostMessage(PostThreadMessage),SendMessage

PostMessage:把消息放到指定窗口所在的线程消息队列中后立即返回。 PostThreadMessage:把消息放到指定线程的消息队列中后立即返回。

SendMessage:直接把消息送到窗口过程处理, 处理完了才返回。

6.GetMessage,PeekMessage

PeekMessage:会立即返回,可以保留消息

GetMessage:在有消息时返回,会删除消息

7.TranslateMessage,TranslateAccelerator

TranslateMessage: 把一个virtual-key消息转化成字符消息(character message),并放到当前线程的消息队列中,消息循环下一次取出处理。

TranslateAccelerator: 将快捷键对应到相应的菜单命令。它会把WM_KEYDOWN 或 WM_SYSKEYDOWN转化成快捷键表中相应的WM_COMMAND 或WM_SYSCOMMAND消息,然后把转化后的WM_COMMAND或WM_SYSCOMMAND直接发送到窗口过程处理,处理完后才会返回。


8.消息循环

MSG msg; //定义消息名
while (GetMessage (& msg, NULL, 0, 0))
{
TranslateMessage (& msg) ; //翻译消息
DispatchMessage (& msg) ; //撤去消息
}
return msg.wParam ;

消息循环以GetMessage调用开始,它从消息队列中取出一个消息。该函数的四个参数可以有控制地获取消息,第一个参数指定要接收消息的MSG结构的地址,第二个参数表示窗口句柄,一般将其设置为空,表示要获取该应用程序创建的所有窗口的消息;第三、四参数用于指定消息范围。后面三个参数被设置为默认值,用于接收发送到属于这个应用程序的任何一个窗口的所有消息。

在接收到除WM_QUIT之外的任何一个消息后,GetMessage()返回 TRUE;如果GetMessage收到一个WM_QUIT消息,则返回FALSE以退出消息循环,终止程序运行。因此,在接收到WM_QUIT之前,带有GetMessage()的消息循环可以一直循环下去。当除WM_QUIT的消息用GetMessage读入后,首先要经过函数 TranslateMessage()对其进行解释,但对大多数消息来说并不起什么作用。

这里起关键作用的是DispatchMessage()函数,把由GetMessage获取的Windows消息传送给在MSG结构中为窗口所指定的窗口过程。在消息处理函数处理完消息之后,代码又循环到开始去接收另一个消息,这样就完成了一个完整的消息循环。

由于Windows操作系统是一种非剥夺式多任务操作系统。只有在应用程序主动交出CPU控制权后,Windows才能把控制权交给其他应用程序。在消息循环中,一定要有能交出控制的系统函数才能实现协同式多任务操作。能完成该功能的只有GetMessage、PeekMessage和 WaitMessage这三个函数,如果在应用程序中长期不去调用这三个函数之一其他任务则无法执行。GetMessage函数在找不到等待应用程序处理的消息时,会自动交出控制权,由Windows把CPU的控制权交给其他等待获取控制权的应用程序。由于任何Windows应用程序都含有一个消息循环,这种隐式交出控制权的方式可以保证合并各个应用程序共享控制权。一旦发往该应用程序的消息到达应用程序队列,即开始执行GetMessage语句的下一条语句。使用GetMessage函数的消息循环在消息队列中没有消息时将等待,如果需要,可以利用这段时间进行I/O端口操作等耗时操作,不过需要在消息循环中使用PeekMessage函数来代替GetMessage。使用PeekMessage的方法同GetMessage类似,下面是一段使用 PeekMessage函数的消息循环的典型例子:

MSG msg;
BOOL bDone=FALSE;
do{
if(PeekMessage(& msg,NULL,0,0,PM_REMOVE)){
if(msg.message==WM_QUIT)
bDone=TRUE;
else{
TranslateMessage(& msg);
DispatchMessage(& msg);
}
}
//无消息处理,进行长时间操作
else{
……//长时间操作
}
} while(!bDone)
……

无论应用程序消息队列中是否有消息,PeekMessage函数都立即返回,如果希望等待新消息入队,可以利用无返回值的函数WaitMessage配合PeekMessage进行消息循环。


文章作者:hgy413
本文地址:https://hgy413.com/1633.html
版权所有 © 转载时必须以链接形式注明作者和原始出处!

本文的评论功能被关闭了.