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



Comments

  • why the screen always flashes when we switch the Tab?

    Posted by Legacy on 11/29/2001 12:00am

    Originally posted by: pear Zhang

    why the screen always flashes when we switch the Tab?

    Reply
  • How to add a tab item manually?

    Posted by Legacy on 06/12/2001 12:00am

    Originally posted by: Martin Unsin

    How can I add a tab item manually?
    
    

    I tried the following:

    void CMDITabFrameWnd::OnFensterNeu()
    {
    MDICREATESTRUCT mdic;
    char docClass[] = "MDICHILD";

    mdic.szClass = docClass;
    mdic.szTitle = "Untitled";
    mdic.hOwner = this;
    mdic.x = mdic.y = mdic.cx = mdic.cy = CW_USEDEFAULT;
    mdic.style = 0;

    ::SendMessage(m_hWndMDIClient, WM_MDICREATE, 0,(LPARAM)&mdic);


    }

    but it doesn't work!

    Can anyone help me?


    Thanks, Martin


    Reply
  • Demo Download not working properly

    Posted by Legacy on 04/19/2001 12:00am

    Originally posted by: Praveen Rao

    We downloaded ur demo , but it's not working properly.
    Can u please tell us clearly , how we can do these tabbed views.

    Reply
  • The case of the disappearing tabs...

    Posted by Legacy on 04/02/2000 12:00am

    Originally posted by: David Bates

    Hi,
    VC6 SP3, Windows NT SP5

    Seems that if you create a lot of tabs quickly in normal sized windows, then maximise a child, then start closing them suddenly they seem to disappear. You need to randomly click the tab bar to find your tabs as the background tabs fail to be redrawn.

    This only seems to happen if you make enough tabs to make the scroll arrows appear on the top right hand corner and if you make them all in normal_size then maximise the last one before starting to close them.

    Might seem trivial however it is not so trivial if your application creates a lot of files at once eg. 20 charts within around 3-4 secs. A user then maximises the window to view the charts. Closing them produces the disappearing tabs...

    Cheers

    David Bates

    Reply
  • Thanks and Integration with Kirk Stowell's DevStudio-like sample

    Posted by Legacy on 07/21/1999 12:00am

    Originally posted by: Scott Smith

    Thanks for such a useful and easy to use enhancement. I will take a look at the print preview problem if I get a chance.

    To use this class with Kirk Stowell's DevStudio-like sample, just edit the MTabWnd.h and MTabWnd.cpp files so that CMDITabFrameWnd is derived from Kirk's CCJMDIFrameWnd class instead of CMDIFrameWnd. Works great.

    Reply
  • Toggling the tab bar on and off

    Posted by Legacy on 06/02/1999 12:00am

    Originally posted by: Rick York

    Thanks for a useful code contribution.
    
    I successfully added the tabs to the enhanced
    CrystalEdit app I have been working on lately.
    One handy capability I did not see described anywhere
    is to toggle the tab bar on and off. I was able to
    figure out how by examining the MFC source code and
    it's actually quite easy. Here's how :

    In resource.h add this definition :

    #define ID_VIEW_TABBAR 32905

    The value doesn't matter much as long as it is unique.

    In the resource script, add the following menu item
    for the IDR_MDITABTYPE and IDR_MAINFRAME menus and
    under the View popup :

    MENUITEM "&Tab Bar", ID_VIEW_TABBAR

    Also, add the following entry to the string table

    ID_VIEW_TABBAR "Show or hide the tab bar\nToggle TabBar"

    In MainFrm.h add these member prototypes to CMainFrame :

    afx_msg BOOL OnTabBarToggle();
    afx_msg void OnUpdateTabBarMenu( CCmdUI* pCmdUI );

    Lastly, in MainFrm.cpp :

    in the CMainFrame message map add these two -

    ON_UPDATE_COMMAND_UI(ID_VIEW_TABBAR, OnUpdateTabBarMenu)
    ON_COMMAND(ID_VIEW_TABBAR, OnTabBarToggle)

    and these two member functions -

    void CMainFrame::OnUpdateTabBarMenu(CCmdUI* pCmdUI)
    {
    CTabBar* pBar = &m_wndTabs;
    pCmdUI->SetCheck((pBar->GetStyle() & WS_VISIBLE) != 0);
    }


    BOOL CMainFrame::OnTabBarToggle()
    {
    CTabBar* pBar = &m_wndTabs;
    ShowControlBar( pBar,
    (pBar->GetStyle() & WS_VISIBLE) == 0,
    FALSE);
    return TRUE;
    }

    This will let you toggle the tab bar just like you would
    the status bar or tool bar. It would be a fairly simple
    matter to extend this so that the state of the tab bar is
    saved to and restored from and ini file or the registry.


    Reply
  • Try this -

    Posted by Legacy on 05/19/1999 12:00am

    Originally posted by: Albert Gao

    1) Use File->new 2 or 3 times to create several child frame windows.
    2) Maximize the child frame window
    3) File->Print Preview
    4) Switch tabs
    5) Close preview window

    Can you find something strange at the corner of the window?

    Albert

    • RE: RE: Try this - (Print Preview Problem - FIXED)

      Posted by kaosd on 05/22/2007 06:35am

      Oops... my apologies for the poor formatting on the previous post... it was my first post here :o) Cheers, kaosd

      Reply
    • RE: Try this - (Print Preview Problem - FIXED)

      Posted by kaosd on 05/22/2007 06:31am

      Hi... I know it's kinda late but this is an excellent article from Dieter and I'm sure people are still stumbling over it and putting it to good use!
      To fix the corrupted system menu in the Print Preview, I changed the behavior so that when tabs are switched, Print Preview is closed and the user is taken back to the tab s/he clicked on. Here's the code:
      //In TabBar.cpp: void CTabBar::OnSelchange()
      {
      //...
      ASSERT_KINDOF(CMDIChildWnd, pFrame);
      //---BEGIN---
      CView *pView = ((CFrameWnd*)AfxGetMainWnd())->GetActiveView();
      
      if (pView  && pView->IsKindOf(RUNTIME_CLASS(CPreviewView)))
       SendMessage(WM_COMMAND, AFX_ID_PREVIEW_CLOSE);
      //---END---
      ((CMDIFrameWnd *)AfxGetMainWnd())->MDIActivate(pFrame);
      //...
      }
      
      HTH!
      Cheers,
      kaosd

      Reply
    Reply
  • Fix for ASSERT in VC6

    Posted by Legacy on 05/17/1999 12:00am

    Originally posted by: Dieter Fauth

    Hi Everybody,
    
    the code snippet below does fix the ASSERT in VC6.
    This ASSERT is not there in VC5.
    Because I do not have VC6 I added a derived ASSERT_VALID
    into CTabBar and added the ASSERT:

    #ifdef _DEBUG
    void CTabBar::AssertValid() const
    {
    // Mimic the behaviour of MSVC6
    ASSERT((m_dwStyle & CBRS_ALL) == m_dwStyle);
    CTabBar_parent::AssertValid();
    }
    #endif

    I do not fully understand why the styles WS_CHILD etc. do cause this
    problem (ASSERT). Anybody out there with a detailed knowledge?
    The fixed create:

    BOOL CTabBar::Create(CWnd* pParentWnd, DWORD dwStyle, UINT nID)
    {
    ASSERT_VALID(pParentWnd); // must have a parent

    // save the style (some of these style bits are MFC specific)
    m_dwStyle = (UINT)dwStyle & CBRS_ALL;

    // translate MFC style bits to windows style bits
    dwStyle &= ~CBRS_ALL;
    dwStyle |= CCS_NOPARENTALIGN|CCS_NOMOVEY|CCS_NODIVIDER|CCS_NORESIZE;

    // initialize common controls
    VERIFY(AfxDeferRegisterClass(AFX_WNDCOMMCTLS_REG));

    // create the HWND
    CRect rect; rect.SetRectEmpty();
    return CWnd::Create(STATUSCLASSNAME, NULL, dwStyle, rect, pParentWnd, nID);
    }

    Reply
  • Add one line --> m_dwStyle &= CBRS_ALL;

    Posted by Legacy on 05/16/1999 12:00am

    Originally posted by: Masaaki Onishi

    Hi.

    At the debug mode problem, I fixed liek this.
    BOOL CTabBar::Create(CWnd* pParentWnd, DWORD dwStyle, UINT nID)
    {
    ASSERT_VALID(pParentWnd); // must have a parent

    // create the HWND
    CRect rect;
    rect.SetRectEmpty();
    // I added these two lines.
    m_dwStyle &= CBRS_ALL;
    ASSERT((m_dwStyle & CBRS_ALL) == m_dwStyle);

    return CWnd::Create(STATUSCLASSNAME, NULL, dwStyle, rect, pParentWnd, nID);
    }

    If I don't add m_dwStyle &= CBRS_ALL, ASSERT gives me assertion failure. Otherwise, it is OK.
    I confirm both debug and release.

    After I check MSDN help about m_dwStyle problem,
    it seems to come from unused style prolem as well as
    bits - lower bits problem like Window3.1?

    If we can't use debug mode, we have much trouble to solve
    the error from execution.

    Regards.
    -Masaaki Onishi-

    Reply
  • Automatic Tab Bar for MDI Windows

    Posted by Legacy on 05/15/1999 12:00am

    Originally posted by: Roger L. Mcelfresh

    Debug Assertion Failure: Mfc\Src\barcore.cpp
    line 977
    void CControlBar::AssertValid() const
    {
    Cwnd::AssertValid();
    ...
    ASSERT((m_dwStyle & CBRS_ALL) == m_dwStyle);
    }

    Environment: VC++6.0, SP2, Windows98.
    The realesed version executes with no problems.

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • As all sorts of data becomes available for storage, analysis and retrieval - so called 'Big Data' - there are potentially huge benefits, but equally huge challenges...
  • The agile organization needs knowledge to act on, quickly and effectively. Though many organizations are clamouring for "Big Data", not nearly as many know what to do with it...
  • Cloud-based integration solutions can be confusing. Adding to the confusion are the multiple ways IT departments can deliver such integration...

Most Popular Programming Stories

More for Developers

RSS Feeds

Thanks for your registration, follow us on our social networks to keep up-to-date