Implementing an owner drawn Tab Control

Ownerdrawn CTabCtrl image

Download source files

Implementing an owner drawn tab control requires that the tab control have the "owner draw fixed" style (TCS_OWNERDRAWFIXED), and that it have a mechanism for drawing itself when needed.

To draw the tabs, just override the "DrawItem" function in your derived class. This function accepts a pointer to a DRAWITEMSTRUCT structure, which contains the index of the tab to draw (itemID), a handle to a DC (hDC) and the rectangle in which to draw the tab (rcItem). It's a simple matter to retrive the text and draw it which ever way you like. The only tricky bit is that the rectangle you get is not quite the one you want - if you blindly draw in the full extent of the rectangle you will overshoot the bounds of your tab.

The demo class below (CTabCtrlEx) allows shows how to draw the tab text and image, and contains two functions: SetFonts and SetColours which allows you to specify different fonts and colours for selected and unselected tabs. SetFonts has two forms: one which accepts two fonts (for selected and unselected tabs repsectively) and one which allows you to just specify whether the tabs will contain bold, underlined or italicised text. Hence you could have your selected tab displayed in a heavy, underlined blue, while unselected tabs could be displayed in a thin light grey.

	void SetFonts(CFont* pSelFont,                     // Selected tabs font
	              CFont* pUnselFont);                  // Unselected tabs font

	void SetFonts(int nSelWeight     = FW_SEMIBOLD,    // Selected tabs font weight
	              BOOL bSelItalic    = FALSE,          // Selected tabs font in italics?
	              BOOL bSelUnderline = FALSE,          // Selected tabs font underlined?
	              int nUnselWeight     = FW_MEDIUM,    // Unselected tabs font weight
	              BOOL bUnselItalic    = FALSE,        // Unselected tabs font in italics?
	              BOOL bUnselUnderline = FALSE);       // Unselected tabs font underlined?

	void SetColours(COLORREF bSelColour,               // Selected text colour
	                COLORREF bUnselColour);            // Unselected tabs font

Since different weighted fonts will give different sized text, the larger of the fonts should be selected as the underlying font for the control so that the sizes of the tabs will be calculated correctly.

To use the tab control, simply subclass it to an existing tab control in a dialog or use CTabCtrl::Create, and then set the fonts and colours, either in the dialogs OnInitDialog or directly after the call to Create().

Source code

#if !defined(AFX_TABCTRLEX_H__28407441_38AF_11D1_ABBA_00A0243D1382__INCLUDED_)
#define AFX_TABCTRLEX_H__28407441_38AF_11D1_ABBA_00A0243D1382__INCLUDED_

#if _MSC_VER >= 1000
#pragma once
#endif // _MSC_VER >= 1000

// TabCtrlEx.h : header file
//

/////////////////////////////////////////////////////////////////////////////
// CTabCtrlEx window

class CTabCtrlEx : public CTabCtrl
{
// Construction/destruction
public:
	CTabCtrlEx();
	virtual ~CTabCtrlEx();

// Attributes:
public:

// Operations
public:
	void SetFonts(CFont* pSelFont, CFont* pUnselFont);
	void SetFonts(int nSelWeight=FW_SEMIBOLD, BOOL bSelItalic=FALSE,   BOOL bSelUnderline=FALSE,
	              int nUnselWeight=FW_MEDIUM, BOOL bUnselItalic=FALSE, BOOL bUnselUnderline=FALSE);

	void SetColours(COLORREF bSelColour, COLORREF bUnselColour);

// Overrides
	// ClassWizard generated virtual function overrides
	//{{AFX_VIRTUAL(CTabCtrlEx)
	public:
	virtual void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct);
	protected:
	virtual void PreSubclassWindow();
	//}}AFX_VIRTUAL

// Implementation
protected:
	COLORREF m_crSelColour, m_crUnselColour;
	CFont    m_SelFont,	m_UnselFont;

// Generated message map functions
protected:
	//{{AFX_MSG(CTabCtrlEx)
	afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
	//}}AFX_MSG
	DECLARE_MESSAGE_MAP()
};

/////////////////////////////////////////////////////////////////////////////

//{{AFX_INSERT_LOCATION}}
// Microsoft Developer Studio will insert additional declarations immediately before the previous line.

#endif // !defined(AFX_TABCTRLEX_H__28407441_38AF_11D1_ABBA_00A0243D1382__INCLUDED_)

/////////////////////////////////////////////////////////////////////////////



// TabCtrlEx.cpp : implementation file
//

#include "stdafx.h"
#include "TabCtrlEx.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CTabCtrlEx

CTabCtrlEx::CTabCtrlEx()
{
	m_crSelColour     = RGB(0,0,255);
	m_crUnselColour   = RGB(50,50,50);
}

CTabCtrlEx::~CTabCtrlEx()
{
	m_SelFont.DeleteObject();
	m_UnselFont.DeleteObject();
}

BEGIN_MESSAGE_MAP(CTabCtrlEx, CTabCtrl)
	//{{AFX_MSG_MAP(CTabCtrlEx)
	ON_WM_CREATE()
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CTabCtrlEx message handlers

int CTabCtrlEx::OnCreate(LPCREATESTRUCT lpCreateStruct) 
{
	if (CTabCtrl::OnCreate(lpCreateStruct) == -1) return -1;
	ModifyStyle(0, TCS_OWNERDRAWFIXED);
	
	return 0;
}

void CTabCtrlEx::PreSubclassWindow() 
{	
	CTabCtrl::PreSubclassWindow();
	ModifyStyle(0, TCS_OWNERDRAWFIXED);
}

void CTabCtrlEx::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct) 
{	
	CRect rect = lpDrawItemStruct->rcItem;
	int nTabIndex = lpDrawItemStruct->itemID;
	if (nTabIndex < 0) return;
	BOOL bSelected = (nTabIndex == GetCurSel());

	char label[64];
	TC_ITEM tci;
	tci.mask = TCIF_TEXT|TCIF_IMAGE;
	tci.pszText = label;     
	tci.cchTextMax = 63;    	
	if (!GetItem(nTabIndex, &tci )) return;

	CDC* pDC = CDC::FromHandle(lpDrawItemStruct->hDC);
	if (!pDC) return;
	int nSavedDC = pDC->SaveDC();

	// For some bizarre reason the rcItem you get extends above the actual
	// drawing area. We have to workaround this "feature".
	rect.top += ::GetSystemMetrics(SM_CYEDGE);

	pDC->SetBkMode(TRANSPARENT);
	pDC->FillSolidRect(rect, ::GetSysColor(COLOR_BTNFACE));

	// Draw image
	CImageList* pImageList = GetImageList();
	if (pImageList && tci.iImage >= 0) {

		rect.left += pDC->GetTextExtent(_T(" ")).cx;		// Margin

		// Get height of image so we 
		IMAGEINFO info;
		pImageList->GetImageInfo(tci.iImage, &info);
		CRect ImageRect(info.rcImage);
		int nYpos = rect.top;

		pImageList->Draw(pDC, tci.iImage, CPoint(rect.left, nYpos), ILD_TRANSPARENT);
		rect.left += ImageRect.Width();
	}

	if (bSelected) {
		pDC->SetTextColor(m_crSelColour);
		pDC->SelectObject(&m_SelFont);
		rect.top -= ::GetSystemMetrics(SM_CYEDGE);
		pDC->DrawText(label, rect, DT_SINGLELINE|DT_VCENTER|DT_CENTER);
	} else {
		pDC->SetTextColor(m_crUnselColour);
		pDC->SelectObject(&m_UnselFont);
		pDC->DrawText(label, rect, DT_SINGLELINE|DT_BOTTOM|DT_CENTER);
	}

	pDC->RestoreDC(nSavedDC);
}

/////////////////////////////////////////////////////////////////////////////
// CTabCtrlEx operations

void CTabCtrlEx::SetColours(COLORREF bSelColour, COLORREF bUnselColour)
{
	m_crSelColour = bSelColour;
	m_crUnselColour = bUnselColour;
	Invalidate();
}

void CTabCtrlEx::SetFonts(CFont* pSelFont, CFont* pUnselFont)
{
	ASSERT(pSelFont && pUnselFont);

	LOGFONT lFont;
	int nSelHeight, nUnselHeight;

	m_SelFont.DeleteObject();
	m_UnselFont.DeleteObject();

	pSelFont->GetLogFont(&lFont);
	m_SelFont.CreateFontIndirect(&lFont);
	nSelHeight = lFont.lfHeight;

	pUnselFont->GetLogFont(&lFont);
	m_UnselFont.CreateFontIndirect(&lFont);
	nUnselHeight = lFont.lfHeight;

	SetFont( (nSelHeight > nUnselHeight)? &m_SelFont : &m_UnselFont);
}


void CTabCtrlEx::SetFonts(int nSelWeight,   BOOL bSelItalic,   BOOL bSelUnderline,
                          int nUnselWeight, BOOL bUnselItalic, BOOL bUnselUnderline)
{
	// Free any memory currently used by the fonts.
	m_SelFont.DeleteObject();
	m_UnselFont.DeleteObject();

	// Get the current font
	LOGFONT lFont;
	CFont *pFont = GetFont();
	if (pFont)
		pFont->GetLogFont(&lFont);
	else {
		NONCLIENTMETRICS ncm;
		ncm.cbSize = sizeof(NONCLIENTMETRICS);
		VERIFY(SystemParametersInfo(SPI_GETNONCLIENTMETRICS, 
		                            sizeof(NONCLIENTMETRICS), &ncm, 0));
		lFont = ncm.lfMessageFont; 
	}

	// Create the "Selected" font
	lFont.lfWeight = nSelWeight;
	lFont.lfItalic = bSelItalic;
	lFont.lfUnderline = bSelUnderline;
	m_SelFont.CreateFontIndirect(&lFont);

	// Create the "Unselected" font
	lFont.lfWeight = nUnselWeight;
	lFont.lfItalic = bUnselItalic;
	lFont.lfUnderline = bUnselUnderline;
	m_UnselFont.CreateFontIndirect(&lFont);

	SetFont( (nSelWeight > nUnselWeight)? &m_SelFont : &m_UnselFont);
}



Comments

  • Changing the background color of the tabctrl

    Posted by RejiKumar on 02/01/2007 07:28am

    Hello, Your article was very useful to me. Can you also tell me how to change the background color of the Tabctrl. I am not able to do it. Thnaks, Reji kumar.reji@gmail.com

    Reply
  • How to make it look ok when on bottom

    Posted by Legacy on 02/25/2002 12:00am

    Originally posted by: Deelight

    It seems to work modifying thge DrawItem method this way:
    
    

    void CTabCtrlEx::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
    {
    CRect rect = lpDrawItemStruct->rcItem;
    int nTabIndex = lpDrawItemStruct->itemID;
    if (nTabIndex < 0) return;
    BOOL bSelected = (nTabIndex == GetCurSel());

    char label[64];
    TC_ITEM tci;
    tci.mask = TCIF_TEXT|TCIF_IMAGE;
    tci.pszText = label;
    tci.cchTextMax = 63;
    if (!GetItem(nTabIndex, &tci )) return;

    CDC* pDC = CDC::FromHandle(lpDrawItemStruct->hDC);
    if (!pDC) return;
    int nSavedDC = pDC->SaveDC();

    // For some bizarre reason the rcItem you get extends above the actual
    // drawing area. We have to workaround this "feature".
    rect.top += ::GetSystemMetrics(SM_CYEDGE);
    pDC->SetBkMode(TRANSPARENT);


    if (bSelected)
    {
    pDC->FillSolidRect(rect.left,rect.top-3,rect.right-rect.left,rect.bottom, ::GetSysColor(COLOR_BTNFACE));
    }
    else
    {
    pDC->FillSolidRect(rect.left,rect.top-3,rect.right,rect.bottom, ::GetSysColor(COLOR_BTNFACE));
    rect.bottom-=3;
    }

    // Draw image
    CImageList* pImageList = GetImageList();
    if (pImageList && tci.iImage >= 0) {

    rect.left += pDC->GetTextExtent(_T(" ")).cx; // Margin

    // Get height of image so we
    IMAGEINFO info;
    pImageList->GetImageInfo(tci.iImage, &info);
    CRect ImageRect(info.rcImage);
    int nYpos = rect.top;

    pImageList->Draw(pDC, tci.iImage, CPoint(rect.left, nYpos), ILD_TRANSPARENT);
    rect.left += ImageRect.Width();
    }

    if (bSelected) {
    pDC->SetTextColor(m_crSelColour);
    pDC->SelectObject(&m_SelFont);
    rect.top -= ::GetSystemMetrics(SM_CYEDGE);
    pDC->DrawText(label, rect, DT_SINGLELINE|DT_VCENTER|DT_CENTER);
    } else {
    pDC->SetTextColor(m_crUnselColour);
    pDC->SelectObject(&m_UnselFont);
    pDC->DrawText(label, rect, DT_SINGLELINE|DT_BOTTOM|DT_CENTER);
    }

    pDC->RestoreDC(nSavedDC);
    }

    Reply
  • VC 6.0 , CTabCtrl::GetItem Bug!!!

    Posted by Legacy on 01/31/2002 12:00am

    Originally posted by: nobrain


    If using vc 6.0 and Win2000 , can't get label text.
    it is bug.(vc 5.0 is clear)
    i did service pack 5, but same error..


    i solution is insert dummy data,

    char label[64];
    UINT dummy=1;
    TC_ITEM tci;
    tci.mask = TCIF_TEXT|TCIF_IMAGE| TCIF_PARAM;
    tci.pszText = label;
    tci.cchTextMax = 63;
    if (!GetItem(nTabIndex, &tci )) return;

    ===
    you show dummy value setting 1,
    GetItem function setting dummy value 0, it's problem..

    if other control function do same it, it is very danger job..(ex: broken stack)


    Reply
  • Bottom and verrtical tabs

    Posted by Legacy on 08/09/2001 12:00am

    Originally posted by: Iain

    I tried this but i doesnt display the text when you make it bottom vertical


    is there a fix for this?

    Reply
  • more correct version of the OnSize method...

    Posted by Legacy on 01/31/1999 12:00am

    Originally posted by: John Franks

    Here's a version of the OnSize method that also copes with
    
    the tab being at the top (it looks at the m_atTop attribute to check,
    which needs to be added).

    void CSizingTabCtrlBar::OnSize(UINT nType, int cx, int cy)
    {
    CSizingControlBar::OnSize(nType, cx, cy);

    const int tabs = 20;
    const int border = 3;
    const int margin = 7;

    int tleft = margin;
    int ttop = margin;
    int twidth = cx - (2*margin);
    int theight = cy - (2*margin);
    m_tabctrl.MoveWindow(tleft, ttop, twidth, theight);

    CWnd *pWnd;

    int wleft = margin+border;
    int wtop = margin + (m_atTop? tabs : 0) + border;
    int wwidth = (cx - wleft) - (margin + border);
    int wheight = (cy - wtop)
    - ((m_atTop ? 0 : tabs) + border + margin);

    for (POSITION pos=m_views.GetHeadPosition();
    pos;
    m_views.GetNext(pos))
    {
    pWnd=m_views.GetAt(pos)->pWnd;

    pWnd->MoveWindow(wleft, wtop, wwidth, wheight);
    }

    }

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

Top White Papers and Webcasts

  • Live Event Date: August 19, 2014 @ 11:00 a.m. ET / 8:00 a.m. PT You deployed your app with the Bluemix PaaS and it's gaining some serious traction, so it's time to make some tweaks. Did you design your application in a way that it can scale in the cloud? Were you even thinking about the cloud when you built the app? If not, chances are your app is going to break. Check out this upcoming webcast to learn various techniques for designing applications that will scale successfully in Bluemix, for the …

  • Live Event Date: September 19, 2014 @ 2:00 p.m. ET / 11:00 a.m. PT In response to the rising number of data breaches and the regulatory and legal impact that can occur as a result of these incidents, leading analysts at Forrester Research have developed five important design principles that will help security professionals reduce their attack surface and mitigate vulnerabilities. Check out this upcoming eSeminar and join Chris Sherman of Forrester Research to learn how to deal with the influx of new device …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds