PreTranslateMessage and doing something inside it - is not recommended by experts. If you think you are an expert and think
PreTranslateMessage handling is cool, then your definition of expert and my definition of the same is varied. However, what we both must agree upon is, there are some practical cases and situations where you cannot live without
PreTranslateMessage. The best example of it is using Enter as Tab where
PreTranslateMessage handling offers the easiest of solutions in quickest quanta of time. But, when you are inside the dreaded COM factory, your life becomes a little difficult since
PreTranslateMessage for modeless dialogs will not be called. This article tries to provide an elegant solution to overcome the problem.
Why PreTranslateMessage is not called inside COM/ActiveX
When you are dealing with Modal dialogs inside COM, you don't face such problems, but this discrimination happens whenever your dialog is modeless. Why is that? In answer to that, it is often said, the COM or the ActiveX control doesn't own the message pump. You might look up at this link for more information. The link provided deals with nearly the same thing as
PreTranslateMessage not being invoked (in other words same story, different version). In my understanding, what this owning of the message pump means is whatever module is running, the windows message loop through
AfxInternalPumpMessage call (or a similar call) is owning the message pump. Incase of modal dialogs, the modal dialogs themselves own the message pump by running a message loop, so they got away with implementing
PreTranslateMessage. Modeless dialogs suffer this terrible fate of not having the blessings of
PreTranslateMessage as they don't own the message pump.
For me, just hearing the COM/ActiveX is not the owner of the message pump wasn't enough. So, I had to dig deep inside the problem. My findings were, whoever is in charge of the message pump is not aware of the window and dialog objects inside the COM component or ActiveX (mind it, I didn't say handle, I said object, meaning
CWnd instance). Therefore whenever that module in charge (usually your executable) calls
CWnd::FromHandlePermanent(...) with a window handle from COM as argument, it receives
NULL and cannot invoke the corresponding
PreTranslateMessage associated with that window/dialog. I now know why that returning of
NULL happens but discussing that is beyond the scope of this article.
Still, those who are curious as cat can debug the entire window creation process of MFC (which includes also the creation of
CCmdTarget) and look at
AFX_MODULE_STATE m_pModuleState member of
AFX_THREAD_STATE instance obtained by
CCmdTarget and look at
m_pmapHWND member of the thread state variable as well as
afxMapHWND ..... Haven't I already thrown enough complicated terms to discourage :) into going deep?
*Small hint: The window instance residing in COM/ActiveX is not contained inside the
m_pmapHWND member of the main application thread state.
How To Get Around It?
In order to get around the problem, one idea is installing a hook procedure into a chain of hooks using
SetWindowsHook(Ex)and this solution is offered in some of the Microsoft sites as well. To me, the solution is not elegant because you cannot directly use the feature of
PreTranslateMessage and have to write up some new codes. This becomes difficult, if you already have existing codes with
PreTranslateMessage and now want it to get invoked inside COM.
The alternate solution is to somehow pass the message structure MSG to COM after a
PeekMessage of the application gets executed. After COM receives the structure, it needs to perform the same things MFC does to transmit the
PreTranslateMessage to the relevant window and its parent and above. This, to me, seems pretty elegant.
CWinApp gives you a lucky break in this regard because
CWinApp::PreTranslateMessage will definitely be called after a
PeekMessage and it contains the proper structure with the proper window handles even if the window resides in COM/ActiveX. So, windows O/S does its job alright. The reason why
PreTranslateMessage is not called for a COM dialog/window is due to the failure of MFC in finding the appropriate window pointer (
CWnd*) given a window handle. In other words, when the hierarchy of windows is traversed by MFC internal mechanizm to call up
PreTranslateMessage, that guy
PreTranslate... never picks up the phone because
CWnd::FromHandlePermanent(...) gave him a heart attack by failing miserably.
So, it's on us to get the job done. What we need do is establish a handshaking mechanizm between the main app and the COM window (initiator of the
CWinApp) so that somehow the
MSG structure in
PreTranslateWindow gets transferred and propagates up to the top level in the hierarchy of parent windows. So, let's make the App and COM talk to each other to make the connection.
LET'S TALK THE TALK
In order to implement what has just been said above, we can send a message which is both common to the COM and the main Application. Hence, we may create a registered user message as such:
const UINT RWM_PRETRANSLATEMSG = ::RegisterWindowMessage(_T("RWM_PRETRANSLATEMSG"));
This message can be considered as the handshaking element that COM and its host Application both can recognize. From the application side, we can transfer the
MSG structure to COM as described below:
BOOL CTestAppApp::PreTranslateMessage(MSG* pMsg)
HWND hWndParent = AppGetTopParent(pMsg->hwnd);
if(::SendMessage(hWndParent, RWM_PRETRANSLATEMSG, 0, (LPARAM)pMsg) == TRUE)
What we are doing here is - from the handle of the window responsible for the message invocation, retrieve the topmost parent which doesn't have the child style set. Obviously this window is an independant dialog or popup window and it gets the first crack at the registered message and relavant
MSG structure that has been passed in the
And that is all there is to it in the application side.
Now comes the more interesting part. What to do with the passed argument and how to process it in the COM side. Intuitive users can already guess from the forementioned discussions - what I am about to do. Simple, just catch this message in COM and do something similar like we see in
BOOL PASCAL CWnd::WalkPreTranslateTree(HWND hWndStop, MSG* pMsg) to walk the chain of windows for whom
PreTranslateMessage needs to be invoked and simply invoke what needs to be invoked.
LET'S WALK THE WALK
For this catching stuff, we can use the late great Paul DiLascia's simplistic yet beautifully designed
CSubclassWnd class as a base for
CPreTranslateMsgHook which will do the job for us. What it will do is catch the
RWM_PRETRANSLATEMSG message and extract the MSG structure passed as
LPARAM and propagate to the desired windows as follows:
LRESULT CPreTranslateMsgHook::WindowProc(UINT msg, WPARAM wp, LPARAM lp)
if(msg == RWM_PRETRANSLATEMSG)
BOOL bRet = FALSE;
MSG* pMsg = (MSG*)(lp);
CWnd* pWnd = CWnd::FromHandlePermanent(m_hWnd);
if(pWnd != NULL)
bRet = WalkPreTranslateMsg(pWnd->GetSafeHwnd(), pMsg);
WalkPreTranslateMsg is the method which does the walking as its name suggests and the code inside it is just a replica of what you see inside
BOOL PASCAL CWnd::WalkPreTranslateTree(HWND hWndStop, MSG* pMsg). I will spare you the browsing of dirty MFC stuffs and just paste the code here:
ASSERT(hWndStop == NULL || ::IsWindow(hWndStop));
ASSERT(pMsg != NULL);
for (HWND hWnd = pMsg->hwnd; hWnd != NULL; hWnd = ::GetParent(hWnd))
CWnd* pWnd = CWnd::FromHandlePermanent(hWnd);
if (pWnd != NULL)
return TRUE; }
if (hWnd == hWndStop)
From the code, it's obvious, the walk starts from the window for which the message is posted in message queue and ends up till the top parent is reached. While doing the walking, the traverser is also calling
CWnd::FromHandlePermanent and this time, for heavens sake, it succeeds - since COM itself is aware of the windows contained inside it. For advanced readers, this is another way of saying, the m_pmapHWND member of the thread state of COM module contains the appropriate window pointer in the handle verses permanent window pointer map.
Needless to say, only the top parent needs to be subclassed/hooked to initiate the walking through our very own
USING THE CPreTranslateMsgHook in the CODE
We have talked the talk, and walked the walk. Now all we need to do is use the code and it is only two lines. In order to enable the
pretranslatemessage support in a COM dialog (modeless ofcourse!!), all you have to do is create the hook and install the hook. See it's simple:
m_pHook = new CPreTranslateMsgHook();
You can call these two lines of code inside the
OnInitDialog override or just after the creation of the dialog through the
By the way, it's actually three lines (not two :)), because you need to
delete the hook (instantiated with the
new operator) after dialog destruction.
Points of Interest
This solution to
PreTranslateMessage invocation also solves the problem of TAB and ARROW key not working in modeless dialogs inside COM/ACTIVE-X, further justifying the effectiveness along with the elegance of the proposed method.
The drawback of the proposed solution is, you yourself must have the authority over both the COM module and the main application source codes. So, usually you apply this solution when you are trying to port some existing codes to COM and you also have the provision to change the main application code. However, even if you don't have the main application in your pocket, you can use a simple workaround by installing a message hook (
WH_GETMESSAGE) inside your COM and consider the Hook Procedure to be the
PretranslateMessage of the main application. From there, you can send a registered message following the same procedure mentioned above (refer to the
CTestAppApp::PreTranslateMessage codes) and let the
CPreTranslateMsgHook class to take care of the rest just like before. This alteration should only be a few lines of additional codes in the COM part.
And all credit goes to the little known greatness of Mr. Jacques Raphanel (probably he enjoys his bit of solitude and quietness), since he is the one who introduced me to this simple technique to achieve what seemed very complex.
Thanks also to late Paul DiLascia for his
- Article uploaded: 16th June, 2011