Abstract
The article presents three simple methods of routing WM_COMMAND
messages through a number of views in a split frame window. This simplifies dealing with the command routing and UI updates for inactive views.
Introduction to the Problem
The standard framework route does not include inactive views, which causes toolbar buttons and menus to gray when their mother view is deactivated. Users are confused. I present three simple methods to bring their happiness back. :) All solutions base on overriding the CCmdTarget::OnCmdMsg
function in the frame class. I assumed that this class is derived directly from CFrameWnd
(SDI case), but these methods can be used with MDI child window as well.
In each case, the overridden function browses through a list of views and calls CCmdTarget::OnCmdMsg
for each of them, passing the received arguments. If TRUE is returned, we can return – the message has undoubtedly been handled by the view and no further processing is needed. Naturally, the active view is excluded from this call, because it is to be processed by the base handler – this is the default case. You may place a base function call in the beginning of the overridden function body if you expect the messages to be successfully processed mostly by an active view, a frame itself or a CWinApp
-derived object, as these three calls are made in the base CFrameWnd::OnCmdMsg
implementation. Our custom routine should then be executed only if the base implementation returns FALSE.
Classic Document/View Case
The first quite obvious method is to use a list of views that is available in the CDocument
class and accessible through the GetFirstViewPosition
/ GetNextView
helper function pair. Along with the active view, this method browses all inactive ones, but you may filter the list as you wish to suit your application’s needs.
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);
}
Splitter Window Case
What if we didn’t like to use any CDocument
-derived class object at all? This may be the case, and the first method would then be useless. Yet, to achieve the routing goal, we don’t need a document, as we only have to get access to windows that process the message, i.e. the splitter panes. This is no hassle if you have an explicit splitter object – either as a pointer, or as a member in your frame class, which I find a common case. Simply use CSplitterWnd::GetPane
and have it done! This case has also been hinted by Samuel Chow some time ago.
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); }
A Possibly Universal Case
A young eager mind would then like to have a universal handler, independent of existence of member splitters or even a document. Seeking inspiration in MFC sources, namely CView
and CSplitterWnd
classes, I noticed that they utilize standard pane IDs (see afxres.h
) to get access to inactive views in a frame. And surely they use neither a document pointer, nor a splitter object directly! Now here comes the Holy Grail:
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);
}
As with the first method, the handler doesn’t have to be called for all views, as the calls may be freely filtered. It may be even done dynamically, e.g. depending on nID
or nCode
values, but I’m afraid this would complicate the routing a bit too much. Such roundabout solutions should always be thought over twice – there are many straightforward methods of distributing the message handling among the existing command targets, and this is not the case.
There is no possibility that these snippets would solve all your trouble with MFC command message routing, but it may get you closer or simply bring you a little clue. Any comments and suggestions are warmly welcome.