Automatic Tab Bar for MDI programs 2

Environment: VC6 SP2, VC5(Should work), AlphaNT4-J SP4, Win95/98(Should work)

1. Screenshot of main window

MDI Tab View-Main window

2. Introduction

This code implements MDI tab view for easy navigation. It is similar in functionality to the code introduced in the article, Automatic Tab Bar for MDI programs. However, this code manages the views instead of the view frame, and is based on the articles by Iuri Apollonio's ( MDI list in the status bar (and a custom Window List dialog)) and Ivan Zhakov ( MDI Windows Manager dialog).

Managing the views does not provide any special advantage per se, but with a little modification, the same code can be adapted to SDI applications to provide MDI functionality. That will be coming soon.

3. Implemented Features

  1. Control bar-based owner-drawn tab view. Supports fonts and color settings, the default is as follows
    • The normal/inactive tab text is painted black,
    • The active tab text is painted blue,
    • The tab text of a modified document is painted red.
  2. The control bar can be docked (currently only top and bottom) or floated.
  3. Custom MDI window list dialogs, similar to VC++.
  4. Full screen modes.
  5. Cool menu popups, three types.
    • From a right click on the tab control itself, as shown above.
    • From the right click on the tab control bar.
    • From a right click in the MDI main window client area, just a place holder (demo) build yours.
  6. Display of company logo text in the MDI main window client area.
  7. MDI client background painting (I managed to overcome the temptation to draw images, let me know if you need it!)
  8. Saves and restores the state of the MDI child window, i.e. normal or maximize, and the position of the control bars and the main window frame.
  9. Tooltips and tab icons (icons!...just a demo-do not know what will be better, let me know your views).
  10. Minimum modifications to existing projects, say automated! Few changes to only existing main frame class and maybe application class, no base class to derive from.

4. Unicode?

There is no reason why it should not work!

5. Files required:

  1. WindowManager.cpp/h: Manages the window list, and subclass the MDI client to create the tab view bar. It also contain the class CDocumentList, which lists all open documents. This is really where life begins...
  2. ViewManager.cpp/h: Manages the views of the application, creating the view tabs.
  3. WindowTabCtrl.cpp/h: Codes implementing the tab control.
  4. PopupMenu.cpp/h: Codes implementing the popup menu.

    Others

  5. tabview.rc: The full screen toolbar, popup menus and window list dialog resource.
  6. tabview.h: Contains the resource ids needed by the controls. (NOTE: Not used directly, deleted later).
  7. tabview.bmp: The full screen and popup menus bitmap.

6. How to use it?

There is a bit of work involved in integrating the resource file into your project. Lets do it, the difficult part first...
  1. Move the bitmap file, tabview.bmp to your res directory, and tabview.rc to the project directory.
  2. Add the resource file tabview.rc to the project's *.rc2 file, and merge the resource id file tabview.h with your resource file, resource.h. If you have not being manually modifying resource files then read this...
    • Identify the last resource type (numbering starts from 128) in your resource.h file, copy and paste the resource type identifiers (2) from the tabview.h, give each an incremental id and finally increment the VC++ object _APS_NEXT_RESOURCE_VALUE by 2, i.e. 1 more than the last incremental id.
    • Similarly, copy and paste the dialog control ids (8) (numbering starts from 1000) and increment the _APS_NEXT_CONTROL_VALUE by 8.
    • Finally, copy and paste the menu items ids (9) (numbering starts from 32771) and increment the _APS_NEXT_COMMAND_VALUE by 9. The project should now compile without any problem, and you can now delete the tabview.h file.
  3. Now, move the files WindowManager.cpp/h, ViewManager.cpp/h, WindowTabCtrl.cpp/h and PopupMenu.cpp/h to the project directory and add the implementation files to the project.
  4. Open the WindowManager.h file, and in the Forward Declaration part change the CMainFrame to your main window frame class name (see TODO). Also include the header file of your main frame class in the WindowManager.cpp.
  5. Include header files, WindowManager.h and ViewManager.h in your main frame header file and declare in a public section the ff:
    
    	CViewManager m_wndViewManager;
    	CMDIClient   m_MDIClient;
    	friend void CMDIClient::OnViewFullscreen();
    
    
    • m_wndViewManager, is an instance of the view manager to provide you access to the views.
    • m_MDIClient, is an instance of the window manager, where all events are coordinated.
    • CMDIClient::OnViewFullscreen(), as a friend of the main frame gives it access to the toolbar, status bar etc., for the hiding and showing processes in full screen mode.
  6. Towards the end of the OnCreate() function of the main frame add the ff:
    
    	VERIFY(m_MDIClient.SubclassMDIClient(this, &m_wndViewManager));
    
    
    The SubclassMDIClient() is prototyped as
    
        BOOL SubclassMDIClient(CMDIFrameWnd* pMDIFrameWnd, 
    		CViewManager* pViewManager, UINT uID = ID_VIEW_VIEWTAB);
    
    
    • pMDIFrameWnd, is a pointer to the application's main frame.
    • pViewManager, a pointer to an instance of the viewmanager. The viewmanager, control bar and tab control, is created by the window manager.
    • uID, an id of the control bar. With the defaulted value, hiding and showing of the control bar is already implemented. If, however, you decide to use a different id-say ID_THE_NEWVALUE, you can easily implement the hiding and showing of the tab control bar by inserting the following in your main frame message map. No further code is required. This is what is done to the default status bar and toolbar created by the AppWizard...
      
      	ON_COMMAND_EX(ID_THE_NEWVALUE, OnBarCheck)
      	ON_UPDATE_COMMAND_UI(ID_THE_NEWVALUE, OnUpdateControlBarMenu)
      
      
  7. In order for command messages to be handled directly in the CWindowManager, use the ClassWizard to override the OnCmdMsg() method of the main frame and modify it to be similar to the ff:
    
    // 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);
    }
    
    
  8. Tired eh! Just compile and have fun...
  9. Well, you will need this too...build the menus, all the menu ids are already defined so just select them from the ID combo box of the property sheet.
    • On the View menu build the ff:

      MENU ITEM ID MENU ITEM STRING
      ID_VIEW_VIEWTAB Op&en File Tabs
      ID_VIEW_FULLSCREEN F&ull Screen

    • On the Window menu build the ff:

      ID_WINDOW_NEXT Ne&xt Window
      ID_WINDOW_PREVIOUS Pre&vious Window
      ID_WINDOW_CLOSE_ALL C&lose All
      ID_WINDOW_SAVE_ALL &Save All

    • Note: The Windows... menu is build for you automatically.
  10. Well, well, well...if you need to support the position and control bar restoration then do this...(unfortunately, neither the main frame class destructor nor the window manager class destructor is called by the MFC framework!)
    • Add message handle for the WM_CLOSE message for your main frame, or modify the existing one adding the following single line, calling the SaveMainFrameState() method...
      
      void CMainFrame::OnClose() 
      {
                     ......
      	m_MDIClient.SaveMainFrameState();
                     ......	
      	CMDIFrameWnd::OnClose();
      }
      
      
    • Finally! replace the main frame displaying code at the end of the InitInstance() of application class with the RestoreMainFrameState() as
      
      BOOL CDemoApp::InitInstance()
      {
          .............................
      	// The main window has been initialized, so show and update it.
      //	pMainFrame->ShowWindow(m_nCmdShow); ///// <--- we do not need this one!
      	pMainFrame->m_MDIClient.RestoreMainFrameState(m_nCmdShow);
      	pMainFrame->UpdateWindow();
      
      	return TRUE;
      }
      
      
      

7. Code Snippets

The icon support in the tab is application specific. What I mean is you will need to build a more suitable solution for your application. If, however, you have some ideas as how to implement something general let me know.

For the current implementation...
The OnCreate() function of the CViewManager, which created both the tab and itself (the tab bar), simply creates a place holder icon to fill an image list, which is then attached to the tab.


int CViewManager::OnCreate(LPCREATESTRUCT lpCreateStruct) 
{
	if (CControlBar::OnCreate(lpCreateStruct) == -1)
		return -1;

    m_ViewTabImages.Create(16, 16, ILC_MASK, 5, 5);	

	m_ViewTabCtrl.Create(WS_CHILD | WS_VISIBLE | WS_EX_NOPARENTNOTIFY | 
		TCS_TOOLTIPS | TCS_SINGLELINE | TCS_FOCUSNEVER | TCS_FORCELABELLEFT, 
		CRect(0, 0, 0, 0), this, ID_VIEWTAB);

	m_ViewTabCtrl.SetImageList(&m_ViewTabImages);
	// Build the image list here
    HICON hIcon = AfxGetApp()->LoadStandardIcon(IDI_APPLICATION);
//    HICON hIcon = AfxGetApp()->LoadIcon(IDR_DEMOTYPE);
	m_ViewTabImages.Add(hIcon);
	// Enable tooltips for all controls
	EnableToolTips(TRUE);
	
	return 0;
}

LoadStandardIcon() is used to load system icon in there. You may wish to replace this with the commented code, IDR_DEMOTYPE is your application specific resource type.

    HICON hIcon = AfxGetApp()->LoadIcon(IDR_DEMOTYPE);

In the AddView() function of the same class, the tab image index is set to 0 (zero) the only image in the image list. Finally, in the DrawItem() function of the tab, CWindowTabCtrl, the dummy icon is replaced by the small icon attached to the frame of your child window, parent of the view.

void CWindowTabCtrl::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct) 
{    
....
	
	CView* pView    = reinterpret_cast(tci.lParam);
	CDocument* pDoc = pView->GetDocument();

	// Draw image
	if (m_bDisplayIcons)
	{
		CImageList* pImageList = GetImageList();
		CMDIChildWnd* pViewFrame = static_cast(pView->GetParent());
        HICON hIcon = reinterpret_cast(GetClassLong(pViewFrame->m_hWnd, GCL_HICONSM));
		pImageList->Replace(nTabIndex, hIcon);
          ....
	}
....	
}

Initially, I considered getting the icon from the Windows shell, based on the registered file extension--SHGetFileInfo() API. However, it does not look nice for the view frame icon to be different from the tab view icon. By the current implementation, all is needed is a good citizenship like the VC++ itself. Let your application child window system icons reflect the file type and there will be no need to write extra codes.

Finally, note that the full screen view implementation assumes that the toolbar and status bar are defined as generated by the AppWizard...


protected:  // control bar embedded members
	CStatusBar  m_wndStatusBar;
	CToolBar    m_wndToolBar;

If your project uses different names, you will need to make the necessary changes. Have fun.

8. Credits

The code is based on codes and ideas shared by the following:
  • Iuri Apollonio, he wrote the initial codes for this project. What is his new email address?
  • Ivan Zhakov, his "MDI Windows Manager dialog" is better than Iuri's. The Window manager class is now the main engine driving the view manager.
  • Chris Maunder, his owner-drawn tab control code snippets are used to improve Iuri's.
  • Adolf Szabo, his Full-screen mode idea is simpler than that implemented by MS and MS's Mike B.
  • YOU, and many others...
Use this code in any project, there is no restriction! Coding is my hobby and will be more than happy if this hobby stuff benefits someone. Write whatever you like or do not like about this code in the comment section, I will take note of all.
Happy coding...
Paul Selormey, Japan.

9. To Do

  1. Ability to dock on all sides of the main frame, involves more work since the tab control is owner-drawn.
  2. Option dialog to choose/set fonts, tab styles (tabs or buttons-normal or flat), colors, docking sides etc. Most of these can easily be added now but the pain of integrating more resources into your project prevented me. This, if requested, and other requests will be considered in the next release.
  3. Improved main frame window position saving and restoration...
    • Support for multiple monitors-I do not have the OS (Win98/Win2000) to test this now.
    • Support for screen resolution changes. I have API code for my C/SDK application, well tested on Win95 using the QuickRes program. I do not use QuickRes currently, so a bit reluctant to implement it.
  4. Your wishes...

10. Known Issues

  1. Currently, when the docking side is changed, the tab is not properly drawn. You may have to manually assist by resizing the window!
  2. The popup menu does not currently support accelerators.
  3. The modified flag is not re-drawn immediately, I do not wish to play any game to introduce flickers!
  4. Add yours...

Downloads

NOTE: To compile the demo on Intel platform, set the active configuration to either Win32 (x86) Debug or Release.
Download demo project - 67 Kb
Download source - 28 Kb


Comments

  • Bugfix: Can now use CSplitterWnd views

    Posted by Legacy on 10/10/2002 12:00am

    Originally posted by: Tomer Shalev

    Add this function to CViewManager.cpp:
    
    CDocument *GetViewDocument(CView *pView)
    {
    ASSERT(pView);
    while (pView && pView->IsKindOf(RUNTIME_CLASS(CSplitterWnd)))
    {
    CSplitterWnd *pSplitter = (CSplitterWnd *)pView;
    ASSERT(pSplitter);
    //pView = (CView *)pSplitter->GetPane(0, 0);
    pView = (CView *)pSplitter->GetActivePane();

    }
    if (!pView)
    {
    return NULL;
    }
    return pView->GetDocument();
    }

    And the declaration to ViewManager.h OUTSIDE the class declaration:

    CDocument * GetViewDocument(CView *pView);


    Now, whenever you have used
    CDocument* pDoc = pView->GetDocument();

    You should now use:
    CDocument* pDoc = GetViewDocument(pView);

    Also, in CWindowTabCtrl::DrawItem(...) after the line:
    CDocument* pDoc = GetViewDocument(pView);
    add:
    ASSERT(pDoc);

    Hope it helps.


    Reply
  • How to add splitter functionality

    Posted by Legacy on 03/19/2002 12:00am

    Originally posted by: Manfred Peter

    Hello,

    since we use this good code in our project AlligatorSQL
    http://www.alligatorsql.com we have problems with frames
    containing splitters.
    For that frames the tab is always deactivated !!!

    Here is a little workarround to provide splitter functionality. It�s not much elegant - but anyway - it works :)

    void CViewManager::OnUpdateCmdUI(CFrameWnd* pTarget, BOOL bDisableIfNoHndler)
    {
    .
    .
    .
    // ...find the active view from the view list
    if (pActiveView == static_cast<CView *>(m_arViews.GetAt(t)))
    iSel = t;
    }

    *****************************
    HERE BEGINS THE NEW CODE !!!!
    *****************************
    // if nothing is selected and we have stored views, than it could be
    // that one of the views has splitter in it
    if(iSel == -1 && m_arViewTitles.GetSize() > 0) {
    CView* pNextView = (CView *)pChild->GetDlgItem(AFX_IDW_PANE_FIRST);

    for (int t = 0; t < m_arViewTitles.GetSize(); t++)
    {
    CView* pViewAt = static_cast<CView *>(m_arViews.GetAt(t));
    // ...find the active view from the view list
    if (pNextView == static_cast<CView *>(m_arViews.GetAt(t)))
    iSel = t;
    }

    }
    ***************
    END OF NEW CODE
    ***************

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

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

    Hope this helps ?

    Greetz
    Manfred Peter
    (Alligator Company)
    http://www.alligatorsql.com

    Reply
  • how to "Add the resource file tabview.rc to the project's *.rc2 file"

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

    Originally posted by: benben

    how to "Add the resource file tabview.rc to the project's *.rc2 file"

    Reply
  • Good work, does not work with CSrollView

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

    Originally posted by: Albert

    Good example and well documented.
    
    

    But when i try with CSrollView, it crash.
    How do i get it work with CSrollView.

    Reply
  • Multiline Tabcontrol - what can I do ?

    Posted by Legacy on 07/02/2001 12:00am

    Originally posted by: Manfred

    Hello everybody ...

    first this code is very useful for us ... thank you
    and all the other authors very much - but -
    for our programm AlligatorSql we want to display the
    Tabs as a Multiline tab control ... the change of the
    style into TCS_MULTILINE do this - but the toolbar size
    is not shown correctly so that we can see only the first
    row of the tab control ...

    Can anybody help us ...

    Thx in advanced for helping

    Greetings

    Manfred Peter
    www.alligatorsql.com

    Reply
  • too slowly!

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

    Originally posted by: shilei

    As everybody knows,the M$ gdi functions
    draw slowly,and you use FillRect bm.bmHeight*bm.bmWidth
    times.Mr Lachand-Robert's got the region,why not use
    SelectClipRgn? After call SelectClipRgn,what you draw
    will be restricted in the region.

    Reply
  • how to make the tab bar undocked?

    Posted by Legacy on 05/26/2001 12:00am

    Originally posted by: ls

    as title

    Reply
  • Fix for possible transparent background

    Posted by Legacy on 10/09/1999 12:00am

    Originally posted by: Jeremy Iverson

    In WindowManager.cpp, CMDIClient overrides OnEraseBkgnd(),
    which is responsible for painting the background of the mainframe window. The background will not be painted,
    however, if m_crBkColor == 0 (a valid RGB value) because of
    an if statement. m_crBkColor is initialized as the same
    color as the Windows desktop color, so if somebody's
    desktop color is black (RGB(0,0,0)), the background of
    their mainframe window will be transparent.

    Here is the relevent code. I've removed the if statement
    from my code; is there a reason for it to be there, or was
    it just an "accidental" test to see if the COLORREF was
    initialized?

    // Paint the background color
    if (m_crBkColor != 0) // remove if statement...
    {
    CBrush NewBrush(m_crBkColor);
    pDC->SetBrushOrg(0, 0);

    CBrush* pOldBrush = static_cast<CBrush*>(pDC->SelectObject(&NewBrush));
    pDC->PatBlt(rect.left, rect.top, rect.Width(), rect.Height(), PATCOPY);
    pDC->SelectObject(pOldBrush);
    NewBrush.DeleteObject();
    }

    BTW, this is a great class. I only had to modify a few things to get it working with our app, and it does it's job wonderfully. Thank you very much!

    Jeremy Iverson

    Reply
  • Try This Too

    Posted by Legacy on 08/30/1999 12:00am

    Originally posted by: Mark

    hello,

    Start the demo.
    Dock the toolbar at the bottom of the main frame.
    Dock the toolbar on the same row as the toolbar.
    Finally dock it in its own row under the normal
    toolbar.

    Sometimes if i do this part of the tab overdraws
    the upper tool bar.

    Now undock the tab bar. the miniframe wnd is smaller
    than the tab bar, it is also cutting off the tab-lets.

    Mark
    nt4 sp5
    vc6 sp3

    Reply
  • Bug 1: Child caption and Full screen mode--Fixed...

    Posted by Legacy on 08/04/1999 12:00am

    Originally posted by: Paul Selormey

    Hello,
    
    The bug reported by Igor is caused by this...
    1. when going full screen, I remove the caption of ONLY the
    active view...an oversight!
    2. on returning from the full screen, I again ONLY restore the caption of the active view, which may not be
    the previous active view before full screen, anyway.

    After some further tests, I came to the conclusion that the
    best fix is to forget about removing the child window caption and retoring it.
    1. In the void CMDIClient::FullScreenOn() handler remove the
    lines

    // 4. Remove the caption of the child window and display it
    dwStyle = ::GetWindowLong(pChild->m_hWnd, GWL_STYLE);
    dwStyle &= ~WS_CAPTION;
    ::SetWindowLong(pChild->m_hWnd, GWL_STYLE, dwStyle);

    2. In the void CMDIClient::FullScreenOff() handler remove the lines

    dwStyle = ::GetWindowLong(pChild->m_hWnd, GWL_STYLE);
    dwStyle |= WS_CAPTION;
    ::SetWindowLong(pChild->m_hWnd, GWL_STYLE, dwStyle);

    This should fix it.

    Anyone else found another bug? please let me know.

    Thank you.
    Paul.

    Reply
  • Loading, Please Wait ...

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

Top White Papers and Webcasts

  • Protecting business operations means shifting the priorities around availability from disaster recovery to business continuity. Enterprises are shifting their focus from recovery from a disaster to preventing the disaster in the first place. With this change in mindset, disaster recovery is no longer the first line of defense; the organizations with a smarter business continuity practice are less impacted when disasters strike. This SmartSelect will provide insight to help guide your enterprise toward better …

  • Event Date: April 15, 2014 The ability to effectively set sales goals, assign quotas and territories, bring new people on board and quickly make adjustments to the sales force is often crucial to success--and to the field experience! But for sales operations leaders, managing the administrative processes, systems, data and various departments to get it all right can often be difficult, inefficient and manually intensive. Register for this webinar and learn how you can: Align sales goals, quotas and …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds