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? ![]() ![]() ![]() ![]() ![]() |
![]() |
![]() |
![]() Message Madness Made Merry
Pump It Up |
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. ![]() ![]() ![]() ![]() ![]() ![]() ![]() |
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. |
![]() |
![]() |
![]() |
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;
}
|
![]() ![]() ![]() ![]() One Framework, One Window Proc |
![]() |
![]() |
![]() |
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);
}
|
![]() |
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;
}
|
![]() ![]() ![]() One Ringie-Dingie, Two Ringie-Dingie |
![]() |
![]() |
![]() |
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;
};
|
![]() |
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)
};
|
![]() |
{ 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. ![]() |
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.![]() |
BEGN_MESSAGE_MAP(...)
ON_MESSAGE(WM_RUN_FOR_CONGRESS, OnRunForCongress)
•
•
•
END_MESSAGE_MAP()
LRESULT OnRunForCongress(WPARAM wp, LPARAM, lp)
{
CCongressionalDistrict* pCd = (CCongressionalDistrict*)lp;
•
•
•
}
|
![]() ![]() |
#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
}
|
![]() What, No Handler? |
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
•
|
![]() ![]() The Evil WM_COMMAND |
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. |
![]() |
![]() |
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);
}
•
•
•
}
|
![]() |
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 |
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.![]() Mother of All 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.![]() ![]() |
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. |
![]() |
![]() |
![]() |
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.![]() Ooey GUI |
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);
};
|
![]() ![]() |
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. ![]() ![]() |
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;
}
•
•
•
}
|
![]() ![]() ![]() ![]() |
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 |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() ![]() ![]() ![]() |
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);
}
|
![]() ![]() |
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!)![]() |
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
}
}
|
![]() ![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() |
Meandering Through the Maze of MFC Message and Command Routing
原文:http://www.cnblogs.com/cutepig/p/5448649.html