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); }