Click here to Skip to main content
Click here to Skip to main content

Tabbed View Interface in an MFC Doc View Application

, 22 Jul 2010 CPOL
Rate this:
Please Sign up or sign in to vote.
Create a tabbed view interface in an MFC based Doc View application.

Introduction

This article is about creating multiple views under a single tab, which can be seen in IE-8, Google Chrome, or MS-DEV 2005 type applications with special tiling option.

The purpose of this article is to show how easily you can incorporate a tabbed interface to an existing MFC doc view project.

Background

In the changing times of Graphical User Interfaces, almost all application are opting for tabbed view interfaces. Similarly, the product that I worked on required a face change, and that is give the cool tabbed interface look to the existing MFC doc/view application. I know this type of work has been done before, but couldn't find a complete solution for multiple doc/view in the Internet when I started developing it.

Basic Concept

The basic idea behind implementing a tabbed interface is fairly easy. First, you subclass the MDIClient inside the main frame. Then create a container as a child of the MDI client, which will hold the tab control, to be shown at the top of the views. Next, you handle OnSizeParent (e.g., LRESULT HsTabContainer::OnSizeParent(WPARAM, LPARAM lParam)) of the container, and accurately calculate the layout, and place the container in the MDI client area. During the sizing of the container, resize the child tab control as well.

Another thing to remember is to notify the tab control about a new view created, or a view destroyed, or a view being set active through notification messages, or through an interface like it is done in this case. This needs to be done to update the UI state of the tab control. Also, any selection change in the tab control should result in setting focus to the view, which the user selects as the active one in the tab.

Using the Code

In order to transform your application to the tabbed view interface, the first step is to subclass the MDI client window. The following code can be placed in the OnCreate method of the CMainFrame class after all other initialization is done.

ASSERT(m_hWndMDIClient);
m_wndMDIClient.SubclassWindow(m_hWndMDIClient);

Then you create a tab bar (a CWnd derived custom class containing a custom tab control as a child inside) for the tabbed views.

CRect rc(0, 0, 0, 0);

m_wndMDIClient.GetTabBar()->Create(NULL, NULL, 
     WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN,

rc, this, ID_HSTABCONTAINER_DEFAULT); 

Next, place the tab bar to the bottom of the z order and then place the MDI client window after that.

// manipulate Z-order so, that our tabbar is above the mdi client, but below any status bar
::SetWindowPos(m_wndMDIClient.GetTabBar()->m_hWnd, HWND_BOTTOM, 
               0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE);
::SetWindowPos(m_hWndMDIClient, m_wndMDIClient.GetTabBar()->m_hWnd, 
               0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE);

The tab control inside the tab bar is also a CWnd derived control, although a normal CTabCtrl derived class could have been used with extra notification handlers.

Here, the tab control is derived from scratch just to give that flat look.

The following notification handlers of the tab control inside the tab bar needs to be called by the views whenever a view is added, deleted, or the active state changes, so that the tab bar can properly update its UI.

void NotifyNewViewAdded(CView* pNewView);
void NotifyViewDeleted(UINT uIndex);
void NotifyActiveViewChanged(CView* pActiveView, bool bRepaint = TRUE);

For example:

void CTabbedViewPrjView::OnSetFocus(CWnd* pOldWnd)
{    
    CView::OnSetFocus(pOldWnd);
    HsMDIClient* pMDIClient = ((CMainFrame*)AfxGetMainWnd())->GetMDIClient();
    pMDIClient->GetTabBar()->GetTabCtrl()->NotifyActiveViewChanged(this);
        ...
}

Similarly, you can call NotifyNewViewAdded in the OnInitialUpdate of the View, and NotifyViewDeleted in OnDestroy() of the view.

The interesting part of the drawing of the tab control is in the DrawTab method. You can see from the following code, pDC->PolyLine is used to draw the flat looking tabs:

/*----------------------------------------------------------------------
Name:       DrawTab()
Type:    Function
Purpose: Draw all the tabs

Param:   pDC           = DC that the tabs should be drawn on.
*/
void HsTabCtrl::DrawTab( CDC* pDC )
{
   UINT  uTabIndex;          // tab that are drawn
   //POINT ppoint[4];          // points that will be used to draw tab
   POINT ppoint[20];          // points that will be used to draw tab
                             
   UINT  uTabHeight;         // the tab height 
   UINT  uDistanceToNextTab; // distance between tabs
   int   iOffsetToLeft;      // distance from left side of client edge
                             
   CRect    rect;
   CPen     penBlack( PS_SOLID, 1, RGB(0,0,0) );
   CPen*    ppenOld;

   if( m_bTabModified ) UpdateTabWidth( pDC );      // update tab ?

   ppenOld = pDC->SelectObject( &penBlack ); // select a black pen

   pDC->SetBkMode( TRANSPARENT );                // just text

   GetClientRect( &rect );                      // get client rect
   //pDC->MoveTo( rect.left,  rect.top );        // move to upper left
   //pDC->LineTo( rect.right, rect.top );        // draw a line from left to right
   pDC->MoveTo( rect.left, rect.bottom - 1 );    // move to upper left
   pDC->LineTo( rect.right, rect.bottom - 1 );   // draw a line from left to right

   uTabHeight         = m_uHeight - 1;
   uDistanceToNextTab = uTabHeight / 2;
   iOffsetToLeft      = m_iOffsetToLeft;

   for( uTabIndex = 0; uTabIndex < (UINT)m_dwarrayTabWidth.GetSize(); uTabIndex++ )
   {
      // ***
      // set all points for tab, then we will be able to draw it
      // ***
      int num_of_points = 6;
      
      ppoint[0].x = iOffsetToLeft;                         // ""
      ppoint[0].y = 0;
      int init_x = iOffsetToLeft;
      int init_y = 0;
      //ppoint[5].x = iOffsetToLeft;   // ""
      //ppoint[5].y = 0;

      
      //iOffsetToLeft -= uDistanceToNextTab;                 // "/" 
      ppoint[1].x   =  iOffsetToLeft - uDistanceToNextTab;
      ppoint[1].y   =  uTabHeight / 2;      
      
      ppoint[2].x   =  iOffsetToLeft - uDistanceToNextTab;
      ppoint[2].y   =  uTabHeight;
      /*if(uTabIndex == m_uSelectedViewIndex)
      {
          ppoint[2].x   =  iOffsetToLeft - 2 * uDistanceToNextTab;
      }    */  
      //draw tab-text 
      pDC->TextOut( ppoint[0].x + m_uOffsetFontGap,
                    1,
                    m_sarrayViewName[uTabIndex] );

      iOffsetToLeft += m_dwarrayTabWidth[uTabIndex];       // "____"
      
      ppoint[3].x   =  iOffsetToLeft + uDistanceToNextTab;
      ppoint[3].y   =  uTabHeight;

      if( uTabIndex == (UINT)(m_dwarrayTabWidth.GetSize() - 1) ) // "\____/"
      {
         iOffsetToLeft += uDistanceToNextTab;
         ppoint[4].x   =  iOffsetToLeft;
         ppoint[4].y   =  0;    
         
         ppoint[5].x   =  init_x;
         ppoint[5].y   =  init_y;    
         num_of_points = 6;
      }
      else
      {
         
         ppoint[4].x   =  iOffsetToLeft;
         ppoint[4].y   =  uTabHeight;
         ppoint[5].x   =  iOffsetToLeft;
         ppoint[5].y   =  uTabHeight / 2;
         
         iOffsetToLeft += (uDistanceToNextTab);
         
         ppoint[6].x   =  iOffsetToLeft;
         ppoint[6].y   =  0;
         
         ppoint[7].x = init_x;
         ppoint[7].y = init_y;         
         num_of_points = 8;
         //iOffsetToLeft -= (uDistanceToNextTab / 2);         
         //iOffsetToLeft += uDistanceToNextTab;         
      }
      
      if(uTabIndex == m_uSelectedViewIndex)
      {
          CRgn   rgn;
          CBrush brush;
          brush.CreateSolidBrush( ::GetSysColor( COLOR_WINDOW ) );
          rgn.CreatePolygonRgn( ppoint, num_of_points, ALTERNATE );
          //pDC->SetBkColor(::GetSysColor( COLOR_WINDOW ));
          pDC->FillRgn( &rgn, &brush );
          pDC->TextOut( ppoint[0].x + m_uOffsetFontGap,
                    1,
                    m_sarrayViewName[uTabIndex]);
      }   
      pDC->Polyline( ppoint, num_of_points );
   }

   pDC->SelectObject( ppenOld );
}

Another thing of interest for the tab is the selection of the view when a tab is clicked, and that part of the code is in OnLButtonDown. Note the calling of the SelectView method when the mouse is clicked in one of the tabs which becomes selected:

/*------------------------------------------------------------------
Name:       OnLButtonDown()
Type:    Message
Purpose: Check if klicked on a tab, and if so switch view
*/
void HsTabCtrl::OnLButtonDown(UINT nFlags, CPoint point) 
{
   int  iOffsetToLeft;
   UINT uDistanceToNextTab;

   uDistanceToNextTab = (m_uHeight - 1) / 2;
   
   iOffsetToLeft = m_iOffsetToLeft;

   // *** check if clicked on a tab ***
   for( UINT uCounter = 0; uCounter < 
       (UINT)m_dwarrayTabWidth.GetSize(); uCounter++ )
   {
      iOffsetToLeft += uDistanceToNextTab;

      if( ( point.x >= (iOffsetToLeft - 1) ) && 
          ( point.x <= (int)(iOffsetToLeft + m_dwarrayTabWidth[uCounter] + 1) ) )
      {         
         SelectView( uCounter );
         break;
      }

      iOffsetToLeft += m_dwarrayTabWidth[uCounter];
   }

    CWnd::OnLButtonDown(nFlags, point);
}

Points of Interest

In my original project, I needed to tile the views using my own algorithm, and that part of the code can be found in HsAlgorithm.h and cpp. Interested users can look it up.

The special tiling can be seen if you choose the Window->Tile option from the menu bar.

If you click in the drop down arrow of the tab bar, a popup appears to check/uncheck the views to include into/exclude from tiling.

Acknowledgements

Bahrudin Hrnjica, for his TabbedRebarSrc class, and Per Ghosh, for his splitterTabWnd class.

Special thanks goes to Mazharul Islam Khan (the writer of the Simple XP COM tutorial) for getting me interested in submitting articles to CodeProject.

History

  • Article submitted on July 20, 2010

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Mukit, Ataul
Chief Technology Officer Rational Technologies
Bangladesh Bangladesh
C++ is not C with classes, JQuery is not Javascript, Google Search is not Learning, Design Patterns are not fashion, A code written in 2005 is not backdated just because it's 2015
Follow on   Twitter

Comments and Discussions

 
GeneralMy vote of 5 Pinmembermanoj kumar choubey26-Feb-12 20:50 
QuestionVC6 project PinmemberFlaviu226-Dec-11 8:57 
By now, I work only with VC6 .. did you have an VC6 of this project ? Thanks.
AnswerRe: VC6 project PinmemberMukit, Ataul26-Dec-11 18:35 
Generalchange view's effect is so smooth, the article is excellen PinmemberLoseSpeed8-Jan-11 18:17 
GeneralRe: change view's effect is so smooth, the article is excellen PinmemberMukit, Ataul16-Jan-11 0:46 
GeneralMy vote of 5 PinmemberLoseSpeed8-Jan-11 18:11 
GeneralTabbed Docs Pinmembergeoyar26-Jul-10 13:37 
GeneralRe: Tabbed Docs PinmemberMukit, Ataul26-Jul-10 19:16 
GeneralMy vote of 2 Pinmemberemilio_grv20-Jul-10 3:12 
GeneralRe: My vote of 2 PinmemberMukit, Ataul20-Jul-10 19:02 
GeneralRe: My vote of 2 Pinmemberemilio_grv20-Jul-10 20:37 
GeneralyRe: My vote of 2 [modified] PinmemberMukit, Ataul21-Jul-10 0:42 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.141030.1 | Last Updated 22 Jul 2010
Article Copyright 2010 by Mukit, Ataul
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid