Sending messages to views
MFC has a wonderful message routing mechanism. A command message first comes
to the active view, then the view frame, then the document, and so on. This works
for WM_COMMAND and WM_UPDATE_COMMAND_UI messages, but does not
work for any other kind of message, including user-defined messages. This is
a real pain, and somewhat silly because it would be easy for Microsoft to implement.
So a problem arises when you need to send a message to some sub-window, but you
can't send a WM_COMMAND message because you are not directly responding
to a GUI component.
Of course, you could invent a fictitious control and generate your own imitation
WM_COMMAND messages. But this is very risky, and contributes to significant
future unmaintainability.
What can you do?
I've done several things. Sometimes, I just put a message handler in the child
frame whose sole purpose is to route the message to the child window contained
in the frame. This can be a pain, but gives you serious control over how the
messages are processed. Thus, the main frame, to which I post a message (often
from a thread), has a handler of the form
// ... in the message map
ON_REGISTERED_MESSAGE(UWM_THREAD_DID_SOMETHING, OnThreadDidSomething)
// ... the handler
LRESULT CMainFrame::OnThreadDidSomething(WPARAM wParam, LPARAM lParam)
{
CView * active = GetActiveView();
if(active != NULL)
active->SendMessage(UWM_THREAD_DID_SOMETHING,
wParam, lParam);
return 0;
}
Sometimes you have to send the message not to the view, but to its parent frame
window. This is done by the following code:
RESULT CMainFrame::OnThreadDidSomething(WPARAM wParam, LPARAM lParam)
{
CFrameWnd * active = GetActiveFrame();
if(active != this)
active->SendMessage(UWM_THREAD_DID_SOMETHING,
wParam, lParam);
return 0;
}
Note the special test above. If, for some reason, you had managed to kill off
all the MDI children while the thread was still running, GetActiveFrame
returns this, which would mean you would get into a semi-infinite SendMessage
loop which would terminate when you ran out of stack space. Note that because
the function is defined as returning this we don't have to worry about
the possibility that a temporary window handle has been returned, a caution I
discuss in my essay on the use of Attach/Detach.
This requires that you use the CMDIChildFrame subclass, and introduce
a similar handler there:
BEGIN_MESSAGE_MAP(CMyMDIChildFrame, CMDIChildWnd)
ON_REGISTERED_MESSAGE(UWM_THREAD_DID_SOMETHING, OnThreadDidSomething)
// and the handler is
LRESULT CMyMDIChildFrame::OnThreadDidSomething(WPARAM wParam, LPARAM lParam)
{
// ... do things to the frame window (resize, reposition?)
// and if appropriate, pass the message down...
SendMessageToDescendants(UWM_THREAD_DID_SOMETHING,
wParam, lParam,
FALSE, // only go one level deep
TRUE); // only to permanent windows
return 0;
}
Now, you may wonder why I didn't send it directly to the child window. Partly
because I'm lazy, and SendMessageToDescendants does what I need. Partly
because I don't need the result, because it came in from PostMessage.
In a case where I had to route a message to the descendant where I needed the
result, I'd instead write
CView * view = (CView *)GetWindow(GW_CHILD);
ASSERT(view != NULL && view->IsKindOf(RUNTIME_CLASS(CView)));
view->SendMessage(...);
Summary
User-defined messages are a powerful and flexible mechanism for handling the
passing of information and control between levels of your application, threads
of your application, and processes. However, using them effectively requires
certain degrees of care. I favor using ::RegisterWindowMessage and GUIDs
to define all messages. Interthread and interprocess sends are risky, and should
be avoided; if you must, use SendMessageTimeout to ensure that you will
not end up in deadlock.