在MFC(Microsoft Foundation Classes)框架中,命令消息路由是一个关键的机制,它负责将用户界面操作(如点击按钮或选择菜单项)映射到相应的处理函数。然而,标准的框架路由并不包括非活动视图,这会导致当视图被停用时,工具栏按钮和菜单项变灰,从而引起用户的困惑。本文将介绍三种简单的方法来解决这个问题,使用户的体验更加流畅。所有这些解决方案都基于重写框架类中的CCmdTarget::OnCmdMsg
函数。
在标准框架中,当视图被停用时,相关的命令消息无法正确路由,导致用户界面响应性下降。为了解决这个问题,提出了三种方法,这些方法都基于重写CCmdTarget::OnCmdMsg
函数。假设这个类是从CFrameWnd
派生的(单文档界面,SDI),但这些方法同样适用于多文档界面(MDI)子窗口。
第一种方法是利用CDocument
类中可用的视图列表,通过GetFirstViewPosition
和GetNextView
辅助函数对视图进行遍历。除了活动视图之外,这个方法还会遍历所有非活动视图,但可以根据应用程序需求对列表进行过滤。
以下是C++代码示例:
BOOL CMainFrame::OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo) {
CDocument *pDoc = GetActiveDocument();
if (pDoc) {
POSITION pos = pDoc->GetFirstViewPosition();
CView *pView = NULL;
while (pView = pDoc->GetNextView(pos)) {
if (pView != GetActiveView() && pView->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))
return TRUE;
}
}
return CFrameWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo);
}
在这个例子中,首先获取当前活动的文档,然后遍历所有视图,并对每个视图调用OnCmdMsg
函数。如果返回值为TRUE,表示消息已被视图处理,无需进一步处理。
如果不想使用任何从CDocument
派生的类对象,那么第一种方法就无法使用。但是,为了实现消息路由的目标,不需要文档,只需要访问处理消息的窗口,即分割窗口。如果有一个明确的分割对象,无论是作为指针还是作为框架类中的成员,这都是一个常见的情况。简单地使用CSplitterWnd::GetPane
就可以实现。
以下是C++代码示例:
BOOL CMainFrame::OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo) {
if (m_wndSplitter.GetSafeHwnd()) {
int rc = m_wndSplitter.GetRowCount(), cc = m_wndSplitter.GetColumnCount();
for (int r = 0; r < rc; r++) {
for (int c = 0; c < cc; c++) {
CWnd *pWnd = m_wndSplitter.GetPane(r, c);
if (pWnd != m_wndSplitter.GetActivePane() && pWnd->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))
return TRUE;
}
}
}
return CFrameWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo);
}
在这个例子中,遍历分割窗口的所有窗格,并对每个窗格调用OnCmdMsg
函数。如果返回值为TRUE,表示消息已被窗格处理。
一个有抱负的年轻开发者可能会想要一个通用的处理程序,它不依赖于成员分割器或文档的存在。在MFC源代码中,特别是在CView
和CSplitterWnd
类中,注意到它们使用标准的窗格ID(见afxres.h
)来访问框架中的非活动视图。它们既不使用文档指针,也不直接使用分割对象!现在找到了圣杯:
以下是C++代码示例:
BOOL CMainFrame::OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo) {
for (UINT id = AFX_IDW_PANE_FIRST; id <= AFX_IDW_PANE_LAST; id++) {
CWnd *pWnd = GetDescendantWindow(id, TRUE);
if (pWnd && pWnd != GetActiveView() && pWnd->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))
return TRUE;
}
return CFrameWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo);
}