MFC把消息分成了两大类,一般消息(WM_xxx)和 命令消息(WM_COMMAND)。因为一般消息的传递路线简单而明确:一定是由派生类流向基类,没有旁流的可能。所以,重中之重就落到了命令消息的头上。
对于命令消息传递路线的学习,有几点重要信息要事先请大家注意:
1、MFC对于命令消息WM_COMMAND的特殊处理顺序;
(其实在这里我们可以把特殊二字拿掉,因为特殊之处在此涉及不到)
2、与消息循环有关的成员函数皆为虚函数,所以理清它们所属的基类及是否被子类改写就成为把握程序走向的关键;
3、因为虚函数的存在,程序执行过程中对象指针的指向(实例化)就尤为重要,这直接决定了虚函数的调用。
时刻注意这三点,对于把握命令传递的路线会有所帮助。现在我把前两点再展开一下,大家可以在看后面的详述时,与这里进行对照。
1、命令消息 WM_COMMAND的处理顺序:
(1)Frame 窗口: View
‚Frame窗口本身
ƒCWinApp
(2)View : View本身
‚Document
2、虚函数脉络 (其中,小括号内是虚函数的首次定义;中括号为子类对它的改写)
CDocument
【OnCmdMsg】
CCmdTarget CFrameWnd
(OnCmdMsg) CWnd 【OnCommand】
(WindowProc) 【OnCmdMsg】
(OnCommand)
(DefWindwProc) CView
【OnCmdMsg】
这些准备,都是自己在学习过程中因不够注意而耗费时间的地方,希望能够引起大家的重视。准备工作完成,接下来就是在这些“指导思想”的引导下,开始命令传递的长征了。
消息的传递从全局函数 AfxWndProc()开始,每调用一次便推进一个消息。
LRESULT AfxWndProc(HWND hWnd, UINT nMsg, WPARAM wParam,
LPARAM lParam, CWnd* pWnd)
{
cout << "AfxWndProc()" << endl; //追踪路线
return AfxCallWndProc(pWnd, hWnd, nMsg, wParam, lParam);
}
看下函数AfxWndProc()的5个参数,其中:
hWnd 是一个窗口句柄,它唯一标志了一个应用程序窗口;
nMsg 是一个消息,这个参数就代表了消息本身;
wParam和lParam,它们是消息nMsg的附加参数,在进行消息传递的过程中会用到;函数体内的cout 显示了追踪的路线,return AfxCallWndProc进入下一步。
LRESULT AfxCallWndProc(CWnd* pWnd, HWND hWnd, UINT nMsg,
WPARAM wParam, LPARAM lParam)
{
cout << "AfxCallWndProc" << endl; //追踪路线
LRESULT lResult = pWnd->WindowProc(nMsg, wParam, lParam);
return lResult;
}
回顾一下我们的虚函数脉络,WindowProc()是CWnd类的虚函数,CWnd的子类(CFrameWnd和CView)都没有改写它,所以不论pWnd指针被哪个子类的对象实例化,调用的总是CWnd::WindowProc()。尽管如此,仍然有两点原因让我们必须要明晰将pWnd实例化的对象是哪个:
(1)虽然CFrameWnd::WindowProc()和CView::WindowProc()都相当于调用CWnd::WindowProc(),但意义是不同的。这点是侯捷先生着重强调的。
(2)接下来还有多个虚函数在等着我们,不可因这一次的殊途同归而掉以轻心。
进入到CWnd::WindowProc()中,就要对消息的本质做出判别了。其分类就是我们前面提到的 一般消息(WM_xxx)和 命令消息(WM_COMMAND)两大类。如果是一般消息,那么它的处理在WindowProc()函数内部就到达终点了,只需依据消息映射表循线而上即可,这里不再赘述。如果是命令消息(WM_COMMAND),那就要由CWnd::WindowProc()调用OnCommand()。
回想我们的虚函数脉络,OnCommand是CWnd类的虚函数,并且子类CFrameWnd中有改写它,而CView则没有。所以,如果this指针被不同的子类对象实例化,那么就不会再殊途同归了了。
以调用CFrameWnd::OnCommand为例,继续往下看。走到这里,或许应该留意一下函数参数的变化。OnCommand(WPARAM wParam, LPARAM lParam),只有消息的两个附加参数了,因为在此消息的本质已经确定,就是命令消息。伴随着传递的深入,参数也不断完成着它们的使命。
BOOL CFrameWnd::OnCommand(WPARAM wParam, LPARAM lParam)
{
Cout << "CFrameWnd::OnCommand" << endl; //追踪路线
return CWnd::OnCommand(wParam, lParam);
}
进入 CWnd::OnCommand
BOOL CWnd::OnCommand(WPARAM wParam, LPARAM lParam)
{
Cout << " CWnd::OnCommand" << endl; //追踪路线
return OnCmdMsg(0, 0);
}
面对OnCmdMsg,还要用到我们的虚函数脉络。OnCmdMsg是CCmdTarget的虚函数,其子类(CDocument、CFrameWnd、CView)都改写了它。根据this指针所指,当前调用CFrameWnd::OnCmdMsg()。在这个函数里就大有文章了。之所以这么说,因为它体现出了Frame窗口处理WM_COMMAND的次序。不仅如此,看到函数参数 (UINT nID, int nCode),是否想起了数据结构AFX_MSGMAP_ENTRY中的内容呢?它里面可是记录了消息的基本信息,既然这些参数都已出现,说明我们距离最后的消息处理程序已经不远了。
在WM_COMMAND的处理次序上,CFrameWnd::OnCmdMsg函数的结构十分清晰:
(1)CView* pView = GetActiveView();
if (pView->OnCmdMsg(nID, nCode))
return TRUE;
(2)if (CWnd::OnCmdMsg(nID, nCode))
return TRUE;
(3)CWinApp* pApp = AfxGetApp();
if (pApp->OnCmdMsg(nID, nCode))
return TRUE;
最先调用pView->OnCmdMsg,即
BOOL CView::OnCmdMsg(UINT nID, int nCode)
{
if (CWnd::OnCmdMsg(nID, nCode))
return TRUE;
BOOL bHandled = FALSE;
bHandled = m_pDocument->OnCmdMsg(nID, nCode);
return bHandled;
}
这个函数,CView::OnCmdMsg又反应了View窗口处理WM_COMMAND的次序:
a、if (CWnd::OnCmdMsg(nID, nCode))
return TRUE;
b、BOOL bHandled = FALSE;
bHandled = m_pDocument->OnCmdMsg(nID, nCode);
return bHandled;
对于a中CWnd::OnCmdMsg,根据虚函数脉络,即CCmdTarget::OnCmdMsg
BOOL CCmdTarget::OnCmdMsg(UINT nID, int nCode)
{
AFX_MSGMAP* pMessageMap;
AFX_MSGMAP_ENTRY lpEntry;
for (pMessageMap = GetMessageMap(); pMessageMap != NULL;
pMessageMap = pMessageMap->pBaseMessageMap)
{
lpEntry = pMessageMap->lpEntries;
printlpEntries(lpEntry);
}
return FALSE;
}
这是走访消息映射表的操作,我们就要找到消息最后的归宿——处理程序了。如果在映射表中找到了对应的消息,就调用对应的处理程序;否则,就要回到分岔口去走另一条路,即退回到CView::OnCmdMsg执行b、bHandled = m_pDocument->OnCmdMsg(nID, nCode)。如果消息到最后也没能找到对应的处理程序,那么它走过的路线应该是这样的:
(1)a -> (1) b -> (2) -> (3)
注意,这是在本示例程序调用CFrameWnd::OnCommand为开始的前提下进行的。如果换做其它的调用顺序,只要把握MFC对WM_COMMAND的处理顺序和虚函数脉络,其中的条理自然就明确了。
最后,还要再重复一下,在命令传递的过程中,影响执行路线的是MFC处理WM_COMMAND的顺序和this指针的指向。理解这两点,对理解本节侯捷先生仿真出的命令传递长征路线有很好的帮助。
向侯捷先生致谢,鼓励交流,只求进步。
本文详细探讨了MFC中命令消息WM_COMMAND的处理顺序,包括Frame窗口和View的处理流程,以及CDocument、CFrameWnd和CView中的OnCmdMsg函数如何参与消息传递。通过理解这些关键点,可以更好地掌握MFC程序中命令消息的传递路径。
11万+





