Click here to Skip to main content
15,888,077 members
Articles / Desktop Programming / MFC
Article

XP Themes Tab Control in any orientation

Rate me:
Please Sign up or sign in to vote.
4.59/5 (34 votes)
6 Mar 20044 min read 311.2K   11.8K   112   56
How to make XP Themes Tab Control work properly in other than top orientation.

XPTabApp Image Using CXPTabCtrl

Using CXPTabCtrl, XP Themes Tab Control, left, bottom and right orientation

Introduction

The article shows how to solve a problem, which appears on the Windows Tab Control when using Windows XP operating system with XP Themes enabled. Tab control behaves as if it has only one orientation - top. In fact, it has all four orientations, but all four of them (for some reason) have the same appearance.

It is demonstrated how to use the proposed class CXPTabCtrl, Windows tab control extension as a solution to this problem. CXPTabCtrl detects the operating system and tab control orientation:

  • If it is not XP system or XP Themes are not enabled - it uses default Windows tab behavior (it does not do anything extra).
  • If it is XP system and XP Themes are enabled - it draws tab control properly in appropriate direction.

CXPTabCtrl control also works properly if the state of XP Themes enabling is changed during the running of the application. Tab control hot tracking is also supported.

Background

There are a lots of Windows applications developed for earlier Windows versions, which use bottom or other "not-top" oriented Windows tab control. The problem is: if the application is running on an XP system with XP Themes, Windows tab control is not shown properly as it used to be.

XPTabApp Image default SysTabControl32

Problems when using default SysTabControl32 (CTabCtrl) with XP Themes, bottom orientation

One possibility to solve this problem is to change all tab controls in these applications to be top oriented. This would involve change of the look of the applications and painful Windows resource changes and possible code changes.

Another possibility (used for CXPTabCtrl) is to use a thin wrapper around the tab control and to simulate missing orientations. CXPTabCtrl uses available XP Themes "top-tab" look as a template and adapts it for other tab orientations:

  • Bottom orientation:
    1. use top background bitmap,
    2. mirror it vertically,
    3. draw icon and text on mirrored background.

    Build CXPTabCtrl bottom orientation

  • Left orientation:
    1. use top background bitmap,
    2. draw icon and text on the background,
    3. rotate the bitmap for 90°.

    Build CXPTabCtrl left orientation

  • Right orientation:
    1. use top background bitmap,
    2. mirror it vertically,
    3. draw icon and text on the background,
    4. rotate the bitmap for 90°.

    Build CXPTabCtrl right orientation

  • Top orientation (used only for test purposes, could use default from XP Themes Windows XP):
    1. use top background bitmap,
    2. draw icon and text.

    Build CXPTabCtrl top orientation

Notice also that the body of the tab control has to be manipulated, mirrored or rotated, not only the "tab" parts of the tab control (because a "tab body" has texture and shadows).

How to use it

The CXPTabCtrl class is simple to use. To add it to your project, please follow the steps below:

  1. Put its source files (XPTabCtrl.cpp and XPTabCtrl.h) into the proper project folder and add their file names to your Visual Studio project.
  2. Include its header to the appropriate header file - usually dialog class header where class CXPTabCtrl is used. If you plan to use CXPTabCtrl in several places of your application, you can add it only to your StdAfx.h file:
    #include "XPTabCtrl.h"
  3. You should replace CTabCtrl with CXPTabCtrl everywhere in the project where you want new XP Themes tab behavior.
    CXPTabCtrl  m_tabCtrl
  4. If CXPTabCtrl has images, then appropriate bitmap IDB_TABIMAGES (or other with correct tab images) should be included as resource bitmap. It is also necessary to call InitImageList(IDB_TABIMAGES) function, from OnInitDialog or other appropriate initializing place:
    m_tabCtrl.InitImageList(IDB_TABIMAGES);
    // only necessary to call if tabs have images

Although tab control orientation is usually set in resource editor or in Tab's Create function, CXPTabCtrl orientation can be changed any time while application is running.

Points of Interest

This sample could also be used as an example of how to solve some other problems in Windows programming. Other techniques shown in this sample include:

  1. How to mirror bitmap image vertically fast and simply by using only two Windows calls GetDIBits and SetDIBits. This works in any color resolution.
  2. How to rotate rectangular bitmap image fast and in any color resolution.
  3. How to use inline assembler for lengthy repetitive processing, in this case, rotating bitmap image pixels. If image is of large dimensions (large tab control body), a C style coding could take considerable more time than assembler version.
  4. How to use XP Themes functions in applications, which can run on earlier operating systems.
  5. How to enable XP Themes dialog texture background.
  6. How to make applications XP Themes aware by simply copying XP manifest, resource "24", ID "1" from one application resource to another.
  7. How to use tab ToolTips.

Please note

  • The sample is created and compiled on VC++ 6.0 service pack 5.
  • The sample has been tested on Windows XP (XP Themes enabled or disabled while sample was running).
  • The sample has also been tested on Windows 2000, Windows NT 4.0 (CXPTabCtr does not have effect).
  • Other Windows systems could have possible problems (?)
  • The sample has been tested on the following color resolutions: 8 bit, 16 bit, 24 bit and 32 bit colors.
  • The sample has not been tested on multi-line (stacked) tabs, could be problems (?)

Correspondence

Please post your questions, suggestions and bug reports only to the forum below.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


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

Comments and Discussions

 
GeneralUsing true color images on tabs Pin
MMaker30-Nov-04 20:58
MMaker30-Nov-04 20:58 
Questionanyone fixed left orientation with themes disabled ? Pin
freddycam1-Nov-04 23:52
freddycam1-Nov-04 23:52 
GeneralAdding pages Pin
Steve Kelly28-May-04 5:37
Steve Kelly28-May-04 5:37 
GeneralRe: Adding pages Pin
SiHot26-Jul-07 3:44
SiHot26-Jul-07 3:44 
GeneralRough patch for owner-draw tabs Pin
David Pritchard25-May-04 2:57
David Pritchard25-May-04 2:57 
GeneralCrash problem Pin
David Pritchard21-Sep-04 23:41
David Pritchard21-Sep-04 23:41 
GeneralPatch for the patched patch Pin
David Pritchard22-Sep-04 1:33
David Pritchard22-Sep-04 1:33 
GeneralFix for resize ghosting Pin
18-May-04 22:06
suss18-May-04 22:06 
When resizing the tab, ghosting appears in the part of the screen where labels could be drawn (but are not). Let's say you have a tab control of 400 pixels wide, with 2 tabs under it, both 50 pixels wide. You would then have an area to the right (300 pixels wide) that would not be redrawn on resizing.

I wrote an "as flicker free as possible" solution for it, and here is the code:
(this is old code, and this is new code)

<br />
void CXPTabCtrl::OnPaint() <br />
{<br />
	if(!IsExtendedTabTheamedXP())			// if it is not XP or it is not Themes, behave as default<br />
		{ Default(); return; }	<br />
	CPaintDC dc(this);						// device context for painting<br />
<br />
	CRect rcClip; rcClip.SetRectEmpty();<br />
	dc.GetClipBox(rcClip);<br />
<br />
	// 1st paint the tab body<br />
	CRect rcPage,rcItem,rcClient;<br />
	GetClientRect(&rcPage);<br />
	rcClient=rcPage;<br />
	AdjustRect(FALSE,rcPage);<br />
<br />
	switch(m_eTabOrientation)<br />
	{	case e_tabTop:	  rcClient.top   =rcPage.top   -2; break;<br />
		case e_tabBottom: rcClient.bottom=rcPage.bottom+3; break;<br />
		case e_tabLeft:	  rcClient.left  =rcPage.left  -1; break;<br />
		case e_tabRight:  rcClient.right =rcPage.right +3; break;<br />
		default: ASSERT(FALSE); return;<br />
	}<br />
	UINT uiVertBottm;<br />
	uiVertBottm =(m_eTabOrientation&1)? 8:0;		//  8=bottom<br />
	uiVertBottm|=(m_eTabOrientation&2)?16:0;		// 16=vertical<br />
	UINT uiFlags=1|uiVertBottm;						//  1=body	<br />
	DrawThemesXpTabItem(&dc, -1, rcClient,uiFlags);	// TABP_PANE=9,0,'TAB'<br />
	<br />
	int nTab=GetItemCount();						// paint the tabs first and then the borders<br />
	if(!nTab) return;								// no tab pages added<br />
<br />
<br />
	///////////////////////////////////////////////////////////////////////////////<br />
	// The label background needs to be redrawn, to prevent ghosting<br />
	// Otherwise the space where the labels are drawn might not be drawn at all<br />
	///////////////////////////////////////////////////////////////////////////////<br />
	// Declare the variables	(this should be at the top of the function,<br />
	//							 but I keep it here to keep my changed code together)<br />
	CRgn	rgnItem;			// The region of an item that does not need to be repainted<br />
	CRgn	rgnRepaintArea;		// The region that needs to be repainted<br />
	CRect	rcRepaintArea;		// The rectangle surrounding the region that needs to be repainted<br />
<br />
	// Get the size of the first tab<br />
	VERIFY( GetItemRect( 0, &rcItem) );<br />
	// Adjust the client rectangle, so that only the label is redrawn<br />
	if( m_eTabOrientation == e_tabTop )<br />
	{<br />
		rcRepaintArea.top			= rcItem.top - 2;		// Add 2 pixels to clear the extra border<br />
		rcRepaintArea.bottom		= rcItem.bottom;<br />
		rcRepaintArea.left			= rcClient.left;<br />
		rcRepaintArea.right			= rcClient.right;<br />
	}<br />
	else if( m_eTabOrientation == e_tabBottom )<br />
	{<br />
		rcRepaintArea.top			= rcItem.top;<br />
		rcRepaintArea.bottom		= rcItem.bottom + 2;	// Add 2 pixels to clear the extra border<br />
		rcRepaintArea.left			= rcClient.left;<br />
		rcRepaintArea.right			= rcClient.right;<br />
	}<br />
	else if( m_eTabOrientation == e_tabLeft )<br />
	{<br />
		rcRepaintArea.top			= rcClient.top;<br />
		rcRepaintArea.bottom		= rcClient.bottom;<br />
		rcRepaintArea.left			= rcItem.left - 2;		// Add 2 pixels to clear the extra border<br />
		rcRepaintArea.right			= rcClient.left;		// rcItem.right is 1 pixel to far to the left<br />
	}<br />
	else if( m_eTabOrientation == e_tabRight )<br />
	{<br />
		rcRepaintArea.top			= rcClient.top;<br />
		rcRepaintArea.bottom		= rcClient.bottom;<br />
		rcRepaintArea.left			= rcItem.left;<br />
		rcRepaintArea.right			= rcItem.right + 2;		// Add 2 pixels to clear the extra border<br />
	}<br />
<br />
	// Create the repaint region<br />
	rgnRepaintArea.CreateRectRgnIndirect( rcRepaintArea );<br />
	// Initialize the item region<br />
	rgnItem.CreateRectRgn( 0, 0, 0, 0 );<br />
<br />
<br />
	// 2nd paint the inactive tabs<br />
	TCHITTESTINFO hti;	hti.flags=0;<br />
	::GetCursorPos(&hti.pt); ScreenToClient(&hti.pt);<br />
	int ixHot=HitTest(&hti);<br />
	int ixSel=GetCurSel();<br />
		<br />
	for(int ixTab=0; ixTab<nTab; ixTab++)<br />
	{	if(ixTab==ixSel)<br />
			continue;<br />
		VERIFY(GetItemRect(ixTab, &rcItem));<br />
		if(m_eTabOrientation==e_tabLeft) rcItem.right++;<br />
		uiFlags=uiVertBottm|(ixTab==ixHot?4:0);		// 4= hot<br />
		DrawThemesXpTabItem(&dc,ixTab,rcItem,uiFlags);<br />
		// Create the item region<br />
		rgnItem.SetRectRgn( rcItem );<br />
		// Exclude the item region from the repaint region<br />
		rgnRepaintArea.CombineRgn( &rgnRepaintArea, &rgnItem, RGN_DIFF );<br />
	}<br />
	// 3rd paint the active selected tab<br />
	VERIFY(GetItemRect(ixSel, &rcItem));			// now selected tab<br />
	rcItem.InflateRect(2,2);<br />
	if(m_eTabOrientation==e_tabTop) rcItem.bottom--;<br />
	uiFlags=uiVertBottm|2;							// 2= selected<br />
	DrawThemesXpTabItem(&dc, ixSel, rcItem,uiFlags);<br />
	// Create the item region<br />
	rgnItem.SetRectRgn( rcItem );<br />
	// Exclude the item region from the repaint region<br />
	rgnRepaintArea.CombineRgn( &rgnRepaintArea, &rgnItem, RGN_DIFF );<br />
<br />
	// 4th Draw the rest of the label space (the space without labels on it)<br />
	// Create a brush with the background color<br />
	HBRUSH	hBrBack	= (HBRUSH) ::GetClassLong( GetParent()->GetSafeHwnd(), GCL_HBRBACKGROUND );<br />
	if( hBrBack == 0 )<br />
	{<br />
		hBrBack	= ::GetSysColorBrush( COLOR_BTNFACE );<br />
	}<br />
	// Fill the repaint region with the background color<br />
	dc.FillRgn( &rgnRepaintArea, CBrush::FromHandle( hBrBack ) );<br />
}<br />

GeneralRe: Fix for resize ghosting Pin
David Pritchard24-May-04 11:05
David Pritchard24-May-04 11:05 
GeneralRe: Fix for resize ghosting Pin
Anonymous24-May-04 11:10
Anonymous24-May-04 11:10 
GeneralRe: Fix for resize ghosting Pin
David Pritchard24-May-04 12:55
David Pritchard24-May-04 12:55 
GeneralRe: Fix for resize ghosting Pin
GamePlayHeaven24-May-04 22:13
GamePlayHeaven24-May-04 22:13 
GeneralRe: Fix for resize ghosting Pin
David Pritchard24-May-04 22:36
David Pritchard24-May-04 22:36 
GeneralTCN_SELCHANGE no longer handled by parent class Pin
EKL18-Mar-04 7:14
EKL18-Mar-04 7:14 
GeneralRe: TCN_SELCHANGE no longer handled by parent class Pin
isglass23-Apr-04 23:46
isglass23-Apr-04 23:46 
GeneralRe: TCN_SELCHANGE no longer handled by parent class Pin
GamePlayHeaven29-Apr-04 0:09
GamePlayHeaven29-Apr-04 0:09 
GeneralRe: TCN_SELCHANGE no longer handled by parent class Pin
isglass29-Apr-04 10:51
isglass29-Apr-04 10:51 
GeneralRe: TCN_SELCHANGE no longer handled by parent class Pin
GamePlayHeaven16-May-04 21:42
GamePlayHeaven16-May-04 21:42 
GeneralLeft tab being chopped Pin
Joel Lucsy9-Mar-04 12:10
Joel Lucsy9-Mar-04 12:10 
Generala bug Pin
benben8-Mar-04 16:54
benben8-Mar-04 16:54 
GeneralNice work Pin
snakeware7-Mar-04 22:05
snakeware7-Mar-04 22:05 
GeneralRe: Nice work Pin
Adi DEDIC7-Mar-04 23:43
Adi DEDIC7-Mar-04 23:43 
GeneralGood work, but not finished! Pin
mortal7-Mar-04 21:14
mortal7-Mar-04 21:14 
GeneralProgramming is never finished Pin
Adi DEDIC8-Mar-04 0:52
Adi DEDIC8-Mar-04 0:52 
GeneralUse of your control Pin
ng_pearce7-Mar-04 17:05
ng_pearce7-Mar-04 17:05 

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.