Click here to Skip to main content
15,885,757 members
Articles / Desktop Programming / WTL

WTL Helper

Rate me:
Please Sign up or sign in to vote.
4.92/5 (116 votes)
27 Aug 200713 min read 702.8K   8.8K   190  
Add-in for Microsoft VC++.NET 2003 that helps to insert message handlers for WTL.
#ifndef __DOTNET_TABCTRL_H__
#define __DOTNET_TABCTRL_H__

#pragma once

/////////////////////////////////////////////////////////////////////////////
// CDotNetTabCtrl - Tab control derived from CCustomTabCtrl
//    meant to look like the tabs in VS.Net (MDI tabs,
//    solution explorer tabs, etc.)
// CDotNetButtonTabCtrl - Tab control derived from CCustomTabCtrl
//    meant to look like VS.Net view of HTML with the Design/HTML buttons
//  
//
// Written by Daniel Bowen (dbowen@es.com).
// Copyright (c) 2001-2004 Daniel Bowen.
//
// This code may be used in compiled form in any way you desire. This
// file may be redistributed by any means PROVIDING it is 
// not sold for profit without the authors written consent, and 
// providing that this notice and the authors name is included. 
//
// This file is provided "as is" with no expressed or implied warranty.
// The author accepts no liability if it causes any damage to you or your
// computer whatsoever. It's free, so don't hassle me about it.
//
// Beware of bugs.
//
// History (Date/Author/Description):
// ----------------------------------
//
// 2005/07/13: Daniel Bowen
// - Namespace qualify the use of more ATL and WTL classes.
//
// 2005/03/14: Daniel Bowen
// - Fix warnings when compiling for 64-bit.
//
// 2004/06/28: Daniel Bowen:
// - Clean up warnings on level 4
//
// 2004/06/21: Peter Carlson:
// - "CanClose" for items.
//
// 2004/04/29: Daniel Bowen
// - Update CDotNetTabCtrlImpl::OnSettingChange so that
//   if the color depth is not greater than 8bpp
//   (so, 256 colors or less), instead of this
//			m_hbrBackground =  CDCHandle::GetHalftoneBrush();
//   (which makes things unreadable at 256 colors), do this:
//			m_hbrBackground.CreateSysColorBrush(COLOR_WINDOW);
// - Support for highlighting items.
//   Add CCustomTabItem::GetHighlight/SetHighlight.
//   Uses the custom draw state CDIS_MARKED.
//
// 2004/01/06: Daniel Bowen
// - Fix compile issue under ICL thanks to Richard Crossley.
//   CDotNetButtonTabCtrl missing template parameters.
//
// 2003/08/01: Daniel Bowen
// - CDotNetButtonTabCtrl can now be used for MDI tabs.
// - There is now a CDotNetButtonTabCtrlImpl, which
//   CDotNetButtonTabCtrl inherits from.
// - Reorganize so that CDotNetTabCtrlImpl is broken up a little bit
//   more into overridable pieces, and have CDotNetButtonTabCtrlImpl
//   inherit from CDotNetTabCtrlImpl.  Have CDotNetButtonTabCtrlImpl
//   only override the parts of CDotNetTabCtrlImpl needed.
//
// 2003/06/27: Daniel Bowen
// - Replace DECLARE_WND_CLASS with DECLARE_WND_CLASS_EX.
//   Use CS_DBLCLKS as the style and COLOR_WINDOW as the brush -
//   which essentially means that it doesn't use CS_HREDRAW or CS_VREDRAW now.
//
// 2002/12/05: Daniel Bowen
// - In CDotNetTabCtrlImpl::UpdateLayout_Default,
//   the code that calculates nRatioWithSelectionFullSize
//   would sometimes get a 0 in the divisor.
//   Guard against divide-by-0.
// - Handle WM_SYSCOLORCHANGE in case its broadcast to us
//   from a top-level window. Call OnSettingChange.
//
// 2002/11/13: Daniel Bowen
// - CDotNetTabCtrl:
//   * Tweaks so that CDotNetTabCtrl matches just a little more
//     closely to the tabs in VS.NET (when tabs are on top).
//   * Override new "CalcSize_NonClient" method as part of the
//     tweaks for CDotNetTabCtrl.  Currently, only the left
//     and right sides of the client area are adjusted for
//     non-client areas (since the drawing code already considers
//     non-client areas above and below in its drawing).
//     In the future, non-client areas could be accounted for
//     in CalcSize_NonClient, and the drawing code could
//     be updated appropriately.
//   * New CTCS_FLATEDGE style.  CDotNetTabCtrl asks about this
//     style when drawing the outline around the tab.  A good
//     use for this style is if you are using the tab control
//     for the MDI tabs, and you have your MDIClient drawn flat
//     (like VS.NET)
//
// 2002/07/16: Daniel Bowen
// - Fix problem that would cause applications using CDotNetTabCtrl
//   with CTCS_CLOSEBUTTON and/or CTCS_SCROLL to not exit "cleanly".
//   There were 2 places referencing m_tooltip that needed to
//   first check if(m_tooltip.IsWindow()).
//
// 2002/06/12: Daniel Bowen
// - Publish codeproject article.  For history prior
//   to the release of the article, please see the article
//   and the section "Note to previous users"

#ifndef __CUSTOMTABCTRL_H__
#include "CustomTabCtrl.h"
#endif

// NOTE: If you are compiling under VC7, be sure to put the following in
// your precompiled header:
//
//extern "C" const int _fltused = 0;

template<typename T, typename TItem = CCustomTabItem, class TBase = ATL::CWindow, class TWinTraits = CCustomTabCtrlWinTraits>
class CDotNetTabCtrlImpl : 
	public CCustomTabCtrl<T, TItem, TBase, TWinTraits>
{
protected:
	typedef CDotNetTabCtrlImpl<T, TItem, TBase, TWinTraits> thisClass;
	typedef CCustomTabCtrl<T, TItem, TBase, TWinTraits> customTabClass;

protected:
	CBrush m_hbrBackground;
	COLORREF m_clrTextInactiveTab, m_clrSelectedTab;

	signed char m_nFontSizeTextTopOffset;

	const signed char m_nMinWidthToDisplayText;

// Constructor
public:
	CDotNetTabCtrlImpl() :
		m_clrTextInactiveTab(::GetSysColor(COLOR_GRAYTEXT)),
		m_clrSelectedTab(::GetSysColor(COLOR_BTNFACE)),
		m_nFontSizeTextTopOffset(0),
		m_nMinWidthToDisplayText(12)
	{
	}

// Message Handling
public:
	DECLARE_WND_CLASS_EX(_T("WTL_DotNetTabCtrl"), CS_DBLCLKS, COLOR_WINDOW)

	BEGIN_MSG_MAP(thisClass)
		MESSAGE_HANDLER(WM_SETTINGCHANGE, OnSettingChange)
		MESSAGE_HANDLER(WM_SYSCOLORCHANGE, OnSettingChange)
		CHAIN_MSG_MAP(customTabClass)
	END_MSG_MAP()

	LRESULT OnSettingChange(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
	{
		DWORD dwStyle = this->GetStyle();

		// Initialize/Reinitialize font
		// Visual Studio.Net seems to use the "icon" font for the tabs
		LOGFONT lfIcon = { 0 };
		::SystemParametersInfo(SPI_GETICONTITLELOGFONT, sizeof(lfIcon), &lfIcon, 0);

		bool bResetFont = true;
		if(!m_font.IsNull())
		{
			LOGFONT lf = {0};
			if(m_font.GetLogFont(&lf))
			{
				if(lstrcmpi(lf.lfFaceName, lfIcon.lfFaceName) == 0 &&
					lf.lfHeight == lfIcon.lfHeight)
				{
					bResetFont = false;
				}
			}
		}

		if(bResetFont)
		{
			if(!m_font.IsNull()) m_font.DeleteObject();
			if(!m_fontSel.IsNull()) m_fontSel.DeleteObject();

			HFONT font = m_font.CreateFontIndirect(&lfIcon);
			if(font==NULL)
			{
				m_font.Attach(AtlGetDefaultGuiFont());
			}

			if(CTCS_BOLDSELECTEDTAB == (dwStyle & CTCS_BOLDSELECTEDTAB))
			{
				lfIcon.lfWeight = FW_BOLD;
			}

			font = m_fontSel.CreateFontIndirect(&lfIcon);
			if(font==NULL)
			{
				m_fontSel.Attach(AtlGetDefaultGuiFont());
			}
		}

		// Background brush
		if(!m_hbrBackground.IsNull()) m_hbrBackground.DeleteObject();
		WTL::CWindowDC dcWindow(NULL);
		int nBitsPerPixel = dcWindow.GetDeviceCaps(BITSPIXEL);
		if(nBitsPerPixel > 8)
		{
			//COLORREF clrBtnHilite = ::GetSysColor(COLOR_BTNHILIGHT);
			//COLORREF clrBtnFace = ::GetSysColor(COLOR_BTNFACE);
			//m_clrBackground =
			//	RGB( GetRValue(clrBtnFace) + ((GetRValue(clrBtnHilite) - GetRValue(clrBtnFace)) / 3 * 2),
			//		GetGValue(clrBtnFace) + ((GetGValue(clrBtnHilite) - GetGValue(clrBtnFace)) / 3 * 2),
			//		GetBValue(clrBtnFace) + ((GetBValue(clrBtnHilite) - GetBValue(clrBtnFace)) / 3 * 2),
			//	);
			//m_hbrBackground.CreateSolidBrush(m_clrBackground);

			COLORREF clrBtnFace = ::GetSysColor(COLOR_BTNFACE);

			// This is a brave attempt to mimic the algorithm that Visual Studio.Net
			// uses to calculate the tab's background color and inactive tab color.
			// The other colors that VS.Net uses seems to be standard ones,
			// but these two colors are calculated.
			BYTE nRed = 0, nGreen = 0, nBlue = 0, nMax = 0;

			// Early experiments seemed to reveal that the background color is dependant
			// on COLOR_BTNFACE.  The following algorithm is just an attempt
			// to match several empirical results.  I tested with 20 variations
			// on COLOR_BTNFACE and kept track of what the tab background became.
			// I then brought the numbers into Excel, and started crunching on the numbers
			// until I came up with a formula that seems to pretty well match.

			nRed = GetRValue(clrBtnFace);
			nGreen = GetGValue(clrBtnFace);
			nBlue = GetBValue(clrBtnFace);

			nMax = (nRed > nGreen) ? ((nRed > nBlue) ? nRed : nBlue) : ((nGreen > nBlue) ? nGreen : nBlue);
			const BYTE nMagicBackgroundOffset = (nMax > (0xFF - 35)) ? (BYTE)(0xFF - nMax) : (BYTE)35;
			if(nMax == 0)
			{
				nRed = (BYTE)(nRed + nMagicBackgroundOffset);
				nGreen = (BYTE)(nGreen + nMagicBackgroundOffset);
				nBlue = (BYTE)(nBlue + nMagicBackgroundOffset);
			}
			else
			{
				nRed = (BYTE)(nRed + (nMagicBackgroundOffset*(nRed/(double)nMax) + 0.5));
				nGreen = (BYTE)(nGreen + (nMagicBackgroundOffset*(nGreen/(double)nMax) + 0.5));
				nBlue = (BYTE)(nBlue + (nMagicBackgroundOffset*(nBlue/(double)nMax) + 0.5));
			}

			m_hbrBackground.CreateSolidBrush(RGB(nRed, nGreen, nBlue));


			// The inactive tab color seems to be calculated in a similar way to
			// the tab background, only instead of lightening BNTFACE, it darkens GRAYTEXT.
			COLORREF clrGrayText = ::GetSysColor(COLOR_GRAYTEXT);

			nRed = GetRValue(clrGrayText);
			nGreen = GetGValue(clrGrayText);
			nBlue = GetBValue(clrGrayText);

			nMax = (nRed > nGreen) ? ((nRed > nBlue) ? nRed : nBlue) : ((nGreen > nBlue) ? nGreen : nBlue);
			const BYTE nMagicInactiveOffset = 43;
			if(nMax != 0)
			{
				if(nRed < nMagicInactiveOffset)
					nRed = (BYTE)(nRed / 2);
				else
					nRed  = (BYTE)(nRed - (nMagicInactiveOffset*(nRed/(double)nMax) + 0.5));

				if(nGreen < nMagicInactiveOffset)
					nGreen = (BYTE)(nGreen / 2);
				else
					nGreen = (BYTE)(nGreen - (nMagicInactiveOffset*(nGreen/(double)nMax) + 0.5));

				if(nBlue < nMagicInactiveOffset)
					nBlue = (BYTE)(nBlue / 2);
				else
					nBlue = (BYTE)(nBlue - (nMagicInactiveOffset*(nBlue/(double)nMax) + 0.5));
			}

			m_clrTextInactiveTab = RGB(nRed, nGreen, nBlue);
		}
		else
		{
			m_hbrBackground.CreateSysColorBrush(COLOR_WINDOW);
			m_clrTextInactiveTab = ::GetSysColor(COLOR_GRAYTEXT);
		}

		m_settings.iIndent = 5;
		m_settings.iPadding = 4;
		m_settings.iMargin = 3;
		m_settings.iSelMargin = 3;

		int nHeightLogicalUnits = -lfIcon.lfHeight;
		// In MSDN for "LOGFONT", they give the following formula for calculating
		// the log font height given a point size.
		//long lfHeight = -MulDiv(PointSize, GetDeviceCaps(hDC, LOGPIXELSY), 72);

		const int nNominalFontLogicalUnits = 11;	// 8 point Tahoma with 96 DPI
		m_nFontSizeTextTopOffset = (BYTE)((nHeightLogicalUnits - nNominalFontLogicalUnits) / 2);

		T* pT = static_cast<T*>(this);
		pT->UpdateLayout();
		pT->Invalidate();
		return 0;
	}


// Overrideables
public:
	void DrawBackground(RECT rcClient, LPNMCTCCUSTOMDRAW lpNMCustomDraw)
	{
		WTL::CDCHandle dc( lpNMCustomDraw->nmcd.hdc );

		// Set up the text color and background mode
		dc.SetTextColor(lpNMCustomDraw->clrBtnText);
		dc.SetBkMode(TRANSPARENT);

		RECT rcClip = {0};
		dc.GetClipBox(&rcClip);

		if(::EqualRect(&rcClip, &m_rcCloseButton) ||
			::EqualRect(&rcClip, &m_rcScrollRight) ||
			::EqualRect(&rcClip, &m_rcScrollLeft))
		{
			// Paint needed in only "other button" area

			HBRUSH hOldBrush = dc.SelectBrush(lpNMCustomDraw->hBrushBackground);
			dc.PatBlt(rcClip.left, rcClip.top, rcClip.right-rcClip.left, rcClip.bottom-rcClip.top, PATCOPY);
			dc.SelectBrush(hOldBrush);
		}
		else
		{
			// Paint needed in tab item area or more

			// Erase Background
			//  (do it here instead of a handler for WM_ERASEBKGND
			//   so that we can do flicker-free drawing with the help
			//   of COffscreenDrawRect that's in the base class)

			// TODO: Don't "erase" entire client area.
			//  Do a smarter erase of just what needs it

			RECT rc = rcClient;

			HBRUSH hOldBrush = dc.SelectBrush(lpNMCustomDraw->hBrushBackground);
			dc.PatBlt(rc.left, rc.top, rc.right-rc.left, rc.bottom-rc.top, PATCOPY);
			dc.SelectBrush(hOldBrush);

			// Connect with the client area.
			DWORD dwStyle = this->GetStyle();

			if(CTCS_BOTTOM == (dwStyle & CTCS_BOTTOM))
			{
				rc.bottom = rc.top + 3;
				dc.FillSolidRect(&rc, lpNMCustomDraw->clrBtnFace);

				CPen penText;
				penText.CreatePen(PS_SOLID, 1, lpNMCustomDraw->clrBtnText);
				CPenHandle penOld = dc.SelectPen(penText);

				dc.MoveTo(rc.left, rc.bottom);
				dc.LineTo(rc.right, rc.bottom);

				dc.SelectPen(penOld);
			}
			else
			{
				int nOrigTop = rc.top;
				rc.top = rc.bottom - 2;
				dc.FillSolidRect(&rc, lpNMCustomDraw->clrBtnFace);

				CPen penHilight;
				penHilight.CreatePen(PS_SOLID, 1, lpNMCustomDraw->clrBtnHighlight);
				CPenHandle penOld = dc.SelectPen(penHilight);

				dc.MoveTo(rc.left, rc.top-1);
				dc.LineTo(rc.right, rc.top-1);

				rc.top = nOrigTop;

				CPen penShadow, pen3D;
				penShadow.CreatePen(PS_SOLID, 1, lpNMCustomDraw->clrBtnShadow);
				pen3D.CreatePen(PS_SOLID, 1, lpNMCustomDraw->clrBtnFace);
				dc.SelectPen(penShadow);

				dc.MoveTo(rc.left, rc.bottom);
				dc.LineTo(rc.left, rc.top);
				dc.LineTo(rc.right-1, rc.top);
						
				if(0 == (dwStyle & CTCS_FLATEDGE))
				{
					dc.SelectPen(penHilight);
				}
				dc.LineTo(rc.right-1, rc.bottom);

				dc.SelectPen(pen3D);
				dc.MoveTo(rc.right-2, rc.bottom-3);
				dc.LineTo(rc.right-2, rc.top);
				dc.MoveTo(rc.left+1, rc.bottom-3);
				dc.LineTo(rc.left+1, rc.top);

				dc.SelectPen(penOld);
			}

		}
	}

	void DrawItem_InitBounds(DWORD dwStyle, RECT rcItem, RECT& rcTab, RECT& rcText, int& nIconVerticalCenter)
	{
		if(CTCS_BOTTOM == (dwStyle & CTCS_BOTTOM))
		{
			rcTab.top += 3;
			rcTab.bottom -= 2;

			rcText.top = rcTab.top+1 + m_nFontSizeTextTopOffset;
			rcText.bottom = rcItem.bottom;
			//nIconVerticalCenter = rcTab.top + (rc.bottom - rcTab.top) / 2;
			//nIconVerticalCenter = rcTab.top + rcText.Height() / 2;
			nIconVerticalCenter = (rcItem.bottom + rcItem.top) / 2 + rcTab.top / 2;
		}
		else
		{
			rcTab.top += 3;
			rcTab.bottom -= 2;

			rcText.top = rcItem.top+1 + m_nFontSizeTextTopOffset;
			rcText.bottom = rcItem.bottom;
			nIconVerticalCenter = (rcItem.bottom + rcItem.top) / 2 + rcTab.top / 2;
		}
	}

	void DrawItem_TabSelected(DWORD dwStyle, LPNMCTCCUSTOMDRAW lpNMCustomDraw, RECT& rcTab)
	{
		// Tab is selected, so paint tab folder

		bool bHighlighted = (CDIS_MARKED == (lpNMCustomDraw->nmcd.uItemState & CDIS_MARKED));

		WTL::CDCHandle dc(lpNMCustomDraw->nmcd.hdc);

		rcTab.right--;
		if(bHighlighted)
		{
			dc.FillSolidRect(&rcTab, lpNMCustomDraw->clrHighlight);
		}
		else
		{
			dc.FillSolidRect(&rcTab, lpNMCustomDraw->clrSelectedTab);
		}

		WTL::CPen penText, penHilight;
		penText.CreatePen(PS_SOLID, 1, lpNMCustomDraw->clrBtnText);
		penHilight.CreatePen(PS_SOLID, 1, lpNMCustomDraw->clrBtnHighlight);

		if(CTCS_BOTTOM == (dwStyle & CTCS_BOTTOM))
		{
			WTL::CPenHandle penOld = dc.SelectPen(penText);

			dc.MoveTo(rcTab.right, rcTab.top);
			dc.LineTo(rcTab.right, rcTab.bottom);
			dc.LineTo(rcTab.left, rcTab.bottom);
			dc.SelectPen(penHilight);
			dc.LineTo(rcTab.left, rcTab.top-1);

			dc.SelectPen(penOld);
		}
		else
		{
			WTL::CPenHandle penOld = dc.SelectPen(penHilight);

			dc.MoveTo(rcTab.left, rcTab.bottom-1);
			dc.LineTo(rcTab.left, rcTab.top);
			dc.LineTo(rcTab.right, rcTab.top);
			dc.SelectPen(penText);
			dc.LineTo(rcTab.right, rcTab.bottom);

			dc.SelectPen(penOld);
		}
	}

	void DrawItem_TabInactive(DWORD dwStyle, LPNMCTCCUSTOMDRAW lpNMCustomDraw, RECT& rcTab)
	{
		// Tab is not selected

		bool bHighlighted = (CDIS_MARKED == (lpNMCustomDraw->nmcd.uItemState & CDIS_MARKED));

		int nItem = (int)lpNMCustomDraw->nmcd.dwItemSpec;
		WTL::CDCHandle dc( lpNMCustomDraw->nmcd.hdc );

		if(bHighlighted)
		{
			if(CTCS_BOTTOM == (dwStyle & CTCS_BOTTOM))
			{
				RECT rcHighlight = {rcTab.left+1, rcTab.top+3, rcTab.right-2, rcTab.bottom-1};
				if(nItem - 1 == m_iCurSel) rcHighlight.left += 1;  // Item to the right of the selected tab
				dc.FillSolidRect(&rcHighlight, lpNMCustomDraw->clrHighlight);
			}
			else
			{
				RECT rcHighlight = {rcTab.left+1, rcTab.top+2, rcTab.right-2, rcTab.bottom-2};
				if(nItem - 1 == m_iCurSel) rcHighlight.left += 1;  // Item to the right of the selected tab
				dc.FillSolidRect(&rcHighlight, lpNMCustomDraw->clrHighlight);
			}
		}

		// Draw division line on right, unless we're the item
		// on the left of the selected tab
		if(nItem + 1 == m_iCurSel)
		{
			// Item just left of selected tab
		}
		else
		{
			WTL::CPen pen;
			pen.CreatePen(PS_SOLID, 1, lpNMCustomDraw->clrBtnShadow);
			WTL::CPenHandle penOld = dc.SelectPen(pen);
			if(CTCS_BOTTOM == (dwStyle & CTCS_BOTTOM))
			{
				// Important!  Be sure and keep within "our" tab area horizontally
				dc.MoveTo(rcTab.right-1, rcTab.top + 3);
				dc.LineTo(rcTab.right-1, rcTab.bottom - 1);
			}
			else
			{
				// Important!  Be sure and keep within "our" tab area horizontally
				dc.MoveTo(rcTab.right-1, rcTab.top + 2);
				dc.LineTo(rcTab.right-1, rcTab.bottom - 2);
			}
			dc.SelectPen(penOld);
		}
	}

	void DrawItem_ImageAndText(DWORD /*dwStyle*/, LPNMCTCCUSTOMDRAW lpNMCustomDraw, int nIconVerticalCenter, RECT& rcTab, RECT& rcText)
	{
		WTL::CDCHandle dc( lpNMCustomDraw->nmcd.hdc );
		bool bHighlighted = (CDIS_MARKED == (lpNMCustomDraw->nmcd.uItemState & CDIS_MARKED));
		bool bSelected = (CDIS_SELECTED == (lpNMCustomDraw->nmcd.uItemState & CDIS_SELECTED));
		bool bHot = (CDIS_HOT == (lpNMCustomDraw->nmcd.uItemState & CDIS_HOT));
		int nItem = (int)lpNMCustomDraw->nmcd.dwItemSpec;

		TItem* pItem = this->GetItem(nItem);

		HFONT hOldFont = NULL;
		if(bSelected)
		{
			hOldFont = dc.SelectFont(lpNMCustomDraw->hFontSelected);
		}
		else
		{
			hOldFont = dc.SelectFont(lpNMCustomDraw->hFontInactive);
		}

		COLORREF crPrevious = 0;
		if(bHighlighted)
		{
			crPrevious = dc.SetTextColor(lpNMCustomDraw->clrHighlightText);
		}
		else if(bHot)
		{
			crPrevious = dc.SetTextColor(lpNMCustomDraw->clrHighlightHotTrack);
		}
		else if(bSelected)
		{
			crPrevious = dc.SetTextColor(lpNMCustomDraw->clrTextSelected);
		}
		else
		{
			crPrevious = dc.SetTextColor(lpNMCustomDraw->clrTextInactive);
		}


		//--------------------------------------------
		// This is how CDotNetTabCtrlImpl interprets padding, margin, etc.:
		//
		//  M - Margin
		//  P - Padding
		//  I - Image
		//  Text - Tab Text
		//
		// With image:
		//     __________________________
		//
		//    | M | I | P | Text | P | M |
		//     --------------------------
		//
		// Without image:
		//     ______________________
		//
		//    | M | P | Text | P | M |
		//     ----------------------

		//rcText.left += (bSelected ? m_settings.iSelMargin : m_settings.iMargin);
		rcText.left += m_settings.iMargin;
		rcText.right -= m_settings.iMargin;
		if (pItem->UsingImage() && !m_imageList.IsNull())
		{
			// Draw the image.
			IMAGEINFO ii = {0};
			int nImageIndex = pItem->GetImageIndex();
			m_imageList.GetImageInfo(nImageIndex, &ii);

			if((ii.rcImage.right - ii.rcImage.left) < (rcTab.right - rcTab.left))
			{
				int nImageHalfHeight = (ii.rcImage.bottom - ii.rcImage.top) / 2;
				m_imageList.Draw(dc, nImageIndex, rcText.left, nIconVerticalCenter - nImageHalfHeight + m_nFontSizeTextTopOffset, ILD_NORMAL);
			}

			// Offset on the right of the image.
			rcText.left += (ii.rcImage.right - ii.rcImage.left);
		}

		if (rcText.left + m_nMinWidthToDisplayText < rcText.right)
		{
			::InflateRect(&rcText, -m_settings.iPadding, 0);

			_CSTRING_NS::CString sText = pItem->GetText();
			dc.DrawText(sText, sText.GetLength(), &rcText, DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_PATH_ELLIPSIS);
		}

		dc.SetTextColor(crPrevious);
		dc.SelectFont(hOldFont);
	}

	void DrawCloseButton(LPNMCTCCUSTOMDRAW lpNMCustomDraw)
	{
		WTL::CDCHandle dc( lpNMCustomDraw->nmcd.hdc );

		WTL::CPen penButtons;
		penButtons.CreatePen(PS_SOLID, 1, lpNMCustomDraw->clrTextInactive);
		WTL::CBrush brushArrow;
		brushArrow.CreateSolidBrush(lpNMCustomDraw->clrTextInactive);

		WTL::CPenHandle penOld = dc.SelectPen(penButtons);
		WTL::CBrushHandle brushOld = dc.SelectBrush(brushArrow);

		RECT rcX = m_rcCloseButton;

		if(ectcMouseDownL_CloseButton == (m_dwState & ectcMouseDown))
		{
			if(ectcMouseOver_CloseButton == (m_dwState & ectcMouseOver))
			{
				::OffsetRect(&rcX, 1, 1);
			}
		}

		const int sp = 4;

		dc.MoveTo(rcX.left+sp+ -1, rcX.top+sp);
		dc.LineTo(rcX.right-sp -1, rcX.bottom-sp);
		dc.MoveTo(rcX.left+sp, rcX.top+sp);
		dc.LineTo(rcX.right-sp, rcX.bottom-sp);

		dc.MoveTo(rcX.left+sp -1, rcX.bottom-sp -1);
		dc.LineTo(rcX.right-sp -1, rcX.top+sp -1 );
		dc.MoveTo(rcX.left+sp, rcX.bottom-sp -1);
		dc.LineTo(rcX.right-sp, rcX.top+sp -1);

		if(ectcMouseDownL_CloseButton == (m_dwState & ectcMouseDown))
		{
			if(ectcMouseOver_CloseButton == (m_dwState & ectcMouseOver))
			{
				dc.DrawEdge(&m_rcCloseButton, BDR_SUNKENOUTER, BF_RECT);
			}
		}
		else if(ectcHotTrack_CloseButton == (m_dwState & ectcHotTrack))
		{
			dc.DrawEdge(&m_rcCloseButton, BDR_RAISEDINNER, BF_RECT);
		}

		dc.SelectBrush(brushOld);
		dc.SelectPen(penOld);
	}

	void DrawScrollButtons(LPNMCTCCUSTOMDRAW lpNMCustomDraw)
	{
		WTL::CDCHandle dc( lpNMCustomDraw->nmcd.hdc );

		WTL::CPen penButtons;
		penButtons.CreatePen(PS_SOLID, 1, lpNMCustomDraw->clrTextInactive);
		WTL::CBrush brushArrow;
		brushArrow.CreateSolidBrush(lpNMCustomDraw->clrTextInactive);

		WTL::CPenHandle penOld = dc.SelectPen(penButtons);
		WTL::CBrushHandle brushOld = dc.SelectBrush(brushArrow);

		RECT rcArrowRight = m_rcScrollRight;
		RECT rcArrowLeft = m_rcScrollLeft;

		if(ectcMouseDownL_ScrollRight == (m_dwState & ectcMouseDown))
		{
			if(ectcMouseOver_ScrollRight == (m_dwState & ectcMouseOver))
			{
				if(ectcOverflowRight == (m_dwState & ectcOverflowRight))
				{
					::OffsetRect(&rcArrowRight, 1, 1);
				}
			}
		}
		if(ectcMouseDownL_ScrollLeft == (m_dwState & ectcMouseDown))
		{
			if(ectcMouseOver_ScrollLeft == (m_dwState & ectcMouseOver))
			{
				if(ectcOverflowLeft == (m_dwState & ectcOverflowLeft))
				{
					::OffsetRect(&rcArrowLeft, 1, 1);
				}
			}
		}

		const int spRight = 5;
		const int spLeft = 6;

		POINT ptsArrowRight[] = {
			{rcArrowRight.left+spRight, rcArrowRight.top+spRight -2},
			{rcArrowRight.left+spRight, rcArrowRight.bottom-spRight +1},
			{rcArrowRight.right-spRight -1, (rcArrowRight.bottom + m_rcScrollRight.top) / 2},
			{rcArrowRight.left+spRight, rcArrowRight.top+spRight -2}
		};
		if(ectcOverflowRight != (m_dwState & ectcOverflowRight))
		{
			dc.Polyline(ptsArrowRight, 4);
		}
		else
		{
			dc.Polygon(ptsArrowRight, 4);

			if(ectcMouseDownL_ScrollRight == (m_dwState & ectcMouseDown))
			{
				if(ectcMouseOver_ScrollRight == (m_dwState & ectcMouseOver))
				{
					dc.DrawEdge(&m_rcScrollRight, BDR_SUNKENOUTER, BF_RECT);
				}
			}
			else if(ectcHotTrack_ScrollRight == (m_dwState & ectcHotTrack))
			{
				dc.DrawEdge(&m_rcScrollRight, BDR_RAISEDINNER, BF_RECT);
			}
		}

		POINT ptsArrowLeft[] = {
			{rcArrowLeft.right-spLeft, rcArrowLeft.top+spLeft -3},
			{rcArrowLeft.right-spLeft, rcArrowLeft.bottom-spLeft +2},
			{rcArrowLeft.left+spLeft -1, (rcArrowLeft.bottom + m_rcScrollLeft.top) / 2},
			{rcArrowLeft.right-spLeft, rcArrowLeft.top+spLeft -3}
		};
		if(ectcOverflowLeft != (m_dwState & ectcOverflowLeft))
		{
			dc.Polyline(ptsArrowLeft, 4);
		}
		else
		{
			dc.Polygon(ptsArrowLeft, 4);

			if(ectcMouseDownL_ScrollLeft == (m_dwState & ectcMouseDown))
			{
				if(ectcMouseOver_ScrollLeft == (m_dwState & ectcMouseOver))
				{
					dc.DrawEdge(&m_rcScrollLeft, BDR_SUNKENOUTER, BF_RECT);
				}
			}
			else if(ectcHotTrack_ScrollLeft == (m_dwState & ectcHotTrack))
			{
				dc.DrawEdge(&m_rcScrollLeft, BDR_RAISEDINNER, BF_RECT);
			}
		}

		dc.SelectBrush(brushOld);
		dc.SelectPen(penOld);
	}

// Overrides for painting from CCustomTabCtrl
public:

	void InitializeDrawStruct(LPNMCTCCUSTOMDRAW lpNMCustomDraw)
	{
		//DWORD dwStyle = this->GetStyle();

		lpNMCustomDraw->hFontInactive = m_font;
		lpNMCustomDraw->hFontSelected = m_fontSel;
		lpNMCustomDraw->hBrushBackground = m_hbrBackground;
		lpNMCustomDraw->clrTextSelected = ::GetSysColor(COLOR_BTNTEXT);
		lpNMCustomDraw->clrTextInactive = m_clrTextInactiveTab;
		lpNMCustomDraw->clrSelectedTab = m_clrSelectedTab;
		lpNMCustomDraw->clrBtnFace = ::GetSysColor(COLOR_BTNFACE);
		lpNMCustomDraw->clrBtnShadow = ::GetSysColor(COLOR_BTNSHADOW);
		lpNMCustomDraw->clrBtnHighlight = ::GetSysColor(COLOR_BTNHIGHLIGHT);
		lpNMCustomDraw->clrBtnText = ::GetSysColor(COLOR_BTNTEXT);
		lpNMCustomDraw->clrHighlight = ::GetSysColor(COLOR_HIGHLIGHT);
#if WINVER >= 0x0500 || _WIN32_WINNT >= 0x0500
		lpNMCustomDraw->clrHighlightHotTrack = ::GetSysColor(COLOR_HOTLIGHT);
#else
		lpNMCustomDraw->clrHighlightHotTrack = ::GetSysColor(COLOR_HIGHLIGHT);
#endif
		lpNMCustomDraw->clrHighlightText = ::GetSysColor(COLOR_HIGHLIGHTTEXT);
	}

	void DoPrePaint(RECT rcClient, LPNMCTCCUSTOMDRAW lpNMCustomDraw)
	{
		T* pT = static_cast<T*>(this);

		pT->DrawBackground(rcClient, lpNMCustomDraw);
	}

	void DoItemPaint(LPNMCTCCUSTOMDRAW lpNMCustomDraw)
	{
		T* pT = static_cast<T*>(this);

		bool bSelected = (CDIS_SELECTED == (lpNMCustomDraw->nmcd.uItemState & CDIS_SELECTED));
		// NOTE: lpNMCustomDraw->nmcd.rc is in logical coordinates
		RECT &rcItem = lpNMCustomDraw->nmcd.rc;

		DWORD dwStyle = pT->GetStyle();
		RECT rcTab = rcItem;
		RECT rcText = rcItem;
		int nIconVerticalCenter = 0;

		pT->DrawItem_InitBounds(dwStyle, rcItem, rcTab, rcText, nIconVerticalCenter);

		if(bSelected)
		{
			pT->DrawItem_TabSelected(dwStyle, lpNMCustomDraw, rcTab);
		}
		else
		{
			pT->DrawItem_TabInactive(dwStyle, lpNMCustomDraw, rcTab);
		}

		pT->DrawItem_ImageAndText(dwStyle, lpNMCustomDraw, nIconVerticalCenter, rcTab, rcText);
	}

	void DoPostPaint(RECT /*rcClient*/, LPNMCTCCUSTOMDRAW lpNMCustomDraw)
	{
		T* pT = static_cast<T*>(this);

		DWORD dwStyle = this->GetStyle();

		if(0 == (dwStyle & (CTCS_CLOSEBUTTON | CTCS_SCROLL)))
		{
			return;
		}

		// Close Button
		if(CTCS_CLOSEBUTTON == (dwStyle & CTCS_CLOSEBUTTON))
		{
			if( (m_iCurSel >= 0) && ((size_t)m_iCurSel < m_Items.GetCount()) )
			{
				TItem* pItem = m_Items[m_iCurSel];
				ATLASSERT(pItem != NULL);
				if((pItem != NULL) && pItem->CanClose())
				{
					pT->DrawCloseButton(lpNMCustomDraw);
				}
			}
		}

		// Scroll Buttons
		if(CTCS_SCROLL == (dwStyle & CTCS_SCROLL))
		{
			pT->DrawScrollButtons(lpNMCustomDraw);
		}
	}

// Overrides from CCustomTabCtrl
public:

	void CalcSize_NonClient(LPRECT prcTabItemArea)
	{
		// account for "non-client" areas
		// TODO: For the short term, we will use this
		//  for the non-client areas on the left and right.
		//  The drawing code for the tabs already accounts
		//  for the "non-client" areas on the top and bottom, and
		//  would need to be updated if we account for it here.
		//  Tab item rect methods also would need to be
		//  updated to account for the non-client areas
		//  on top and bottom (and effected drawing code
		//  would need to be updated).
		DWORD dwStyle = this->GetStyle();

		if(CTCS_BOTTOM == (dwStyle & CTCS_BOTTOM))
		{
			// TODO: Update to actually specify the
			//  non-client areas, and adjust all of the
			//  effected drawing code, as well as
			//  tab item rect related things
			//prcTabItemArea->top += 3;
		}
		else
		{
			prcTabItemArea->left += 2;
			prcTabItemArea->right -= 2;

			// TODO: Update to actually specify the top and bottom
			//  non-client areas, and adjust all of the
			//  effected drawing code, as well as
			//  tab item rect related things
			//prcTabItemArea->top += 1;
			//// We would have bottom as 3, but we want the
			//// selected tab to actually paint over highlight part
			//prcTabItemArea->bottom -= 2;
		}
	}

	void CalcSize_CloseButton(LPRECT prcTabItemArea)
	{
		//int nButtonSizeX = ::GetSystemMetrics(SM_CXSMSIZE);
		//int nButtonSizeY = ::GetSystemMetrics(SM_CYSMSIZE);
		// NOTE: After several tests, VS.Net does NOT depend on
		//  any system metric for the button size, so neither will we.
		int nButtonSizeX = 15;
		int nButtonSizeY = 15;

		if((prcTabItemArea->right - prcTabItemArea->left) < nButtonSizeX)
		{
			::SetRectEmpty(&m_rcCloseButton);
			return;
		}

		m_rcCloseButton = *prcTabItemArea;

		DWORD dwStyle = this->GetStyle();

		if (CTCS_BOTTOM == (dwStyle & CTCS_BOTTOM))
		{
			m_rcCloseButton.top += 3;
			m_rcCloseButton.right -= 3;
		}
		else
		{
			m_rcCloseButton.top += 1;
			m_rcCloseButton.bottom -= 2;
			m_rcCloseButton.right -= 2;
		}
		m_rcCloseButton.top = (m_rcCloseButton.bottom + m_rcCloseButton.top - nButtonSizeY) / 2;
		m_rcCloseButton.bottom = m_rcCloseButton.top + nButtonSizeY;

		m_rcCloseButton.left = m_rcCloseButton.right - (nButtonSizeX);

		if(m_tooltip.IsWindow())
		{
			m_tooltip.SetToolRect(m_hWnd, (UINT)ectcToolTip_Close, &m_rcCloseButton);
		}

		// Adjust the tab area
		prcTabItemArea->right = m_rcCloseButton.left;
	}

	void CalcSize_ScrollButtons(LPRECT prcTabItemArea)
	{
		//int nButtonSizeX = ::GetSystemMetrics(SM_CXSMSIZE);
		//int nButtonSizeY = ::GetSystemMetrics(SM_CYSMSIZE);
		// NOTE: After several tests, VS.Net does NOT depend on
		//  any system metric for the button size, so neither will we.
		int nButtonSizeX = 15;
		int nButtonSizeY = 15;

		if((prcTabItemArea->right - prcTabItemArea->left) < nButtonSizeX)
		{
			::SetRectEmpty(&m_rcScrollRight);
			::SetRectEmpty(&m_rcScrollLeft);
			return;
		}

		RECT rcScroll = *prcTabItemArea;

		DWORD dwStyle = this->GetStyle();

		if (CTCS_BOTTOM == (dwStyle & CTCS_BOTTOM))
		{
			rcScroll.top += 3;
			if(0 == (dwStyle & CTCS_CLOSEBUTTON))
			{
				rcScroll.right -= 3;
			}
		}
		else
		{
			rcScroll.top += 1;
			rcScroll.bottom -= 2;
			if(0 == (dwStyle & CTCS_CLOSEBUTTON))
			{
				rcScroll.right -= 2;
			}
		}
		rcScroll.top = (rcScroll.bottom + rcScroll.top - nButtonSizeY) / 2;
		rcScroll.bottom = rcScroll.top + nButtonSizeY;

		m_rcScrollRight = rcScroll;
		m_rcScrollLeft = rcScroll;

		m_rcScrollRight.left = m_rcScrollRight.right - nButtonSizeX;

		m_rcScrollLeft.right = m_rcScrollRight.left;
		m_rcScrollLeft.left = m_rcScrollLeft.right - nButtonSizeX;

		if(m_tooltip.IsWindow())
		{
			m_tooltip.SetToolRect(m_hWnd, (UINT)ectcToolTip_ScrollRight, &m_rcScrollRight);
			m_tooltip.SetToolRect(m_hWnd, (UINT)ectcToolTip_ScrollLeft, &m_rcScrollLeft);
		}

		// Adjust the tab area
		prcTabItemArea->right = m_rcScrollLeft.left;
	}


	void UpdateLayout_Default(RECT rcTabItemArea)
	{
		long nMinInactiveWidth = 0x7FFFFFFF;
		long nMaxInactiveWidth = 0;

		//DWORD dwStyle = this->GetStyle();

		WTL::CClientDC dc(m_hWnd);
		//HFONT hOldFont = dc.SelectFont(lpNMCustomDraw->hFontInactive);
		HFONT hOldFont = dc.SelectFont(m_font);

		LONG nTabAreaWidth = (rcTabItemArea.right - rcTabItemArea.left);

		RECT rcItem = rcTabItemArea;
		// rcItem.top and rcItem.bottom aren't really going to change

		// Recalculate tab positions and widths
		// See DrawItem_ImageAndText for a discussion of how CDotNetTabCtrlImpl
		//  interprets margin, padding, etc.
		size_t nCount = m_Items.GetCount();
		int xpos = m_settings.iIndent;
		HFONT hRestoreNormalFont = NULL;
		for( size_t i=0; i<nCount; ++i )
		{
			bool bSelected = ((int)i == m_iCurSel);
			if(bSelected)
			{
				//hRestoreNormalFont = dc.SelectFont(lpNMCustomDraw->hFontSelected);
				hRestoreNormalFont = dc.SelectFont(m_fontSel);
			}

			TItem* pItem = m_Items[i];
			ATLASSERT(pItem != NULL);
			rcItem.left = rcItem.right = xpos;
			//rcItem.right += ((bSelected ? m_settings.iSelMargin : m_settings.iMargin));
			rcItem.right += m_settings.iMargin;
			if(pItem->UsingImage() && !m_imageList.IsNull())
			{
				IMAGEINFO ii = {0};
				int nImageIndex = pItem->GetImageIndex();
				m_imageList.GetImageInfo(nImageIndex, &ii);
				rcItem.right += (ii.rcImage.right - ii.rcImage.left);
			}
			if(pItem->UsingText())
			{
				RECT rcText = {0};
				_CSTRING_NS::CString sText = pItem->GetText();
				dc.DrawText(sText, sText.GetLength(), &rcText, DT_SINGLELINE | DT_CALCRECT);
				rcItem.right += (rcText.right - rcText.left) + (m_settings.iPadding * 2);
			}
			rcItem.right += m_settings.iMargin;
			pItem->SetRect(rcItem);
			xpos += (rcItem.right - rcItem.left);

			if(hRestoreNormalFont != NULL)
			{
				dc.SelectFont(hRestoreNormalFont);
				hRestoreNormalFont = NULL;
			}

			if(!bSelected)
			{
				if((rcItem.right - rcItem.left) < nMinInactiveWidth)
				{
					nMinInactiveWidth = (rcItem.right - rcItem.left);
				}
				if((rcItem.right - rcItem.left) > nMaxInactiveWidth)
				{
					nMaxInactiveWidth = (rcItem.right - rcItem.left);
				}
			}
		}
		xpos += m_settings.iIndent;

		if(xpos > nTabAreaWidth && nCount > 0 && m_iCurSel >= 0)
		{
			// Our desired widths are more than the width of the client area.
			// We need to have some or all of the tabs give up some real estate

			// We'll try to let the selected tab have its fully desired width.
			// If it can't, we'll make all the tabs the same width.

			RECT rcSelected = m_Items[m_iCurSel]->GetRect();
			LONG nSelectedWidth = (rcSelected.right - rcSelected.left);

			long cxClientInactiveTabs = nTabAreaWidth - (m_settings.iIndent * 2) - nSelectedWidth;
			long cxDesiredInactiveTabs = xpos - (m_settings.iIndent * 2) - nSelectedWidth;

			double nRatioWithSelectionFullSize = 0.0;
			if(cxDesiredInactiveTabs != 0)
			{
				nRatioWithSelectionFullSize = (double) (cxClientInactiveTabs) / (double)(cxDesiredInactiveTabs);
			}

			long nInactiveSameSizeWidth = (m_nMinWidthToDisplayText + (m_settings.iMargin*2) + (m_settings.iPadding));

			if(cxClientInactiveTabs > (nInactiveSameSizeWidth * (long)(nCount-1)))
			{
				//  There should be enough room to display the entire contents of
				//  the selected tab plus something for the inactive tabs

				bool bMakeInactiveSameSize = ((nMinInactiveWidth * nRatioWithSelectionFullSize) < nInactiveSameSizeWidth);

				xpos = m_settings.iIndent;
				for(size_t i=0; i<nCount; ++i )
				{
					TItem* pItem = m_Items[i];
					ATLASSERT(pItem != NULL);
					RECT rcItemDesired = pItem->GetRect();
					rcItem.left = rcItem.right = xpos;
					if((int)i == m_iCurSel)
					{
						rcItem.right += (rcItemDesired.right - rcItemDesired.left);
					}
					else
					{
						if(bMakeInactiveSameSize && (nCount != 1))
						{
							rcItem.right += (long)((cxClientInactiveTabs / (nCount-1)) + 0.5);
						}
						else
						{
							rcItem.right += (long)(((rcItemDesired.right - rcItemDesired.left) * nRatioWithSelectionFullSize) + 0.5);
						}
					}
					pItem->SetRect(rcItem);
					xpos += (rcItem.right-rcItem.left);
				}
			}
			else
			{
				// We're down pretty small, so just make all the tabs the same width
				int cxItem = (nTabAreaWidth - (m_settings.iIndent*2)) / (int)nCount;

				xpos = m_settings.iIndent;

				for(size_t i=0; i<nCount; ++i)
				{
					rcItem.left = rcItem.right = xpos;
					rcItem.right += cxItem;
					m_Items[i]->SetRect(rcItem);
					xpos += (rcItem.right-rcItem.left);
				}
			}
		}

		dc.SelectFont(hOldFont);
	}

	void UpdateLayout_ScrollToFit(RECT rcTabItemArea)
	{
		//DWORD dwStyle = this->GetStyle();

		// When we scroll to fit, we ignore what's passed in for the
		// tab item area rect, and use the client rect instead
		RECT rcClient;
		this->GetClientRect(&rcClient);

		WTL::CClientDC dc(m_hWnd);
		//HFONT hOldFont = dc.SelectFont(lpNMCustomDraw->hFontInactive);
		HFONT hOldFont = dc.SelectFont(m_font);

		RECT rcItem = rcClient;
		// rcItem.top and rcItem.bottom aren't really going to change

		// Recalculate tab positions and widths
		// See DrawItem_ImageAndText for a discussion of how CDotNetTabCtrlImpl
		//  interprets margin, padding, etc.
		size_t nCount = m_Items.GetCount();
		int xpos = m_settings.iIndent;
		HFONT hRestoreNormalFont = NULL;
		for( size_t i=0; i<nCount; ++i )
		{
			bool bSelected = ((int)i == m_iCurSel);
			if(bSelected)
			{
				//hRestoreNormalFont = dc.SelectFont(lpNMCustomDraw->hFontSelected);
				hRestoreNormalFont = dc.SelectFont(m_fontSel);
			}

			TItem* pItem = m_Items[i];
			ATLASSERT(pItem != NULL);
			rcItem.left = rcItem.right = xpos;
			//rcItem.right += ((bSelected ? m_settings.iSelMargin : m_settings.iMargin));
			rcItem.right += m_settings.iMargin;
			if(pItem->UsingImage() && !m_imageList.IsNull())
			{
				IMAGEINFO ii = {0};
				int nImageIndex = pItem->GetImageIndex();
				m_imageList.GetImageInfo(nImageIndex, &ii);
				rcItem.right += (ii.rcImage.right - ii.rcImage.left);
			}
			if(pItem->UsingText())
			{
				RECT rcText = {0};
				_CSTRING_NS::CString sText = pItem->GetText();
				dc.DrawText(sText, sText.GetLength(), &rcText, DT_SINGLELINE | DT_CALCRECT);
				rcItem.right += (rcText.right - rcText.left) + (m_settings.iPadding * 2);
			}
			rcItem.right += m_settings.iMargin;
			pItem->SetRect(rcItem);
			xpos += (rcItem.right - rcItem.left);

			if(hRestoreNormalFont != NULL)
			{
				dc.SelectFont(hRestoreNormalFont);
				hRestoreNormalFont = NULL;
			}
		}
		xpos += m_settings.iIndent;

		// If we've been scrolled to the left, and resize so
		// there's more client area to the right, adjust the
		// scroll offset accordingly.
		if((xpos + m_iScrollOffset) < rcTabItemArea.right)
		{
			m_iScrollOffset = (rcTabItemArea.right - xpos);
		}

		dc.SelectFont(hOldFont);
	}

};

template <class TItem = CCustomTabItem>
class CDotNetTabCtrl :
	public CDotNetTabCtrlImpl<CDotNetTabCtrl<TItem>, TItem>
{
protected:
	typedef CDotNetTabCtrl thisClass;
	typedef CDotNetTabCtrlImpl<CDotNetTabCtrl<TItem>, TItem> baseClass;

// Constructors:
public:
	CDotNetTabCtrl()
	{
	}

public:

	DECLARE_WND_CLASS_EX(_T("WTL_DotNetTabCtrl"), CS_DBLCLKS, COLOR_WINDOW)

	//We have nothing special to add.
	//BEGIN_MSG_MAP(thisClass)
	//	CHAIN_MSG_MAP(baseClass)
	//END_MSG_MAP()
};

template<typename T, typename TItem = CCustomTabItem, class TBase = ATL::CWindow, class TWinTraits = CCustomTabCtrlWinTraits>
class CDotNetButtonTabCtrlImpl : 
	public CDotNetTabCtrlImpl<T, TItem, TBase, TWinTraits>
{
protected:
	typedef CDotNetButtonTabCtrlImpl<T, TItem, TBase, TWinTraits> thisClass;
	typedef CDotNetTabCtrlImpl<T, TItem, TBase, TWinTraits> baseClass;

// Constructor
public:
	CDotNetButtonTabCtrlImpl()
	{
		// We can't use a member initialization list to initialize
		// members of our base class, so do it explictly by assignment here.
		m_clrTextInactiveTab = ::GetSysColor(COLOR_BTNTEXT);
		m_clrSelectedTab = ::GetSysColor(COLOR_WINDOW);
	}

// Message Handling
public:
	DECLARE_WND_CLASS_EX(_T("WTL_DotNetButtonTabCtrl"), CS_DBLCLKS, COLOR_WINDOW)

	BEGIN_MSG_MAP(thisClass)
		MESSAGE_HANDLER(WM_SETTINGCHANGE, OnSettingChange)
		MESSAGE_HANDLER(WM_SYSCOLORCHANGE, OnSettingChange)
		CHAIN_MSG_MAP(baseClass)
	END_MSG_MAP()

	LRESULT OnSettingChange(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
	{
		DWORD dwStyle = this->GetStyle();

		// Initialize/Reinitialize font
		// Visual Studio.Net seems to use the "icon" font for the tabs
		LOGFONT lfIcon = { 0 };
		::SystemParametersInfo(SPI_GETICONTITLELOGFONT, sizeof(lfIcon), &lfIcon, 0);

		bool bResetFont = true;
		if(!m_font.IsNull())
		{
			LOGFONT lf = {0};
			if(m_font.GetLogFont(&lf))
			{
				if(lstrcmpi(lf.lfFaceName, lfIcon.lfFaceName) == 0 &&
					lf.lfHeight == lfIcon.lfHeight)
				{
					bResetFont = false;
				}
			}
		}

		if(bResetFont)
		{
			if(!m_font.IsNull()) m_font.DeleteObject();
			if(!m_fontSel.IsNull()) m_fontSel.DeleteObject();

			HFONT font = m_font.CreateFontIndirect(&lfIcon);
			if(font==NULL)
			{
				m_font.Attach(AtlGetDefaultGuiFont());
			}

			if(CTCS_BOLDSELECTEDTAB == (dwStyle & CTCS_BOLDSELECTEDTAB))
			{
				lfIcon.lfWeight = FW_BOLD;
			}

			font = m_fontSel.CreateFontIndirect(&lfIcon);
			if(font==NULL)
			{
				m_fontSel.Attach(AtlGetDefaultGuiFont());
			}
		}

		// Background brush
		if(!m_hbrBackground.IsNull() ) m_hbrBackground.DeleteObject();

		m_hbrBackground.CreateSysColorBrush(COLOR_BTNFACE);

		m_settings.iIndent = 5;
		m_settings.iPadding = 4;
		m_settings.iMargin = 3;
		m_settings.iSelMargin = 3;

		T* pT = static_cast<T*>(this);
		pT->UpdateLayout();
		pT->Invalidate();
		return 0;
	}

// Overrides for painting from CDotNetTabCtrlImpl
public:

	void DrawBackground(RECT /*rcClient*/, LPNMCTCCUSTOMDRAW lpNMCustomDraw)
	{
		WTL::CDCHandle dc( lpNMCustomDraw->nmcd.hdc );

		// Set up the text color and background mode
		dc.SetTextColor(lpNMCustomDraw->clrBtnText);
		dc.SetBkMode(TRANSPARENT);

		// Erase Background
		//  (do it here instead of a handler for WM_ERASEBKGND
		//   so that we can do flicker-free drawing with the help
		//   of COffscreenDrawRect that's in the base class)
		// Note: Because the "erase" part is very simple, and only coloring
		//  it with the background color, we can do a smarter erase.
		//  Instead of erasing the whole client area (which might be clipped),
		//  We'll just ask the HDC for the clip box.

		RECT rc = {0};
		//GetClientRect(&rc);
		dc.GetClipBox(&rc);


		HBRUSH hOldBrush = dc.SelectBrush(lpNMCustomDraw->hBrushBackground);
		dc.PatBlt(rc.left, rc.top, rc.right-rc.left, rc.bottom-rc.top, PATCOPY);
		dc.SelectBrush(hOldBrush);
	}

	void DrawItem_InitBounds(DWORD /*dwStyle*/, RECT /*rcItem*/, RECT& rcTab, RECT& /*rcText*/, int& nIconVerticalCenter)
	{
		rcTab.top += 3;
		rcTab.bottom -= 3;
		nIconVerticalCenter = (rcTab.bottom + rcTab.top) / 2;
	}

	void DrawItem_TabSelected(DWORD /*dwStyle*/, LPNMCTCCUSTOMDRAW lpNMCustomDraw, RECT& rcTab)
	{
		// Tab is selected, so paint as select

		bool bHighlighted = (CDIS_MARKED == (lpNMCustomDraw->nmcd.uItemState & CDIS_MARKED));

		WTL::CDCHandle dc( lpNMCustomDraw->nmcd.hdc );

		WTL::CPen penOutline;
		WTL::CBrush brushSelected;
		if(bHighlighted)
		{
			penOutline.CreatePen(PS_SOLID, 1, lpNMCustomDraw->clrBtnHighlight);
			brushSelected.CreateSolidBrush(lpNMCustomDraw->clrHighlight);
		}
		else
		{
			penOutline.CreatePen(PS_SOLID, 1, lpNMCustomDraw->clrHighlight);
			brushSelected.CreateSolidBrush(lpNMCustomDraw->clrSelectedTab);
		}

		HPEN hOldPen = dc.SelectPen(penOutline);
		HBRUSH hOldBrush = dc.SelectBrush(brushSelected);

		dc.Rectangle(&rcTab);

		dc.SelectPen(hOldPen);
		dc.SelectBrush(hOldBrush);
	}

};

template <class TItem = CCustomTabItem>
class CDotNetButtonTabCtrl :
	public CDotNetButtonTabCtrlImpl<CDotNetButtonTabCtrl<TItem>, TItem>
{
protected:
	typedef CDotNetButtonTabCtrl<TItem> thisClass;
	typedef CDotNetButtonTabCtrlImpl<CDotNetButtonTabCtrl<TItem>, TItem> baseClass;

// Constructors:
public:
	CDotNetButtonTabCtrl()
	{
	}

public:

	DECLARE_WND_CLASS_EX(_T("WTL_DotNetButtonTabCtrl"), CS_DBLCLKS, COLOR_WINDOW)

	//We have nothing special to add.
	//BEGIN_MSG_MAP(thisClass)
	//	CHAIN_MSG_MAP(baseClass)
	//END_MSG_MAP()
};

#endif // __DOTNET_TABCTRL_H__

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

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
Web Developer
Belarus Belarus
I am a software developer for 3 years.

Comments and Discussions