Click here to Skip to main content
15,881,173 members
Articles / Desktop Programming / MFC
Article

Fully owner drawn tab control

Rate me:
Please Sign up or sign in to vote.
4.93/5 (16 votes)
31 Jan 2000CPOL 347.1K   9.9K   98   35
A better looking tab control
  • Download demo project - 56 Kb
  • Download source files - 37 Kb
  • I think that the default look of common CTabCtrl is not cool enough (for the year 2000) when the control is used with scrolling tabs:

    Image 1

    The CLBTabCtrl class uses my CLBSpinButtonCtrl class (see "Owner drawn spin button control with autodisabling arrow buttons" article) and completly draw itself to look like this:

    Image 2

    Beside that, my CLBTabCtrl class has "autoraising items" feature. It means, that when mouse is over inactive item, that item is drawn higher then other inactive items and its right border became darker. This feature also persist when tab control is used in stacked mode:

    Image 3

    How to use CLBTabCtrl

    1. Add LBTabCtrl.h and LBTabCtrl.cpp to your project.
    2. Create a new class derived from CPropertySheet and add it to project.
    3. Include LBTabCtrl.h file in the header file of just created CPropertySheet-derived class.
      #include "LBTabCtrl.h"; 
    4. Add a member variable to that CPropertySheet-derived class.
      CLBTabCtrl m_LBTabCtrl;
      
    5. Subclass the common tab control to CLBTabCtrl. The good place to do this - in the virtual member function OnInitDialog() of CPropertySheet-derived class:
      BOOL CLBTabDemoPropSheet::OnInitDialog() 
      {
      	BOOL bResult =CPropertySheet::OnInitDialog(); 
      	
      	//Subclass default CTabCtrl to CLBTabCtrl
      	CTabCtrl *pTab =GetTabControl();
      	m_LBTabCtrl.SubclassWindow(pTab->m_hWnd);
      
      	return bResult;
      }

      After that you can use that CPropertySheet-derived class everywhere as usual CPropertySheet. For instance:

      CLBTabDemoPropSheet sheet("Any Title");
      CPropertyPage page1(IDD_PROPPAGE1),page2(IDD_PROPPAGE2),page3(IDD_PROPPAGE3);
      
      sheet.AddPage(&page1);
      sheet.AddPage(&page2);
      sheet.AddPage(&page3);
      
      // The following line is required if you wish to switch on 
      // the scrolling mode,since stacked mode is default 
      // for tab controls.
      sheet.EnableStackedTabs(FALSE); 
      
      sheet.DoModal();

      Note: Certainly you also can subclass any tab control, which does not lives within CPropertySheet.

    How it works

    1. Overview

    The CLBTabCtrl is owner drawn tab control. To make its job this control handles the following messages:

    • WM_MOUSEMOVE
    • WM_MOUSELEAVE
    • TCN_SELCHANGING
    • TCN_SELCHANGE
    • TCM_SETCURSEL
    • WM_HSCROLL
    • WM_PAINT
    • WM_ERASEBKGND
    • WM_KILLFOCUS
    • WM_SETFOCUS
    It also overrides virtual member functions of CTabCtrl:
    • PreSubclassWindow
    • WindowProc

    Note: There is a possibility to switch off /on the "autoraising items" feature of CLBTabCtrl, using its public member function [bool SetAutoRaising(bool bOn)].

    2. How autoraising items work

    Only handling of WM_MOUSEMOVE and WM_MOUSELEAVE does the trick. When the mouse enters the CLBTabCtrl it receive WM_MOUSEMOVE message and in that moment I use _TrackMouseEvent API to be notified with WM_MOUSELEAVE message, when the mouse leaves tab control. This API,declared in "Commctrl.h", tries to use the window manager's implementation of TrackMouseEvent if it is present(for Win98/NT), otherwise it emulates (for Win95).
    TRACKMOUSEEVENT stTRACKMOUSEEVENT;
    stTRACKMOUSEEVENT.cbSize = sizeof(stTRACKMOUSEEVENT);
    stTRACKMOUSEEVENT.hwndTrack=m_hWnd;
    stTRACKMOUSEEVENT.dwFlags=TME_LEAVE;
    _TrackMouseEvent(&stTRACKMOUSEEVENT);

    After that used approach is straightforward:
    • In WM_MOUSEMOVE handler I invalidate the item which is under mouse to draw it as raised and also invalidate item which was raised before as normal (unraised).
    • In WM_MOUSELEAVE handler I invalidate item which was raised before mouse leaved CLBTabCtrl, to draw it as normal (unraised).

    3. How CLBSpinButtonCtrl is involved

    CLBSpinButtonCtrl substitutes the common up-down control which used within CTabCtrl in scrolling mode. It is done in the virtual member function PreSubclassWindow() of CLBTabCtrl. There I look for msctls_updown32 control and if it is present, modify its size, position and subclass it to CLBSpinButtonCtrl, which have autodisabling arrow buttons.

    CWnd* pWnd = GetWindow(GW_CHILD);
    while(pWnd)
    {
      char buf[]="msctls_updown32";
      intnRet= ::GetClassName(pWnd->m_hWnd,
      buf,sizeof(buf)/sizeof(buf[0]));
      if(nRet&&strcmp(buf,"msctls_updown32"))
      {
        pWnd=pWnd->GetWindow(GW_HWNDNEXT);
      }
      else
      {
        //The msctls_updown32 control isfound.
        //
        pWnd->GetWindowRect(&m_rectUpDn);
        ScreenToClient(&m_rectUpDn);
        //Update size and position of msctls_updown32 control
        m_rectUpDn.DeflateRect(3,2);
        m_rectUpDn.OffsetRect(3,5);
    
        pWnd->MoveWindow(&m_rectUpDn);
        //Subclass common msctls_updown32 control to
        //my CLBSpinButtonCtrl, which have autodisabling arrow
        buttons.m_Spin.SubclassWindow(pWnd->m_hWnd);
        pWnd=0;
      }
    }
    

    4. Handling the TCN_SELCHANGING and TCN_SELCHANGE messages

    These messages are handled as reflected notification messages. After my handlers do the job, the parent window also get a chance to handle it.

    After CLBTabCtrl control (and its parent) handles TCN_SELCHANGING message, the system will send to it WM_PAINT/WM_ERASEBKGND messages. Since at that moment I still don't know which item has become active, and can not properly draw items, I have to avoid these WM_PAINT/WM_ERASEBKGND messages to get rid of flickering.

    Since up-down control do not receive WM_PAINT/WM_ERASEBKGND messages if it is invisible, so I temporary set the appropriate visible bits off and the control thinks it is visible even though it is not.

     DWORD dwStyle = ::GetWindowLong(m_hWnd,GWL_STYLE);
    if (dwStyle & WS_VISIBLE)
      ::SetWindowLong(m_hWnd, GWL_STYLE, (dwStyle & ~ WS_VISIBLE));//switch off WS_VISIBLE
    I set visible bits back on in TCN_SELCHANGE handler, when it is good time to redraw CLBTabCtrl.

    5. Handling the WM_PAINT message

    1. First of all to get rid of flickering I'm drawing to memory DC (dc). So I have to create compatible memory DC and select bitmap into it.
      CPaintDC RealDC(this);
      CDC dc;
      CBitmap bmpMem,*pOldMemBmp;
      rctPnt=dcReal.m_ps.rcPaint;
      dc.CreateCompatibleDC(&RealDC);
      bmpMem.CreateCompatibleBitmap(&RealDC,
          rctPaint.Width(),rctPaint.Height());
      pOldMemBmp=dc.SelectObject(&bmpMem);
      
      // Since we are bypassing WM_ERASEBCKGND, by returning TRUE from its
      //handler - let's do its job here
      dc.FillSolidRect(&rctPaint,::GetSysColor(COLOR_BTNFACE));
      
    2. After that I check if current repaint has happened due to autoraised item. If so, I repaint only raised item. Otherwise I repaint parts of border of CLBTabCtrl which overlapped by update region. After that I draw all inactive items which intersects the update region. And in the last turn the active item is drawn, in case if it also intersects the update region.

      For drawing of items I've used virtual member function DrawItem of CTabCtrl and virtual member function DrawItemRect, added to CLBTabCtrl.

    3. At the final step I copy the resulting bitmap from memory DC to the screen, using BitBlt with SRCCOPY ROP.

    Standard Disclaimer

    These files may be redistributed unmodified by any means providing it is not sold for profit without the authors written consent, and providing that the authors name and all copyright notices remains intact. This code may be used in compiled form in any way you wish with the following conditions:

    If the source code is used in any commercial product then a statement along the lines of "Portions Copyright (C) 1999 Oleg Lobach" must be included in the startup banner, "About" box or printed documentation. The source code may not be compiled into a standalone library and sold for profit. In any other cases the code is free to whoever wants it anyway!

    This software is provided "as is" without express or implied warranty.The author accepts no liability for any damages to your computer or data these products may cause.

    License

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


    Written By
    Russian Federation Russian Federation
    This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

    Comments and Discussions

     
    Generalthank you for providing this source and article Pin
    Richard Chambers19-Jan-14 5:49
    Richard Chambers19-Jan-14 5:49 
    NewsWS_MINIMIZEBOX - How to (solved) Pin
    Hugo B. Slivinskis19-Jun-12 14:28
    Hugo B. Slivinskis19-Jun-12 14:28 
    GeneralOwner drawn tab control problem in Vista Pin
    Ramya_Indian6922-Mar-07 23:34
    Ramya_Indian6922-Mar-07 23:34 
    GeneralBackground color of the CLBTabCtrl Pin
    Reji_Kumar1-Feb-07 1:14
    Reji_Kumar1-Feb-07 1:14 
    GeneralRe: Background color of the CLBTabCtrl Pin
    Prashant Singh Sisodiya30-Sep-08 19:55
    Prashant Singh Sisodiya30-Sep-08 19:55 
    QuestionMenu Bar? Pin
    Big Mouse22-Jun-04 16:16
    Big Mouse22-Jun-04 16:16 
    GeneralTab Orientation Problem Pin
    YoSilver16-Jan-04 18:38
    YoSilver16-Jan-04 18:38 
    GeneralRe: Tab Orientation Problem Pin
    Oleg Lobach18-Jan-04 22:30
    Oleg Lobach18-Jan-04 22:30 
    QuestionHandler? Pin
    Nirav Doshi4-Jun-03 7:22
    Nirav Doshi4-Jun-03 7:22 
    QuestionWhat would be cool... Pin
    peterchen31-Mar-03 19:58
    peterchen31-Mar-03 19:58 
    AnswerRe: What would be cool... Pin
    JWood22-Aug-03 3:03
    JWood22-Aug-03 3:03 
    QuestionHow subclass Spin control in CView Pin
    Wolfram Steinke2-Mar-03 16:52
    Wolfram Steinke2-Mar-03 16:52 
    GeneralTab Control bitmap Pin
    Vivek Dabral12-Feb-03 19:35
    Vivek Dabral12-Feb-03 19:35 
    GeneralRe: Tab Control bitmap Pin
    abedel26-Mar-03 19:32
    abedel26-Mar-03 19:32 
    GeneralStuff your message Pin
    wm24-Oct-02 23:50
    wm24-Oct-02 23:50 
    GeneralRe: Stuff your message Pin
    James T. Johnson25-Oct-02 0:18
    James T. Johnson25-Oct-02 0:18 
    GeneralRe: Stuff your message Pin
    JWood22-Aug-03 2:58
    JWood22-Aug-03 2:58 
    GeneralRe: Stuff your message Pin
    Phil Outram3-Apr-12 1:17
    Phil Outram3-Apr-12 1:17 
    GeneralAccessing text controls of other dialog box Pin
    16-Apr-02 5:08
    suss16-Apr-02 5:08 
    QuestionHow to show in FormView Pin
    housefreak19-Mar-02 9:34
    housefreak19-Mar-02 9:34 
    GeneralCMap Pin
    17-Mar-02 23:13
    suss17-Mar-02 23:13 
    General1 pixel border and border color Pin
    Manikroy4-Dec-01 22:09
    Manikroy4-Dec-01 22:09 
    GeneralTAb Control in Wince Pin
    25-Sep-01 20:27
    suss25-Sep-01 20:27 
    QuestionHow can I do to transfert datas ? Pin
    youssef24-Aug-01 0:16
    youssef24-Aug-01 0:16 
    QuestionHow to delete [apply] and [help] button on sheet. Pin
    18-Jan-01 15:54
    suss18-Jan-01 15:54 

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

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