本章将会对MFC的消息映射和 命令传递机制做深入探讨。
MFC规定了消息传递的路线,消息会按照这个路线传递下去,找不到归宿的话就交给DefWindowProc。
在产生窗口之前,如果我们在创建窗口时指定的窗口类为NULL时,MFC会自动注册五个默认的窗口类,每个窗口类有自己的窗口函数。不同窗口得到的消息应该交由不同的窗口函数来处理。所谓的命令传递机制是为了让消息的流动有线路可循,实现一个巨大的网,实现所有可能的路线。这就是所谓的消息映射图。
WM_COMMAND是来自菜单或工具栏,被称为命令消息。wParam记录着这一消息来自哪个菜单项目。
除了命令消息控件传给父窗口的消息,也是以WM_COMMAND类型的,但是它们被称为notification消息。
Command target即命令的目的地。因此所有派生自CCmdTarget类的类都可以处理命令消息。
派生自CWnd的类可以处理一般的windows消息和COMMAND消息。
DECLARE_MESSAGE_MAP()
BEGIN_MESSAGE_MAP
ON_COMAMND()
END_MESSAGE()
这些宏实现了消息和消息处理函数的映射,不再介绍哦。
在BEGIN和END之间除了ON_COMMAND之外,还可以有其他的形式,标准的windows消息并不需要我们指定处理函数的名称。如:
ON_WM_CHAR();WM_CHAR OnChar
ON_WM_CLOSE(); WM_CLOSE OnClose
ON_WM_CREATE(); WM_CREATE OnCreate
这些宏MFC已经定义好,将标准的消息和消息处理函数对应起来。
AFX_DAT,AFX_DATADEF,AFX_MSG_CALL和afx_msg一样现在还没有被使用。
虽然所有继承自CCmdTarget的类都可以处理命令消息,都应该有DECLARE_MESSAGE_MAP。。。但是CWinThread却没有。它不参与消息处理。因此CWinApp在填写消息映射表是就会跳过CWinThread。
DECLARE_MESSAGE_MAP(CWinApp,CCmdTarget);
消息映射表有C++多态的味道,但是MFC却没有使用多态来实现它,究其原因估计是多态会导致很大的负担。
前面的介绍中,以AfxWinProc作为起点,此处MFC仍以此作为开始,但是却省略了很多的细节。通常消息是在消息队列中等待窗口抓取,但是MFC安装了钩子,就可以提前抓取或抓取不是自己的消息。这是在所有派生自CWnd的对象创建之际发生的。如:
Bool CWnd::CreateEx()
{
。。。
AfxHookWindowCreate(this);
HWND hWnd=::CreateWindowEx();
...
}
AfxHookWindowCreate安装了WH_CBT类型的钩子,任何窗口显示之前,注册的钩子函数会被调用。
细节不再深入介绍,只要知道以上这些干了些偷天换日的勾当,把注册窗口类时注册的窗口函数,更换为AfxWndProc。于是AfxWndProc便是DispatchMessage的目的地。之所以弄得这么复杂书上说是为了兼容什么3D constrols。不懂,暂且放这儿吧。记住结论就是了。
上面可以知道AfxWndProc是消息的源头,这在前面几张也提到过。在此函数内部会调用AfxClassWndProc,后者会调用pWnd->WindowProc();在整个MFC中拥有windowProc的类有CWnd,CControlBar,COleControl,CDialog......具体调用谁的WindowProc要看pWnd 的类型。
在CWnd::windowProc中会调用OnWndMsg函数,它是用来分辨并处理消息的专职机构。如果是命令就交给OnCommand处理,如果是通知消息就交给OnNotify处理。之所以要区分命令消息和通知消息是因为它们的上溯路径并不是单纯的只往父类上去,可能有其他路径。而一般的windows消息却沿着直线上溯。
在OnCommand又调用了OnCmdMsg函数。具体调用哪个OnCommand,OnCmdMsg也是不定的,也得看this指针的类型。OnCmdMsg它是专门处理命令消息的函数。
bool CFrameWnd::OnCmdMsg(UINT nID,int nCode)
{
CView*pView=GetActiveView();
if(pView->OnCmdMsg(nID,nCode))//处理则返回否则继续传递。
return true;
if(CWnd::OnCmdMsg(nID,nCode))
return true;
CWinApp*pApp=AfxGetApp();
if(pApp->OnCmdMsg(nID,nCode)
return true;
return fasle;
}
bool CView::OnCmdMsg(UINT nID,int nCode)
{
cout<<"CView::OnCmdMsg()"<<endl;
if(CWnd::OnCmdMsg(nID,nCode))
return true;
bool bHandled=false;
bHandled=m_pDocument->OnCmdMsg(nID,nCode);
return bHandled;
}
Bool CDocument::OnCmdMsg(UINT nID,int nCode)
{
cout<<"CDocument::OnCmdMsg()"<<endl;
if(CCmdTarget::OnCmdMsg(nID,nCode))
return true;
return false;
}
以上就是消息的传递路线,这里跟第三章模拟的命令传递路线是相同的,具体可以第三章。
OnCmdMsg是各类专门对付命令消息的函数。本类处理过后又会调用下一个类的OnCmdMsg,这样命令消息就会不断传递下去。
如果MDI主窗口接收到一个命令消息,主窗口会把消息传递给CMDIChildWnd子窗口。子窗口传递给它对应的视图,视图检查自己的消息映射表,如果没有找到对应项,将其传递给document。Document检查自己的消息 映射表,找到对应项则调用该函数,消息传递 结束。如果没有找到继续传递,document把该消息传递到document template对象。如果还是没有找到,则传回到view,view将其传递给MDI子窗口本身。继续传递给CWinApp,交给 默认的消息处理函数。
在定义CRuntimeClass时,MessageMapEntries内定义了消息函数的指针类型,
typedef void(AFX_MSG_CALL
CCmdTarget:::*AFX_PMSG)(void);
此时消息处理函数指针类型为返回值为空,参数为空,这是不符合实际的,实际使用中会经过类型转换,使其可以接收参数和返回值。
由于消息处理函数的类型各异,MFC使用了AfxSig_来说明消息处理函数的类型。在找到某消息的消息处理函数之后,判断其类型再进行响应转换。
union MessageMapFunctions mmf;
mmf.pfn=lpEntry->pfn;
switch(lpEntry->nSig)
{
case AfxSig_isg:
lResult=(this->*mmf.pfn_is)(LPTSTR)lParam);
break;
case Afx_Sig_lwl:
lResult=(this->*mmf.pfn_lwl)(wParam,lParam);
break;
case AfxSig_vv:
(this->*mmf.pfn_vv)();
break;
........
}
AfxSig_is代表参数为LPTSTR字符串,返回值为int.
Afx_lwl代表参数wiewParam和lParam。返回值为LRESULT。
Afx_vv代表参数和返回值都为void.
(this->*mmf.pfn_vv)();中的pfn_vv是union MessageMapFunctions的一个成员。如
union MessageMapFunctions
{
AFX_PMSG pfn;
bool (AFX_MSG_CALL CWnd::*pfn_bD)(CDC*);
void (AFX_MSG_CALL CWnd::*pfn_VV)(CDC*);
.............
};
注意MessageMapFunctions是union类型的哦。
真是佩服当年MFC开发人员的智慧!!!!!