让MFC DLL 中的窗口响应PreTranslateMessage

PreTranslateMessage是CWnd的虚函数,在这条函数里处理一些按键消息非常方便。但最近参与一个项目,这个项目由主程序和多个插件DLL组成,其中的一个插件DLL是带有界面的,并且以主程序的窗口为父窗口,在这个插件DLL窗口中怎么也响应不了PreTranslateMessage函数。

看了看MFC的PreTranslateMessage实现终于找到了解决方法。

当按下键盘时,首先主程序的CWinApp对象的PreTranslateMessage会被调用。在这条函数中最重要的函数是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
}

可以看到MFC会从当前窗口一直向最顶层窗口查找并调用CWnd的PreTranslateMessage,直到PreTranslateMessage返回True,或找到最顶层窗口。跟踪发现当在MFC DLL的窗口上按下键盘时,CWnd::FromHandlePermanent 的返回总是NULL,继续跟踪发现问题出在FromHandlePermanent这条函数里。MFC实现如下:

CWnd *  PASCAL CWnd::FromHandlePermanent(HWND hWnd)
{
    CHandleMap
*  pMap  =  afxMapHWND();
    CWnd
*  pWnd  =  NULL;
    
if  (pMap  !=  NULL)
    {
        
//  only look in the permanent map - does no allocations
        pWnd  =  (CWnd * )pMap -> LookupPermanent(hWnd);
        ASSERT(pWnd 
==  NULL  ||  pWnd -> m_hWnd  ==  hWnd);
    }
    
return  pWnd;
}

CHandleMap
*  PASCAL afxMapHWND(BOOL bCreate)
{
    AFX_MODULE_THREAD_STATE
*  pState  =  AfxGetModuleThreadState();
    
if  (pState -> m_pmapHWND  ==  NULL  &&  bCreate)
    {
        BOOL bEnable 
=  AfxEnableMemoryTracking(FALSE);
#ifndef _AFX_PORTABLE
        _PNH pnhOldHandler 
=  AfxSetNewHandler( & AfxCriticalNewHandler);
#endif
        pState
-> m_pmapHWND  =   new  CHandleMap(RUNTIME_CLASS(CWnd),
            ConstructDestruct
< CWnd > ::Construct, ConstructDestruct < CWnd > ::Destruct, 
            offsetof(CWnd, m_hWnd));

#ifndef _AFX_PORTABLE
        AfxSetNewHandler(pnhOldHandler);
#endif
        AfxEnableMemoryTracking(bEnable);
    }
    
return  pState -> m_pmapHWND;
}

原来CWnd::FromHandlePermanent 中调用了afxMapHWND,而该函数返回的是当前模块的模块线程状态中HandleMap,该Map记录了此模块中的HWND对应的CWnd对象的指针。而在调用CWnd的Create函数时会调用MFC会调用CWnd的Attach,该函数会把当前的CWnd指针记录到当前模块的模块线程状态中,代码如下:

BOOL CWnd::Attach(HWND hWndNew)
{
    ASSERT(m_hWnd 
==  NULL);      //  only attach once, detach on destroy
    ASSERT(FromHandlePermanent(hWndNew)  ==  NULL);
        
//  must not already be in permanent map

    
if  (hWndNew  ==  NULL)
        
return  FALSE;

    CHandleMap
*  pMap  =  afxMapHWND(TRUE);  //  create map if not exist
    ASSERT(pMap  !=  NULL);

    pMap
-> SetPermanent(m_hWnd  =  hWndNew,  this );

#ifndef _AFX_NO_OCC_SUPPORT
    AttachControlSite(pMap);
#endif

    
return  TRUE;
}

因为DLL中窗口的创建是在一个导出函数中,并在调用CWnd::Create这前调用了
AFX_MANAGE_STATE(AfxGetStaticModuleState())来切换模块线程状态,导致该窗口所在的模块线程状态和MFC调用CWinApp::PreTranslateMessage时的不同,所以DLL中的窗口就无法响应PreTranslateMessage函数了。

解决方案:

1.dll导出一条函数 DllPreTranslateMessage

BOOL PASCAL DllPreTranslateMessage(MSG  * pMsg)
{
    AFX_MANAGE_STATE(AfxGetStaticModuleState());
    
return  theApp.PreTranslateMessage(pMsg);
}

2.在主程序的CWinApp的PreTranslateMessage中直接调用DLL的DllPreTranslateMessage函数。但记住要先调用DLL中的函数。

BOOL CMyApp::PreTranslateMessage(MSG *  pMsg)
{
    
//  TODO: Add your specialized code here and/or call the base class
     if (DllPreTranslateMessage(pMsg))
        
return  TRUE;
    
return  CWinApp::PreTranslateMessage(pMsg);
}

经过以上两步,DLL中的窗口就可以响应PreTranslateMessage了。

你可能感兴趣的:(mfc,dll,null,thread,module,class)