Saturday, May 01, 2004


How to implement a TabView

Here, we use TabView to refer the special UI that is used in Visual Studio and etc. More specifically, all the views are shown in a tab ctrl that one the top or the bottom of the main frame window.

The most easy way to implement such a TabView is proposed as

By Christian Rodemeyer

The main idea of this article is

"Instead of monitoring all possible view changing events (close, open, new, changing titles/icons) I hooked into the CMainFrame::OnUpdateFrameTitle() function. I discovered, that this function gets called when something view-related happens. Here you have to call CMDITabs::Update() which in response completely rebuilds its tab list. It does so by querying the child windows of the MDIClient window, circumventing the complex doc/view organization of MFC! The simpler the solution the robuster it works!"

However, this method does not work in many cases especially when we open a file whose file type has not been regist in the Doc/View Framework. Nevertheless, as mentioned by adrian_conlon in the following comments

"use CMDItabs in AutoCAD and noticed a problem when toolbars were being positioned in AutoCAD 2004 and 2005. While the toolbar was being moved around, the tabs "jumped" to the top of the frame and hid underneath the other toolbars. Resizing the frame made the tabs jump back into their correct position.

Delving into the MFC source code showed the problem and the solution. The problem lies with the AFX_SIZEPARENTPARAMS structure. Its usage is a bit more complex than I thought. Basically, the "hDWP" member needs to be checked as to whether a query or move operation is being requested. If hDWP is NULL, then a query operation is active, and everything except moving the windows is required (i.e. the rect member should still be filled in). If the HDWP is not NULL, then the move should be carried out as well.

As an aside, the window positioning should be carried out using DeferWindowPos rather than MoveWindow which I believe allows the operation to be optimised."

A much better method is developed in the series below

MDI Windows Manager Dialog
By Ivan Zhakov

Automatic Tab Bar for MDI Frameworks
By Paul Selormey

A multi document tabbed interface
By Dundas Software

This approach first overrides OnCmdMsg as

// This function routes commands to window manager, then to rest of system.
BOOL CMainFrame::OnCmdMsg(UINT nID, int nCode,
void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo)
// Without this, the window manager menu commands will be disabled,
// this is because without routing the command to the window manager,
// MFC thinks there is no handler for it.

if (m_MDIClient.OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))
return TRUE;

return CMDIFrameWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo);

Then, it subclass CMDICLIENT to peek the corresponding MDI messages

// try to sublass MDIClient window
return FALSE;

// save the pointer to parent MDIFrame


// unsubclass MDIClient window

Then, we override

afx_msg LRESULT OnMDICreate(WPARAM wParam, LPARAM lParam);
afx_msg LRESULT OnMDIDestroy(WPARAM wParam, LPARAM lParam);

to add and remove views in Tab.

Moreover, we can derive a class CViewManager from CCBaseControlBar, since MFC MDI Framework will automatically send message to CControlBar for notification. What we need is to override OnUpdateCmdUI to redraw the Tab

void CViewManager::OnUpdateCmdUI(CFrameWnd* pTarget, BOOL bDisableIfNoHndler)
CMDIFrameWnd* pFrame = static_cast(AfxGetApp()->m_pMainWnd);
if (pFrame == NULL)
return; // this is not meant for us...

// Get the active MDI child window.
CMDIChildWnd* pChild = static_cast(pFrame->GetActiveFrame());
// Get the active view attached to the active MDI child window.
CView* pActiveView = reinterpret_cast(pChild->GetActiveView());
if (pActiveView == NULL) //...Is there a view anyway?
//...since there is no view hide the tab control, otherwise it looks...guess!
//...we might have hidden the tab control, show it now
if (!m_ViewTabCtrl.IsWindowVisible())

// Now, we go...
int iSel = -1;
if (pChild->IsKindOf(RUNTIME_CLASS(CMDIChildWnd)))
CString strWin;
// scan the TabCtrl Tabs
for (int nTabs = 0; nTabs < m_ViewTabCtrl.GetItemCount() ; ++nTabs)

CMDIChildWnd* pViewAt = GetTabInfo(nTabs, szLabel);

// Note that we don't use GetDocument()->GetTitle() here as it doesn't include
// the :n suffix when a Doc is open in multiple views. eg. test.cpp:1

// TRACE( "OnUpdateCmdUI() strWin = '%s', tci.pszText = '%s'\n", strWin, tci.pszText );

// ...if there is window title change since the last update set the new title
if (szLabel != strWin)
SetViewName(strWin, pViewAt);

// ...find the active view from the view list
if (pViewAt == pChild)
iSel = nTabs;

m_ViewTabCtrl.SetCurSel(iSel); // set the tab for the active view

// Be polite! update the dialog controls added to the CControlBar
UpdateDialogControls(pTarget, bDisableIfNoHndler);

The view title shown in the Tab will not be added when dealing with OnMDICreate message, since the Doc/View Framework does not determine it yet. It should be handled after OnMDICreate and in TabView redrawing process.

Furthermore, to activate the related view, we can just use


and change the status of the TabView when we handle the WM_MDIACTIVATE message. The process order is really important, otherwise we may get weird results.

We can find that XT and BCG implement a TabView in a similar way. However, they also implement some other functions we do not discuss here, i.e. Saving and Loading Muliple Views,

Finally, thanks a lot for machmachmachmach for kind encouragement.

Comments: Post a Comment

<< Home

This page is powered by Blogger. Isn't yours?