MFC中PreTranslateMessage的實現

PreTranslateMessage

  PreTranslateMessage是消息在送給TranslateMessage函數之前被調用的,絕大多數本窗口的消息都要通過這裏,比較常用,當你需要在MFC之前處理某些消息時,常常要在這裏添加代碼.
  MFC消息控制流最具特色的地方是CWnd類的虛擬函數 PreTranslateMessage(),通過重載這個函數,我們可以改變MFC的消息控制流程,甚至可以作一個全新的控制流出來。只有穿過消息隊列的消息才受PreTranslateMessage()影響,採用SendMessage()或其他類似的方式向窗口直接發送的而不經過消息隊列的消息根本不會理睬PreTranslateMessage()的存在。
  一、是否調用TranslateMessage()和DispatchMessage()是由一個名稱爲PreTranslateMessage()函數的返回值決定的,如果該函數返回TRUE,則不會把該消息分發給窗口函數處理。
  二、傳給PreTranslateMessage()的消息是未經翻譯過的消息,它沒有經過TranslateMessage()處理。可以在該函數中使用(pMsg->wParam==VK_RETURN)來攔截回車鍵。
  三、在WindowProc裏不能處理WM_Char消息。
  四、SetWindowText會發送WM_Char給窗口。
  五、PeekMessage和GetMessage的區別:
  GetMessage在沒有消息的時候等待消息,cpu當然低
  PeekMessage沒有消息的時候立刻返回,可以在沒有消息的時候可以做其他處理,但cpu佔用率一般較高。
  大多遊戲都用PeekMessage();

 

關於CWnd* FromHandlePermanent(HWND)函數的解釋,解決了我的疑惑:
爲什麼CWnd*可以調用派生類的PreTranslateMessage???答案就在於FromHandlePermanent. 不是簡單的把把句柄封裝, 而是查找HWND對應的CWnd, 然後返回其指針.
而FromHandle()函數就是簡單的封裝了.

MFC裏面,Pretranslatemessage是一個很重要的虛函數。這個函數的作用這裏就不談了,很多地方都有涉及,這裏只談一下其實現的機制。
談到PretranslateMessage的實現,便不得不談到MFC消息循環的實現。MFC通過CWinApp類中的Pumpmessage函數實現消息循環,但是實際的消息循環代碼位於CWinThread中,CWinApp只是從CWinThread繼承過來。其簡化後的代碼大概如下:
BOOL CWinThread::PumpMessage()
{
    _AFX_THREAD_STATE *pState = AfxGetThreadState();

    ::GetMessage(&(pState->m_msgCur), NULL, NULL, NULL))
 
    if (!AfxPreTranslateMessage(&(pState->m_msgCur)))
    {
        ::TranslateMessage(&(pState->m_msgCur));
        ::DispatchMessage(&(pState->m_msgCur));
    }
    return TRUE;
}
可以看到,PumpMessage在實際的TranslateMessageDispatchMessage發生之前會調用AfxPreTranslateMessageAfxPreTranslateMessage又會調用CWnd::WalkPreTranslateTree(雖然也會調用其他函數,但是這個最爲關鍵),其代碼如下:

BOOL PASCAL CWnd::WalkPreTranslateTree(HWND hWndStop, MSG* pMsg)
{
    ASSERT(hWndStop == NULL || ::IsWindow(hWndStop));
    ASSERT(pMsg != NULL);

    // walk from the target window up to the hWndStop window checking
    //  if any window wants to translate this message

    for (HWND hWnd = pMsg->hwnd; hWnd != NULL; hWnd = ::GetParent(hWnd))
    {
        CWnd* pWnd = CWnd::FromHandlePermanent(hWnd);
        if (pWnd != NULL)
        {
            // target window is a C++ window
            if (pWnd->PreTranslateMessage(pMsg))
                return TRUE; // trapped by target window (eg: accelerators)
        }
    
        // got to hWndStop window without interest
        if (hWnd == hWndStop)
            break;
    }
    return FALSE;       // no special processing
}

可以看到,代碼還是很直接的。從接受到消息的窗口層層往上遍歷,並調用PretranslateMessage看是否返回TRUE,是則結束,否則繼續。

這裏有一個地方非常關鍵:CWnd *pWnd = CWnd::FromHandlePermanent(hWnd)這一句代碼從當前AfxModuleThreadState拿到Permanent句柄表,從而找到hWnd對應的CWnd對象。關於PreTranslateMessage有一個常見的問題就是與此有關:如果編寫了一個MFC DLL並從另外的一個MFC主工程之中調用這個MFC DLL中的Modeless Dialog的話,Modeless Dialog的PreTranslateMessage不會被調。因爲MFC DLL和這個MFC工程擁有不同的AfxModuleThreadState,因此在MFC DLL中創建的modeless CDialog對象不在MFC工程的句柄表中(CWnd::FromhandlePermanent返回NULL),因此雖然MFC主工程中的CWinAppPretranslatemessage會被調(注意此時Dialog的消息循環在MFC主工程裏面),但是不會調用MFC DLL中創建的那個modeless CDialogPreTranslateMessage函數。因此需要特殊處理。一般有兩種方法,一種是直接在MFC主工程中的CWinApp::PreTranslatemessage裏面調用MFC DLLCWinApp::PreTranslateMessage(可以專門在MFC DLLexport一個專門的函數來做這件事情)。另外的方法是使用鉤子,在鉤子消息處理函數之中,判斷目標窗口是否是當前具有焦點的窗口,如果是,則直接調用目標窗口的PreTranslateMessage函數(前提是你有要保存這個對象的指針)。

在MFC裏面,Pretranslatemessage是一個很重要的虛函數。這個函數的作用這裏就不談了,很多地方都有涉及,這裏只談一下其實現的機制。
談到PretranslateMessage的實現,便不得不談到MFC消息循環的實現。MFC通過CWinApp類中的Pumpmessage函數實現消息循環,但是實際的消息循環代碼位於CWinThread中,CWinApp只是從CWinThread繼承過來。其簡化後的代碼大概如下:
BOOL CWinThread::PumpMessage()
{
       _AFX_THREAD_STATE *pState = AfxGetThreadState();

       ::GetMessage(&(pState->m_msgCur), NULL, NULL, NULL))

       if (!AfxPreTranslateMessage(&(pState->m_msgCur)))
       {
            ::TranslateMessage(&(pState->m_msgCur));
            ::DispatchMessage(&(pState->m_msgCur));
       }
       return TRUE;
}
可以看到,PumpMessage在實際的TranslateMessage和DispatchMessage發生之前會調用AfxPreTranslateMessage,AfxPreTranslateMessage又會調用CWnd::WalkPreTranslateTree(雖然也會調用其他函數,但是這個最爲關鍵),其代碼如下:
BOOL PASCAL CWnd::WalkPreTranslateTree(HWND hWndStop, MSG* pMsg)
{
       ASSERT(hWndStop == NULL || ::IsWindow(hWndStop));
       ASSERT(pMsg != NULL);

       // walk from the target window up to the hWndStop window checking
       // if any window wants to translate this message

       for (HWND hWnd = pMsg->hwnd; hWnd != NULL; hWnd = ::GetParent(hWnd))
       {
             CWnd* pWnd = CWnd::FromHandlePermanent(hWnd);
             if (pWnd != NULL)
             {
                    // target window is a C window
                    if (pWnd->PreTranslateMessage(pMsg))
                    return TRUE; // trapped by target window (eg: accelerators)
             }

             // got to hWndStop window without interest
             if (hWnd == hWndStop)
                    break;
       }
       return FALSE; // no special processing
}

可以看到,代碼還是很直接的。從接受到消息的窗口層層往上遍歷,並調用PretranslateMessage看是否返回TRUE,是則結束,否則繼續。
這裏有一個地方非常關鍵:CWnd *pWnd = CWnd::FromHandlePermanent(hWnd) 這一句代碼從當前AfxModuleThreadState拿到Permanent句柄表,從而找到hWnd對應的CWnd對象。關於PreTranslateMessage有一個常見的問題就是與此有關:如果編寫了一個MFC DLL並從另外的一個MFC主工程之中調用這個MFC DLL中的Modeless Dialog的話,Modeless Dialog的PreTranslateMessage不會被調。因爲MFC DLL和這個MFC工程擁有不同的AfxModuleThreadState,因此在MFC DLL中創建的modeless CDialog對象不在MFC工程的句柄表中(CWnd::FromhandlePermanent返回NULL),因此雖然MFC主工程中的CWinApp的Pretranslatemessage會被調(注意此時Dialog的消息循環在MFC主工程裏面),但是不會調用MFC DLL中創建的那個modeless CDialog的PreTranslateMessage函數。因此需要特殊處理。一般有兩種方法,一種是直接在MFC主工程中的CWinApp::PreTranslatemessage裏面調用MFC DLL的CWinApp::PreTranslateMessage(可以專門在MFC DLL中export一個專門的函數來做這件事情)。另外的方法是使用鉤子,在鉤子消息處理函數之中,判斷目標窗口是否是當前具有焦點的窗口,如果是,則直接調用目標窗口的PreTranslateMessage函數(前提是你有要保存這個對象的指針)。
Ok。基本上就是這些。關於AfxThreadModuleState以及HandleMap我會寫一些有關的文章,把這篇文章沒有cover到的地方補齊。這是我的第一篇Blog,希望不要受到打擊就好。BTW,其實我很討厭MFC的,我更喜歡用API一些。 

      MFC消息控制流最具特色的地方是CWnd類的虛擬函數PreTranslateMessage(),通過重載這個函數,我們可以改變MFC的消息控制流程,甚至可以作一個全新的控制流出來。只有穿過消息隊列的消息才受PreTranslateMessage()影響,採用SendMessage()或其他類似的方式向窗口直接發送的而不經過消息隊列的消息根本不會理睬PreTranslateMessage()的存在。

 一、是否調用TranslateMessage()和DispatchMessage()是由一個名稱爲PreTranslateMessage()函數的返回值決定的,如果該函數返回TRUE,則不會把該消息分發給窗口函數處理。

二、傳給PreTranslateMessage()的消息是未經翻譯過的消息,它沒有經過TranslateMessage()處理。可以在該函數中使用(pMsg->wParam==VK_RETURN)來攔截回車鍵。

三、在WindowProc裏不能處理WM_Char消息。

四、SetWindowText會發送WM_Char給窗口。

五、PeekMessage和GetMessage的區別:

GetMessage在沒有消息的時候等待消息,cpu當然低

PeekMessage沒有消息的時候立刻返回,所以cpu佔用率高。

因爲遊戲不能靠windows消息驅動,所以要用PeekMessage();

     BOOL AfxInternalPreTranslateMessage(MSG* pMsg)
{
       // ASSERT_VALID(this);

       CWinThread *pThread = AfxGetThread();
        if( pThread )
        {
              // if this is a thread-message, short-circuit this function
             if (pMsg->hwnd == NULL && pThread->DispatchThreadMessageEx(pMsg))
                    return TRUE;
        }

       // walk from target to main window
       CWnd* pMainWnd = AfxGetMainWnd();
       if (CWnd::WalkPreTranslateTree(pMainWnd->GetSafeHwnd(), pMsg))
       return TRUE;

       // in case of modeless dialogs, last chance route through main
       // window's accelerator table
       if (pMainWnd != NULL)
        {
              CWnd* pWnd = CWnd::FromHandle(pMsg->hwnd);
             if (pWnd->GetTopLevelParent() != pMainWnd)
                    return pMainWnd->PreTranslateMessage(pMsg);
       }

        return FALSE; // no special processing
}


 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章