开发文章

Windows消息循环解说

学VC的人第一件事就是必须了解windows消息机制,什么是windows消息机制:Windows操作系统为每一个正在运行的应用程序保持有一个消息队列。当有事件发生后,Windows并不是将这个激发事件直接送给应用程序,而是先将其翻译成一个Windows消息,然后再把这个消息加入到这个应用程序的消息队列中去。应用程序需要通过消息循环来接收这些消息。
首先来看最容易的消息循环:

C/C++ Code复制内容到剪贴板
  1. MSG  msg  
  2. while( GetMessage(&msg,NULL,0,0) )  
  3. {  
  4.  TranslateMessage (&msg);  
  5.  DispatchMessage(&msg);  
  6. }  

 

GetMessage函数第一 个参数是用来获取MSG结构的指针。第二个参数是一个窗口句柄(HWND),用来获取指定窗口的消息,填 NULL表示获取当前线程所有窗口的消息或者线程消息(Thread message)。最后两个参数是 wMsgFilterMin和wMsgFilterMax,用来获取指定的消息,当都填0则表示获取所有的消息。

TranslateMessage函数根据WM_KEYUP,WM_KEYDOWN之类的时间,生成相应的WM_CHAR之类的消息。

DispatchMessage函数将窗口消息,交给相应的窗口过程(WindowProc)来处理。

以上的 这个消息循环,在大部分情况下都能工作得很好,尤其是一般写点小程序,写成上面的形式完全没有问 题。不过偶尔可能会出些问题,所以请继续往下看,还有哪些改进的余地。

第二个版本

如果我们仔细的看一下MSDN上关于GetMessage函数的说明,那么就可以看到MSDN指出了 while( GetMessage(...) ) 的方式是错误的,并给出了如下的形式:

C/C++ Code复制内容到剪贴板
  1. BOOL bRet;  
  2.   
  3. while( (bRet = GetMessage( &msg, NULL, 0, 0 )) != 0)  
  4. {   
  5.  if (bRet == -1)  
  6.  {  
  7.   // handle the error and  possibly exit  
  8.  }  
  9.  else  
  10.  {  
  11.   TranslateMessage(&msg);  
  12.   DispatchMessage(&msg);  
  13.  }  
  14. }  

原来GetMessage除了在收到WM_QUIT消息的时候返回0之外,在发生错误的时候返回的是-1。在大部分 情况下,即使发生了错误,msg也保存了上一次的消息,一个消息处理了两次,我想大部分人都不会察觉 到吧。不过,如果我们想自己写一个类似于MFC之类的框架程序或者严于律己的人来说,这点程序的健壮 性还是不容忽略的。

在继续下一个版本的改进以前,现在模仿MFC或WTL,做一个 PreTranslateMessage:

C/C++ Code复制内容到剪贴板
  1. BOOL PreTranslateMessage(LPMSG pMsg)  
  2. {  
  3.  return FALSE;  
  4. }  
  5.   
  6. BOOL bRet;  
  7. while( (bRet = GetMessage(  &msg, NULL, 0, 0 )) != 0)  
  8. {   
  9.  if (bRet == -1)  
  10.  {  
  11.   // handle the error and possibly exit  
  12.  }  
  13.  else if (!PreTranslaeMessage(&msg))  
  14.  {  
  15.   TranslateMessage(&msg);  
  16.   DispatchMessage(&msg);  
  17.  }  
  18. }  

 

PreTranslateMessage函数的作用是非常重要的。由于DispatchMessage函数是将 消息分发给各个窗口过程(WindowProc)处理,我觉得有3种情况要放在PreTranslateMessage里处理:

全局性的东西,不适宜或不方便放在窗口过程中处理的东西。

要处理某些第三方或通用 控件的消息。

线程消息。

下面要讲的东西,都将放在PreTranslateMessage函数中。

第三个版本

首先要放在PreTraslateMessage函数中的,属于上面3中情况的第一条,全 局性的东西,快捷键。


 

C/C++ Code复制内容到剪贴板
  1. HACCEL hAcc = LoadAccelerator(hInst,MAKEINTRESOURCE (IDA_XXX));  
  2.   
  3. BOOL PreTranslateMessage(LPMSG pMsg)  
  4. {  
  5.  if(TranslateAccelerator(hWnd,hAcc,pMsg))   
  6.   return TRUE;  
  7.  return FALSE;  
  8. }  

 

这里要注意的是,除了自己程序定义的快捷键之外,很多ActiveX控件,都暴露出了有 TranslateAccelerator的接口。如果没有在PreTranslateMessage中调用的话,那程序一定会缺乏某些使 人不方便的行为,比如tab键导航。尤其是嵌入了webbrowser控件的程序,如果想让用户舒适得使用这个 内嵌的浏览器的话,一定要调用下面类似的代码:

C/C++ Code复制内容到剪贴板
  1. IWebBrowser2 * m_pBrowser;  
  2.   
  3. BOOL PreTranslateMessage(LPMSG pMsg)  
  4. {  
  5.  IOleInPlaceActiveObject  * pObj;  
  6.  if( SUCCESSED(m_pBrowser->QueryInterface (IID_IOleInPlaceActiveObject,&pObj)) &&  
  7.  S_OK == pObj- >TranslateAccelerator(pMsg))  
  8.  {  
  9.   return TRUE;  
  10.  }  
  11.  return FALSE;  
  12. }  

 

第四个版本

如果不查MSDN,是否 能立刻说出IsDialogMessage函数的作用呢?IsDialogMessage函数并不仅仅是一个IsXXX的函数,它的作 用是:判断一个消息是否为一个对话框的消息,如果是,就处理它。所以,代码应该如下:

 

C/C++ Code复制内容到剪贴板
  1. BOOL PreTranslateMessage(LPMSG pMsg)  
  2. {  
  3.  if(IsDialogMessage(hDlg,pMsg))  
  4.   return TRUE;  
  5.  return  FALSE;  
  6. }  

 

IsDialogMessage是用来处理对话框上面控件的键盘导航的。例如:当焦 点在一个按钮上面的时候,按下tab键,这时应该将焦点设到下一个控件上面,而由于焦点在这个按钮上 面,所以只有这个按钮才收得到这个tab键的键盘消息,因此我们需要在消息循环中也就是 PreTranslateMessage中调用IsDialogMessage来处理这样的消息。

一般而言,上面的hDlg参数,是一个当前存在的非模态窗口。当然,如MSDN所说,如果一个普通的窗 口上面的控件需要使用键盘导航的话,也可以调用IsDialogMessage来处理。那么,为什么上面指定的是 非模态窗口,模态窗口不需要了吗?是的,因为模态窗口自带消息循环,用不着我们自己的消息循环。

 

第五个版本?

我想我暂时是想不出第五个版本了,即便是一个简简单单的消息循环,也 还有很多深层次的东西可以挖掘。我们平时用惯了MFC/ATL/WTL之类的框架,它们已经将消息循环封装的 很好了,很多东西都已经自动处理了。我想,虽然有些东西我们不需要亲自处理,但是还是需要对此有 一定了解的。

最后,就已WTL的消息循环的源代码,结束这篇文章吧:

 

C/C++ Code复制内容到剪贴板
  1. ////////////////////////////////////////////////////////////////////////////// /  
  2. // CMessageLoop - message loop implementation  
  3.   
  4. class CMessageLoop  
  5. {  
  6. public:  
  7.     ATL::CSimpleArray<CMessageFilter*> m_aMsgFilter;  
  8.     ATL::CSimpleArray<CIdleHandler*> m_aIdleHandler;  
  9.     MSG m_msg;  
  10.   
  11. // Message filter operations  
  12.     BOOL AddMessageFilter(CMessageFilter*  pMessageFilter)  
  13.     {  
  14.         return m_aMsgFilter.Add (pMessageFilter);  
  15.     }  
  16.   
  17.     BOOL RemoveMessageFilter (CMessageFilter* pMessageFilter)  
  18.     {  
  19.         return  m_aMsgFilter.Remove(pMessageFilter);  
  20.     }  
  21.   
  22. // Idle handler  operations  
  23.     BOOL AddIdleHandler(CIdleHandler* pIdleHandler)  
  24.     {  
  25.         return m_aIdleHandler.Add(pIdleHandler);  
  26.     }  
  27.   
  28.      BOOL RemoveIdleHandler(CIdleHandler* pIdleHandler)  
  29.     {  
  30.          return m_aIdleHandler.Remove(pIdleHandler);  
  31.     }  
  32.   
  33. #ifndef  _ATL_NO_OLD_NAMES  
  34.     // for compatilibility with old names only  
  35.      BOOL AddUpdateUI(CIdleHandler* pIdleHandler)  
  36.     {  
  37.          ATLTRACE2(atlTraceUI, 0, _T("CUpdateUIObject and AddUpdateUI are deprecated.  Please change your code to use CIdleHandler and OnIdle\n"));  
  38.          return AddIdleHandler(pIdleHandler);  
  39.     }  
  40.   
  41.     BOOL  RemoveUpdateUI(CIdleHandler* pIdleHandler)  
  42.     {  
  43.         ATLTRACE2 (atlTraceUI, 0, _T("CUpdateUIObject and RemoveUpdateUI are deprecated. Please  change your code to use CIdleHandler and OnIdle\n"));  
  44.          return RemoveIdleHandler(pIdleHandler);  
  45.     }  
  46. #endif // ! _ATL_NO_OLD_NAMES  
  47.   
  48. // message loop  
  49.     int Run()  
  50.     {  
  51.         BOOL bDoIdle = TRUE;  
  52.         int nIdleCount = 0;  
  53.          BOOL bRet;  
  54.   
  55.         for(;;)  
  56.         {  
  57.              while(bDoIdle && !::PeekMessage(&m_msg, NULL, 0, 0,  PM_NOREMOVE))  
  58.             {  
  59.                 if(! OnIdle(nIdleCount++))  
  60.                     bDoIdle = FALSE;  
  61.             }  
  62.   
  63.             bRet = ::GetMessage (&m_msg, NULL, 0, 0);  
  64.   
  65.             if(bRet == -1)  
  66.              {  
  67.                 ATLTRACE2(atlTraceUI, 0, _T ("::GetMessage returned -1 (error)\n"));  
  68.                  continue;   // error, don't process  
  69.             }  
  70.              else if(!bRet)  
  71.             {  
  72.                  ATLTRACE2(atlTraceUI, 0, _T("CMessageLoop::Run - exiting\n"));  
  73.                 break;   // WM_QUIT, exit message loop  
  74.              }  
  75.   
  76.             if(!PreTranslateMessage (&m_msg))  
  77.             {  
  78.                  ::TranslateMessage(&m_msg);  
  79.                 ::DispatchMessage (&m_msg);  
  80.             }  
  81.   
  82.             if (IsIdleMessage(&m_msg))  
  83.             {  
  84.                  bDoIdle = TRUE;  
  85.                 nIdleCount = 0;  
  86.              }  
  87.         }  
  88.   
  89.         return (int) m_msg.wParam;  
  90.     }  
  91.   
  92.     static BOOL IsIdleMessage(MSG* pMsg)  
  93.     {  
  94.         // These messages should NOT cause idle  processing  
  95.         switch(pMsg->message)  
  96.         {  
  97.          case WM_MOUSEMOVE:  
  98. #ifndef _WIN32_WCE  
  99.         case  WM_NCMOUSEMOVE:  
  100. #endif // !_WIN32_WCE  
  101.         case WM_PAINT:  
  102.          case 0x0118:    // WM_SYSTIMER (caret blink)  
  103.              return FALSE;  
  104.         }  
  105.   
  106.         return TRUE;  
  107.      }  
  108.   
  109. // Overrideables  
  110.     // Override to change message  filtering  
  111.     virtual BOOL PreTranslateMessage(MSG* pMsg)  
  112.     {  
  113.         // loop backwards  
  114.         for(int i =  m_aMsgFilter.GetSize() - 1; i >= 0; i--)  
  115.         {  
  116.              CMessageFilter* pMessageFilter = m_aMsgFilter[i];  
  117.              if(pMessageFilter != NULL && pMessageFilter->PreTranslateMessage(pMsg))  
  118.                 return TRUE;  
  119.         }  
  120.          return FALSE;   // not translated  
  121.     }  
  122.   
  123.     //  override to change idle processing  
  124.     virtual BOOL OnIdle(int  /*nIdleCount*/)  
  125.     {  
  126.         for(int i = 0; i <  m_aIdleHandler.GetSize(); i++)  
  127.         {  
  128.              CIdleHandler* pIdleHandler = m_aIdleHandler[i];  
  129.             if (pIdleHandler != NULL)  
  130.                 pIdleHandler->OnIdle();  
  131.         }  
  132.         return FALSE;   // don't continue  
  133.     }  
  134. };  

 

 

感谢 磐实 支持 磐实编程网 原文地址:
http://www.panshy.com

文章信息

发布时间:2013-09-17

作者:磐实

发布者:aquwcw

浏览次数: