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
}


 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值