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? |
Figure 1 Where's the Minotaur? |
So let me guide you through the labyrinth of message and command Message Madness Made Merry
Simple, right? The first involves not
In the sections that follow, I'll Pump It Up 以前,黑客花费了几个小时的时间写程序,此时c++还局限于科研大厅。每个人都要写一个winmain如下 |
MSG msg; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); // } |
This message pump is the heart of every Windows-based 这个消息泵是每个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 TranslateAccelerator是一个voodoo函数,你必须调用来完成加速键。TranslateAccelerator查看你的加速键表,把他转换成WM_ COMMAND消息。不想TranslateMessage,它分发WM_COMMAND消息+ ID_EDIT_CUT ID到你的程序过程。对你程序来讲,他就像看到用户从菜单式选了Edit Cut菜单。TranslateMessage返回TRUE表示消息已经翻译了以及分发了,所以不需要在分发了 Windows Windows有很多这样的woodoo函数用来转换消息,你不需要明白原因,只需要按照他们告诉你的来做。你用IsDialogMessage处理modeless dialog, 以便tab,ctrl能工作。你可能希望他们自己搞定-毕竟,他们为model dialog处理了-但是现实不是。你需要有TranslateMDISysAccel处理MDI加速键,像Ctrl-F6 for Next Window and Shift-F5 for By the time you're finished with all the voodoo, your message pump is 当你完成所有voodoo后,你的消息泵会比windows版本的helloworld更加复杂 不要担心,几年过去了,Bjarne But underneath all the object glitz, your Wizard-generated app still 但是在所有东西下面,你的wizard geberator的程序仍旧做同样的old muck。你只是看不到而已,隐藏在了函数CWinApp::Run之下,作为所有消息泵的母亲。MFC隐藏了voodoo,至少重新打包成了可口的方式 为了明白他是如何工作的,听一下,想一想古老的世界的情形。你刚写完你的Acme程序,完成了WinMain里面的中心loop。都已经debug完了,闪闪发光,等待着你来压缩。突然高层命令来了。 If you think there's something wrong with this picture, you win the 如果你觉得这样是有问题的,那你赢了。生命不应该这样。object应该完成他们自己的行为,不应该要做一些很奇怪的函数,不要让winmain做他!那就像搞你的肩膀来治疗你的肾病 MFC Mfc纠正了这个问题,他通过让窗口完成他们自己的消息转换。在mfc里,dialog,而不是winmain,调用IsDialogMessage。他是怎么做的呢?为了理解,我们看看mfc的消息泵 |
int CWinApp::Run() { • • • for (;;) { while (!::PeekMessage(&m_msgCur,...)) { if (!OnIdle(...)) // do some break; } // I have a message, or else no idle work to do: // pump it if (!PumpMessage()) break; } return ExitInstance(); } |
If there are 如果没有消息,mfc调用onidle,你可以重载来做有用的事情比如计算twin |
BOOL { • • • if return } if ::TranslateMessage(&m_msgCur); ::DispatchMessage(&m_msgCur); } return TRUE; } |
Look 看起来很熟悉?除了PreTranslateMessage。那是一个新的虚函数。默认的实现是遍历window hierarchy,从发送消息的窗口开始,直到他的parent和grandparents,直到最上层窗口。对每个窗口调用CWnd::PreTranslateMessage |
BOOL { for (pWnd = if return |
if (pMainWnd if return return } |
That's 现在窗口可以转换消息了,当一个做了之后他返回true一路网上 |
Figure 2 The Message Pump |
It may make your head spin, but this translation tango is actually 这可能让你头都大了,但是这个转换非常漂亮因为现在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 这样在dialog而不是cwinapp里面转换dialog明显合理多了。你觉得呢?cwinapp根本不知道dialog。当然,IsDialogMessage技术上只适用于modeless dialog,但是对于model dialog,PreTranslateMessage不会调用到,因为当你调用domodel的时候,windows开始了另个message loop,控制权直到dialog关掉之后才会返回。但是真正伟大的事情是你再也不用担心IsDialogMessage了。事实上,你可以忘了他的存在,任何从cdialog集成的dialog,即使你匆忙的在最后一分钟添加。如果你一直用mfc,你可能从未听说过IsDialogMessage Likewise, 类似的,由于frame窗口拥有菜单,理所当然他要处理加速键。事实上也是这样。当你在Init Instance函数里面创建frame时候,他会架子啊加速键表,改表定义了document Figure 图3显示各种各样的mfc class如何实现PreTranslateMessage的, mfc处理了99%的情形,所以你完全可以忘了他。这个月的c qa举例子另外的1% 我直接讲出来先,但我不想这么快讲(??)。在多task的情况下,每个线程有自己的消息泵,一个程序只是一个线程的特化。因此大多数cwinapp的mfc函数迁移到了cwinthread,而cwinapp从他继承 One Framework, One Window Proc |
Figure 4 Window Message Routing |
You know the window proc. It's that big function you wrote in the old 你知道windows过程,他是在以前你需要写的一个大函数,有着巨大的switch语句。现在很少人这样做了。mfc里面,windows过程消失了。好吧,不是真的消失了。而是被mfc的统一的过程AfxWndProc代替了。一个windows 过程怎样为所有窗口工作呢?很简单,他没有做任何appl specific的事事情 |
LRESULT AfxWndProc(HWND hWnd, UINT nMsg, WPARAM { • • • CWnd* pWnd = CWnd::FromHandlePermanent(hWnd); return AfxCallWndProc(pWnd, hWnd, nMsg, wParam, lParam); } |
CWnd::FromHandlePermanent looks up the HWND in map and retrieves the CWnd::FromHandlePermanent查找hwnd map,找到对应的cwnd。利用cwnd,流程跑向AfxCallWndProc |
LRESULT AFXAPI AfxCallWndProc(CWnd* pWnd,HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam) { • • •// LRESULT lResult = pWnd-> WindowProc(nMsg, wParam, lParam); • • • return lResult; } |
AfxCallWndProc calls WindowProc. Instead of passing an HWND to a C AfxCallWndProc调用wndowsproc。mfc调用了cwnd的虚函数WindowProc,而不是传递hwnd到c的windows过程。所以所有的完成的就是从c编导c++。但是,这是多大的进步啊,因为windowproc是虚函数! CWnd::WindowProc 你知道CWnd::WindowProc不是我们一般处理消息的地方,但你应该明白如果需要,你可以在这里处理消息。WindowProc是消息高速路上的第一站。如果你在移植c程序,你可以或多或少的把整个windows proc拷贝到CMainFrame::WindowProc。 One Ringie-Dingie, Two Ringie-Dingie 每个人都知道,正确的处理消息的方法是通过消息map。消息map从哪里来?CWnd::WindowProc。首先,他检查消息是否WM_COMMAND or |
Figure 5 Message Maps |
Message maps are tables that associate WM_XXX message IDs with C++ 消息map是一些表格,吧WM_XXX消息和C++ virtual functions联系起来。WM_SIZE goes to OnSize. WM_CLOSE goes to OnClose等等。细节隐藏在DECLARE_MESSAGE_MAP, BEGIN_MESSAGE_MAP, |
class CMyFrame : public CFrameWnd { DECLARE_MESSAGE_MAP() }; BEGIN_MESSAGE_MAP(CMyFrame, CFrameWnd) • • • 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 AFX_MSGMAP CMyFrame::messageMap = { &CFrameWnd::messageMap, // &CMyFrame::_messageEntries[0] // this class's entries }; const AFX_MSGMAP_ENTRY • • • // END_MESSAGE_MAP: {0, 0, 0, 0, 0, 0 } }; |
AFX_MSGMAP contains just two members: a 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, Base map指针提供了一种方式来遍历继承链,从而有效的实现了消息map的继承。派生类自动继承了基类处理的消息。如果你连接dll版本的mfc,pBaseMap其实是一个指针函数,它返回的事base map,而不是base map自身 |
struct AFX_MSGMAP_ENTRY { UINT nMessage; // windows UINT nCode; // control code UINT nID; // control ID // messages) UINT nLastID; // used for // range of control id's UINT nSig; // signature // pointer to message # AFX_PMSG pfn; // routine to // value) }; |
Each entry maps a particular Windows message, including control ID and 每一项map到一个特别的windows消息,包括空间id,通知码,一个CmdTarget继承的成员函数,ncode和nid是32位mfc新加的,他们支持ON_NOTIFY and ON_COMMAND_RANGE 比如ON_WM_CREATE展开如下 |
{ (AFX_PMSG)(AFX_PMSGW)(int (CWnd::*)(LPCREATESTRUCT))OnCreate }, |
At first glance, it looks like something 第一眼,看起来像走下神坛。WM_CREATE很直接,消息id,CWnd::WindowProc直到如何使用这个entry。0是因为通知码和消息id没用,三个很丑的cast用来确保oncreate有正确的签名。换句话,他使macro类型安全。不是所有macro都对应硬编码的函数。ON_MESSAGE(msg, mbrfn)展开如下 |
{ (AFX_PMSG)(AFX_PMSGW)(LRESULT (CWnd::*)(WPARAM, LPARAM))mbrfn }, |
You can use whatever member function you 你可以用任意你喜欢的成员函数来给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 分发码如何传递参数到你的handle函数呢?那就是AfxSig??这里是WindowProc如何调用你的函数的代码 |
LRESULT CWnd::WindowProc(UINT nMsg, WPARAM { • • • const AFX_MSGMAP_ENTRY* lpEntry = // (entry // message) union MessageMapFunctions mmf; // described 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); • • • } |
AfxSig_is means the function takes a 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*, AfxSig_hDWw, // HBRUSH (CDC*, AfxSig_iwWw, // int (UINT, AfxSig_iWww, // int (CWnd*, AfxSig_is, // int (LPTSTR) AfxSig_lwl, // LRESULT • • • }; |
You get the idea. That "union |
union MessageMapFunctions { AFX_PMSG pfn; // generic // specific type safe variants BOOL (CWnd::*pfn_bD)(CDC*); BOOL (CWnd::*pfn_bb)(BOOL); BOOL (CWnd::*pfn_bWww)(CWnd*, HBRUSH (CWnd::*pfn_hDWw)(CDC*, int (CWnd::*pfn_iwWw)(UINT, int (CWnd::*pfn_iWww)(CWnd*, int (CWnd::*pfn_is)(LPTSTR); LRESULT (CWnd::*pfn_lwl)(WPARAM, LPARAM); • • // etc, for each AfxSig code • }; |
There's only one real function (pfn), 下面是如何处理你自定义的消息 |
BEGN_MESSAGE_MAP(...) ON_MESSAGE(WM_RUN_FOR_CONGRESS, OnRunForCongress) • • • END_MESSAGE_MAP() LRESULT OnRunForCongress(WPARAM wp, LPARAM, { CCongressionalDistrict* pCd = (CCongressionalDistrict*)lp; • • • } |
But say you're writing a library. Maybe even an MFC extension library. 如果你写的是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->RunForHouse(); pCd->RunForSenate(); return 0; // mission } |
If you want other parameters, chances are you can find a suitable AfxSig_xxx What, No Handler? 如果没有合适的entry,会调用CWnd::DefWindowProc |
CMyWnd::OnFooMumbleBletch() { • • • CWnd::OnFooMumbleBletch(); } |
The base implementations go like this. |
// (From AFXWIN2.INL) inline void CWnd::OnSize(UINT, int, int) { inline void CWnd::OnSetFocus(CWnd*) { Default(); } inline BOOL CWnd::OnNcActivate(BOOL) // return (BOOL)Default(); } • • • |
Default() does the same thing as DefWindowProc, the only difference The Evil WM_COMMAND WM_COMMAND是一个重载的函数 Overloading |
struct NMHDR { HWND hwndFrom; // control UINT idFrom; // ID of UINT code; // }; |
NMHDR is intended to be used as the |
struct TOOLTIPTEXT { // In C++, you can derive from NMHDR NMHDR hdr; // standard LPSTR lpszText; // tip text or char szText[80]; // tip text HINSTANCE hinst; UINT uFlags; }; |
The details of TOOLTIPTEXT and NMHDR are |
Figure 6 WM_COMMAND and WM_NOTIFY Message Flow |
LRESULT CWnd::WindowProc(UINT msg, WPARAM { // 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, return lResult; // command handled else return DefWindowProc(msg, wp, lp); } • • • } |
If WindowProc is the first stop on the message processing highway, |
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给了孩子机会首先检查他们自己的通知,你的combobox可以如下 |
BOOL CMamboCombo::OnChildNotify(UINT msg, { 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 } } return CComboBox::OnChildNotify(msg, wp, lp, pResult); } |
Now CMamboCombo is entirely Mother of All Targets 处理command和其他消息不同的主要原因是:非窗口对象可以收到他们!比如documents 事实上,为什么document有消息map?这是因为documen从ccmdtarget继承 |
CObject CCmdTarget CWnd CWinThread // (Win32 only) CWinApp CDocTemplate CDocument |
And the heart of CCmdTarget is 他的心脏就是CCmdTarget::OnCmdMsg,是command路由中的第二站 那么document如何受到command呢?是因为CFrameWnd重载了CCmdMsg从而把command传给view和其他object |
BOOL CFrameWnd::OnCmdMsg(...) { if (pActiveView->OnCmdMsg(...)) return TRUE; // handled by if (CWnd::OnCmdMsg(...)) return TRUE; // handled by if (pApp->OnCmdMsg(...)) return TRUE; // handled by return FALSE; // not } |
Figure 8 illustrates |
Figure 8 Document/View Command Routing |
So far, all the routing I've been talking about takes place within a |
BOOL CMyView::OnCmdMsg(...) { if (CMyView::OnCmdMsg(...)) return TRUE; // handled by return m_wndGizmo.OnCmdMsg(...); // pass to gizmo } |
Or you might want to give the gizmo Ooey GUI |
class CCmdUI { CMenu* m_pMenu; // if a 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 |
void CFrameWnd::OnInitMenuPopup(CMenu* { • • • 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 |
void CCmdUI::DoUpdate(CCmdTarget* pTarget, { • • • pTarget->OnCmdMsg(m_nID, CN_UPDATE_COMMAND_UI, this, NULL) • • • } |
The first argument, pTarget, is the |
struct AFX_CMDHANDLERINFO { CCmdTarget* pTarget; void (CCmdTarget::*pmf)(void); // }; BOOL CCmdTarget::OnCmdMsg(UINT nID, int AFX_CMDHANDLERINFO* { • • • 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 |
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_ CMDLEARN |
Figure 9 Figure 1 Revised |
CMDLEARN is an ordinary old doc/view app that displays information about |
Figure 10 CMDLEARN |
First, CMDLEARN traces all calls to WindowProc, OnCommand, and OnCmdMsg |
BOOL CApp::OnCmdMsg(UINT nID, int nCode, void* pExtra, { if (TWinApp::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo)) return TRUE; // handled by |
// 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 |
void CMainFrame::OnTimer(UINT nIDEvent) { if (nIDEvent = = ID_UPDATE_STATUS) { CAppDocs alldocs; alldocs.SendCommand(ID_UPDATE_STATUS); } } |
This effectively lets documents handle |
struct MyNMHDR : public NMHDR { // WM_NOTIFY LPCSTR whereBill; // Bill's double worthBill; // Bill's }; 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 } } |
Finally, CMDLEARN contains a dialog that actually does a bit of command |
Figure 13 ComboCombo |
Figure 14 shows how I implemented CComboCombo, a |
Figure 15 Combo Box Message Flow |
In CComboCombo, a new class, COwnedButton, represents the buttons. |
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 MFC消息路由机制分析的更多相关文章
- MFC消息响应机制分析
---- 摘要: ---- MFC是Windows下程序设计的最流行的一个类库,但是该类库比较庞杂,尤其是它的消息映射机制,更是涉及到很多低层的东西,我们在这里,对它的整个消息映射机制进行了系统的分析 ...
- MFC消息响应机制 q
MFC消息响应机制分析 1 引言微软公司提供的MFC基本类库(Microsoft Foundation Classes),是进行可视化编程时使用最为流行的一个类 库.MFC封装了大部分Windows ...
- MFC消息映射机制以及画线功能实现
---此仅供用于学习交流,切勿用于商业用途,转载请注明http://www.cnblogs.com/mxbs/p/6213404.html. 利用VS2010创建一个单文档标准MFC工程,工程名为Dr ...
- VS2010/MFC编程入门之四(MFC应用程序框架分析)
VS2010/MFC编程入门之四(MFC应用程序框架分析)-软件开发-鸡啄米 http://www.jizhuomi.com/software/145.html 上一讲鸡啄米讲的是VS2010应用 ...
- MFC的消息映射机制揭秘
MFC的设计者们在设计MFC时,紧紧把握一个目标,那就是尽可能使得MFC的代码要小,速度尽可能快.为了这个目标,他们使用了许多技巧,其中很多技巧体现在宏的运用上,实现MFC的消息映射的机制就是其中之一 ...
- MFC的消息反射机制
1.消息反射解释: 父窗口将子窗口发给它的通知消息,首先反射回子窗口进行处理(即给子窗口一个机会,让子窗口处理此消息),这样通知消息就有机会能被子窗口自身进行处理. 2.MFC中引入消息反射的原因: ...
- 图解MFC基本框架(深入消息映射机制)
首先,先看整体的消息流向图: 上图解释: 起点是消息循环,在winmain函数中(mfc中winmain函数是隐含的调用的,在app全局对象构造完后紧接着调用winmain函数),while循环中不断 ...
- MFC消息路由
1.Command Routing(命令传递):当消息进来时,会有一个泵推动它前进.消息如何进来,以有泵函数如何推动,都是属于windows程序设计的范畴, 消息如果是从子类流向父类(纵向流动),那么 ...
- MFC消息反射机制
消息反射机制要解决什么问题呢? 消息反射机制主要是为了控件而实现的.每当控件需要某些资讯(比如,绘制自身背景的画刷,显示字体的颜色等等)时,都会频繁地向其父窗口发送通告消息(notification ...
随机推荐
- JDK的下载及配置
下载地址,为了兼容最好安装1.8版本 jdk1.8:http://jbox.jd.com/quickshare/d2wheyazjtdccshou2dbo24roejdk1.7:http://jbox ...
- LINQ(数据库操作增、删、改及并发管理)
本文将演示如何通过 Entity Framework 数据模型创建.修改.删除数据库记录. Customer cust = new Customer() { CustomerID = "LA ...
- 20165326 java第四周学习笔记
第四周学习笔记 ch5 子类和父类 子类只能有一个父类 使用关键字extendsyclass 子类 extends 父类 系统默认的祖先类Object(java.lang包中) 继承:子类继承父类的方 ...
- Vue.js 2.0生命周期
1.beforeCreate 组建实例刚被创建,属性和方法等都还没有 2.created 实例已经创建完成,属性已经绑定 3.beforeMount 模板编译之前 4.mounted ...
- HDU 6075 Questionnaire 17多校4 水题
Problem Description In order to get better results in official ACM/ICPC contests, the team leader co ...
- redis集群cluster模式搭建
实验服务器 :192.168.44.139 192.168.44.138 192.168.44.144 在 192.168.44.139上操作: 将redis的包上传的新建的目录newtouc ...
- 安卓 dex 通用脱壳技术研究(一)
注:以下4篇博文中,部分图片引用自DexHunter作者zyqqyz在slide.pptx中的图片,版本归原作者所有: 0x01 背景介绍 安卓 APP 的保护一般分为下列几个方面: JAVA/C代码 ...
- 句法分析工具 LTP HanLP
参考:http://cslt.riit.tsinghua.edu.cn/mediawiki/images/e/e5/%E5%8F%A5%E6%B3%95%E5%B7%A5%E5%85%B7%E5%88 ...
- golang 六宫格、九宫格头像生成
图片示例就不传了,在原WordPress上. //Merge6Grid 6宫格 //rule NO1:至少3张图 最多6张图 // NO2:第一张大小 60*60 其他大小 28*28 间隔4px 合 ...
- 改变html元素