Click here to Skip to main content
15,886,004 members
Articles / Desktop Programming / MFC

Understanding CDockablePane

Rate me:
Please Sign up or sign in to vote.
4.95/5 (79 votes)
19 Aug 2015Apache12 min read 359.5K   11.8K   133  
A good reference for CDockablePane
/* 
 * Kenny Liu
 * http://www.codeproject.com/Members/yonken
 *
 * THIS MATERIAL IS PROVIDED AS IS, WITH ABSOLUTELY NO WARRANTY EXPRESSED
 * OR IMPLIED.  ANY USE IS AT YOUR OWN RISK.
 *
 * Permission is hereby granted to use or copy this program
 * for any purpose,  provided the above notices are retained on all copies.
 * Permission to modify the code and to distribute modified code is granted,
 * provided the above notices are retained, and a notice that the code was
 * modified is included with the above copyright notice.
 */
#include "StdAfx.h"
#include "CustomDrawCommon.h"
#include "CustomDrawUtils.h"

#include <shlwapi.h>	// IsThemeEnabled

#pragma comment(lib, "msimg32.lib")		// Import GDI GradientFill() and AlphaBlend()

void DrawSimpleRoundRectBorder( CDC* pDC, RECT rect, COLORREF clrLine, bool bForClipping /*= false*/ )
{
	const int nOffset = 2;
	
	CPen penLine(PS_SOLID, 1, clrLine);
	CPen* pOldPen = pDC->SelectObject(&penLine);
	
	int nExtraOffset;
	if ( bForClipping )
	{
		nExtraOffset = 1;
		++rect.right;
		++rect.bottom;
	}
	else
	{
		nExtraOffset = 0;
	}
	
	// top border
	pDC->MoveTo(rect.left + nOffset, rect.top);
	pDC->LineTo(rect.right - nOffset - 1, rect.top);
	
	// top-right corner
	pDC->LineTo(rect.right - 1, rect.top + nOffset);
	
	// right border
	pDC->LineTo(rect.right - 1, rect.bottom - 1 - nOffset - nExtraOffset);
	
	// bottom-right corner
	pDC->LineTo(rect.right - nOffset - 1, rect.bottom - 1 - nExtraOffset);
	
	// bottom border
	pDC->LineTo(rect.left + nOffset + nExtraOffset, rect.bottom - 1);
	
	// bottom-left corner
	pDC->LineTo(rect.left, rect.bottom - 1 - nOffset - nExtraOffset);
	
	// left border
	pDC->LineTo(rect.left, rect.top + nOffset);
	
	// top-left corner
	pDC->LineTo(rect.left + nOffset, rect.top);
	
	pDC->SelectObject(pOldPen);
}

BOOL OPrintWindow( HWND hwnd, HDC hdcBlt, UINT nFlags /*= 0*/ )
{
	typedef BOOL (WINAPI *PFNPrintWindow)( HWND hwnd, HDC hdcBlt, UINT nFlags );
	static PFNPrintWindow pfn = NULL;
	if ( NULL == pfn )
	{
		HMODULE hModule = GetModuleHandle(_T("User32.dll"));
		ASSERT(hModule);	// can't fail!
		pfn = (PFNPrintWindow)GetProcAddress(hModule, "PrintWindow");
	}
	if (pfn)
		return pfn(hwnd, hdcBlt, nFlags);
	return FALSE;
}

#if _MSC_VER < 1400
BOOL OAnimateWindow(HWND hwnd, DWORD dwTime, DWORD dwFlags)
{
	typedef     BOOL (WINAPI *PFNAnimateWindow)(HWND hwnd, DWORD dwTime, DWORD dwFlags);
	static      PFNAnimateWindow      l_pfn = NULL;
	if ( NULL == l_pfn )
	{
		HMODULE           hModule = ::GetModuleHandle(_T("User32.dll"));
		ASSERT( hModule );            // cannot fail
		
		l_pfn = (PFNAnimateWindow)GetProcAddress(hModule, _T("AnimateWindow"));
		if ( NULL == l_pfn )
		{
			ASSERT(FALSE);          // something wrong?
			return FALSE;
		}
	}
	return l_pfn(hwnd, dwTime, dwFlags);
}
#endif	 // _MSC_VER < 1400

BOOL ORandomAnimateWindow( HWND hwnd, DWORD dwTIme )
{
	srand( (unsigned)time( NULL ) );
	int rnd = rand();
	DWORD dwAnimateFlags = AW_ACTIVATE;
	if ( (rnd % 6) == 0 )
		dwAnimateFlags |= AW_BLEND;
	else
	{
		rnd = 1 << (rnd % 5);
		dwAnimateFlags |= AW_SLIDE|rnd;
	}

	return ::OAnimateWindow(hwnd, dwTIme, dwAnimateFlags);
}

/*----------------------------------------------------------------------------*/
/* GradientDrawParams
/*----------------------------------------------------------------------------*/
#ifdef CUSTOMDRAW_GRADIENT

#ifdef __ENABLE_GDIPLUS__
	#include <math.h>	// for atan2()
	#ifndef PI
		#define PI 3.14159f
	#endif

Gdiplus::REAL getAngleForLinearGradient( int nGradientType, const Gdiplus::Rect& rect )
{
	switch (nGradientType)
	{
	case GFT_TOP_BOTTOM:
	case GFT_VERTICAL_CENTER_OUT:
		return 90.0f;
	case GFT_BOTTOM_TOP:
	case GFT_VERTICAL_CENTER_IN:
		return -90.0f;
	case GFT_LEFT_RIGHT:
	case GFT_HORIZONTAL_CENTER_OUT:
		return 0.0f;
	case GFT_RIGHT_LEFT:
	case GFT_HORIZONTAL_CENTER_IN:
		return 180.0f;
	case GFT_DIAG_TL_BR:
	case GFT_DIAG_UP_CENTER_OUT:
		return (Gdiplus::REAL)atan2( (float)rect.Width, (float)rect.Height ) * 180.0f / PI;
	case GFT_DIAG_TR_BL:
	case GFT_DIAG_DOWN_CENTER_OUT:
		return (Gdiplus::REAL)(180.0f - atan2( (float)rect.Width, (float)rect.Height ) * 180.0f / PI);
	default:
		ASSERT(FALSE);
		break;
	}
	return 0.0f;
}

void RectToPoints( const Gdiplus::Rect& rect, Gdiplus::Point (&pts)[4] )
{
	pts[0].X = pts[3].X = rect.X;
	pts[0].Y = pts[1].Y = rect.Y;
	pts[1].X = pts[2].X = rect.X + rect.Width;
	pts[2].Y = pts[3].Y = rect.Y + rect.Height;
}

struct GradientDrawParams
{
	HDC			m_hDC;
	int			m_nGradientType;
	COLORREF	m_c1;
	COLORREF	m_c2;
};

#define GDIPLUSCOLOR_FROM_CRF_ALPHA_ARGUMENT_LIST(_clr, _byAlpha)		(_byAlpha), GetRValue(_clr), GetGValue(_clr), GetBValue(_clr)

Gdiplus::Brush*	GetBrushForRect(GradientDrawParams& params, const Gdiplus::Rect* pRect, BYTE byAlpha = 255)
{
	Gdiplus::Brush* pBrush = NULL;
	Gdiplus::Color color1( GDIPLUSCOLOR_FROM_CRF_ALPHA_ARGUMENT_LIST(params.m_c1, byAlpha) );
	Gdiplus::Color color2( GDIPLUSCOLOR_FROM_CRF_ALPHA_ARGUMENT_LIST(params.m_c2, byAlpha) );
	if ( IS_GFT_LINEAR_DIRECTION(params.m_nGradientType) )
	{
		BOOL bNeedReverseColors	= IS_GFT_CENTER_OUT(params.m_nGradientType);
		Gdiplus::Color* pColor1	= bNeedReverseColors ? &color2 : &color1;
		Gdiplus::Color* pColor2	= bNeedReverseColors ? &color1 : &color2;
		Gdiplus::REAL rAngle	= getAngleForLinearGradient(params.m_nGradientType, *pRect);
		
		BOOL bIsCenterOutIn		= IS_GFT_CENTER_OUT_IN(params.m_nGradientType);
		
		Gdiplus::LinearGradientBrush* pLinGrBrush = new Gdiplus::LinearGradientBrush(*pRect, *pColor1, *pColor2, rAngle);
		
		if ( pLinGrBrush )
		{
			Gdiplus::REAL rFocus = bIsCenterOutIn ? 0.5f : 1.0f;
			pLinGrBrush->SetBlendBellShape(rFocus);
			pLinGrBrush->SetGammaCorrection(TRUE);
			pLinGrBrush->SetWrapMode(Gdiplus::WrapModeTileFlipXY);
		}
		
		return pLinGrBrush;
	}
	else
	{
		Gdiplus::Point pts[4];
		RectToPoints(*pRect, pts);
		Gdiplus::PathGradientBrush* pPathGrBrush = new Gdiplus::PathGradientBrush(pts, 4);
		if ( pPathGrBrush )
		{
			pPathGrBrush->SetCenterColor(color1);
			INT nCount = 1;
			pPathGrBrush->SetSurroundColors(&color2, &nCount);
			if ( !IS_GFT_FROM_CENTER(params.m_nGradientType) )
			{
				const INT nPtIndex = GET_GFT_CORNER_INDEX(params.m_nGradientType);
				ASSERT(nPtIndex >= 0 && nPtIndex < 4);
				pPathGrBrush->SetCenterPoint( pts[nPtIndex] );
			}
			
			pPathGrBrush->SetBlendBellShape(1.0f);
			
			pPathGrBrush->SetGammaCorrection(TRUE);
			pPathGrBrush->SetWrapMode(Gdiplus::WrapModeTileFlipXY);
			pBrush = pPathGrBrush;
		}
	}
	return pBrush;
}

BOOL FillGradient(HDC hDC, PRECT pRect, COLORREF c1, COLORREF c2, int nGradientType /*= GFT_TOP_BOTTOM*/, BYTE byAlpha /*= 255*/)
{
	if ( !pRect || !hDC || !IS_VALID_GRADIENT_TYPE(nGradientType) )
	{
		ASSERT(FALSE);
		return FALSE;
	}

	GradientDrawParams params;
	params.m_hDC	= hDC;
	params.m_c1		= c1;
	params.m_c2		= c2;
	params.m_nGradientType	= nGradientType;
	
	Gdiplus::Rect brushRect(pRect->left, pRect->top, pRect->right - pRect->left, pRect->bottom - pRect->top);
	
	Gdiplus::Brush* pBrush = GetBrushForRect(params, &brushRect, byAlpha);
	
	if ( !pBrush )
	{
		ASSERT(FALSE);
		return FALSE;
	}
	
	Gdiplus::Graphics gg(hDC);
	Gdiplus::Status status = gg.FillRectangle(pBrush, brushRect);
	
	delete pBrush;
	
	return status == Gdiplus::Ok;
}
#else	// __ENABLE_GDIPLUS__

// Reference: <Windows Graphics Programming Win32 GDI and DirectDraw> by FengYuan
// Chapter 9.7 GRADIENT FILLS

#define IMPLEMENT_COLORREF_TO_16(cName)	\
	inline COLOR16 Get##cName##16Value(COLORREF cc)\
	{\
		return MAKEWORD(0x00, Get##cName##Value(cc));\
	}\
\
	inline COLOR16 Get##cName##16Value(COLORREF c1, COLORREF c2)\
	{\
		return MAKEWORD(0x00, (Get##cName##Value(c1) + Get##cName##Value(c2))/2 );\
	}

IMPLEMENT_COLORREF_TO_16(R)
IMPLEMENT_COLORREF_TO_16(G)
IMPLEMENT_COLORREF_TO_16(B)

#define DECLARE_COLOR16(cName, cRef)	\
	const COLOR16 cName##_R = GetR16Value(cRef);\
	const COLOR16 cName##_G = GetG16Value(cRef);\
	const COLOR16 cName##_B = GetB16Value(cRef);

#define DECLARE_COLOR16_MID(cName, cRef1, cRef2)	\
	const COLOR16 cName##_R = GetR16Value(cRef1, cRef2);\
	const COLOR16 cName##_G = GetG16Value(cRef1, cRef2);\
	const COLOR16 cName##_B = GetB16Value(cRef1, cRef2);

static BOOL _gradient_fill_rect_single_direction(HDC hDC, PRECT pRect, COLORREF c0, COLORREF c1, int nGradientType)
{
	BOOL bRet = FALSE;
	DECLARE_COLOR16(c0, c0)
	DECLARE_COLOR16(c1, c1)
	DECLARE_COLOR16_MID(cMid, c0, c1)
	TRIVERTEX vtx[4] = {
		{pRect->left,	pRect->top,		c0_R,	c0_G,	c0_B,	0},
		{pRect->right,	pRect->bottom,	c1_R,	c1_G,	c1_B,	0},
		{pRect->left,	pRect->bottom,	cMid_R,	cMid_G,	cMid_B,	0}, 
		{pRect->right,	pRect->top,		cMid_R,	cMid_G,	cMid_B,	0}
	};
	ULONG meshIndices[] = {0, 1, 2, 0, 1, 3};
	
	switch (nGradientType)
	{
	case GFT_TOP_BOTTOM:
		bRet = GradientFill(hDC, vtx, 2, meshIndices, 1, GRADIENT_FILL_RECT_V);
		break;
	case GFT_LEFT_RIGHT:
		bRet = GradientFill(hDC, vtx, 2, meshIndices, 1, GRADIENT_FILL_RECT_H);
		break;
	case GFT_DIAG_TL_BR:
		bRet = GradientFill(hDC, vtx, 4, meshIndices, 2, GRADIENT_FILL_TRIANGLE);
		break;
	case GFT_DIAG_TR_BL:
		vtx[0].x = pRect->right;
		vtx[3].x = pRect->left;
		vtx[1].x = pRect->left;
		vtx[2].x = pRect->right;
		bRet = GradientFill(hDC, vtx, 4, meshIndices, 2, GRADIENT_FILL_TRIANGLE);
		break;
	default:
		ASSERT(FALSE);
		break;
	}
	return bRet;
}

static BOOL _gradient_fill_rect_from_center(HDC hDC, PRECT pRect, COLORREF c0, COLORREF c1, int nGradientType)
{
	const int nMidX = pRect->left + (pRect->right - pRect->left)/2;
	const int nMidY = pRect->top + (pRect->bottom - pRect->top)/2;
	DECLARE_COLOR16(c0, c0)
	DECLARE_COLOR16(c1, c1)
	TRIVERTEX vtx[5] = {
		{pRect->left,	pRect->top,		c1_R,	c1_G,	c1_B,	0},
		{pRect->right,	pRect->top,		c1_R,	c1_G,	c1_B,	0},
		{pRect->right,	pRect->bottom,	c1_R,	c1_G,	c1_B,	0}, 
		{pRect->left,	pRect->bottom,	c1_R,	c1_G,	c1_B,	0},
		{nMidX,			nMidY,			c0_R,	c0_G,	c0_B,	0},
	};
	
	ULONG meshIndices[] = {0, 1, 4, 1, 2, 4, 2, 3, 4, 3, 0, 4};
	return GradientFill(hDC, vtx, 5, meshIndices, 4, GRADIENT_FILL_TRIANGLE);
}

static BOOL _gradient_fill_rect_from_corner(HDC hDC, PRECT pRect, COLORREF c0, COLORREF c1, int nGradientType)
{
	DECLARE_COLOR16(c0, c0)
	DECLARE_COLOR16(c1, c1)
	TRIVERTEX vtx[4] = {
		{pRect->left,	pRect->top,		c1_R,	c1_G,	c1_B,	0},
		{pRect->right,	pRect->top,		c1_R,	c1_G,	c1_B,	0},
		{pRect->right,	pRect->bottom,	c1_R,	c1_G,	c1_B,	0}, 
		{pRect->left,	pRect->bottom,	c1_R,	c1_G,	c1_B,	0}
	};
	
	int nCorner = GET_GFT_CORNER_INDEX(nGradientType);
	vtx[nCorner].Red   = c0_R;
	vtx[nCorner].Green = c0_G;
	vtx[nCorner].Blue  = c0_B;

	ULONG meshIndices[] = {nCorner, (nCorner+1)%4, (nCorner+2)%4, nCorner, (nCorner+3)%4, (nCorner+2)%4};
	return GradientFill(hDC, vtx, 4, meshIndices, 2, GRADIENT_FILL_TRIANGLE);
}

static BOOL _gradient_fill_rect_center_out(HDC hDC, PRECT pRect, COLORREF c0, COLORREF c1, int nGradientType)
{
	BOOL bRet = FALSE;
	RECT rect1 = *pRect;
	RECT rect2 = *pRect;;
	switch (nGradientType)
	{
	case GFT_VERTICAL_CENTER_OUT:
	case GFT_HORIZONTAL_CENTER_OUT:
		{
			const bool bHorizontal = GFT_VERTICAL_CENTER_OUT == nGradientType;
			nGradientType = bHorizontal ? GFT_TOP_BOTTOM : GFT_LEFT_RIGHT;
			if ( bHorizontal )
				rect1.bottom = rect2.top = pRect->top + (pRect->bottom - pRect->top)/2;
			else
				rect1.right = rect2.left = pRect->left + (pRect->right - pRect->left)/2;
			// Fill the two parts
			bRet = _gradient_fill_rect_single_direction(hDC, &rect1, c1, c0, nGradientType);
			bRet &= _gradient_fill_rect_single_direction(hDC, &rect2, c0, c1, nGradientType);
		}
		break;
	case GFT_DIAG_UP_CENTER_OUT:
	case GFT_DIAG_DOWN_CENTER_OUT:
		{
			const bool bDiagonalUp = GFT_DIAG_UP_CENTER_OUT == nGradientType;
			DECLARE_COLOR16(c0, c0)
			DECLARE_COLOR16(c1, c1)
			TRIVERTEX vtx[4] = {
				{pRect->left,	pRect->top,		c1_R,	c1_G,	c1_B,	0},
				{pRect->right,	pRect->bottom,	c1_R,	c1_G,	c1_B,	0},
				{pRect->left,	pRect->bottom,	c1_R,	c1_G,	c1_B,	0}, 
				{pRect->right,	pRect->top,		c1_R,	c1_G,	c1_B,	0}
			};
			const int nC1IndexBegin = bDiagonalUp ? 2 : 0;	// the index of vertices that should be filled in color c1
			for (int ii = nC1IndexBegin; ii < nC1IndexBegin + 2; ++ii)
			{
				vtx[ii].Red		= c0_R;
				vtx[ii].Green	= c0_G;
				vtx[ii].Blue	= c0_B;
			}
			ULONG meshIndices[] = {0, 2, 3, 2, 1, 3};
			if ( !bDiagonalUp )
			{
				meshIndices[2] = 1;	// change 3 to 1
				meshIndices[3] = 0;	// change 2 to 0
			}
			bRet = GradientFill(hDC, vtx, 4, meshIndices, 2, GRADIENT_FILL_TRIANGLE);
		}
		break;
	default:
		ASSERT(FALSE);
		break;
	}
	return bRet;
}

#define CHECK_CORRECT_GRADIENT_COLOR(_nGrad1, _nGrad2, _nGradientType, _c0, _c1)	\
		{\
			if ( _nGrad1 == _nGradientType || _nGrad2 == _nGradientType )\
			{\
				--_nGradientType;\
				COLORREF ctmp = _c0;\
				_c0 = _c1;\
				_c1 = ctmp;\
			}\
		}

#ifndef AC_SRC_ALPHA
	#define AC_SRC_ALPHA 0x01
#endif

BOOL FillGradient(HDC hDC, PRECT pRect, COLORREF c1, COLORREF c2, int nGradientType /*= GFT_TOP_BOTTOM*/, BYTE byAlpha /*= 255*/)
{
#ifdef _DEBUG
	if ( !pRect || !IS_VALID_GRADIENT_TYPE(nGradientType) )
	{
		ASSERT(FALSE);
		return FALSE;
	}
#endif // _DEBUG
	if (0 == byAlpha)
		return TRUE;

	BOOL bAlphaBlend = byAlpha < 255;
	BOOL bRet = FALSE;
	HDC hDrawDC = hDC;
	CRect rectDraw(pRect);

	CMemBitmap bitmapDrawer(bAlphaBlend ? CDC::FromHandle(hDC) : NULL);
	int cx = rectDraw.Width();
	int cy = rectDraw.Height();

	CRect rectGradient(pRect);
	if ( bAlphaBlend )
	{
		if (bitmapDrawer.BeginDraw(cx*2, cy*2))
		{
			hDrawDC = bitmapDrawer.GetDC().GetSafeHdc();
			rectGradient.left = 0;
			rectGradient.top = 0;
			rectGradient.right = cx;
			rectGradient.bottom = cy;
		}
		else
		{
			bAlphaBlend = FALSE;
		}
	}

	if ( IS_GFT_FROM_CENTER(nGradientType) )
	{
		bRet = _gradient_fill_rect_from_center(hDrawDC, rectGradient, c1, c2, nGradientType);
	}
	else if ( IS_GFT_FROM_CORNER(nGradientType) )
	{
		bRet = _gradient_fill_rect_from_corner(hDrawDC, rectGradient, c1, c2, nGradientType);
	}
	else if ( IS_GFT_CENTER_OUT_IN(nGradientType) )
	{
		CHECK_CORRECT_GRADIENT_COLOR ( GFT_VERTICAL_CENTER_IN, GFT_HORIZONTAL_CENTER_IN, nGradientType, c1, c2);
		bRet = _gradient_fill_rect_center_out(hDrawDC, rectGradient, c1, c2, nGradientType);
	}
	else
	{
		// must be single direction now.
		ASSERT( IS_GFT_SINGLE_DIRECTION(nGradientType) );
		CHECK_CORRECT_GRADIENT_COLOR ( GFT_BOTTOM_TOP, GFT_RIGHT_LEFT, nGradientType, c1, c2);
		bRet = _gradient_fill_rect_single_direction(hDrawDC, rectGradient, c1, c2, nGradientType);
	}

	if (bAlphaBlend)
	{
		BLENDFUNCTION bf;
		bf.BlendFlags			= 0;
		bf.AlphaFormat			= 0;
		bf.BlendOp				= AC_SRC_OVER;
		bf.SourceConstantAlpha	= byAlpha;
		bRet = ::AlphaBlend(hDC, rectDraw.left, rectDraw.top, cx, cy, hDrawDC, 0, 0, cx, cy, bf);
		bitmapDrawer.EndDraw();
	}

	return bRet;
}
#endif // __ENABLE_GDIPLUS__

BOOL SimpleGradientRoundRect(HDC hDC, 
							 PRECT pRect, 
							 COLORREF c1, 
							 COLORREF c2, 
							 COLORREF clrBorder /*= CLR_INVALID*/, 
							 int nGradientType /*= GFT_TOP_BOTTOM*/,
							 BYTE byAlpha /*= 255*/
							 )
{
	if ( !pRect || !hDC || !IS_VALID_GRADIENT_TYPE(nGradientType) )
	{
		ASSERT(FALSE);
		return FALSE;
	}
	
	// Set the clip region.
	CDC* pDC = CDC::FromHandle(hDC);
	
	VERIFY( ::BeginPath(hDC) );
	
	DrawSimpleRoundRectBorder(pDC, *pRect, clrBorder, true);
	
	VERIFY( ::EndPath(hDC) );
	
	::SelectClipPath(hDC, RGN_COPY);
	
	BOOL bRet = FillGradient(hDC, pRect, c1, c2, nGradientType, byAlpha);
	
	if ( clrBorder != CLR_INVALID )
	{
		DrawSimpleRoundRectBorder(pDC, *pRect, clrBorder);
	}

	// Restore the clipping region
	::SelectClipRgn(hDC, NULL);
	
	return bRet;
}

void DrawRoundRectItem(CDC* pDC, CRect rect, 
					   COLORREF clrFillColor1, COLORREF clrFillColor2, 
					   COLORREF clrInnerBorder, COLORREF clrOuterBorder, 
					   BYTE byAlpha /*= 255*/
					   )
{
	CRect rectInner(rect);
	rectInner.DeflateRect(1, 1);
	SimpleGradientRoundRect(pDC->GetSafeHdc(), rectInner, clrFillColor1, clrFillColor2, clrInnerBorder, GFT_TOP_BOTTOM, byAlpha);
	DrawSimpleRoundRectBorder(pDC, rect, clrOuterBorder);
}

void Fill4ColorsGradient( HDC hDC, CRect rect, 
						 COLORREF colorStart1, COLORREF colorFinish1, 
						 COLORREF colorStart2, COLORREF colorFinish2, 
						 BOOL bHorz /*= TRUE*/, 
						 int nPercentage /*= 50*/,
						 BYTE byAlpha /*= 255*/
						 )
{
	ASSERT(nPercentage >= 0);
	ASSERT(nPercentage <= 100);
	
	CRect rectFirst = rect;
	CRect rectSecond = rect;
	
	if (!bHorz)
	{
		rectFirst.right = rectFirst.left + rectFirst.Width() * nPercentage / 100;
		rectSecond.left = rectFirst.right;
	}
	else
	{
		rectFirst.bottom = rectFirst.top + rectFirst.Height() * nPercentage / 100;
		rectSecond.top = rectFirst.bottom;
	}
	int nGradientType = bHorz ? GFT_TOP_BOTTOM : GFT_LEFT_RIGHT;
	FillGradient(hDC, rectFirst, colorStart1, colorFinish1, nGradientType, byAlpha);
	FillGradient(hDC, rectSecond, colorStart2, colorFinish2, nGradientType, byAlpha);
}

void Fill4ColorsGradient( HDC hDC, CRect rect, PCCOLORS4 pColors, BOOL bHorz /*= TRUE*/, int nPercentage /*= 50*/, BYTE byAlpha /*= 255*/ )
{
	ASSERT(pColors);
	Fill4ColorsGradient(hDC, rect, pColors->c1, pColors->c2, pColors->c3, pColors->c4, bHorz, nPercentage, byAlpha);
}

#ifdef CUSTOMDRAW_THEME_BACKGROUND
const static COLORS4 s_ButtonClrsNormal		= {RGB_BUTTON_NORMAL_FILL_COLOR1,	RGB_BUTTON_NORMAL_FILL_COLOR2,	
												RGB_BUTTON_NORMAL_FILL_COLOR3,	RGB_BUTTON_NORMAL_FILL_COLOR4};

const static COLORS4 s_ButtonClrsHot		= {RGB_BUTTON_HOT_FILL_COLOR1,		RGB_BUTTON_HOT_FILL_COLOR2,	
												RGB_BUTTON_HOT_FILL_COLOR3,		RGB_BUTTON_HOT_FILL_COLOR4};

const static COLORS4 s_ButtonClrsPressed	= {RGB_BUTTON_PUSHED_FILL_COLOR1,	RGB_BUTTON_PUSHED_FILL_COLOR2,	
												RGB_BUTTON_PUSHED_FILL_COLOR3,	RGB_BUTTON_PUSHED_FILL_COLOR4};

const static COLORS4 s_HeaderClrsNormal		= {RGB_HEADER_NORMAL_FILL_COLOR1,	RGB_HEADER_NORMAL_FILL_COLOR2,	
												RGB_HEADER_NORMAL_FILL_COLOR3,	RGB_HEADER_NORMAL_FILL_COLOR4};

const static COLORS4 s_HeaderClrsHot		= {RGB_HEADER_HOT_FILL_COLOR1,		RGB_HEADER_HOT_FILL_COLOR2,	
												RGB_HEADER_HOT_FILL_COLOR3,		RGB_HEADER_HOT_FILL_COLOR4};

const static COLORS4 s_HeaderClrsPressed	= {RGB_HEADER_PUSHED_FILL_COLOR1,	RGB_HEADER_PUSHED_FILL_COLOR2,	
												RGB_HEADER_PUSHED_FILL_COLOR3,	RGB_HEADER_PUSHED_FILL_COLOR4};

typedef struct tagItem3DBackgrnd 
{
	PCCOLORS4	clrs[CDSTATEID_MAX];
	int			nPercentage;
}Item3DBackgrnd, *PItem3DBackgrnd;

const static Item3DBackgrnd s_itemButton = {
	NULL, &s_ButtonClrsHot, &s_ButtonClrsNormal, &s_ButtonClrsPressed, BUTTON_FIRST_HALF_PERCENTAGE
};

const static Item3DBackgrnd s_itemHeader = {
	NULL, &s_HeaderClrsHot, &s_HeaderClrsNormal, &s_HeaderClrsPressed, HEADER_FIRST_HALF_PERCENTAGE
};

const static Item3DBackgrnd s_itemComboBtn = {
	NULL, &s_ButtonClrsHot, NULL, &s_ButtonClrsPressed, BUTTON_FIRST_HALF_PERCENTAGE
};

const static void DrawBorderLines( CDC* pDC, CRect rect, COLORREF clrLine, UINT nOffset)
{
	CPen penLine(PS_SOLID, 1, clrLine);
	CPen* pOldPen = pDC->SelectObject(&penLine);
	
	// top border
	pDC->MoveTo(rect.left+nOffset, rect.top);
	pDC->LineTo(rect.right-nOffset+1, rect.top);
	
	// bottom border
	pDC->MoveTo(rect.left+nOffset, rect.bottom);
	pDC->LineTo(rect.right-nOffset+1, rect.bottom);
	
	// left border
	pDC->MoveTo(rect.left, rect.top+nOffset);
	pDC->LineTo(rect.left, rect.bottom-nOffset+1);
	
	// right border
	pDC->MoveTo(rect.right, rect.top+nOffset);
	pDC->LineTo(rect.right, rect.bottom-nOffset+1);
	
	pDC->SelectObject(pOldPen);
}

static void DrawButtonBorder( CDC* pDC, CRect rect, CDSTATEID iStateId, 
							 bool bDrawShadow = true, 
							 int nOuterBorderOffset = 3, 
							 int nInnerBorderOffset = 1
							 )
{
	COLORREF clrOuterBorder, clrInnerBorder, clrShadow;
	switch (iStateId)
	{
	case CDSTATEID_PRESSED:
		clrOuterBorder	= RGB(44, 98, 139);
		clrInnerBorder	= RGB(158, 176, 186);
		clrShadow		= RGB(46, 96, 134);
		break;
	case CDSTATEID_HOT:
		clrOuterBorder	= RGB(60, 127, 177);
		clrInnerBorder	= RGB(250, 253, 254);
		clrShadow		= RGB(66, 131, 179);
		break;
	case CDSTATEID_DISABLED:
		clrOuterBorder	= ::GetSysColor(COLOR_BTNSHADOW);
		clrInnerBorder	= RGB(250, 253, 254);
		clrShadow		= clrOuterBorder;
		break;
	default:
		clrOuterBorder	= RGB(112, 112, 112);
		clrInnerBorder	= RGB(252, 252, 252);
		clrShadow		= RGB(144, 144, 144);
		break;
	}
	
	DrawBorderLines(pDC, rect, clrOuterBorder, nOuterBorderOffset);
	
	if ( bDrawShadow )
	{
		// Top-left
		pDC->SetPixel(rect.left+1, rect.top, clrShadow);
		pDC->SetPixel(rect.left+2, rect.top, clrShadow);
		pDC->SetPixel(rect.left, rect.top+1, clrShadow);
		pDC->SetPixel(rect.left+1, rect.top+1, clrShadow);
		pDC->SetPixel(rect.left, rect.top+2, clrShadow);
		
		// Top-right
		pDC->SetPixel(rect.right-2, rect.top, clrShadow);
		pDC->SetPixel(rect.right-1, rect.top, clrShadow);
		pDC->SetPixel(rect.right-1, rect.top+1, clrShadow);
		pDC->SetPixel(rect.right, rect.top+1, clrShadow);
		pDC->SetPixel(rect.right, rect.top+2, clrShadow);
		
		// Bottom-left
		pDC->SetPixel(rect.left, rect.bottom-2, clrShadow);
		pDC->SetPixel(rect.left, rect.bottom-1, clrShadow);
		pDC->SetPixel(rect.left+1, rect.bottom-1, clrShadow);
		pDC->SetPixel(rect.left+1, rect.bottom, clrShadow);
		pDC->SetPixel(rect.left+2, rect.bottom, clrShadow);
		
		// Bottom-right
		pDC->SetPixel(rect.right, rect.bottom-2, clrShadow);
		pDC->SetPixel(rect.right-1, rect.bottom-1, clrShadow);
		pDC->SetPixel(rect.right, rect.bottom-1, clrShadow);
		pDC->SetPixel(rect.right-2, rect.bottom, clrShadow);
		pDC->SetPixel(rect.right-1, rect.bottom, clrShadow);
	}
	
	rect.DeflateRect(1, 1);
	DrawBorderLines(pDC, rect, clrInnerBorder, nInnerBorderOffset);
}
#endif // CUSTOMDRAW_THEME_BACKGROUND

void DrawDropDownArrow( HDC hDC, const RECT* pRect )
{
	CDC* pDC = CDC::FromHandle(hDC);
	ASSERT(pRect);
	CRect rcFill(*pRect);

	// Draw the down arrow
	CPen penLine(PS_SOLID, 1, RGB(0, 0, 0));
	CPen* pOldPen = pDC->SelectObject(&penLine);
	
	CPoint ptTopCenter( rcFill.CenterPoint().x, (rcFill.top + rcFill.bottom-1) / 2 - 1);
	for (int ii = 0, yy = ptTopCenter.y; ii < 4; ++ii, ++yy)
	{
		int cx = 7 - ii * 2;
		int xx = ptTopCenter.x - cx / 2;
		pDC->MoveTo(xx, yy);
		pDC->LineTo(xx+cx, yy);
	}
	pDC->SelectObject(pOldPen);
}

HRESULT CustomDrawThemeBackground( HWND hwnd, HDC hDC, CDTBT type, CDSTATEID state, const RECT* pRect, const RECT* pClipRect /*= NULL*/ )
{
	HRESULT hresult = S_OK;
#ifdef CUSTOMDRAW_THEME_BACKGROUND
	CDC* pDC = CDC::FromHandle(hDC);
	ASSERT(pRect);
	CRect rcFill(*pRect);

	const Item3DBackgrnd* pItem	= NULL;
	switch (type)
	{
	case CDTBT_PUSHBUTTON:
	case CDTBT_COMBOBUTTON:
		pItem = &s_itemButton;
		rcFill.DeflateRect(2, 2);
		break;
	case CDTBT_COMBODOWNBUTTON:
		pItem = &s_itemComboBtn;
		rcFill.DeflateRect(2, 2);
		break;
	case CDTBT_HEADER:
		pItem = &s_itemHeader;
		break;
	default:
		ASSERT(0);
		return hresult;
		break;
	}

	ASSERT(pItem);
	PCCOLORS4 pFillColors = pItem->clrs[state];
	if (pFillColors)
		Fill4ColorsGradient(*pDC, rcFill, pFillColors, TRUE, pItem->nPercentage);

	bool bDrawShadow = true;
	int nOuterBorderOffset = 3;
	int nInnerBorderOffset = 1;
	bool bDrawBtnBorder = false;
	bool bDisableFill = false;
	if ( CDSTATEID_DISABLED == state && hwnd )
	{
		if ( CDTBT_PUSHBUTTON == type || CDTBT_COMBOBUTTON == type )
		{
			bDisableFill = true;
		}
	}

	switch (type)
	{
	case CDTBT_PUSHBUTTON:
	case CDTBT_COMBOBUTTON:
		{
			bDrawBtnBorder = true;
		}
		break;
	case CDTBT_COMBODOWNBUTTON:
		{
			if (pFillColors)
			{
				bDrawBtnBorder		= true;
				bDrawShadow			= false;
				nOuterBorderOffset	= nInnerBorderOffset = 0;
			}

			DrawDropDownArrow(hDC, rcFill);
		}
		break;
	case CDTBT_HEADER:
		break;
	}

	if ( bDisableFill )
	{
		pDC->FillSolidRect(rcFill, ::GetSysColor(COLOR_3DFACE));
	}

	if ( bDrawBtnBorder )
	{
		CRect rect(*pRect);
		--rect.right;
		--rect.bottom;
		DrawButtonBorder(pDC, rect, state, bDrawShadow, nOuterBorderOffset, nInnerBorderOffset);
	}
#else
	ASSERT( IsWindow(hwnd) );
	LPCWSTR pszClassList = NULL;
	int iPartId = 0, iStateId = 0;
	switch (type)
	{
	case CDTBT_PUSHBUTTON:
	case CDTBT_COMBOBUTTON:
		pszClassList	= VSCLASS_BUTTON;
		iPartId			= BP_PUSHBUTTON;
		switch (state)
		{
		case CDSTATEID_DISABLED:
			iStateId = PBS_DISABLED;
			break;
		case CDSTATEID_HOT:
			iStateId = PBS_HOT;
			break;
		case CDSTATEID_PRESSED:
			iStateId = PBS_PRESSED;
			break;
		case CDSTATEID_NORMAL:
		default:
			iStateId = PBS_NORMAL;
			break;
		}
		break;
	case CDTBT_COMBODOWNBUTTON:
		pszClassList	= VSCLASS_COMBOBOX;
		iPartId			= CP_DROPDOWNBUTTON;
		switch (state)
		{
		case CDSTATEID_DISABLED:
			iStateId = CBXS_DISABLED;
			break;
		case CDSTATEID_HOT:
			iStateId = CBXS_HOT;
			break;
		case CDSTATEID_PRESSED:
			iStateId = CBXS_PRESSED;
			break;
		case CDSTATEID_NORMAL:
		default:
			iStateId = CBXS_NORMAL;
			break;
		}
		break;
	case CDTBT_HEADER:
		pszClassList	= VSCLASS_HEADER;
		iPartId			= HP_HEADERITEM;
		switch (state)
		{
		case CDSTATEID_DISABLED:
			iStateId = HIS_NORMAL;	// disabled state is not applicable for a header control
			break;
		case CDSTATEID_HOT:
			iStateId = HIS_HOT;
			break;
		case CDSTATEID_PRESSED:
			iStateId = HIS_PRESSED;
			break;
		case CDSTATEID_NORMAL:
		default:
			iStateId = HIS_NORMAL;
			break;
		}
		break;
	default:
		ASSERT(0);
		return hresult;
		break;
	}
	HTHEME hTheme = OOpenThemeData(hwnd, pszClassList);
	if ( hTheme )
	{
		if ( OIsThemeBackgroundPartiallyTransparent(hTheme, iPartId, iStateId) )
		{
			ODrawThemeParentBackground(hwnd, hDC, pRect);
		}
		ODrawThemeBackground(hTheme, hDC, iPartId, iStateId, pRect, pClipRect);
		OCloseThemeData(hTheme);
	}
#endif // CUSTOMDRAW_THEME_BACKGROUND
	return hresult;
}
#endif // CUSTOMDRAW_GRADIENT

void DrawSortArrow( CDC* pDC, CRect rectArrow, BOOL bIsUp )
{
	COLORREF clrLight	= RGB(154,199,224);//GetSysColor(COLOR_3DHILIGHT);
	COLORREF clrDark	= RGB(67,95,111);//GetSysColor(COLOR_3DSHADOW);
	CPen penLight(PS_SOLID, 1, clrLight);
	CPen penDark(PS_SOLID, 1, clrDark);
	
	CPen* pPenOld = pDC->SelectObject(&penLight);
	ASSERT_VALID(pPenOld);
	
	if (!bIsUp)
	{
		pDC->MoveTo(rectArrow.right, rectArrow.top);
		pDC->LineTo(rectArrow.CenterPoint().x, rectArrow.bottom);
		
		pDC->SelectObject(&penDark);
		pDC->LineTo(rectArrow.left, rectArrow.top);
		pDC->LineTo(rectArrow.right, rectArrow.top);
	}
	else
	{
		pDC->MoveTo(rectArrow.left, rectArrow.bottom);
		pDC->LineTo(rectArrow.right, rectArrow.bottom);
		pDC->LineTo(rectArrow.CenterPoint().x, rectArrow.top);
		
		pDC->SelectObject(&penDark);
		pDC->LineTo(rectArrow.left, rectArrow.bottom);
	}
	
	pDC->SelectObject(pPenOld);
}

// http://www.codeproject.com/KB/list/CGridListCtrlEx.aspx

bool CheckOSVersion(WORD requestOS)
{
	static WORD fullver = 0;
	if (fullver==0)
	{
		OSVERSIONINFO osver = {0};
		osver.dwOSVersionInfoSize = sizeof(osver);
		GetVersionEx(&osver);
		fullver = MAKEWORD(osver.dwMinorVersion, osver.dwMajorVersion);
	}
	return requestOS <= fullver;
}

bool IsCommonControlsEnabled()
{
	bool commoncontrols = false;
	
	// Test if application has access to common controls
	HMODULE hinstDll = ::LoadLibrary(_T("comctl32.dll"));
	if (hinstDll)
	{
		DLLGETVERSIONPROC pDllGetVersion = (DLLGETVERSIONPROC)::GetProcAddress(hinstDll, "DllGetVersion");
		if (pDllGetVersion != NULL)
		{
			DLLVERSIONINFO dvi = {0};
			dvi.cbSize = sizeof(dvi);
			HRESULT hRes = pDllGetVersion ((DLLVERSIONINFO *) &dvi);
			if (SUCCEEDED(hRes))
				commoncontrols = dvi.dwMajorVersion >= 6;
		}
		::FreeLibrary(hinstDll);
	}
	return commoncontrols;
}

bool IsThemeEnabled()
{
	bool XPStyle = false;
	bool (__stdcall *pIsAppThemed)();
	bool (__stdcall *pIsThemeActive)();
	
	// Test if operating system has themes enabled
	HMODULE hinstDll = ::LoadLibrary(_T("UxTheme.dll"));
	if (hinstDll)
	{
		(FARPROC&)pIsAppThemed = ::GetProcAddress(hinstDll, "IsAppThemed");
		(FARPROC&)pIsThemeActive = ::GetProcAddress(hinstDll,"IsThemeActive");
		if (pIsAppThemed != NULL && pIsThemeActive != NULL)
		{
			if (pIsAppThemed() && pIsThemeActive())
			{
				// Test if application has themes enabled by loading the proper DLL
				XPStyle = IsCommonControlsEnabled();
			}
		}
		::FreeLibrary(hinstDll);
	}
	return XPStyle;
}

#ifndef LINK_TO_UXTHEME
HTHEME OOpenThemeData( HWND hwnd, LPCWSTR pszClassList )
{
	HTHEME hTheme = NULL;
	HMODULE hinstDll = ::LoadLibrary(_T("UxTheme.dll"));
	if (hinstDll)
	{
		HTHEME (__stdcall *pfn)(HWND hwnd, LPCWSTR pszClassList);
		(FARPROC&)pfn = ::GetProcAddress(hinstDll, "OpenThemeData");
		if ( pfn )
		{
			hTheme = pfn(hwnd, pszClassList);
		}
		::FreeLibrary(hinstDll);
	}
	return hTheme;
}

HRESULT OCloseThemeData( HTHEME hTheme )
{
	HRESULT hResult = S_FALSE;
	HMODULE hinstDll = ::LoadLibrary(_T("UxTheme.dll"));
	if (hinstDll)
	{
		HRESULT (__stdcall *pfn)(HTHEME hTheme);
		(FARPROC&)pfn = ::GetProcAddress(hinstDll, "CloseThemeData");
		if ( pfn )
		{
			hResult = pfn(hTheme);
		}
		::FreeLibrary(hinstDll);
	}
	return hResult;
}

HRESULT OSetWindowTheme( HWND hwnd, LPCWSTR pszSubAppName, LPCWSTR pszSubIdList )
{
	HRESULT hResult = S_FALSE;
	HMODULE hinstDll = ::LoadLibrary(_T("UxTheme.dll"));
	if (hinstDll)
	{
		HRESULT (__stdcall *pfn)(HWND hwnd, LPCWSTR pszSubAppName, LPCWSTR pszSubIdList);
		(FARPROC&)pfn = ::GetProcAddress(hinstDll, "SetWindowTheme");
		if ( pfn )
		{
			hResult = pfn(hwnd, pszSubAppName, pszSubIdList);
		}
		::FreeLibrary(hinstDll);
	}
	return hResult;
}

HRESULT ODrawThemeBackground( HTHEME hTheme, HDC hdc, int iPartId, int iStateId, const RECT* pRect, const RECT* pClipRect )
{
	HRESULT hResult = S_FALSE;
	HMODULE hinstDll = ::LoadLibrary(_T("UxTheme.dll"));
	if (hinstDll)
	{
		HRESULT (__stdcall *pfn)(HTHEME hTheme, HDC hdc, int iPartId, int iStateId, const RECT* pRect, const RECT* pClipRect);
		(FARPROC&)pfn = ::GetProcAddress(hinstDll,"DrawThemeBackground");
		if ( pfn )
		{
			hResult = pfn(hTheme, hdc, iPartId, iStateId, pRect, pClipRect);
		}
		::FreeLibrary(hinstDll);
	}
	return hResult;
}

BOOL OIsThemeBackgroundPartiallyTransparent( HTHEME hTheme, int iPartId, int iStateId )
{
	BOOL bRet = FALSE;
	HMODULE hinstDll = ::LoadLibrary(_T("UxTheme.dll"));
	if (hinstDll)
	{
		HRESULT (__stdcall *pfn)(HTHEME hTheme, int iPartId, int iStateId);
		(FARPROC&)pfn = ::GetProcAddress(hinstDll, "IsThemeBackgroundPartiallyTransparent");
		if ( pfn )
		{
			bRet = pfn(hTheme, iPartId, iStateId);
		}
		::FreeLibrary(hinstDll);
	}
	return bRet;
}

HRESULT ODrawThemeParentBackground( HWND hwnd, HDC hdc, const RECT* prc )
{
	HRESULT hResult = S_FALSE;
	HMODULE hinstDll = ::LoadLibrary(_T("UxTheme.dll"));
	if (hinstDll)
	{
		HRESULT (__stdcall *pfn)(HWND hwnd, HDC hdc, const RECT* prc);
		(FARPROC&)pfn = ::GetProcAddress(hinstDll,"DrawThemeParentBackground");
		if ( pfn )
		{
			hResult = pfn(hwnd, hdc, prc);
		}
		::FreeLibrary(hinstDll);
	}
	return hResult;
}
#endif // LINK_TO_UXTHEME

LRESULT EnableWindowTheme(HWND hwnd, LPCWSTR classList, LPCWSTR subApp, LPCWSTR idlist)
{
	LRESULT lResult = S_FALSE;
	
	HRESULT (__stdcall *pSetWindowTheme)(HWND hwnd, LPCWSTR pszSubAppName, LPCWSTR pszSubIdList);
	HANDLE (__stdcall *pOpenThemeData)(HWND hwnd, LPCWSTR pszClassList);
	HRESULT (__stdcall *pCloseThemeData)(HANDLE hTheme);
	
	HMODULE hinstDll = ::LoadLibrary(_T("UxTheme.dll"));
	if (hinstDll)
	{
		(FARPROC&)pOpenThemeData = ::GetProcAddress(hinstDll, "OpenThemeData");
		(FARPROC&)pCloseThemeData = ::GetProcAddress(hinstDll, "CloseThemeData");
		(FARPROC&)pSetWindowTheme = ::GetProcAddress(hinstDll, "SetWindowTheme");
		if (pSetWindowTheme && pOpenThemeData && pCloseThemeData)
		{
			HANDLE theme = pOpenThemeData(hwnd,classList);
			if (theme != NULL)
			{
				VERIFY(pCloseThemeData(theme)==S_OK);
				lResult = pSetWindowTheme(hwnd, subApp, idlist);
			}
		}
		::FreeLibrary(hinstDll);
	}
	return lResult;
}

SIZE GetBmpSize( HBITMAP hBmp )
{
	BITMAPINFO bmp = {0};
	GetObject(hBmp, sizeof(BITMAPINFO), &bmp);
	return CSize(bmp.bmiHeader.biWidth, bmp.bmiHeader.biHeight);
}


SIZE GetIconSize( HICON hIcon )
{
	ICONINFO iconInfo = {0};
	GetIconInfo(hIcon, &iconInfo);
	BITMAPINFO bmp = {0};
	HBITMAP hBitmap = iconInfo.hbmColor ? iconInfo.hbmColor : iconInfo.hbmMask;
	SIZE size = GetBmpSize(hBitmap);
	if ( iconInfo.hbmColor != NULL )
		DeleteObject(iconInfo.hbmColor);
	if ( iconInfo.hbmMask != NULL )
		DeleteObject(iconInfo.hbmMask);
	return size;
}

void DrawAlphaBlendedSelection( CDC& dc, CRect rcPaint, CRect rcSelBox, COLORREF clrSelFill, COLORREF clrSelBorder )
{
	CRect rcSelUpdate;
	rcPaint.NormalizeRect();
	rcSelBox.NormalizeRect();
	if ( rcSelUpdate.IntersectRect(rcSelBox, rcPaint) && !rcSelUpdate.IsRectEmpty() )
	{
		CMemBitmap bmpFill(&dc);
		if ( bmpFill.BeginDraw(rcSelUpdate.Width(), rcSelUpdate.Height()) )
		{
			CDC& dcSel = bmpFill.GetDC();
			CRect rcFill;
			rcFill.left		= 0;
			rcFill.top		= 0;
			rcFill.right	= rcSelUpdate.Width();
			rcFill.bottom	= rcSelUpdate.Height();
			
			dcSel.FillSolidRect(rcFill, clrSelFill);
			
			BLENDFUNCTION bf;
			bf.BlendFlags			= 0;
			bf.AlphaFormat			= 0;
			bf.BlendOp				= AC_SRC_OVER;
			bf.SourceConstantAlpha	= ALPHA_SELECTION_FILL;
			::AlphaBlend(dc, rcSelUpdate.left, rcSelUpdate.top, rcSelUpdate.Width(), rcSelUpdate.Height(), 
				dcSel, 0, 0, rcFill.right, rcFill.bottom, bf);
			
			CBrush brush(clrSelBorder);
			dc.FrameRect(rcSelBox, &brush);
		}
	}
}


/*----------------------------------------------------------------------------*/
/* CCustomDrawHelper
/*----------------------------------------------------------------------------*/
CCustomDrawHelper::CCustomDrawHelper()
	: m_bWndHasFocus(false)
	, m_bFocusItem(false)
	, m_bSelected(false)
	, m_bIsHotItem(false)
	, m_bDrawBorderWhenFill(true)
	, m_bIsPressed(false)
	, m_bWndEnabled(true)
	, m_rcBounds(0,0,0,0)
	, m_hWnd(NULL)
{

}

CCustomDrawHelper::~CCustomDrawHelper()
{
	
}

/*
	Different cases with different colors:
					wnd has focus							 				  wnd has no focus
           +--------------------------------+                          +-------------------------+
		selected					 	unselected					selected				 	unselected
     +---------------+               +------------+              +-------------+            +---------+
	hot		      not hot			hot		   not hot			hot		 	not hot		   hot	   not hot
     1           +--------+          3            x              1             4            3         x
	 		  focus   no focus
				1		2
*/

// TODO: The colors can be generated by some kind of algorithm so that the the caller can control the main color.
// e.g. With given color (like sky-blue), all the other colors can be generated by reducing/increasing its luminosity,
// so RGB2HSL and HSL2RGB will be needed in that case. However, the difficult part is how to design the rule, which I
// do not have time by now, so I just use hard coded value here.
void CCustomDrawHelper::DrawItemBackground( CDC* pDC, CRect rect, BYTE byAlpha /*= 255*/ )
{
	if ( ShouldFillBackground() )
	{
#ifdef CUSTOMDRAW_GRADIENT
		COLORREF clrFillColor1;
		COLORREF clrFillColor2;
		if ( m_bSelected )
		{
			if ( m_bIsHotItem || (m_bFocusItem && m_bWndHasFocus) )
			{
				clrFillColor1	= RGB_ITEM_SELFOCUS_FILL_COLOR1;
				clrFillColor2	= RGB_ITEM_SELFOCUS_FILL_COLOR2;
			}
			else if ( m_bWndHasFocus )
			{
				ASSERT( !m_bFocusItem );
				clrFillColor1	= RGB_ITEM_SELECTED_FILL_COLOR1;
				clrFillColor2	= RGB_ITEM_SELECTED_FILL_COLOR2;
			}
			else
			{
				clrFillColor1	= RGB_ITEM_SELNOFOCUS_FILL_COLOR1;
				clrFillColor2	= RGB_ITEM_SELNOFOCUS_FILL_COLOR2;
			}
		}
		else
		{
			// not selected, hot item
			clrFillColor1 = RGB_ITEM_HOT_FILL_COLOR1;
			clrFillColor2 = RGB_ITEM_HOT_FILL_COLOR2;
		}
		if ( m_bDrawBorderWhenFill )
		{
			COLORREF clrInnerBorder;
			COLORREF clrOuterBorder;
			if ( m_bSelected )
			{
				clrInnerBorder = (m_bWndHasFocus || m_bIsHotItem) ? RGB_ITEM_SELFOCUS_INNER_BORDER_COLOR : RGB_ITEM_SELNOFOCUS_INNER_BORDER_COLOR;
				clrOuterBorder = (m_bWndHasFocus || m_bIsHotItem) ? RGB_ITEM_SELFOCUS_OUTER_BORDER_COLOR : RGB_ITEM_SELNOFOCUS_OUTER_BORDER_COLOR;
			}
			else
			{
				clrInnerBorder = RGB_ITEM_HOT_INNER_BORDER_COLOR;
				clrOuterBorder = RGB_ITEM_HOT_OUTER_BORDER_COLOR;
			}
			DrawRoundRectItem(pDC, rect, clrFillColor1, clrFillColor2, clrInnerBorder, clrOuterBorder, byAlpha);
		}
		else
		{
			rect.DeflateRect(2, 2);
			FillGradient(pDC->GetSafeHdc(), rect, clrFillColor1, clrFillColor2, GFT_TOP_BOTTOM, byAlpha);
		}
#else
		pDC->FillSolidRect(rect, ::GetSysColor( m_bWndHasFocus ? COLOR_HIGHLIGHT : COLOR_BTNFACE));
#endif // CUSTOMDRAW_GRADIENT
		//TRACE("====> hilight %d %d [%d, %d]\n", nItem, nSubItem, rect.left, rect.right);
	}
	else if ( m_bFocusItem )
	{
		CRect& rcItem = m_rcBounds.IsRectEmpty() ? rect : m_rcBounds;
		DrawFocus(pDC, rcItem, byAlpha);
	}
}

void CCustomDrawHelper::DrawFocus( CDC* pDC, CRect rect, BYTE byAlpha /*= 255*/ )
{
	ASSERT( !ShouldFillBackground() && m_bFocusItem );
#ifdef CUSTOMDRAW_GRADIENT
	DrawSimpleRoundRectBorder(pDC, rect, RGB_ITEM_SELFOCUS_OUTER_BORDER_COLOR);
#else
	pDC->DrawFocusRect(rect);
#endif // CUSTOMDRAW_GRADIENT	
}

#ifdef CUSTOMDRAW_GRADIENT
void CCustomDrawHelper::DrawThemeBackground( CDC* pDC, CRect rect, CDTBT type )
{
	CDSTATEID state;
	if ( !m_bWndEnabled )
		state = CDSTATEID_DISABLED;
	else if ( m_bIsPressed )
		state = CDSTATEID_PRESSED;
	else if ( m_bIsHotItem )
		state = CDSTATEID_HOT;
	else
		state = CDSTATEID_NORMAL;
	CustomDrawThemeBackground(m_hWnd, pDC->GetSafeHdc(), type, state, rect);
}
#endif // CUSTOMDRAW_GRADIENT

/*----------------------------------------------------------------------------*/
/* CMemBitmap
/*----------------------------------------------------------------------------*/
CMemBitmap::CMemBitmap(CDC* pDC /*= NULL*/, CBitmap* pBmp /*= NULL*/)
	: m_pBmpOld(NULL)
	, m_pDC(pDC)
	, m_sizeBmp(0, 0)
	, m_pBmp(pBmp)
{
	
}

CMemBitmap::~CMemBitmap()
{
	if ( m_pBmpOld )
	{
		EndDraw();
	}
}

void CMemBitmap::SetBitmap( CBitmap* pBmp )
{
	ASSERT(pBmp);
	ASSERT(NULL == m_pBmpOld);	// you've called BeginDraw() ?
	m_pBmp = pBmp;
}

void CMemBitmap::SetCompatibleDC( CDC* pDC )
{
	ASSERT(pDC);
	ASSERT(NULL == m_pBmpOld);	// you've called BeginDraw() ?
	m_pDC = pDC;
}

BOOL CMemBitmap::BeginDraw( int cx, int cy, BOOL bAutoCreate /*= TRUE*/ )
{
	ASSERT(NULL == m_dcMemory.GetSafeHdc());
	cx = abs(cx);
	cy = abs(cy);
	CWindowDC	dctemp(NULL);
	CDC* pDC = m_pDC ? m_pDC : &dctemp;
	
	if( !m_dcMemory.GetSafeHdc() && !m_dcMemory.CreateCompatibleDC(pDC) )
	{
		ASSERT(FALSE);
		return FALSE;
	}

	CBitmap* pBmp = m_pBmp ? m_pBmp : &m_bmp;
	ASSERT( pBmp );	// can't fail!
	if ( bAutoCreate )
	{
		if ( pBmp->GetSafeHandle() )
		{
			pBmp->DeleteObject();
		}
		if( !pBmp->CreateCompatibleBitmap(pDC, cx, cy) )
		{
			ASSERT(FALSE);
			return FALSE;
		}
	}
	else
	{
		ASSERT( pBmp->GetSafeHandle() );
	}
	
	m_sizeBmp.cx = cx;
	m_sizeBmp.cy = cy;

	m_pBmpOld = (CBitmap*)m_dcMemory.SelectObject( GetBitmapPtr() );
	return TRUE;
}

void CMemBitmap::EndDraw()
{
#ifdef _DEBUG
	static bool s_bTestDraw = false;
	bool& bTestDraw = s_bTestDraw;
	if (bTestDraw)
	{
		::BitBlt( ::GetDC(NULL), 10, 10, m_sizeBmp.cx, m_sizeBmp.cy, m_dcMemory.GetSafeHdc(), 0, 0, SRCCOPY);
		bTestDraw = false;
	}
#endif // _DEBUG

	// Finish the bitmap
	m_dcMemory.SelectObject(m_pBmpOld);
	m_pBmpOld = NULL;
}

CMemBitmap& CMemBitmap::operator=( CMemBitmap& src )
{
	ASSERT( GetBitmapSize() == src.GetBitmapSize() );
	GetDC().BitBlt(0, 0, GetBitmapWidth(), GetBitmapHeight(), &src.GetDC(), 0, 0, SRCCOPY);
	return *this;
}

/*----------------------------------------------------------------------------*/
/* CSimpleMemDC
/*----------------------------------------------------------------------------*/
CSimpleMemDC::CSimpleMemDC( CDC& dc, CWnd* pWnd, OUTPUTTYPE type /*= OUTPUTTYPE_BITBLT*/ )
	: m_dc(dc)
	, m_memBitmap(&dc)
	, m_pWnd(pWnd)
	, m_ptOldWndOrg(0, 0)
	, m_outputType(type)
	, m_dwRop(SRCCOPY)
{
	ASSERT_VALID(m_pWnd);
	
	m_pWnd->GetClientRect(m_rect);
	
	m_rect.right += m_pWnd->GetScrollPos(SB_HORZ);
	m_rect.bottom += m_pWnd->GetScrollPos(SB_VERT);

	if ( m_memBitmap.BeginDraw(m_rect.Width(), m_rect.Height()) )
	{
		// additional stuff.
	}
}

CSimpleMemDC::CSimpleMemDC( CDC& dc, const CRect& rect, OUTPUTTYPE type /*= OUTPUTTYPE_BITBLT*/ )
	: m_dc(dc)
	, m_rect(rect)
	, m_memBitmap(&dc)
	, m_pWnd(NULL)
	, m_ptOldWndOrg(0, 0)
	, m_outputType(type)
	, m_dwRop(SRCCOPY)
{
	//ASSERT(!m_rect.IsRectEmpty());
	if ( m_memBitmap.BeginDraw(m_rect.Width(), m_rect.Height()) )
	{
		// make the drawing area starts from the top-left of the bitmap
		m_ptOldWndOrg = GetDC().SetWindowOrg(m_rect.left, m_rect.top);
	}
}

CSimpleMemDC::~CSimpleMemDC()
{
	// Copy the results to the on-screen DC
	CRect rectClip;
	int nClipType = m_dc.GetClipBox(rectClip);
	
	if ( m_pWnd )
	{
		if (nClipType != NULLREGION)
		{
			if (nClipType != SIMPLEREGION)
			{
				rectClip = m_rect;
			}
			
			//m_dc.BitBlt(rectClip.left, rectClip.top, rectClip.Width(), rectClip.Height(), &m_memBitmap.GetDC(), rectClip.left, rectClip.top, m_dwRop);
			PaintResult(rectClip, rectClip.left, rectClip.top);
		}
		else
		{
			// nothing is drawn
		}
	}
	else
	{
		//ASSERT( !m_rect.IsRectEmpty() && !m_rect.IsRectNull() );

		// restore the window origin
		GetDC().SetWindowOrg(m_ptOldWndOrg);

		CRect rectDraw;
		if ( rectDraw.IntersectRect(rectClip, m_rect) )
		{
			int nBmpX = rectDraw.left - m_rect.left;
			int nBmpY = rectDraw.top - m_rect.top;
			ASSERT(nBmpX >= 0 && nBmpY >= 0);
			//m_dc.BitBlt(rectDraw.left, rectDraw.top, rectDraw.Width(), rectDraw.Height(), &m_memBitmap.GetDC(), nBmpX, nBmpY, m_dwRop);
			PaintResult(rectDraw, nBmpX, nBmpY);
		}
	}
}

CSimpleMemDC& CSimpleMemDC::operator=( CSimpleMemDC& src )
{
	m_memBitmap = src.m_memBitmap;
	return *this;
}

CSimpleMemDC& CSimpleMemDC::operator=( CMemBitmap& src )
{
	m_memBitmap = src;
	return *this;
}

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, along with any associated source code and files, is licensed under The Apache License, Version 2.0


Written By
Software Developer (Senior)
Syrian Arab Republic Syrian Arab Republic
C++ , MFC , Win32 professional Developer.

Comments and Discussions