[转]windows消息机制(MFC)

消息分类与消息队列html

Windows中,消息使用统一的结构体(MSG)来存放信息,其中message代表消息的具体的类型,程序员

而wParam,lParam是其最灵活的两个变量,为不一样的消息类型时,存放数据的含义也不同。windows

time表示产生消息的时间,pt表示产生消息时鼠标的位置。数组

按照类型,Windows将消息分为:并发

(0) 消息ID范围框架

系统定义消息ID范围:[0x0000, 0x03ff]
用户自定义的消息ID范围:
WM_USER: 0x0400-0x7FFF (例:WM_USER+10)
WM_APP(winver> 4.0):0x8000-0xBFFF (例:WM_APP+4)
RegisterWindowMessage:0xC000-0xFFFF【用来和其余应用程序通讯,为了ID的惟一性,使用::RegisterWindowMessage来获得该范围的消息ID 】函数

(1) 窗口消息:即与窗口的内部运做有关的消息,如建立窗口,绘制窗口,销毁窗口等。工具

     能够是通常的窗口,也能够是MainFrame,Dialog,控件等。 ui

如:WM_CREATE, WM_PAINT, WM_MOUSEMOVE, WM_CTLCOLOR, WM_HSCROLL等this

(2) 当用户从菜单选中一个命令项目、按下一个快捷键或者点击工具栏上的一个按钮,都将发送WM_COMMAND命令消息。

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

     #define LOWORD(l) ((WORD)(l))

     #define HIWORD(l) ((WORD)(((DWORD)(l) >> 16) & 0xFFFF))

(3) 随着控件的种类愈来愈多,愈来愈复杂(如列表控件、树控件等),仅仅将wParam,lParam将视为一个32位无符号整数,已经装不下太多信息了。

    为了给父窗口发送更多的信息,微软定义了一个新的WM_NOTIFY消息来扩展WM_COMMAND消息。

    WM_NOTIFY消息仍然使用MSG消息结构,只是此时wParam为控件ID,lParam为一个NMHDR指针,

    不一样的控件能够按照规则对NMHDR进行扩充,所以WM_NOTIFY消息传送的信息量能够至关的大。

注:Window 9x 版及之后的新控件通告消息再也不经过WM_COMMAND 传送,而是经过WM_NOTIFY 传送,
      可是老控件的通告消息, 好比CBN_SELCHANGE 仍是经过WM_COMMAND 消息发送。

(4) windwos也容许程序员定义本身的消息,使用SendMessage或PostMessage来发送消息。

windows消息还能够分为:

(1) 队列消息(Queued Messages)
消息会先保存在消息队列中,消息循环会今后队列中取出消息并分发到各窗口处理
如:WM_PAINT,WM_TIMER,WM_CREATE,WM_QUIT,以及鼠标,键盘消息等。
其中,WM_PAINT,WM_TIMER只有在队列中没有其余消息的时候才会被处理,
WM_PAINT消息还会被合并以提升效率。其余全部消息以先进先出(FIFO)的方式被处理。

(2) 非队列消息(NonQueued Messages) 
消息会绕过系统消息队列和线程消息队列,直接发送到窗口过程进行处理 
如:WM_ACTIVATE, WM_SETFOCUS, WM_SETCURSOR,WM_WINDOWPOSCHANGED

Windows系统的整个消息系统分为3个层级:

    ① Windows内核的系统消息队列

    ② App的UI线程消息队列

    ③ 处理消息的窗体对象

Windows内核维护着一个全局的系统消息队列;按照线程的不一样,系统消息队列中的消息会分发到应用程序的UI线程的消息队列中;

应用程序的每个UI线程都有本身的消息循环,会不停地从本身的消息队列取出消息,并发送给Windows窗体对象;

每个窗体对象都使用窗体过程函数(WindowProc)来处理接收到的各类消息。

复制代码

 1 LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
 2 {
 3     PAINTSTRUCT ps;
 4     HDC hdc;
 5 
 6     switch (message)
 7     {
 8     case WM_COMMAND:
 9         break;
10     case WM_PAINT:
11         hdc = BeginPaint(hWnd, &ps);
12         // TODO: 在此添加任意绘图代码...
13         EndPaint(hWnd, &ps);
14         break;
15     case WM_DESTROY:
16         PostQuitMessage(0);
17         break;
18     default:
19         return DefWindowProc(hWnd, message, wParam, lParam);
20     }
21     return 0;
22 }

复制代码

须要的话,在WindowProc中,能够用::GetMessageTime获取当前消息产生的时间,
用::GetMessagePos获取当前消息产生时鼠标光标所在的位置。

(1) 各个窗口消息由各个窗体(或控件)自身的WindowProc(虚函数)接收并处理。

(2) WM_COMMAND命令消息统一由当前活动主窗口的WindowProc接收,通过绕行后,可被其余的CCmdTarget对象处理。

(3) WM_COMMAND控件通知统一由子窗口(控件)的父窗口的WindowProc接收并处理,也能够进行绕行被其余的CCmdTarget对象处理。

     (例如:CFormView具有接受WM_COMMAND控件通知的条件,又具有把WM_COMMAND消息派发给关联文档对象处理的能力,

         因此给CFormView的WM_COMMAND控件通知是可让文档对象处理的。)

     另外,WM_COMMAND控制通知会先调用ReflectLastMsg反射通知子窗口(控件),若是子窗口(控件)处理了该消息并返回TRUE,则消息会中止分发;

     不然,会继续调用OnCmdMsg进行命令发送(如同WM_COMMAND命令消息同样)。

注:WM_COMMAND命令消息与WM_COMMAND控件通知的类似之处:
WM_COMMAND命令消息和WM_COMMAND控制通知都是由WindowProc给OnCommand处理,
OnCommand经过wParam和lParam参数区分是命令消息或通知消息,而后送给OnCmdMsg处理。
事实上,BN_CLICKED控件通知消息的处理和WM_COMMAND命令消息的处理彻底同样。
由于该消息的通知代码是0,ON_BN_CLICKED(id,memberfunction)和ON_COMMAND(id,memberfunction)是等同的。

(4)WM_NOTIFY消息只是对WM_COMMAND控件通知进行了扩展,与WM_COMMAND控件通知具备相同的特色。

SendMessage与PostMessage

PostMessage 把消息投递到消息队列后,当即返回;
SendMessage把消息直接送到窗口过程处理,处理完才返回。

GetMessage与PeekMessage

GetMessage 有消息且该消息不为WM_QUIT,返回TRUE。
            有消息且该消息为WM_QUIT,返回FALSE。
                  没有消息时,挂起该UI线程,控制权交还给系统。
PeekMessage 有消息返回TRUE,若是没有消息返回FALSE。
                   是否从消息队列中删除此消息(PM_REMOVE),由函数参数来指定。

要想在没有消息时作一些工做,就必须使用PeekMessage来抓取消息,以便在没有消息时,能在OnIdle中执行空闲操做(以下):

复制代码

 1 while (TRUE) 
 2 {
 3     if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) 
 4     {
 5         if (msg.message == WM_QUIT)
 6             break;
 7         TranslateMessage(&msg);
 8         DispatchMessage(&msg);
 9     }
10     else 
11     {
12         OnIdle();
13     }
14 }

复制代码

例如:MFC使用OnIdle函数来清理一些临时对象及未使用的动态连接库。

只有在OnIdle返回以后程序才能继续处理用户的输入,所以不该在OnIdle进行较长的任务。

MFC消息处理

在CWnd中,MFC使用OnWndMsg来分别处理各种消息:

若是是WM_COMMAND消息,交给OnCommand处理;而后返回。

若是是WM_NOTIFY消息,交给OnNotify处理;而后返回。

若是是WM_ACTIVATE消息,先交给_AfxHandleActivate处理,再继续下面的处理。

若是是WM_SETCURSOR消息,先交给_AfxHandleSetCursor处理,而后返回。

若是是其余的窗口消息(包括WM_ACTIVATE消息),则

  首先在消息缓冲池(一个hash表,用于加快消息处理函数的查找)进行消息匹配,
    若匹配成功,则调用相应的消息处理函数;
    若不成功,则在消息目标的消息映射数组中进行查找匹配,看它是否能处理当前消息。
  若是消息目标处理了该消息,则会匹配到消息处理函数,调用它进行处理;

不然,该消息没有被应用程序处理,OnWndMsg返回FALSE。

MFC消息映射

消息映射实际是MFC内建的一个消息分派机制。

把MFC中的宏进行展开(以下),能够获得消息映射表整个全貌。

注:GetMessageMap为虚函数。
     {0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0}:对象消息映射表的结束标识

窗口消息只能由CWnd对象来处理,采用向基类直线上朔的方式,来查找对应的消息响应函数进行处理。

一旦找到消息响应函数(如有返回值且为TRUE),就中止上朔。所以,咱们常常会看到这样的代码:

增长一个消息处理函数来写咱们的逻辑时,MFC ClassWizard会在该函数以前或以后显示调用其基类对应的函数,保证基类中逻辑被执行。

命令消息可由CCmdTarget对象接收并处理(OnCmdMsg为虚函数),除了向基类直线上朔方式外,还有命令绕行机制(要防止造成圈,死循环)。

在某种程度上,控制通知消息由窗口对象处理是一种习惯和约定。然而,控件通知消息也是能够有CCmdTarget对象接收并处理,并进行命令绕行的。

下图为MFC经典单文档视图框架的命令消息绕行路线:

函数调用过程以下(若是没有任何对象处理该条WM_COMMAND消息,最后会被::DefWindowProc处理)。

非模态对话框的消息处理

1 static CAboutDlg aboutDlg;
2 aboutDlg.Create(IDD_ABOUTBOX, this);
3 aboutDlg.ShowWindow(SW_SHOW);

应用程序只有一个消息循环。

对于窗口消息,非模态对话框(及其子控件)与父窗口(及其子控件)都是用自身的WindowProc函数接收并处理,互不干扰。

对于命令消息,由当前活动主窗口的WindowProc接收(例如:当前活动主窗口为非模态对话框,则命令消息会被非模态对话框接收)。

能够在当前活动主窗口的OnCmdMsg中作命令绕行,使得其余的CCmdTarget对象也能够处理命令消息。

对于控件通知,由其父窗口的WindowProc接收并处理,通常不进行命令绕行被其余的CCmdTarget对象处理。

模态对话框的消息处理

1 CAboutDlg aboutDlg;
2 aboutDlg.DoModal();

(1) 模态对话框弹出来后,首先会让父窗口失效,使其不能接受用户的输入(键盘鼠标消息)。

1 EnableWindow(hwndParent, FALSE) ;

(2) 父窗口消息循环被阻塞(会卡在DoModal处,等待返回),由模态对话框的消息循环来接管(所以整个程序不会卡住)。

    接管后,模态对话框的消息循环仍然会将属于父窗口及其子控件的窗口消息(不包括键盘鼠标相关的窗口消息)发送给它们各自的WindowProc窗口函数,进行响应处理。

(3) 模态对话框销毁时(点击IDOK或IDCANCEL),父窗口消息循环从新激活,继续DoModal后的逻辑。

    激活后,父窗口有能够从新接受用户的输入(键盘鼠标消息)。

1 EnableWindow(hwndParent, TRUE) ;

从上面的过程当中,咱们能够获得以下结论:

对于窗口消息,模态对话框主窗口(及其子控件)与父窗口(及其子控件)都是用自身的WindowProc函数接收并处理,互不干扰。

只是父窗口(及其子控件)没法接受到键盘鼠标消息相关的窗口消息。

对于命令消息,由模态对话框主窗口的WindowProc接收。能够在模态对话框主窗口的OnCmdMsg中作命令绕行,使得其余的CCmdTarget对象也能够处理命令消息。

对于控件通知,由其父窗口的WindowProc接收并处理,通常不进行命令绕行被其余的CCmdTarget对象处理。

参考

《深刻浅出MFC》- 侯捷

转载:http://www.cnblogs.com/kekec/p/3210696.html