MFC的命令大体上由两类界面元素引发,一种是菜单项,另一种是如按钮,复选框等的通用控件。从消息来看,其实就是处理WM_COMMAND消息。尽管命令消息的进入点仍然是CWnd::OnWndMsg,不过MFC让它走了另一条路,即OnCommand。
让命令消息作另外处理是有原因的,比如说菜单命令,往往处理它的并不是FrameWnd,而是View,Document或其他的类。所以菜单命令并不是简单的发给拥有菜单的窗口,而是在整个框架绕了一圈。
CWnd::OnCommand调用了OnCmdMsg, 这是一个虚函数,不同的类对命令的派发是不一样的,因此各各覆盖OnCmdMsg作具体的处理。
先看看FrameWnd的:
BOOL CFrameWnd::OnCmdMsg(UINT nID, int nCode, void* pExtra,
AFX_CMDHANDLERINFO* pHandlerInfo)
{
// pump through current view FIRST
CView* pView = GetActiveView();
if (pView != NULL && pView->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))
return TRUE;
// then pump through frame
if (CWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))
return TRUE;
// last but not least, pump through app
CWinApp* pApp = AfxGetApp();
if (pApp != NULL && pApp->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))
return TRUE;
return FALSE;
}
FrameWnd上的命令大多数是菜单命令,为了让框架中的其他类能够处理菜单命令,它在三个地方调用OnCmdMsg,首先得到处理的是View,其次是FrameWnd自己,最后是WinApp。其实View处理的时候会让Document类处理命令,而Document又会让DocTemplate类处理,如果不中断,整个处理顺序大概是这样的:
CView::OnCmdMsg
CDocument::OnCmdMsg
CDocTemplate::OnCmdMsg
CFrameWnd::OnCmdMsg
pApp->OnCmdMsg
无论上面的类怎么处理,最后的分派都归结到CCmdTarget::OnCmdMsg,这个函数可以简化成这两句代码:
BOOL CCmdTarget::OnCmdMsg(UINT nID, int nCode, void* pExtra,
AFX_CMDHANDLERINFO* pHandlerInfo)
{
if (FindMessageEntry(…))
_AfxDispatchCmdMsg(…)
}
_AfxDispatchCmdMsg真正将消息派发到处理函数去,其做法与CWnd::OnWndMsg是很相似的。
对话框的命令要简单得多,去除其他复杂的因素,甚至可以认为CDialog::OnCmdMsg直接分派消息,也就是只有CDialog的具体子类才能处理它上面的按钮命令。
MFC的命令处理可以应付简单的应用程序,但对于复杂如Office这样的程序,则太过于简陋了。举一个简单的例子,一个对话框有非常多的界面元素,并且这些界面元素要根据数据状态实时更新,利用MFC的命令处理会比较麻烦,因为它没有对话框的实时更新机制。一种理想的方法是这样的,在消息处理Idle的时候,每个界面元素向数据查询状态,这种查询可以认为是非常快速的,根据状态实时更新自己的表现形式,比如Enabled,Visible等。这就不仅为命令引入了“执行”的行为,还引入了“更新”的行为。执行和更新是命令机制的基础,看看Office的工具条,如果没有Idle的更新机制,每个按钮的更新可以想象有多么的复杂。
这一点在VCL里面做得比较好,它设计了一套Action的框架,与界面元素最大程度的分离开来,更加集中的处理“执行”和“更新”这种两种行为。每一种界面元素,不管是菜单还是按钮,只要与一个Action挂接,那么它的状态和行为完成由Action来掌管。这样做的好处很多,我列出两个:
1. 多个界面元素可以对应一个Action,这种情况经常出现,比如菜单项与工具栏上的某个按钮,如果它们都挂上同一个Action,则它们的行为和状态都会变得一致。
2. 减少命令与界面的依赖,以后若想换一套新的菜单控件,只要这套控件支持Action机制,则可以在不影响程序行为的情况下轻松更换。
如果是MFC,如何来提供这样的命令机制呢?我想这是考验MFC的可扩展性的时候了。