Automatic Tab Bar for MDI programs

Environment VC++ V5.0; Win98 (NT should work too)

Yet another tabbed view thing? Yes and no. This one is different. No changes to the DOC, VIEW or CHILDFRAME classes are neccessary.

The TabBar is a child window of CMainFrame and can maintain the MDI child frames containing the various views.

We subclasses the MDI-CLIENT window to get the required information automatically. No matter which view classes you are using, it's tab appears automatically. The Frames title is used as the label for the tabs.

I tried to mimic the behaviour of my favorite editor "Multi-Edit V8". One major design goal was to find a "plug in" solution. Easy to use.

How to use it:

1.Add these files to your project.

MdiClient.cpp

MTabWnd.cpp

TabBar.cpp

MdiClient.h

MTabWnd.h

TabBar.h

app.h

TabCtrlEx.cpp (Written by Chris Maunder)

TabCtrlEx.h (Written by Chris Maunder)

2. Change app.h to contain an #include "YourAPP.h" .
3. Derive your CMainFrame window from CMDITabFrameWnd.
4. Call the CreateTabs function at the end of CMainFrame::OnCreate.

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
	......
	if (CMDITabFrameWnd::CreateTabs() == -1)
		return -1;

	return 0;
 }

5.With "View/Resource Symbols" add two unique ID's to resource.h.
#define IDC_MDI_TAB_CTRL_BAR ???

#define IDC_MDI_TAB_CTRL ???

6.With the resource editor add two menu entries to the windows menus.
Both are not required for the tabs, but I added this functionality to CMDITabFrameWnd .

MENUITEM "Ne&xt MDI Window", ID_WINDOW_NEXT_PANE

MENUITEM "Pre&vious MDI Window", ID_WINDOW_PREV_PANE

7.Done!
Good luck.

Unicode

I did not test to compile with Unicode, but it should work.

To Do

Adding icons to the tabs.
Could be either automatic icons from the resources or a list of icons. Any ideas?
Adding an option to hide the tabs if the MDI childs are not maximized.
I tried to switch from tabs to buttons in non-maximized mode, but failed. The style change seams to be ignored by the TabCntrl. Any ideas?
Tooltips!
In case of a file name it can show the complete path.

Acknowledgements

Ivan Zhakovs "Windows Manager" gave me some usefull ideas.

Chris Maunder wrote the CTabCntrlEx class for a better look and feel of the tabs.

Many articles here in codeguru.com.

"MFC Internals" from George Shepherd and Scot Wingo.

"The MFC Answer Book" from Eugene Kain.

Code Snippets

Below are a few snippets out of the source code. Just the highlights are shown to give you an idea how it works. Download the sources for details.

The creation of the TabBar and subclassing of the MDI-CLIENT window:


BOOL CMDITabFrameWnd::CreateTabs(void)
{
	if (!m_wndTabs.Create(this,WS_VISIBLE|WS_CHILD|CBRS_TOP|WS_EX_WINDOWEDGE, IDC_MDI_TAB_CTRL_BAR)  )
	{
		TRACE("Failed to create test status bar\n");
		return -1;      // fail to create
	}

	// The MDI Client needs to talk to the TabBar
	m_wndMdiClient.m_pWndTabs = &m_wndTabs;
	ASSERT(m_hWndMDIClient != NULL);

	// This is to get notifications for adding/relemoving tabs (automatically)
	if (!m_wndMdiClient.SubclassWindow(m_hWndMDIClient)  )
	{
		TRACE("Failed to subclass MDI client\n");
		return -1;      // fail to create
	}
	return 0;
}

This is how we get the the window handles of the MDI frames.

The CMdiClient class does subclass the MDI-CLIENT window and therefore has message handlers for creating and destroying the MDI child frames. Only the cration path is show here, because the deletion path works pretty similar.


/////////////////////////////////////////////////////////////////////////////
// CMdiClient message handlers

LRESULT CMdiClient::OnMDICreate(WPARAM wParam, LPARAM lParam)
{
	HWND hWnd = (HWND) DefWindowProc(WM_MDICREATE,  wParam, lParam);
	AddHandle(hWnd);
	return (LRESULT) hWnd;
}

/////////////////////////////////////////////////////////////////////////////
void CMdiClient::AddHandle(HWND hWnd)
{
	ASSERT(m_pWndTabs != NULL);
	m_pWndTabs->AddHandle(hWnd);
}

m_pWndTabs points to an instance of CTabBar.



/////////////////////////////////////////////////////////////////////////////
// Add a new window handle to the tab control
// Also creates a new tab
void CTabBar::AddHandle(HWND hWnd)
{
	CWnd *pFrame = FromHandle(hWnd);
	ASSERT(pFrame != NULL);
	if ( pFrame->IsKindOf(RUNTIME_CLASS(CMDIChildWnd)) )
	{
		TC_ITEM tci;
		tci.mask = TCIF_PARAM|TCIF_TEXT;
		tci.pszText = "";
		tci.lParam = (long) hWnd;
		m_tabctrl.InsertItem(m_tabctrl.GetItemCount(), &tci);
	}
}


Automatic update the tab labels:


// This is called in idle time.
// Here we update the tab labels and selection
void CTabBar::SetTitles(void)
{
	CString csName;
	TC_ITEM tci;
	char buf[MAX_PATH+1];
	CMDIFrameWnd *pMainframe = ((CMDIFrameWnd *)AfxGetMainWnd());
	if (pMainframe == NULL)
		return;	// no official mainframe window yet

	CMDIChildWnd* pActive = pMainframe->MDIGetActive( NULL );

	// update all tab labels if neccessary
	int numitems = m_tabctrl.GetItemCount();
	for (int item = 0; item < numitems; item++)
	{
		csName.Empty();
		tci.mask = TCIF_PARAM|TCIF_TEXT;
		tci.pszText = buf;
		tci.cchTextMax = MAX_PATH;
		if (!m_tabctrl.GetItem(item, &tci))
			continue;	// skip bad ones (never saw one yet)

		CWnd *pFrame = FromHandle((HWND) tci.lParam);
		ASSERT(pFrame != NULL);
		if ( pFrame->IsKindOf(RUNTIME_CLASS(CMDIChildWnd)) )
		{
			pFrame->GetWindowText(csName);
			if (buf != csName)	// avoid flicker
			{
				tci.mask = TCIF_TEXT;
				tci.pszText = csName.LockBuffer();
				m_tabctrl.SetItem(item, &tci);
				csName.UnlockBuffer();
			}
			if (pActive == pFrame)	// mark the active one
				m_tabctrl.SetCurSel(item);
		}
	}
}

Select MDI child frame:


// The user clicked onto a tab.
// Now select a new MDI child frame
void CTabBar::OnSelchange(NMHDR* pNMHDR, LRESULT* pResult)
{
	int idx = m_tabctrl.GetCurSel();

	TC_ITEM tci;
	tci.mask = TCIF_PARAM;
	if (m_tabctrl.GetItem(idx, &tci))
	{
		CWnd *pFrame = FromHandle((HWND) tci.lParam);
		ASSERT(pFrame != NULL);
		ASSERT_KINDOF(CMDIChildWnd, pFrame);
		((CMDIFrameWnd *)AfxGetMainWnd())->MDIActivate(pFrame);
	}
	*pResult = 0;
}

Files of interrest:

Source files:

MdiClient.cpp Subclasses the MDI-CLIENT

MTabWnd.cpp Derive your mainframe from this class

TabBar.cpp The TabBar with auto update of the tab labels

Header Files:

MdiClient.h

MTabWnd.h

TabBar.h

app.h redirects to Mditab.h

Other contributions:

The following files contain a class to improve the look and feel of the tabs. Downloaded from codegure.com and used unchanged in this demo. Written by Chris Maunder. See "Implementing an owner drawn Tab Control" in codeguru.com. The classes above also work without using this nice pice of code.

TabCtrlEx.cpp

TabCtrlEx.h

Generated by AppWizard and changed:

mainfrm.cpp

mainfrm.h

resource.h Updated by the resource editor: Two entries for unique ID's, two menu ID's. App, View and doc classes need no change at all! The files TabCtrlEx.cpp and EditView.cpp are generating compiler warning with level 4. The warnings seam ok to me and I did not change the code on purpose. Both files are not writen by myself and I did not want to change them just because of this, I only use them to demonstrate my code. EditView.cpp is even generated by AppWizard!

Download demo project - 45 KB

Download source - 11 KB

Date Last Updated: May 14, 1999