码迷,mamicode.com
首页 > 编程语言 > 详细

Meandering Through the Maze of MFC Message and Command Routing

时间:2016-04-30 14:17:21      阅读:278      评论:0      收藏:0      [点我收藏+]

标签:

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.
  2. Process it.
    • Process ordinary messages.
    • Process command messages.
  3. 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:


 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.

 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. 
技术分享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.
技术分享By the time you‘re finished with all the voodoo, your message pump is even more complicated than the Windows version of "Hello, world"! 
技术分享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. 
技术分享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. 
技术分享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! 
技术分享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. 
技术分享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.

 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:

 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.

 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.
技术分享
技术分享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:

 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! 
技术分享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.
技术分享 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.

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). 

技术分享
技术分享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.

 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.

 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!
技术分享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!

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.

技术分享
技术分享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:

 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.

 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:

 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:

 { 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:

 { 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.

 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.

 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.

 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:

 #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.

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.


 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.

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.
技术分享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.


 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:

 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."


 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 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.

 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.

Meandering Through the Maze of MFC Message and Command Routing

标签:

原文地址:http://www.cnblogs.com/cutepig/p/5448649.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!