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了。