Meandering Through the Maze of MFC Message and Command Routing MFC消息路由机制分析

Meandering Through the Maze of MFC Message and Command Routing

Paul DiLascia

Paul DiLascia is a freelance software consultant specializing in developing C++ applications for Windows. He is the author of Windows++: Writing Reusable Code in C++ (Addison-Wesley, 1992).

If you've ever written even a simple program using Visual C++™ and MFC, you're familiar with messages and commands. You know that MFC uses something called "message maps" to route Windows®messages to your virtual functions. But being familiar with something is not the same as understanding it. How does it all work? And what if you ever want to do something unusual? 
It
goes something like this. You run AppWizard to create a fledgling app. You
fill in the blanks, write some code to handle OnFileNew and OnFileOpen. You
write your view's OnDraw function. You add more menu commands and maybe some
toolbar buttons for them. With ClassWizard, hooking everything up is a
breeze. You add some message handlers: WM_CLOSE to clean up as your main
frame is dying and WM_SIZE to shuffle the child windows when your view
changes size. You're really flying now. Your app is growing by leaps and
bounds. Serialization works, your view manages several windows, they all
paint themselves correctly. You've got a half dozen dialog boxes and a cute
bitmap in your About box. Hey, this is fun. Aren't you glad you switched to
C++? 
Then
it happens. 
Something
unusual comes up. Maybe it's a modeless dialog or a specialized control bar
and you figured you could use ON_UPDATE_COMMAND_UI to enable and disable the
buttons. But it doesn't work. Or maybe you want to handle a command, but the
ID is stored in a variable, not a #define symbol. Or maybe you have a
specialized child window with ON_COMMAND entries in its message map. But,
funny, the menu item shows up grayed. So you crank up the debugger, pick the
Fooble command, and watch closely as control flows through the twisty maze.
Mumble calls Fumble calls Bumble calls Bletch. Where oh where will your
message go next? Poof! Into a black hole.
And
what if you want to route commands to new objects? Where do you begin? Which
of the seemingly gazillions of virtual functions do you override to change
MFC's behavior? If you unplug that wire, will the whole thing fizzle? How do
you reroute a few cortical pathways without lobotomizing your program? 
MFC's
command routing paths are bogglesome even to supposed experts like me. The
first time I traced the flow of a simple command like ID_APP_EXIT, I came
away with a mental picture of MFC's message routing system that resembled
something like Figure 1. Judging from the scores of questions I
get, I'm not alone.

 

Figure 1 Where's the Minotaur?

So let me guide you through the labyrinth of message and command
routing, show you how MFC handles commands differently from other window
messages, why menu items sometimes appear mysteriously grayed, how and when
user interface objects get updated and how to solve all the problems I just
mentioned. And more. Want to punt the document/view architecture in favor of
something else? I'll show you how. I'll even show you some tricks to impress
your friends. So grab your compass and golden thread, here we go.

Message Madness Made Merry 
I
have a friend who's fond of saying, when confronted with any daunting task,
"How do you eat an elephant? One bite at a time." Frankly, I can't
for the life of me figure why anyone would attempt to eat an elephant, but I
suspect the overall experience may not be unlike trying to digest MFC source
code. To explain the intricacies of message and command handling, I'll break
it into bite-sized pieces. At its simplest level, there are two steps:

  1. Get a message.
  2. Process it.

Simple, right? The first involves not
only getting the message, but also possibly translating it. As for step two,
MFC devotes so much effort to processing WM_COMMAND messages that it makes
sense to consider commands separately from other Windows messages. MFC also
adds a new concept that doesn't exist in Windows: user interface objects. You
know, those little CCmdUI doodads that come your way when it's time to enable
menu items and toolbar buttons? The ones in the UPDATE_COMMMAND_UI handlers?
So the revised elephant dissection looks something like this:

  1. Get a message.
  • Maybe translate it.
  1. Process it.
  • Process ordinary messages.
  • Process command messages.
  1. Handle user interface objects

In the sections that follow, I'll
discuss these topics in detail. I'll describe the vanilla way things work,
then how MFC implements specific behavior for various classes, and finally
ways you might want to override MFC's default behavior. Then I'll attempt to
synthesize everything at the end with a sample program. In a display of
amazing editorial synchronicity, this month's C/C++ Q&A column also
answers a number of questions related to command and message handling.

Pump It Up 
In
the days of yore, when a hacker was anyone who spent most waking hours
writing programs and C++ was confined to research halls, everyone wrote a
function called WinMain with a loop that went like this:

以前,黑客花费了几个小时的时间写程序,此时c++还局限于科研大厅。每个人都要写一个winmain如下

MSG msg;

while (GetMessage(&msg, NULL, 0, 0)) {

TranslateMessage(&msg);

DispatchMessage(&msg);  //
send to window proc

}

This message pump is the heart of every Windows-based
program. MSG is a struct that holds the HWND, message ID, WPARAM, and LPARAM,
plus a couple of other things. You get one, then dispatch it. Pretty
straightforward. Except for that TranslateMessage in the middle—what's that?
Oh, never mind, it's just a function to translate WM_ KEYDOWN and WM_KEYUP
messages into WM_CHAR. Just do it. What's that you say? You want accelerator
keys? You want Ctrl-X and Ctrl-V to do Cut and Paste? Well, then, you need
TranslateAccelerator.

这个消息泵是每个windows函数的核心。MSG结构包含HWND, message ID, WPARAM, and LPARAM,和一些别的东西。你拿到一个,在分发它,非常直接,除了中间的TranslateMessage,这是什么?别介意,他只是一个函数,用来把WM_ KEYDOWN and WM_KEYUP 消息转换成WM_CHAR。你还要做什么?加速键?想把Ctrl-X and Ctrl-V 转换到 Cut and Paste?好的,那你需要TranslateAccelerator

MSG msg;

HWND hwnd =       // your main window

HACCEL hAccel =   // load from resource file

while (GetMessage(&msg, NULL, 0, 0)) {

if (!TranslateAccelerator(hwnd, hAccel, &msg)) {

// Not an accelerator, so dispatch as normal.

TranslateMessage(&msg);

DispatchMessage(&msg);

}

}

TranslateAccelerator is a voodoo
function you must call to do accelerators. TranslateAccelerator sees Ctrl-X
go by, looks in your accelerator table, and translates it into a WM_ COMMAND.
Unlike TranslateMessage, it dispatches a WM_COMMAND message with ID of
ID_EDIT_CUT to your window proc. To your program, it looks just like the user
picked Edit Cut from the menu. TranslateMessage returns TRUE indicating the
message was translated and dispatched, so you know not to dispatch it
again.

TranslateAccelerator是一个voodoo函数,你必须调用来完成加速键。TranslateAccelerator查看你的加速键表,把他转换成WM_ COMMAND消息。不想TranslateMessage,它分发WM_COMMAND消息+ ID_EDIT_CUT ID到你的程序过程。对你程序来讲,他就像看到用户从菜单式选了Edit Cut菜单。TranslateMessage返回TRUE表示消息已经翻译了以及分发了,所以不需要在分发了

Windows
has all sorts of voodoo functions to translate messages: you don't really
understand the rhyme or reason, you just do what they tell you. You use
IsDialogMessage for modeless dialogs, so Tab keys and Ctrl mnemonics work.
You might expect them to work on their own—after all, they do for modal
dialogs—but no. There's TranslateMDISysAccel for MDI accelerators, like
Ctrl-F6 for Next Window and Shift-F5 for Cascade. Oh, and I almost forgot—if
you want to do idle processing, better call PeekMessage, not GetMessage.

Windows有很多这样的woodoo函数用来转换消息,你不需要明白原因,只需要按照他们告诉你的来做。你用IsDialogMessage处理modeless dialog,

以便tab,ctrl能工作。你可能希望他们自己搞定-毕竟,他们为model dialog处理了-但是现实不是。你需要有TranslateMDISysAccel处理MDI加速键,像Ctrl-F6 for Next Window and Shift-F5 for
Cascade。哦,我忘了,你如果想做idle处理,你要用PeekMessage而不是GetMessage

By the time you're finished with all the voodoo, your message pump is
even more complicated than the Windows version of "Hello,
world"!

当你完成所有voodoo后,你的消息泵会比windows版本的helloworld更加复杂
Not
to worry. A few years pass, Bjarne Stroustrup is a household name, your
mother-in-law is writing Windows-based apps, and even Microsoft has a C++
compiler. Life is so easy all you have to do is press a button to generate
apps by the score. Message pumps go the way of the passenger pigeon.

不要担心,几年过去了,Bjarne
Stroustrup, 一个家庭主妇的名字。正在写windows程序,甚至MS有一个c++编译器了。生命如此简单,你所有需要做的就是按一下按钮来生成apps,不想要搞什么消息泵了

But underneath all the object glitz, your Wizard-generated app still
trudges through the same old muck. You just don't see it, that's all. Buried
inside a function called CWinApp::Run is the mother-of-all-message-pumps. MFC
hides the voodoo, or at least repackages it in more palatable ways.

但是在所有东西下面,你的wizard geberator的程序仍旧做同样的old muck。你只是看不到而已,隐藏在了函数CWinApp::Run之下,作为所有消息泵的母亲。MFC隐藏了voodoo,至少重新打包成了可口的方式
To
understand how it works, stop a moment to consider a hypothetical old-world
situation. You just finished your Acme App, complete with central loop in
WinMain. It's all debugged, shining brightly, waiting for shrink wrap.
Suddenly orders come from on high: you need an XYZ feature, which calls for a
modeless dialog box. You race to the keyboard. Tap-tappity-tap-tap and . . .
the dialog is running. Except the Tab key doesn't work. Shoot. After beating
your head a while, you realize you forgot your voodoo: you're supposed to
call IsDialogMessage from your main message loop in WinMain!

为了明白他是如何工作的,听一下,想一想古老的世界的情形。你刚写完你的Acme程序,完成了WinMain里面的中心loop。都已经debug完了,闪闪发光,等待着你来压缩。突然高层命令来了。
你需要xyz功能,它需要调用一个modeless dialog。你冲向键盘开始敲起来。。。程序跑起来了,但是tab还不工作。nnd,你意识到你忘记了你的voodoo,你需要支持IsDialogMessage!

If you think there's something wrong with this picture, you win the
grand prize. This is not the way life should be, folks! Objects should
implement their own behavior! It's bad enough you have to call some weirdo
function, but puh-lease, don't make WinMain do it! That's like operating on
your shoulder to fix your kidneys.

如果你觉得这样是有问题的,那你赢了。生命不应该这样。object应该完成他们自己的行为,不应该要做一些很奇怪的函数,不要让winmain做他!那就像搞你的肩膀来治疗你的肾病

MFC
corrects this situation by letting windows do their own message translation.
In MFC, the dialog, not WinMain, calls IsDialogMessage. How does it work? To
understand, let's start with MFC's version of the message pump. It starts in
CWinApp::Run.

Mfc纠正了这个问题,他通过让窗口完成他们自己的消息转换。在mfc里,dialog,而不是winmain,调用IsDialogMessage。他是怎么做的呢?为了理解,我们看看mfc的消息泵

int CWinApp::Run()

{

for (;;) {

while (!::PeekMessage(&m_msgCur,...)) {

if (!OnIdle(...))    // do some
idle work

break;

}

// I have a message, or else no idle work to do: // pump it

if (!PumpMessage())

break;

}

return ExitInstance();

}

If there are
no messages waiting, MFC calls OnIdle, which you can override to do something
useful like calculate twin primes in your spare time. Just make sure you call
CWinApp::OnIdle at the end or you'll be in deep doodoo. If there are no
messages, or no more idle work to do, CWinApp calls PumpMessage, which does
the Get/Translate/Dispatch schtick, just like in the old days:

如果没有消息,mfc调用onidle,你可以重载来做有用的事情比如计算twin
primes。只需要保证在最后面要调用CWinApp::OnIdle,否则你会在deep doodo里面。如果没有消息或者没有idle
work,cwinapp调用pumpMessage,这个函数Get/Translate/Dispatch的事情

BOOL
CWinApp::PumpMessage()

{

if
(!::GetMessage(&m_msgCur,...)) {

return
FALSE;

}

if
(!PreTranslateMessage(&m_msgCur)) {

::TranslateMessage(&m_msgCur);

::DispatchMessage(&m_msgCur);

}

return TRUE;

}

Look
familiar? Except for PreTranslateMessage. That's a new virtual function. The
default implementation for CWinApp walks the window hierarchy starting with
the window that sent the message, up through its parent and grandparents, all
the way up to the topmost window, calling CWnd::PreTranslateMessage for each
one.

看起来很熟悉?除了PreTranslateMessage。那是一个新的虚函数。默认的实现是遍历window hierarchy,从发送消息的窗口开始,直到他的parent和grandparents,直到最上层窗口。对每个窗口调用CWnd::PreTranslateMessage

BOOL
CWinApp::PreTranslateMessage(MSG* pMsg)

{

for (pWnd =
/* window that sent msg */; pWnd; pWnd=pWnd->getParent())

if
(pWnd->PreTranslateMessage(pMsg))

return
TRUE;

if (pMainWnd
= /* main frame and it's not one of the parents */)

if
(pMainWnd->PreTranslateMessage(pMsg))

return
TRUE;

return
FALSE;  // not handled

}

That's
right, there's PreTranslateMessage for CWinApp and PreTranslateMessage for
CWnd. Now windows can translate messages. When one does, it returns TRUE all
the way up the stack. Message received. Next, please. The flow is illustrated
in Figure 2.
就是这样,CWinApp和cwnd各自有PreTranslateMessage

现在窗口可以转换消息了,当一个做了之后他返回true一路网上

 

Figure 2 The Message Pump

It may make your head spin, but this translation tango is actually
pretty slick because now that dialog problem is tidily dispatched (ahem).
Take a look at how MFC implements PreTranslateMessage for dialogs:

这可能让你头都大了,但是这个转换非常漂亮因为现在dialog问题解决了

看看mfc如何为dialog实现PreTranslateMessage

BOOL CDialog::PreTranslateMessage(MSG* pMsg)

{

if (pMsg->message >= WM_KEYFIRST && // for performance

pMsg->message <= WM_KEYLAST)

// maybe translate dialog key

return ::IsDialogMessage(m_hWnd, pMsg);

return FALSE;

}

It makes a lot more sense to translate dialog messages in the dialog
class instead of CWinApp, don't you think? CWinApp doesn't know a dialog from
a hole in the wall. Of course, ::IsDialogMessage is technically only required
for modeless dialogs, but PreTranslateMessage doesn't get called for modal
dialogs because when you call DoModal, Windows starts another message loop
and control doesn't return until the dialog is over. But the really wonderful
thing is you never have to worry about ::IsDialogMessage again! In fact, you
can just forget it exists: any dialog you write inherits the goodness from CDialog,
even if you add it in a flurry of haste at the last minute. If you've never
programmed without MFC, you've probably never even heard of IsDialogMessage.
And a good thing, too!

这样在dialog而不是cwinapp里面转换dialog明显合理多了。你觉得呢?cwinapp根本不知道dialog。当然,IsDialogMessage技术上只适用于modeless dialog,但是对于model dialog,PreTranslateMessage不会调用到,因为当你调用domodel的时候,windows开始了另个message loop,控制权直到dialog关掉之后才会返回。但是真正伟大的事情是你再也不用担心IsDialogMessage了。事实上,你可以忘了他的存在,任何从cdialog集成的dialog,即使你匆忙的在最后一分钟添加。如果你一直用mfc,你可能从未听说过IsDialogMessage

Likewise,
since the frame window owns the menus, it makes sense for it to handle
accelerators. And so it does. When you create the frame in your app's
InitInstance function, it loads the accelerator table with whatever ID you
used to define the document template, usually IDR_ MAINFRAME or
IDR_MYDOCTYPE. CFrameWnd stores the table in m_hAccel, which
CFrameWnd::PreTranslateMessage passes to ::TranslateAccelerator, magically
translating your accelerators without you having to lift so much as a finger.
(Well, you have to lift your finger to press the key, but that's all.) One
interesting tidbit I discovered writing this article is that CFrameWnd first
tries to get the accelerator table from CDocument::GetDefaultAccelerator. In
other words, if you want document-specific accelerators, all you have to do
is override this virtual function. (In older versions of MFC it's called
GetDefaultAccel.) It's amazing the things you discover when you start poking
around.

类似的,由于frame窗口拥有菜单,理所当然他要处理加速键。事实上也是这样。当你在Init Instance函数里面创建frame时候,他会架子啊加速键表,改表定义了document
template,如IDR_ MAINFRAME or IDR_MYDOCTYPE。CFrameWnd吧这个表奥存在m_hAccel。CFrameWnd::PreTranslateMessag把他传给TranslateAccelerator,很魔术师的。另一个有趣的事情是我发现CFrameWnd首先会试图用CDocument::GetDefaultAccelerator火的加速剪标。换句话说,如果你想要文档相关的加速剪标,你只需要重载这个虚函数!

Figure
3
 shows how various MFC classes implement PreTranslateMessage. MFC
handles about 99.9 percent of the situations you're likely to run into, so
you can mostly forget about PreTranslateMessage. This month's C/C++ Q&A
gives an example of the other 0.1 percent.
Oh,
one more thing. In 32-bit versions of MFC, you can replace CWinApp with
CWinThread everywhere in the preceding discussion. There's CWinThread::Run,
CWinThread::OnIdle, CWinThread::PumpMessage, and CWinThread::PreTranslateMessage.
I'd have mentioned it straight away, but I didn't want to give you a heart
attack so early in the story. In multitaskingland, each thread has its own
message pump, and an app is just a specialization of a thread. So most MFC
functions that were in CWinApp have migrated to CWinThread, from which
CWinApp is now derived.

图3显示各种各样的mfc class如何实现PreTranslateMessage的, mfc处理了99%的情形,所以你完全可以忘了他。这个月的c qa举例子另外的1%
另外一件事情是,32位的mfc,你可以认为cwinapp、和wcinthread一样。比如有CWinThread::Run, CWinThread::OnIdle, CWinThread::PumpMessage, and
CWinThread::PreTranslateMessage。

我直接讲出来先,但我不想这么快讲(??)。在多task的情况下,每个线程有自己的消息泵,一个程序只是一个线程的特化。因此大多数cwinapp的mfc函数迁移到了cwinthread,而cwinapp从他继承

One Framework, One Window Proc 
Most
messages are never translated. PreTranslateMessage returns FALSE all the way
up the line and CWinApp::PumpMessage calls ::DispatchMessage, which is a kind
of Windows wormhole. Your message disappears inside, then magically reappears
in your window procedure (seeFigure 4). 
大多数消息从未被翻译。PreTranslateMessage会返回false,从而::DispatchMessage会被调用,这是windows的黑洞。你的消息从中消失,然后魔术般的重新再你的windoiws过程中出现

 

Figure 4 Window Message Routing

You know the window proc. It's that big function you wrote in the old
days, the one with the giant switch statement that fills pages and pages,
defying even the most determined compiler's efforts at optimization. A lot
fewer people do that stuff these days. In MFC, window procs are gone. Well,
not really gone, but hidden. Replaced by MFC's universal window procedure,
AfxWndProc. How can one window proc work for all windows? Easy. It does
nothing application-specific.

你知道windows过程,他是在以前你需要写的一个大函数,有着巨大的switch语句。现在很少人这样做了。mfc里面,windows过程消失了。好吧,不是真的消失了。而是被mfc的统一的过程AfxWndProc代替了。一个windows 过程怎样为所有窗口工作呢?很简单,他没有做任何appl specific的事事情

LRESULT

AfxWndProc(HWND hWnd, UINT nMsg, WPARAM
wParam, LPARAM lParam)

{


// minor details omitted

CWnd* pWnd = CWnd::FromHandlePermanent(hWnd);

return AfxCallWndProc(pWnd, hWnd, nMsg, wParam,

lParam);

}

CWnd::FromHandlePermanent looks up the HWND in map and retrieves the
CWnd associated with it. With CWnd in hand, action flows to AfxCallWndProc,
the MFC analog of ::CallWindowProc.

CWnd::FromHandlePermanent查找hwnd map,找到对应的cwnd。利用cwnd,流程跑向AfxCallWndProc

LRESULT AFXAPI

AfxCallWndProc(CWnd* pWnd,HWND hWnd,

UINT nMsg,

WPARAM wParam,

LPARAM lParam)

{


// Save message params in a

•// 
state variable

LRESULT lResult = pWnd->

WindowProc(nMsg, wParam, lParam);

return lResult;

}

AfxCallWndProc calls WindowProc. Instead of passing an HWND to a C
window procedure, MFC calls a CWnd virtual WindowProc function. So all that's
been accomplished is to convert C into C++-. But oh, what an accomplishment
that is! Because WindowProc is virtual!

AfxCallWndProc调用wndowsproc。mfc调用了cwnd的虚函数WindowProc,而不是传递hwnd到c的windows过程。所以所有的完成的就是从c编导c++。但是,这是多大的进步啊,因为windowproc是虚函数!

CWnd::WindowProc
is, as its name implies, the C++ equivalent of the window procedure. It's
where messages are handled. Because it's virtual, different window classes
can implement WindowProc differently. C++ virtual functions (aka
polymorphism) are what make AfxWndProc work for all windows. 
You
all know CWnd::WindowProc is not the place where you normally handle
messages, but you should understand that you can handle messages here if you
need to. WindowProc is the first stop on the message highway, the first CWnd
function to see a message when it arrives. If you're porting a C program, you
can more or less copy your entire window-proc-with-the-giant-switch-statement
to CMainFrame::WindowProc, change "hwnd" to "m_hWnd,"
clean up a few details, and everything should work. Just don't tell anyone.
And definitely don't tell them you learned it from me!
CWnd::WindowProc顾名思义,c++的windows 过程。他就是消息处理的地方。因为他是虚函数。不同的window clsss可以用不同的方式实现。

你知道CWnd::WindowProc不是我们一般处理消息的地方,但你应该明白如果需要,你可以在这里处理消息。WindowProc是消息高速路上的第一站。如果你在移植c程序,你可以或多或少的把整个windows proc拷贝到CMainFrame::WindowProc。

One Ringie-Dingie, Two Ringie-Dingie 
As
everyone knows, the proper way to handle messages is through message maps.
Where do message maps come in? CWnd::WindowProc does it. First, it checks to
see if the message is WM_COMMAND or WM_NOTIFY. If so, the message takes a
fork that I'll describe later. All other messages stay on the main road and
get dispatched via your window's message map. CWnd::WindowProc acts like a
central switchboard, directing messages right and left to the proper handler
functions.

每个人都知道,正确的处理消息的方法是通过消息map。消息map从哪里来?CWnd::WindowProc。首先,他检查消息是否WM_COMMAND or
WM_NOTIFY.如果是。。。否则,所有其他消息继续呗你的windows map分发。CWnd::WindowProc作为*交换机,把消息分发到不同的处理函数

 

Figure 5 Message Maps

Message maps are tables that associate WM_XXX message IDs with C++
virtual functions (see Figure 5). WM_SIZE goes to OnSize.
WM_CLOSE goes to OnClose. And so on. The details are hidden behind the
familiar macros DECLARE_MESSAGE_MAP, BEGIN_MESSAGE_MAP, END_MESSAGE_MAP, and
all those ON_WM_WHOZAMACALLIT macros. If you peek under the covers, you won't
find anything magical, just some disgusting code. DECLARE_MESSAGE_MAP
declares a few variables, and, most importantly, the function GetMessageMap.
Here, let me put on my preprocessor hat and show you. This is what I see:

消息map是一些表格,吧WM_XXX消息和C++ virtual functions联系起来。WM_SIZE goes to OnSize. WM_CLOSE goes to OnClose等等。细节隐藏在DECLARE_MESSAGE_MAP, BEGIN_MESSAGE_MAP,
END_MESSAGE_MAP, and all those ON_WM_WHOZAMACALLIT之类的macro中。这些东西都很直接,DECLARE_MESSAGE_MAP声明了一变量,更重要的是函数GetMessageMap

class CMyFrame : public CFrameWnd {

DECLARE_MESSAGE_MAP()

};

BEGIN_MESSAGE_MAP(CMyFrame, CFrameWnd)


// entries

END_MESSAGE_MAP()

And this is what I spit out:

class CMyFrame : public CFrameWnd {

private:

static const AFX_MSGMAP_ENTRY _messageEntries[];

protected:

static AFX_DATA const AFX_MSGMAP messageMap;

virtual const AFX_MSGMAP* GetMessageMap() const;

};

// BEGIN_MESSAGE_MAP

const AFX_MSGMAP* CMyFrame::GetMessageMap()
const

{
return &CMyFrame::messageMap; }

const AFX_MSGMAP CMyFrame::messageMap = {

&CFrameWnd::messageMap,   //
base class's message map

&CMyFrame::_messageEntries[0] // this class's entries

};

const AFX_MSGMAP_ENTRY
CMyFrame::_messageEntries[] = {


// entries

// END_MESSAGE_MAP:

{0, 0, 0, 0, 0, 0 }

};

AFX_MSGMAP contains just two members: a
pointer to the base class's message map and one to the actual entries.

AFX_MSGMAP包含两个成语,一个指针指向基类的消息map,一个指向真正的map

struct AFX_MSGMAP {

const AFX_MSGMAP* pBaseMap;

const AFX_MSGMAP_ENTRY* lpEntries;

};

The base map pointer provides a way to walk the inheritance chain,
effectively implementing inheritance for message maps. Derived classes
automatically inherit any messages handled by their base. If you link with
the DLL version of MFC (_AFXDLL defined), pBaseMap is actually a pointer to a
function that returns the base map, not the base map itself, but that's an
implementation detail. The entries themselves look like this:

Base map指针提供了一种方式来遍历继承链,从而有效的实现了消息map的继承。派生类自动继承了基类处理的消息。如果你连接dll版本的mfc,pBaseMap其实是一个指针函数,它返回的事base map,而不是base map自身

struct AFX_MSGMAP_ENTRY {

UINT nMessage;   // windows
message

UINT nCode;      // control code
or WM_NOTIFY code

UINT nID;        // control ID
(or 0 for windows

// messages)

UINT nLastID;    // used for
entries specifying a

// range of control id's

UINT nSig;       // signature
type (action) or

// pointer to message #

AFX_PMSG pfn;    // routine to
call (or special

// value)

};

Each entry maps a particular Windows message, including control ID and
notification code (such as EN_CHANGED or CBN_DROPDOWN) if any, to a
CmdTarget-derived member function (AFX_PMSG). The nCode and nID fields are
new in 32-bit MFC. They support ON_NOTIFY and ON_COMMAND_RANGE. Each
ON_WM_WHATEVER macro initializes an entry for the WM_WHATEVER message. For
example, ON_WM_CREATE expands like this:

每一项map到一个特别的windows消息,包括空间id,通知码,一个CmdTarget继承的成员函数,ncode和nid是32位mfc新加的,他们支持ON_NOTIFY and ON_COMMAND_RANGE

比如ON_WM_CREATE展开如下

{
WM_CREATE, 0, 0, 0, AfxSig_is,

(AFX_PMSG)(AFX_PMSGW)(int (CWnd::*)(LPCREATESTRUCT))OnCreate },

At first glance, it looks like something
off the Rosetta Stone. WM_CREATE is obvious: that's the message ID, that's
how CWnd::WindowProc knows to use this entry when WM_CREATE happens. The
zeroes are there because child notification and command IDs don't apply. The
three ugly casts make sure your OnCreate function has the right signature. In
other words, they make the macro type-safe. Not all macros have the function
name hardwired. ON_MESSAGE(msg, mbrfn) expands like this:

第一眼,看起来像走下神坛。WM_CREATE很直接,消息id,CWnd::WindowProc直到如何使用这个entry。0是因为通知码和消息id没用,三个很丑的cast用来确保oncreate有正确的签名。换句话,他使macro类型安全。不是所有macro都对应硬编码的函数。ON_MESSAGE(msg, mbrfn)展开如下

{
msg, 0, 0, 0, AfxSig_lwl,

(AFX_PMSG)(AFX_PMSGW)(LRESULT (CWnd::*)(WPARAM, LPARAM))mbrfn },

You can use whatever member function you
like for ON_MESSAGE, but it must take WPARAM and LPARAM and return LRESULT.
If you try to pass any other kind of function, C++ will have a
conniption. 
The
only remaining mystery is the funny-looking AfxSig_xxx symbols. To understand
what they're for, stop a moment to consider how CWnd knows which arguments
should be passed to your handler function. In AFX_MSGMAP_ENTRY, every
function is declared as AFX_PMSG, which is a pointer to a CCmdTarget member
function that takes no arguments and returns void.

你可以用任意你喜欢的成员函数来给ON_MESSAGE,但是必须要带有WPARAM and LPARAM参数并且返回LRESULT。如果你传了其他类型函数,c++回报做

唯一保留的秘密是有趣的AfxSig_xxx符号。为了明白他们,休息一下考虑cwnd如何知道那个参数应该给你的handle函数。在AFX_MSGMAP_ENTRY,每个函数声明为AFX_PMSG,他使指向CCmdTarget的成员函数,不需要参数

typedef void (CCmdTarget::*AFX_PMSG)(void);

So how can the dispatch code pass
arguments to your handler functions? That's where the AfxSig (signature)
codes come in. Here's how WindowProc actually calls your function once it's
found the right message map entry.

分发码如何传递参数到你的handle函数呢?那就是AfxSig??这里是WindowProc如何调用你的函数的代码

LRESULT CWnd::WindowProc(UINT nMsg, WPARAM
wParam, LPARAM lParam)

{

const AFX_MSGMAP_ENTRY* lpEntry = // (entry
for this

//  message)

union MessageMapFunctions mmf; // described
below ptr

mmf.pfn = lpEntry->pfn;        // to your virtual function

switch (lpEntry->nSig) {

case AfxSig_is:

return (this->*mmf.pfn_is)((LPTSTR)lParam);

case AfxSig_lwl:

return (this->*mmf.pfn_lwl)(wParam, lParam);


// etc

}

AfxSig_is means the function takes a
string (s) and returns an int (i). AfxSig_lwl means it takes a WORD and LONG,
and returns a LONG (lwl). And so on. AFXMSG_.H defines a zillion or so
different signatures—well, actually there are 55.

AfxSig_is表示函数参数为字串返回int。AfxSig_lwl表示参数为word和long,返回long。AFXMSG_.H定义了55个这样的函数

enum AfxSig {

AfxSig_end = 0, // [marks end of message map]

AfxSig_bD,      // BOOL (CDC*)

AfxSig_bb,      // BOOL (BOOL)

AfxSig_bWww,    // BOOL (CWnd*,
UINT, UINT)

AfxSig_hDWw,    // HBRUSH (CDC*,
CWnd*, UINT)

AfxSig_iwWw,    // int (UINT,
CWnd*, UINT)

AfxSig_iWww,    // int (CWnd*,
UINT, UINT)

AfxSig_is,      // int (LPTSTR)

AfxSig_lwl,     // LRESULT
(WPARAM, LPARAM)

};

You get the idea. That "union
MessageMapFunctions mmf" you saw in WindowProc is a bit of grotesquery
used to "cast" the function without casting it:

union MessageMapFunctions

{

AFX_PMSG pfn;   // generic
member function pointer

// specific type safe variants

BOOL    (CWnd::*pfn_bD)(CDC*);

BOOL    (CWnd::*pfn_bb)(BOOL);

BOOL    (CWnd::*pfn_bWww)(CWnd*,
UINT, UINT);

HBRUSH  (CWnd::*pfn_hDWw)(CDC*,
CWnd*, UINT);

int     (CWnd::*pfn_iwWw)(UINT,
CWnd*, UINT);

int     (CWnd::*pfn_iWww)(CWnd*,
UINT, UINT);

int     (CWnd::*pfn_is)(LPTSTR);

LRESULT (CWnd::*pfn_lwl)(WPARAM, LPARAM);

• // etc, for each AfxSig code

};

There's only one real function (pfn),
but depending how you access it through the union, pfn_is is a
string-returning-int function, pfn_lwl is word-and-long-returning-long and so
on. What's pretty on the outside is sometimes ghastly on the inside.
Fortunately, you don't have to look. I only showed you so you wouldn't think
I was hiding anything, and so you can count your blessings you never have to
write code like that.
Having
said that, now let me tell you how you might use this stuff if you're the
masochistic type. Say you have some messages of your own. WM_RUN_FOR_CONGRESS
and WM_KICK_THE_BUMS_OUT. The first takes a pointer to a
CCongressionalDistrict; the second, a pointer to a CListOfBums. The easiest
way to handle these is to use ON_MESSAGE and do old-world casting:

下面是如何处理你自定义的消息

BEGN_MESSAGE_MAP(...)

ON_MESSAGE(WM_RUN_FOR_CONGRESS, OnRunForCongress)

END_MESSAGE_MAP()

LRESULT OnRunForCongress(WPARAM wp, LPARAM,
lp)

{

CCongressionalDistrict* pCd = (CCongressionalDistrict*)lp;

}

But say you're writing a library. Maybe even an MFC extension library.
You don't want to make programmers remember that LPARAM is the district. Or
maybe your fastidiousness exceeds that of Felix Unger. You don't want to use
ON_MESSAGE and put disgusting casts in your code. You want
ON_WM_RUN_FOR_CONGRESS and ON_WM_KICK_THE_BUMS_OUT, so programmers can use
macros in their message maps. Ok, just do it. 
"But
what AfxSig code should I use?" Why, AfxSig_is, of course.
String-returning-int. Because it doesn't matter, a pointer-to-string is the
same as a pointer-to-CCongressionalDistrict is the same as a
pointer-to-anything when it comes to pushing it on the stack. One address,
four bytes. (I'm assuming FAR pointers or Win32®.) Take a closer look at the expansion for ON_WM_CREATE: it uses
AfxSig_is too! So here's your macro:

如果你写的是lib,你不想用户记住LPARAM表示区域,你可以这样

下面是你自己定义macro来让用户更加易用

#define ON_WM_RUN_FOR_CONGRESS() \

{ WM_RUN_FOR_CONGRESS, 0, 0, 0, AfxSig_is, (AFX_PMSG)(AFX_PMSGW) \

(int (CWnd::*)(CCongressionalDistrict*))OnRunForCongress },

BEGIN_MESSAGE_MAP(...)

ON_WM_RUN_FOR_CONGRESS()

END_MESSAGE_MAP()

// Returns int to agree with AfxSig_is.

int OnRunForCongress(CCongressionalDistrict*
pCd)

{

pCd->RunForHouse();

pCd->RunForSenate();

return 0;   // mission
accomplished

}

If you want other parameters, chances are you can find a suitable AfxSig_xxx
code. But like I said, for most purposes there's no need to go to such
extremes; ON_MESSAGE works just fine.
如果你要其他参数,你需要看看有没有合适的AfxSig_xxx码。但我说,大多情况不需要这样极端,ON_MESSAGE已经足够

What, No Handler? 
If
there's no entry in the message map, WindowProc calls CWnd::DefWindowProc,
which as you'd expect is the C++ analogue of ::DefWindowProc, the Windows
default message procedure. The CWnd version passes the message to the
original window proc, the one the window had before MFC subclassed it, which
in fact might very well be ::DefWindowProc! Or it might be the default proc
for an edit control or MDI child window. The point is, that's it. That's the
end of this message, it's been processed, control returns all the way back,
back, back out of AfxWndProc, pops through the wormhole, and
::DispatchMessage returns. Done. Finis. The End. Next, please.
The
other way control can end up in CWnd::DefWindowProc is via one of the default
CWnd::OnFooMumbleBletch implementations. When you handle a message, you
frequently do something special, then call the base class.

如果没有合适的entry,会调用CWnd::DefWindowProc

CMyWnd::OnFooMumbleBletch()

{

CWnd::OnFooMumbleBletch();

}

The base implementations go like this.

// (From AFXWIN2.INL)

inline void CWnd::OnSize(UINT, int, int) {
Default(); }

inline void CWnd::OnSetFocus(CWnd*)      { Default(); }

inline BOOL CWnd::OnNcActivate(BOOL)

// return (BOOL)Default(); }


// etc

Default() does the same thing as DefWindowProc, the only difference
being that Default requires no arguments (msg, wParam, lParam) because it
gets them from the state structure where AfxCallWndProc saved them. Remember?
In
case you're wondering, you've eaten about half the elephant now. You may want
to eat some TUMS.
Default()和DefWindowProc一样。唯一的区别是他不需要参数。因为他会从以保存的状态获取,该状态是被AfxCallWndProc保存的

The Evil WM_COMMAND 
I
mentioned that CWnd::WindowProc sends WM_COMMAND messages down a different
fork along the message routing interstate. It's time to explore that path.
WM_COMMAND
is an unfortunately overloaded message. Windows sends it whenever the user
invokes a command, either through the menu or an accelerator key. WPARAM
tells which. But controls also use WM_ COMMAND to send notifications, for
example when a button sends BN_CLICKED or an edit control sends EN_ CHANGED.
When this happens, the control's HWND, notification code, and ID are packed
like sardines into WPARAM and LPARAM, leaving no room for additional
information. How they're packed depends on whether you're using 16-bit
Windows or Win32.

WM_COMMAND是一个重载的函数

Overloading
WM_COMMAND like this has one nice benefit: pressing a button looks to your
app much like invoking a menu command. But conceptually, they're entirely
different events. A command means "do something," whereas a control
notification means "something happened." Newer versions of Windows,
such as Windows® for
Workgroups 3.11 and versions of Win32 that use the new common controls,
introduce a new message, WM_NOTIFY, that attempts to separate these events.
WM_NOTIFY is a generalized control notification. Instead of packing WPARAM
and LPARAM up the wazoo, LPARAM contains a pointer to a struct.

struct NMHDR {

HWND hwndFrom;     // control
that sent notification

UINT idFrom;       // ID of
control

UINT code;         //
notification code

};

NMHDR is intended to be used as the
basic header to which specific controls can append additional information.
For example, the common Tool Tip control passes a notification struct like
this:

struct TOOLTIPTEXT {  // In C++, you can derive from NMHDR

NMHDR hdr;        // standard
header

LPSTR lpszText;   // tip text or
LPSTR_CALLBACK

char szText[80];  // tip text

HINSTANCE hinst;

UINT uFlags;

};

The details of TOOLTIPTEXT and NMHDR are
unimportant. The main point is, there are two kinds of events: menu commands
and control notifications. WM_NOTIFY is always a notification, but WM_COMMAND
is sometimes a command, sometimes a notification. CWnd::WindowProc handles
both events specially, sending them to Figure 6 of my
elephant map.

 

Figure 6 WM_COMMAND and WM_NOTIFY Message Flow

LRESULT CWnd::WindowProc(UINT msg, WPARAM
wp, LPARAM lp)

{

// special case for commands

if (msg = = WM_COMMAND)  {

if (OnCommand(wp, lp))

return 1L; // command handled

else

return DefWindowProc(msg, wp, lp);

}

// special case for notifies

if (msg = = WM_NOTIFY) {

LRESULT lResult = 0;

NMHDR* pNMHDR = (NMHDR*)lp;

if (pNMHDR->hwndFrom != NULL &&

OnNotify(wp, lp,
&lResult))

return lResult; // command handled

else

return DefWindowProc(msg, wp, lp);

}

}

If WindowProc is the first stop on the message processing highway,
OnCommand is the first stop on a major fork for all WM_COMMAND messages. If
you can't or don't want to use message maps to handle a particular command,
you can override OnCommand. Two common reasons for doing this are when the
command or control ID is a run-time variable, not a #define symbol or if you
want to trap a range of commands in one fell swoop in 16-bit MFC, which
doesn't support ON_COMMAND_RANGE.

CMyFrameWnd::OnCommand(WPARAM wp, LPARAM lp)

{

if (wp= =m_nPrintCommandID)  // ID
stored in data member

OnPrint();

else if (ID_FOO_FIRST<=wp && wp<=ID_FOO_LAST)

// Handle range of IDs

OnFooCommands(wp, lp);

return CFrameWnd::OnCommand(wp, lp);

}

These are convenient applications for OnCommand, but the real reasons for
handling commands and notifications specially are more fundamental: to give
controls a chance to handle their own notifications and to let nonwindow
objects process them.

Child Knows Best 
How
many times have I said it? Objects should implement their own behavior! This
applies to controls. Controls emit notifications whenever something interesting
happens, like when the user changes the contents of an edit control or clicks
the drop-down button on a combo box. Notification is nice, but often it makes
more sense for controls to handle their own notifications. 
Say
you have a combo box in your dance choreography application that displays a list
of mambo figures, which you generate on-the-fly when the user presses the
drop-down button. The normal way of doing things in Windows is to make the
dialog handle the ON_CBN_DROPDOWN notification and fill the combo box. Yuck!
What if you want to use your combo box in another dialog? It's not reusable!
Unless you adhere to the copy-and-paste school of reusability: copy-and-paste
the code from one dialog to another.
Why
do that when you can create a nice self-contained combo box that handles its
own CBN_DROPDOWN? All you have to do is override OnChildNotify. MFC calls this
virtual function whenever the parent receives a notification from a child
window.

我说了多少次了,对象应该实现他们自己的行为!着同样适用于控件。控件每当发生了什么感兴趣的事情就会发出通知,比如用户改变edit内容,点击combobox的下拉按钮。通知很好,但要控件处理他们自己的通知更合理

比如你有一个combobox,显示xxx显示内容是动态生成的。一般做法是叫dialog处理ON_CBN_DROPDOWN通知然后填上combobox。那如果你想在另外一个dialog里面如何使用这个combobox?不重用!

为什么不能创建一个字包含的combobox,他处理自己的CBN_DROPDOWN?你需要做的只是重载OnChildNotify,mfc每当父亲收到子窗口的通知后就会调用这个虚函数

BOOL CWnd::OnCommand(WPARAM wParam, LPARAM
lParam)

{

//
if WM_COMMAND is really a child notification:

if
(pChild->OnChildNotify(message, wParam, lParam, pLResult))

return TRUE;

}

CWnd::OnNotify does the same thing.
OnCommand and OnNotify give children first crack at their own notifications.
So you can write your combo box like this:

OnCommand and OnNotify给了孩子机会首先检查他们自己的通知,你的combobox可以如下

BOOL CMamboCombo::OnChildNotify(UINT msg,
WPARAM wp, LPARAM lp,LRESULT* pResult)

{

if (msg= =WM_COMMAND) {

int nCode = // extract notification code, depends

// on Win version

if (nCode= =CBN_DROPDOWN) {

// fill combo box

return FALSE;  // Pass to parent
dialog

}

}

return CComboBox::OnChildNotify(msg, wp, lp, pResult);

}

Now CMamboCombo is entirely
self-contained. Whether you return TRUE or FALSE is up to you. It seems
better to return FALSE, so dialogs can still know the drop-down button was
pressed if they want to. It's up to you.
Like
PreTranslateMessage, OnChildNotify corrects the architecturally challenged
design of Windows. MFC uses it for other messages too. MFC directs the
owner-draw messages (WM_COMPAREITEM, WM_MEASUREITEM, WM_DRAWITEM,
WM_DELETEITEM) back at the controls that sent them, so they can draw
themselves instead of having the parent window do it. In fact, that's the
only reason the LPRESULT* argument is required for OnChildNotify:
WM_COMPAREITEM is one of the few Windows messages that uses the return code
to indicate something significant: positive, negative, or zero to indicate
the result of the comparison.

Mother of All Targets 
So
much for children. The major, and I mean major, reason for handling commands
differently from other messages is so nonwindow objects can get them. You all
know documents can handle commands—just put ON_ COMMAND in your document's
message map. But it may never have occurred to you that this isn't something
built into Windows! It's a feature of the MFC document/view architecture.
In
fact, come to think of it, how come documents even have message maps? Because
CDocument is derived from CCmdTarget, the root of all classes that can have
message maps and thus handle commands. CDocument, CWnd, CWinApp (or CWinThread),
and CDocTemplate are all derived from CCmdTarget, whose only role in life is
to provide a common base for these "command targets."

处理command和其他消息不同的主要原因是:非窗口对象可以收到他们!比如documents

事实上,为什么document有消息map?这是因为documen从ccmdtarget继承

CObject

CCmdTarget

CWnd

CWinThread       // (Win32 only)

CWinApp

CDocTemplate

CDocument

And the heart of CCmdTarget is
CCmdTarget::OnCmdMsg, the next major major stop on the command route. If the
child doesn't handle the notification, or if the message wasn't a
notification at all, but a command, OnCommand calls OnCmdMsg. OnNotify
behaves similarly.

他的心脏就是CCmdTarget::OnCmdMsg,是command路由中的第二站
CCmdTarget::OnCmdMsg
is yet another virtual function. It's the command target analog of
WindowProc: the function that does the actual dispatching. But only for
commands and notifications. WindowProc is a CWnd function that dispatches
window messages; OnCmdMsg is a CCmdTarget function that dispatches commands
and notifications. OnCmdMsg only handles message map entries generated by the
macros in Figure 7. It looks in the message map for an entry
whose ID and nCode (in the case of a notification) match the ones for
WM_COMMAND or WM_NOTIFY. If it finds one, it calls that function. I won't
bore you with the details, it's exactly the same AfxSig unpleasantness that
goes on in WindowProc. 
But
wait a minute. That still doesn't explain how documents get commands!
WindowProc calls OnCommand calls OnCmdMsg—but the object doing the calling is
still whichever CWnd got the command. How do documents get commands? Well, I
only told you what CCmdTarget::OnCmdMsg does. Don't forget OnCmdMsg is
virtual. Other classes can do other things. In particular, CFrameWnd
overrides CCmdMsg to pass commands to views and the application object.

那么document如何受到command呢?是因为CFrameWnd重载了CCmdMsg从而把command传给view和其他object

BOOL CFrameWnd::OnCmdMsg(...)

{

if (pActiveView->OnCmdMsg(...))

return TRUE;      // handled by
view

if (CWnd::OnCmdMsg(...))

return TRUE;      // handled by
me

if (pApp->OnCmdMsg(...))

return TRUE;      // handled by
app

return FALSE;        // not
handled

}

Figure 8 illustrates
this. And can you guess what CView:: OnCmdMsg does? That's right, it calls
its document's OnCmdMsg function! So commands are not automatically routed to
all the CCmdTargets in the world, only the active view, document, frame and
application. And only because CFrameWnd makes it so.

 

Figure 8 Document/View Command Routing

So far, all the routing I've been talking about takes place within a
single window (or its children, for OnChildNotify). Not until CFrameWnd gets
involved does anything get routed outside the window. This explains one of
the common mysteries I mentioned at the outset: how come sometimes when you
add a child window, the menu items are grayed even though you have ON_COMMAND
handlers in your child window's message map? The answer: because the child
window isn't automatically hooked up to the interstate system. The only
objects that get main frame commands automatically are the active view,
document, frame and application. If you want your arbitrary CGizmoWnd nonview
child window to handle main frame commands, you've gotta hook it up yourself!
Don't worry, it's easy.

BOOL CMyView::OnCmdMsg(...)

{

if (CMyView::OnCmdMsg(...))

return TRUE;   // handled by
view/doc

return m_wndGizmo.OnCmdMsg(...); // pass to gizmo

}

Or you might want to give the gizmo
first crack. Either way, it's up to you to call OnCmdMsg for any CCmdTarget
you want to receive commands. Before you start wondering why MFC doesn't do
that, consider that calling OnCmdMsg for every CCmdTarget in the universe is
not only expensive, it doesn't really make sense. The document/view
architecture is one model, the one MFC supports. You're perfectly free to
implement other designs, but you have to do a little typing to make things
work.
There's
one legitimate complaint, however: there's no way for new objects to hook
themselves up to the routing mechanism. The app, frame, window, or some other
container must explicitly call OnCmdMsg for the new objects. It would be nice
to have a way of registering new command targets with the application, say
CWinApp::RegisterCmdTarget, so new command targets could hook themselves up
to the command highway without requiring their parents or other
supra-entities to participate. Then your gizmo control could hook itself up.
No need to modify CMainFrame:: OnCmdMsg. Maybe one of our friends in Redmond
will read this and add it to the next release of MFC.

Ooey GUI 
We're
almost finished; you should be feeling quite stuffed. In case you hadn't
noticed, I've covered the first two elephant chunks: getting/translating
messages and processing them. There's just one more piece left to digest,
user interface objects. Once again, I'll begin with a history lesson.
Millennia
ago, shortly after the wheel was invented, but before the advent of HDTV,
hominids had problems initializing menus in their Windows-based apps. They
wanted to gray their menu items when commands were unavailable, instead of
dropping a stone on the user's head. The big question was whether you should
maintain menus in real time, enabling and disabling them as your program's
state changes or whether you should gray them on demand, when it's time to
display the menu.
Neanderthals
chose the continuous-update approach while Cro-Magnons opted for
do-it-on-demand. This not only explains why Neanderthals became extinct, it
accounts for WM_INITMENUPOPUP. Windows sends this message when the user
clicks a menu, just before the menu is drawn. You can enable and disable
items, set checks and radio buttons, change text, whatever you like. This
represented a great technological advance, on a par with toilet paper. But
the code still lived in that big switch statement, along with all the other
message-handling code fragments. That meant your program's entire state had
to be accessible from the window proc. Fortunately, they'd already invented
globals. 
Then
C++ and MFC came along and our friends in Redmond screamed, "No, no, no!
That's not the way to do it! Each menu item is really a little user interface
object! Anybody who wants to should be able to turn it on or off!" Thus
was born CCmdUI. These little doohickeys get passed around the cocktail party
like hors d'oeuvres. Any command target can take one. Whichever object you
decide knows best what the state of a particular menu item should be can
enable, disable, check, uncheck, or otherwise update it. If File Save is
enabled only when the document has been modified, then the document should do
the enabling/disabling of that command. If views are the place to gray Window
Split, then by all means do it there. If CBeanCounter knows how many beans to
display in the bean pane, it can do the displaying.
MFC
implements this goodness by piggybacking on the general command routing
system. Once you have that command highway up and running, it's no big deal
to send a special message across the interstate. CN_UPDATE_COMMAND_UI is the
message. It goes to all the OnCmdMsg functions, just like WM_COMMAND. Only
instead of WPARAM and LPARAM, MFC cooks up a thing called a CCmdUI and passes
a pointer to it. ON_UPDATE_COMMAND_UI sticks a CN_UPDATE_COMMAND_UI handler
in your message map, and voilà, the UI object winds up at your doorstep.
There are only two complicating details: first, CCmdUI objects comes in
several flavors for updating menu items, controls, toolbar buttons, and
status bar panes. Second, CN_UPDATE_ COMMAND_UI messages originate in several
places.
I'll
start with the first one, since that's the easy part. The base CCmdUI class
is used for menu items or controls. It stores a pointer to menu or window,
and has virtual functions to change the underlying object's state.

class CCmdUI {

CMenu* m_pMenu;      // if a
menu

CWnd*  m_pOther;     // if a window

public:

virtual void Enable(BOOL bOn = TRUE);

virtual void SetCheck(int nCheck = 1);

virtual void SetRadio(BOOL bOn = TRUE);

virtual void SetText(LPCTSTR lpszText);

void DoUpdate(CCmdTarget* pTarget,

BOOL bDisableIfNoHndler);

};

SetText ends up calling either ModifyMenu or SetWindowText, depending on
whether m_pMenu or m_pOther is set. Likewise, CCmdUI::Enable calls
EnableMenuItem or EnableWindow. Derived variants CStatusCmdUI and CToolCmdUI
are used for status bar panes and toolbar buttons. CStatusCmdUI::SetText
calls SetPaneText; CToolCmdUI::SetText does nothing, since toolbar buttons
are bitmap buttons, which have no text. You get the idea. The point is, MFC
uses different CCmdUI classes to represent different kinds of user interface
objects. Polymorphism working to serve you. 
As
for when CN_UPDATE_COMMAND_UI gets sent, well, if you're thinking
WM_INITMENUPOPUP, you're on the right track. When CFrameWnd gets
WM_INITMENUPOPUP, it creates a CCmdUI object, initializes it successively to
each menu item in the menu, and calls DoUpate for each one. The details are a
bit tedious, but the basic idea goes like this:

void CFrameWnd::OnInitMenuPopup(CMenu*
pMenu, UINT, BOOL bSysMenu)

{


// Reader's Digest version

CCmdUI ui;

ui.m_nIndexMax = pMenu->GetMenuItemCount();

for (ui.m_nIndex = 0; ui.m_nIndex < ui.m_nIndexMax; ui.m_nIndex++)
{

ui.m_nID = pMenu->GetMenuItemID(ui.m_nIndex);

ui.DoUpdate(this, m_bAutoMenuEnable);

}

}

I'm glossing over some details because I
want to focus on CCmdUI::DoUpdate. This is the function that sends the CN_UPDATE_COMMAND_UI
message on its merry way.

void CCmdUI::DoUpdate(CCmdTarget* pTarget,
BOOL bDisableIfNoHndler)

{

pTarget->OnCmdMsg(m_nID, CN_UPDATE_COMMAND_UI, this, NULL)

}

The first argument, pTarget, is the
command target, the one whose OnCmdMsg is called and whose message map is
used to dispatch the message. Usually it's the main frame window, so the
message goes to the active view, document, frame window, and
application. 
The
second argument is a flag that indicates whether the object should be
disabled if there's no handler for it. CFrameWnd passes m_bAutoMenuEnable,
which is TRUE by default. You may have noticed that MFC automatically grays
menu items for commands that have no handlers. If you add File->Barf to your
menu but don't add a handler for ID_FILE_BARF, the command is disabled. This
is where it happens. 
How
does DoUpate know if the command has a handler or not? That's one of the
details I omitted. DoUpdate sends a CN_COMMAND code to OnCmdMsg, exactly as
if it were going to execute the command, only it passes a little struct,
AFX_CMDHANDLERINFO. The presence of this structure tells OnCmdMsg not to
actually do the command, just indicate what function it'd use if it did.

struct AFX_CMDHANDLERINFO

{

CCmdTarget* pTarget;            
// command target

void (CCmdTarget::*pmf)(void);  
// message map

//
function

};

BOOL CCmdTarget::OnCmdMsg(UINT nID, int
nCode, void* pExtra,

AFX_CMDHANDLERINFO*
pHandlerInfo)

{

if (pHandlerInfo != NULL)

{

// just fill in the information, don't do the

// command (actually happens in

// CCmdTarget::DispatchCmdMsg)

pHandlerInfo->pTarget = this;

pHandlerInfo->pmf = mmf.pfn;

return TRUE;

}

}

If OnCmdMsg comes back FALSE, nothing handled the command. So if you
ever want to know if a particular command or notification is handled, and by
what object, just create an AFX_CMDHANDLERINFO object on the stack and pass
it to OnCmdMsg (along with the command ID and notification code, of course).
If it comes back TRUE, something handles the command and you can look at AFX_
CMDHANDLERINFO to see what.
The
idea behind automatic disabling is that as different kinds of views and/or
frames become active (and thus different message maps come into play), the
commands that have no handlers are automatically disabled. This is a nice
feature, and handles one whole category of menu enabling/disabling without
your having to write a line of code. But sometimes you're writing a shell of
a program that you want to use to demo the UI before everything is
implemented. Then the automatic graying can be a pain. The best way to enable
menu items is to write stub handlers for them. You can write a generic
OnNotImplemented function and hook it to every command. But if you're lazy,
or don't want to rely on programmers remembering to use OnNotImplemented, a
quick way to disable the disabling is to set m_bAutoMenuEnable = FALSE in
your frame window constructor.
So
much for menus. What about status bar panes and toolbar buttons? When your
app or thread is just hanging around doing nothing, CWinThread::OnIdle sends
WM_IDLEUPDATECMDUI to the main window and all its descendants. This
MFC-specific message is every item's cue to update itself. CToolBar and
CStatusBar catch it and update their buttons and panes. If you need to know
the details, read the source.
There's
one more function you need to know about before calling it quits:
CWnd::UpdateDialogControls. This function updates all the controls in a
window (usually a dialog) by sending CN_UPDATE_ COMMAND_UI to each one.

void

CWnd::UpdateDialogControls(CCmdTarget*

pTarget, BOOL bDisableIfNoHndler)

{

CCmdUI ui;

for (pCtrl = /* each child

control in "this" */) {

ui.m_pOther = pCtrl;

// it's a window, not a menu

ui.m_nID=pCtrl-GetDlgCtrlID();

ui.DoUpdate(pTarget,

bDisableIfNoHndler);

}

}

This function lets you use the ON_
UPDATE_COMMAND_UI mechanism for dialogs. You can code your dialog's message
map and handler functions as you would for a frame or view, then call
UpdateDialogControls to update the buttons. For pTarget, you should pass the
command target whose message map contains the handlers, usually the dialog
itself or perhaps your main frame. You must call UpdateDialogControls
whenever you want to do the update. For modeless dialogs, you could do it
whenever your frame gets WM_IDLEUPDATECMDUI, but this won't work for modal
dialogs because your message pump is suspended during a modal dialog. For
modal dialogs, it's usually easier to call UpdateDialogControls whenever
something happens that you know might affect the state of the controls (sort
of an improved version of the old Neanderthal approach). You could also do it
when the parent window gets WM_ENTERIDLE, which Windows sends whenever your
modal dialog goes idle.

CMDLEARN 
Whew!
That's plenty of theory to grok in one day.Figure 9 shows the
Grand Unified Theory of message and command routing, a considerable
improvement overFigure 1. Now it's time to write some code!

 

Figure 9 Figure 1 Revised

CMDLEARN is an ordinary old doc/view app that displays information about
files. Figure 10 shows it in action with three files open.
But the real purpose of CMDLEARN isn't to show information about
files—CMDLEARN is a multipurpose command routing demo program with three main
features.

 

Figure 10 CMDLEARN

First, CMDLEARN traces all calls to WindowProc, OnCommand, and OnCmdMsg
for windows and command targets. Its tracing is way better than the
unreadable stuff MFC gives you. Second, CMDLEARN pulls a few command routing
tricks. It routes WM_TIMER messages to all the open documents in the form of
a command, and it routes commands to a new CCmdTarget. Finally, CMDLEARN
contains a dialog with a special combo box that reroutes button messages to
itself to create a self-contained combo-box-with-add-and-delete-buttons.
When
you run CMDLEARN, it dumps a whole bunch of stuff to a file called TRACE.OUT.
I used a file instead of debug output because CMDLEARN generates a ton of
diagnostics and also to avoid requiring the debugger. Instead of displaying
the kind of unformatted gobbledygook only an assembly language hacker could
read, which is what you get with MFC's command and message tracing, CMDLEARN
generates pretty output like Figure 11. It knows about MFC's
private WM_XXX messages and standard ID_MUMBLE commands, as well as
CMDLEARN's own commands like ID_TRACE_MSG. You can run CMDLEARN, invoke some
commands, and examine the output to see what happened. 
 Figure
11
 is a fragment of a trace that shows what happens when I did File
Open in CMDLEARN. I've added running play-by-play in the form of C++-style
comments. It should make everything I've been talking about a little more
clear. Figure 12 is another trace fragment that shows the
tool bar buttons and status bar panes getting updated as a result of a
WM_IDLEUPDATECMDUI message. These traces were generated with message tracing
off, so you don't see zillions of WM_XXXs traveling all over the place.
CMsgTracer
writes the file. It also implements its own command and command update
handlers for trace messages and trace commands. For this to work, the
application class CApp routes commands to the one-and-only-one instance of
CMsgTracer.

BOOL CApp::OnCmdMsg(UINT nID, int nCode,

void* pExtra,
AFX_CMDHANDLERINFO* pHandlerInfo)

{

if (TWinApp::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))

return TRUE;     // handled by
doc/view/frame/app

// Not handled by doc/view: pass to tracer

return theTracer.OnCmdMsg(nID, nCode, pExtra, pHandlerInfo);

}

The intercepting of WindowProc, OnCommand, and OnCmdMsg is done using
template classes. For details, read the sidebar entitled "Born Again
Templates." 
My
specification for CMDLEARN called for its views to update themselves
continually. Rather than setting a timer for each view (I'd run out of timers
pretty quick), CMDLEARN sets one timer in the main window. Then, whenever the
clock ticks, CMainFrame sends a command to all its documents.

void CMainFrame::OnTimer(UINT nIDEvent)

{

if (nIDEvent = = ID_UPDATE_STATUS) {

CAppDocs alldocs;

alldocs.SendCommand(ID_UPDATE_STATUS);

}

}

This effectively lets documents handle
WM_ TIMER. CAppDocs is a helper class I wrote that calls OnCmdMsg directly
for each open document. You might find it useful in your own apps. The main
point is to show how you can use OnCmdMsg as a general mechanism to send
messages to any command target. When a doc gets ID_ UPDATE_STATUS, it updates
itself. Only if something has changed does CFildDoc bother to update its
views. You can witness this feature in action by editing a file while it's
open in CMDLEARN: the file size and modification dates are updated to reflect
your changes. And if the file is deleted, the window automatically closes
with a little popping sound. (Try it!)
Why
not just call OnUpdateStatus from the CMainFrame::OnTimer, instead of sending
a command? That would work, but it has a major drawback. It requires casting
CDocument* to CFileDoc*. In other words, the app must know that the document
is really a CFileDoc. This means you can't have other doc templates with
other doc classes, or if you do, you must make a provision for them. The
simplest way is to do what Windows does: just send a unique integer command
code, and any doc that wants to can handle it. Since all I wanted to do is to
tell the documents "update yourself," a command is all I needed.
Had I wanted to send more information, I'd have defined my own NMHDR class
and used CAppDocs::SendNotify.

struct MyNMHDR : public NMHDR { // WM_NOTIFY
struct

LPCSTR   whereBill; // Bill's
last known whereabouts

double   worthBill; // Bill's
last known net worth

};

void CMainFrame::OnTimer(UINT nIDEvent)

{

if (nIDEvent = = ID_UPDATE_STATUS) {

CAppDocs alldocs;

MyNMHDR nmhdr;

nmhdr.whereBill = "BurgerMaster";

nmhdr.worthBill = 8.3994e43;

alldocs.SendNotify(0, ID_UPDATE_STATUS, &nmhdr); // send info to
all docs

}

}

Finally, CMDLEARN contains a dialog that actually does a bit of command
rerouting. This dialog was inspired by a reader, Jean-Louis Leroy, who wrote
asking if there were some way to make his list box handle button clicks,
instead of the dialog. He had a list box with First and Last buttons that
selected the first and last items and wanted to build a reusable list
box-with-buttons object. 
 Figure
13
 shows my version of this. The dialog contains a combo box with
Add and Delete buttons. If you type some text in the edit box, the Add button
is enabled, and pressing it adds the text to the list. If an item is
selected, you can press Delete to delete it. If not, Delete is disabled. The
normal Windows way to implement this sort of thing is to put all the code in
the dialog. But as Jean-Louis observed, this is not reusable. If you want to
use the same combo box in another dialog, you have to copy and paste a lot of
code. That's gauche.

 

Figure 13 ComboCombo

Figure 14 shows how I implemented CComboCombo, a
self-contained combo box with Add and Delete buttons. Figure 15 shows
the message flow.

 

Figure 15 Combo Box Message Flow

In CComboCombo, a new class, COwnedButton, represents the buttons.
COwnedButton overrides OnChildNotify to handle its own notifications (such as
BN_CLICKED): it sends them its owner window instead of its parent. A flag
prevents infinite recursion when the owner calls OnChildNotify again. I
could've created a new data member to store the owner, but it was expedient
and harmless to use MFC's m_hWndOwner. I didn't want to use SetParent,
because child windows are clipped by their parents and the buttons would
disappear.
CComboCombo
contains two COwnedButtons, for Add and Delete. CComboCombo::SubclassControls
hooks everything up: it subclasses the combo box as well as the buttons, and
it assigns itself ownership of the buttons so the buttons send their
notifications to it. A dialog using CComboCombo must call SubclassControls
with the IDs of the combo box and both buttons. 
Once
CComboCombo hooks everything up, it calls UpdateButtons to update
(enable/disable) the dialog buttons. UpdateButtons is a helper function that
calls UpdateDialogControls for the parent dialog, using itself as the command
target—that is, its own message map—instead of the frame window or dialog.
CComboCombo calls UpdateButtons any time the buttons need updating, such as
after adding or deleting an item.
CComboCombo
overrides its own OnChildNotify to handle a couple of notifications: when the
edit control changes (CBN_EDITCHANGE) or the selection changes
(CBN_SELCHANGE), the buttons need to be updated, so CComboCombo calls
UpdateButtons.
CComboCombo
implements ON_ COMMAND_RANGE and ON_ UPDATE_COMMAND_UI_RANGE handlers for Add
and Delete. Since the IDs of these commands are not known until run time, the
range is 0 to 0xFFFF: in other words, all commands. The handlers look for IDs
equal to the IDs specified by SubclassControls.
The
result of all this is an entirely self-contained class. To use CComboCombo,
all you have to do is include COMCOMBO.H, add a CComboCombo to your dialog,
and call CComboCombo::SubclassControls in OnInitDialog. See DLGCOMBO.H and
DLGCOMBO.CPP in the source code on any MSJ bulletin board. You can adopt the
CComboCombo approach any time you have a group of controls that you want to
act as a unit.
Well,
that's it. Time to go. I hope you learned something besides what it's like to
eat elephant. If all this was too overwhelming, you might try reading MFC
Technical Note 6, "Message Maps," and 21, "Command and Message
Routing," as well as Bob Chiverton's C/C++ Q&A column in the
November 1992 MSJ. If you understood everything, just go ahead and pronounce
yourself a Command Routing Guru. That and a buck twenty-five'll get you a
ride on the subway.

From the July 1995 issue of Microsoft Systems Journal. Get it at your local newsstand, or better yet, subscribe.

上一篇:TAxThread - Inter thread message based communication - Delphi


下一篇:System IPC 与Posix IPC(semaphore信号灯)