Implementing an owner drawn Tab Control
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:28amHello, 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
ReplyHow to make it look ok when on bottom
Posted by Legacy on 02/25/2002 12:00amOriginally posted by: Deelight
ReplyVC 6.0 , CTabCtrl::GetItem Bug!!!
Posted by Legacy on 01/31/2002 12:00amOriginally 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)
ReplyBottom and verrtical tabs
Posted by Legacy on 08/09/2001 12:00amOriginally posted by: Iain
I tried this but i doesnt display the text when you make it bottom vertical
is there a fix for this?
Replymore correct version of the OnSize method...
Posted by Legacy on 01/31/1999 12:00amOriginally posted by: John Franks
Reply