Click here to Skip to main content
15,892,161 members
Articles / Desktop Programming / MFC

VCOMBOBX in MFC - A Virtual Combo Box Control

Rate me:
Please Sign up or sign in to vote.
4.91/5 (33 votes)
4 Jan 2011CPOL8 min read 72K   11.3K   108  
A MFC based virtual combo box
/* 
 * 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 "CustomDrawControl.h"

#include <afxmt.h>		// CMutex

#include <algorithm>	// find

#ifdef DERIVE_FROM_MFCTOOLTIPCTRL
#include "afxtoolbarbutton.h"
#include "afxdrawmanager.h"
#include "afxvisualmanager.h"
#include "afxribbonbutton.h"
#include "afxribbonbar.h"
#endif // DERIVE_FROM_MFCTOOLTIPCTRL


/*----------------------------------------------------------------------------*/
/* CCustomDrawHeaderCtrl
/*----------------------------------------------------------------------------*/

IMPLEMENT_DYNCREATE(CCustomDrawHeaderCtrl, CCustomDrawHeaderCtrlBase)

CCustomDrawHeaderCtrl::CCustomDrawHeaderCtrl()
	: m_hFont(NULL)
	, m_bIsMousePressed(FALSE)
	, m_nHighlightedItem(-1)
	, m_bTracked(FALSE)
	, m_pTextDrawer(NULL)
{
	
}

CCustomDrawHeaderCtrl::~CCustomDrawHeaderCtrl()
{
	
}

BEGIN_MESSAGE_MAP(CCustomDrawHeaderCtrl, CCustomDrawHeaderCtrlBase)
	ON_WM_ERASEBKGND()
	ON_WM_PAINT()
	ON_WM_LBUTTONDOWN()
	ON_WM_LBUTTONUP()
	ON_WM_MOUSEMOVE()
	ON_WM_CANCELMODE()
	ON_WM_CREATE()
	ON_MESSAGE(WM_MOUSELEAVE, OnMouseLeave)
	ON_MESSAGE(WM_SETFONT, OnSetFont)
END_MESSAGE_MAP()

void CCustomDrawHeaderCtrl::PreSubclassWindow()
{
	CCustomDrawHeaderCtrlBase::PreSubclassWindow();

	_AFX_THREAD_STATE* pThreadState = AfxGetThreadState();
	if (pThreadState->m_pWndInit == NULL)
	{
		OnInitHeader();
	}
}

int CCustomDrawHeaderCtrl::OnCreate( LPCREATESTRUCT lpCreateStruct )
{
	if (CCustomDrawHeaderCtrlBase::OnCreate(lpCreateStruct) == -1)
		return -1;
	
	OnInitHeader();
	
	return 0;
}

BOOL CCustomDrawHeaderCtrl::IsDragDropEnabled() const
{
	return (HDS_DRAGDROP & GetStyle());
}

void CCustomDrawHeaderCtrl::EnableDragDrop( BOOL bEnable /*= TRUE*/ )
{
	DWORD dwStyle = (DWORD)::GetWindowLong(m_hWnd, GWL_STYLE);
	if (bEnable)
		dwStyle |= HDS_DRAGDROP;
	else
		dwStyle &= ~HDS_DRAGDROP;
	::SetWindowLong(m_hWnd, GWL_STYLE, dwStyle);
}

BOOL CCustomDrawHeaderCtrl::OnEraseBkgnd( CDC* pDC )
{
	return TRUE;	// flicker free
}

void CCustomDrawHeaderCtrl::OnDrawItem( CDC* pDC, int iItem, CRect rect, BOOL bIsPressed, BOOL bIsHighlighted )
{
	ASSERT_VALID(this);
	ASSERT_VALID(pDC);

	OnFillBackground(pDC, iItem, rect, bIsPressed, bIsHighlighted);

	// Draw border:
	OnDrawBorder(pDC, iItem, rect, bIsPressed, bIsHighlighted);

	if (iItem < 0)
	{
		return;
	}

	HD_ITEM hdItem;
	memset(&hdItem, 0, sizeof(hdItem));
	hdItem.mask = HDI_FORMAT | HDI_BITMAP | HDI_TEXT | HDI_IMAGE;

	TCHAR szText [256];
	hdItem.pszText = szText;
	hdItem.cchTextMax = 255;

	if (!GetItem(iItem, &hdItem))
	{
		return;
	}

	// Draw bitmap and image:
	OnDrawImage(pDC, hdItem, rect, bIsPressed, bIsHighlighted);

	OnDrawBitmap(pDC, hdItem, rect, bIsPressed, bIsHighlighted);

	// Draw text:
	OnDrawText(pDC, hdItem, rect, bIsPressed, bIsHighlighted);
}

void CCustomDrawHeaderCtrl::OnDrawImage( CDC* pDC, HD_ITEM& hdItem, CRect& rect, BOOL bIsPressed, BOOL bIsHighlighted )
{
	if ((hdItem.fmt & HDF_IMAGE) && hdItem.iImage >= 0)
	{
		// The column has a image from imagelist:
		CImageList* pImageList = GetImageList();
		if (pImageList != NULL)
		{
			int cx = 0;
			int cy = 0;
			
			VERIFY(::ImageList_GetIconSize(*pImageList, &cx, &cy));
			
			CPoint pt = rect.TopLeft();
			pt.x ++;
			pt.y = (rect.top + rect.bottom - cy) / 2;
			
			VERIFY(pImageList->Draw(pDC, hdItem.iImage, pt, ILD_NORMAL));
			
			rect.left += cx;
		}
	}
}

void CCustomDrawHeaderCtrl::OnDrawBitmap( CDC* pDC, HD_ITEM& hdItem, CRect& rect, BOOL bIsPressed, BOOL bIsHighlighted )
{
	if ((hdItem.fmt &(HDF_BITMAP | HDF_BITMAP_ON_RIGHT)) && hdItem.hbm != NULL)
	{
		CBitmap* pBmp = CBitmap::FromHandle(hdItem.hbm);
		ASSERT_VALID(pBmp);
		
		BITMAP bmp;
		pBmp->GetBitmap(&bmp);
		
		CRect rectBitmap = rect;
		if (hdItem.fmt & HDF_BITMAP_ON_RIGHT)
		{
			rectBitmap.right--;
			rect.right = rectBitmap.left = rectBitmap.right - bmp.bmWidth;
		}
		else
		{
			rectBitmap.left++;
			rect.left = rectBitmap.right = rectBitmap.left + bmp.bmWidth;
		}
		
		rectBitmap.top += max(0, (rectBitmap.Height() - bmp.bmHeight) / 2);
		rectBitmap.bottom = rectBitmap.top + bmp.bmHeight;
		
		pDC->DrawState(rectBitmap.TopLeft(), rectBitmap.Size(), pBmp, DSS_NORMAL);
	}
}

void CCustomDrawHeaderCtrl::OnDrawText( CDC* pDC, HD_ITEM& hdItem, CRect& rect, BOOL bIsPressed, BOOL bIsHighlighted )
{
	if ((hdItem.fmt & HDF_STRING) && hdItem.pszText != NULL)
	{
		CRect rectLabel = rect;
		const int nTextMargin = 5;
		rectLabel.DeflateRect(nTextMargin, 0);
		if (bIsPressed)
		{
			rectLabel.OffsetRect(1, 1);	// push-like
		}
		
		UINT uiTextFlags = DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS | DT_NOPREFIX;
		if (hdItem.fmt & HDF_CENTER)
		{
			uiTextFlags |= DT_CENTER;
		}
		else if (hdItem.fmt & HDF_RIGHT)
		{
			uiTextFlags |= DT_RIGHT;
		}
		
		CString strLabel = hdItem.pszText;
		if ( GetTextDrawer() )
			GetTextDrawer()->Draw(pDC, strLabel, rectLabel, uiTextFlags);
		else
			pDC->DrawText(strLabel, rectLabel, uiTextFlags);
	}
}

void CCustomDrawHeaderCtrl::OnFillBackground( CDC* pDC )
{
	ASSERT_VALID(this);
	ASSERT_VALID(pDC);
	
	CRect rectClient;
	GetClientRect(rectClient);
	
#ifdef CUSTOMDRAW_GRADIENT
	pDC->FillSolidRect(rectClient, ::GetSysColor(COLOR_WINDOW));
#else
	pDC->FillSolidRect(rectClient, ::GetSysColor(COLOR_BTNFACE));
#endif // CUSTOMDRAW_GRADIENT
}

void CCustomDrawHeaderCtrl::OnFillBackground( CDC* pDC, int iItem, CRect& rect, BOOL bIsPressed, BOOL bIsHighlighted )
{
	ASSERT_VALID(this);
	ASSERT_VALID(pDC);

#ifdef CUSTOMDRAW_GRADIENT
	CCustomDrawHelper drawer;
	drawer.m_bWndEnabled		= !!IsWindowEnabled();
	drawer.m_bWndHasFocus		= ::GetFocus() == GetSafeHwnd();
	drawer.m_bFocusItem			= false;
	drawer.m_bIsHotItem			= bIsHighlighted ? true : false;
	drawer.m_bIsPressed			= bIsPressed ? true : false;
	drawer.m_hWnd				= m_hWnd;
	drawer.DrawThemeBackground(pDC, rect, CDTBT_HEADER);
#endif // CUSTOMDRAW_GRADIENT
}

void CCustomDrawHeaderCtrl::OnDrawBorder( CDC* pDC, int iItem, CRect& rect, BOOL bIsPressed, BOOL bIsHighlighted )
{
	ASSERT_VALID(pDC);
#ifdef CUSTOMDRAW_GRADIENT
	COLORREF clrBorder;
	if (bIsPressed || bIsHighlighted)
	{
		if (bIsPressed)
		{
			clrBorder = RGB(112,185,223);
		}
		else
		{
			clrBorder = RGB(147,201,227);
		}
	}
	else
	{
		clrBorder = RGB(213,213,213);
	}
	CBrush brBorder(clrBorder);
	pDC->FrameRect(rect, &brBorder);
#else
	COLORREF clrHilite	= GetSysColor(COLOR_3DHILIGHT);
	COLORREF clrShadow	= GetSysColor(COLOR_3DSHADOW);
	if (bIsPressed)
	{
		pDC->Draw3dRect(rect, clrShadow, clrShadow);
		
		rect.left++;
		rect.top++;
	}
	else
	{
		pDC->Draw3dRect(rect, clrHilite, clrShadow);
	}
#endif // CUSTOMDRAW_GRADIENT
}

void CCustomDrawHeaderCtrl::OnPaint()
{
	CPaintDC dc(this); // device context for painting
	CSimpleMemDC memDC(dc, this);
	CDC* pDC = &memDC.GetDC();
	
	CRect rectClip;
	dc.GetClipBox(rectClip);
	
	CRect rectClient;
	GetClientRect(rectClient);
	
	OnFillBackground(pDC);
	
	CFont* pOldFont = SelectFont(pDC);
	ASSERT_VALID(pOldFont);
	
	pDC->SetTextColor(RGB(0x4C,0x4C,0x4C));
	pDC->SetBkMode(TRANSPARENT);
	
	CRect rect;
	GetClientRect(rect);
	
	CRect rectItem;
	int nCount = GetItemCount();
	
	int xMax = 0;
	
	for (int i = 0; i < nCount; i++)
	{
		// Is item pressed?
		CPoint ptCursor;
		::GetCursorPos(&ptCursor);
		ScreenToClient(&ptCursor);
		
		HDHITTESTINFO hdHitTestInfo;
		hdHitTestInfo.pt = ptCursor;
		
		int iHit = (int) SendMessage(HDM_HITTEST, 0, (LPARAM) &hdHitTestInfo);
		
		BOOL bIsHighlighted = iHit == i &&(hdHitTestInfo.flags & HHT_ONHEADER);
		BOOL bIsPressed = m_bIsMousePressed && bIsHighlighted;
		
		GetItemRect(i, rectItem);
		
		CRgn rgnClip;
		rgnClip.CreateRectRgnIndirect(&rectItem);
		pDC->SelectClipRgn(&rgnClip);
		
		// Draw item:
		OnDrawItem(pDC, i, rectItem, bIsPressed, m_nHighlightedItem == i);
		
		pDC->SelectClipRgn(NULL);
		
		xMax = max(xMax, rectItem.right);
	}
	
	// Draw "tail border":
	if (nCount == 0)
	{
		rectItem = rect;
		rectItem.right++;
	}
	else
	{
		rectItem.left = xMax;
		rectItem.right = rect.right + 1;
	}
	
	OnDrawItem(pDC, -1, rectItem, FALSE, FALSE);
	
	pDC->SelectObject(pOldFont);
}

void CCustomDrawHeaderCtrl::OnLButtonDown( UINT nFlags, CPoint point )
{
	m_bIsMousePressed = TRUE;
	CCustomDrawHeaderCtrlBase::OnLButtonDown(nFlags, point);
}

void CCustomDrawHeaderCtrl::OnLButtonUp( UINT nFlags, CPoint point )
{
	m_bIsMousePressed = FALSE;
	CCustomDrawHeaderCtrlBase::OnLButtonUp(nFlags, point);
}

void CCustomDrawHeaderCtrl::OnMouseMove( UINT nFlags, CPoint point )
{
	if ((nFlags & MK_LBUTTON) == 0)
	{
		HDHITTESTINFO hdHitTestInfo;
		hdHitTestInfo.pt = point;
		
		int nPrevHighlightedItem = m_nHighlightedItem;
		m_nHighlightedItem = (int) SendMessage(HDM_HITTEST, 0, (LPARAM) &hdHitTestInfo);
		
		if ((hdHitTestInfo.flags & HHT_ONHEADER) == 0)
		{
			m_nHighlightedItem = -1;
		}
		
		if (!m_bTracked)
		{
			m_bTracked = TRUE;
			
			TRACKMOUSEEVENT trackmouseevent;
			trackmouseevent.cbSize		= sizeof(trackmouseevent);
			trackmouseevent.dwFlags		= TME_LEAVE;
			trackmouseevent.hwndTrack	= GetSafeHwnd();
			trackmouseevent.dwHoverTime	= HOVER_DEFAULT;
			//::AFXTrackMouse(&trackmouseevent);
			_TrackMouseEvent(&trackmouseevent);
		}
		
		if (nPrevHighlightedItem != m_nHighlightedItem)
		{
			RedrawWindow();
		}
	}
	
	CCustomDrawHeaderCtrlBase::OnMouseMove(nFlags, point);
}

void CCustomDrawHeaderCtrl::OnCancelMode()
{
	CCustomDrawHeaderCtrlBase::OnCancelMode();
	
	if (m_nHighlightedItem >= 0)
	{
		m_nHighlightedItem = -1;
		Invalidate(FALSE);
	}
}

LRESULT CCustomDrawHeaderCtrl::OnMouseLeave( WPARAM,LPARAM )
{
	m_bTracked = FALSE;
	
	if (m_nHighlightedItem >= 0)
	{
		m_nHighlightedItem = -1;
		Invalidate(FALSE);
	}
	
	return 0;
}

LRESULT CCustomDrawHeaderCtrl::OnSetFont( WPARAM wParam, LPARAM lParam )
{
	BOOL bRedraw = (BOOL) LOWORD(lParam);
	
	m_hFont = (HFONT) wParam;
	
	if (bRedraw)
	{
		RedrawWindow();
		UpdateWindow();
	}
	
	return 0;
}

CFont* CCustomDrawHeaderCtrl::SelectFont( CDC *pDC )
{
	ASSERT_VALID(this);
	ASSERT_VALID(pDC);
	
	CFont* pOldFont = NULL;
	
	if (m_hFont != NULL)
	{
		pOldFont = pDC->SelectObject(CFont::FromHandle(m_hFont));
	}
	else
	{
		pOldFont = (CFont*) pDC->SelectStockObject(DEFAULT_GUI_FONT);
	}
	
	return pOldFont;
}


/*----------------------------------------------------------------------------*/
/* CSortHeaderCtrl
/*----------------------------------------------------------------------------*/
IMPLEMENT_DYNCREATE(CSortHeaderCtrl, CSortHeaderCtrlBase)

CSortHeaderCtrl::CSortHeaderCtrl()
	: m_nSortColumn( -1 )
	, m_bSortable(true)
{
}

CSortHeaderCtrl::~CSortHeaderCtrl()
{
}


BEGIN_MESSAGE_MAP(CSortHeaderCtrl, CSortHeaderCtrlBase)
END_MESSAGE_MAP()

BOOL CSortHeaderCtrl::Init( HWND hWndToSubClass )
{
	const BOOL bStatus = CSortHeaderCtrlBase::SubclassWindow( hWndToSubClass );
	return bStatus;
}

void CSortHeaderCtrl::SwitchSortItem( int nSortItem )
{
	if ( m_nSortColumn == nSortItem )
	{
		m_bAscending = !m_bAscending;
	}
	else
	{
		m_nSortColumn = nSortItem;
		m_bAscending = DEFAULT_SORT_ASCENDING;
	}
}

void CSortHeaderCtrl::OnFillBackground( CDC* pDC, int iItem, CRect& rect, BOOL bIsPressed, BOOL bIsHighlighted )
{
	CSortHeaderCtrlBase::OnFillBackground(pDC, iItem, rect, bIsPressed, bIsHighlighted);
	if ( m_bSortable && iItem >= 0 && iItem == m_nSortColumn )
	{
		if ( !bIsPressed && !bIsHighlighted )
		{
#ifdef CUSTOMDRAW_GRADIENT
			Fill4ColorsGradient(pDC->GetSafeHdc(), rect, 
					RGB_HEADER_SORT_FILL_COLOR1, RGB_HEADER_SORT_FILL_COLOR2, RGB_HEADER_SORT_FILL_COLOR3, RGB_HEADER_SORT_FILL_COLOR4, 
					TRUE, HEADER_FIRST_HALF_PERCENTAGE);
#else
			pDC->FillSolidRect(rect, RGB(216,236,246));
#endif // CUSTOMDRAW_GRADIENT
		}

		CPoint ptCenter		= rect.CenterPoint();
		CRect rectArrow;
		rectArrow.left		= ptCenter.x - 3;
		rectArrow.right		= ptCenter.x + 3;
		rectArrow.top		= rect.top + 2;
		rectArrow.bottom	= rectArrow.top + 3;
		DrawSortArrow(pDC, rectArrow, m_bAscending);
	}
}

void CSortHeaderCtrl::OnDrawBorder( CDC* pDC, int iItem, CRect& rect, BOOL bIsPressed, BOOL bIsHighlighted )
{
	if ( m_bSortable && iItem >= 0 && iItem == m_nSortColumn )
	{
		COLORREF clrBorder = RGB(150,217,249);
		CBrush brBorder(clrBorder);
		pDC->FrameRect(rect, &brBorder);
	}
	else
		CSortHeaderCtrlBase::OnDrawBorder(pDC, iItem, rect, bIsPressed, bIsHighlighted);
}

void CSortHeaderCtrl::SetSortable( bool bSortable /*= true*/, bool bRedraw /*= true*/ )
{
	m_bSortable = bSortable;
	if (bRedraw)
	{
		Invalidate(FALSE);
	}
}

/*----------------------------------------------------------------------------*/
/* CCustomDrawToolTipCtrl
/*----------------------------------------------------------------------------*/

#ifndef TTS_BALLOON
	#define TTS_BALLOON 0x40
#endif

#ifndef TTM_SETTITLE
	#define TTM_SETTITLE (WM_USER + 32)
#endif

IMPLEMENT_DYNCREATE(CCustomDrawToolTipCtrl, CCDToolTipCtrlBase)

CCustomDrawToolTipCtrl::CCustomDrawToolTipCtrl(CCustomDrawToolTipInfo* pParams /*= NULL*/)
	: m_DisplayIDWParam(-1)
	, m_DisplayIDLParam(-1)
#ifndef MSVC_NEW_VER
	, m_hFont(NULL)
#endif // MSVC_NEW_VER
	, m_pTextDrawer(NULL)
{
	SetParams(pParams);
#ifndef DERIVE_FROM_MFCTOOLTIPCTRL
	m_ptMargin					= CPoint(0, 0);
	m_sizeImage					= CSize(0, 0);
	m_ptMargin					= CPoint(0, 0);
	m_ptLocation				= CPoint(-1, -1);
#endif // DERIVE_FROM_MFCTOOLTIPCTRL
	m_sizeDescrImage			= CSize(0, 0);
	m_sizeSupplementalImage			= CSize(0, 0);
	m_nTextTabSize				= 4;
	m_nLabelHeight				= 0;

	m_hLabelIcon				= NULL;
	m_hDescrIcon				= NULL;
	m_hSupplementalBmp			= NULL;
}

CCustomDrawToolTipCtrl::~CCustomDrawToolTipCtrl()
{
	
}

BEGIN_MESSAGE_MAP(CCustomDrawToolTipCtrl, CCDToolTipCtrlBase)
#ifndef DERIVE_FROM_MFCTOOLTIPCTRL
	//ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, OnCustomdraw)
#endif // DERIVE_FROM_MFCTOOLTIPCTRL
	ON_WM_ERASEBKGND()
	ON_WM_PAINT()
	ON_WM_CREATE()
	ON_NOTIFY_REFLECT(TTN_SHOW, OnShow)
	ON_NOTIFY_REFLECT(TTN_POP, OnPop)
#ifndef MSVC_NEW_VER
	ON_MESSAGE(WM_SETFONT, OnSetFont)
#endif // MSVC_NEW_VER
END_MESSAGE_MAP()

#define TIPTEXTBUFFER_SIZE	80

void CCustomDrawToolTipCtrl::SetText( NMHDR* pNMHDR, LPCTSTR lpcszText )
{
	// the szText can not exceed 80 characters.
	// See http://msdn.microsoft.com/en-us/library/bb760252(VS.85).aspx#tooltip_sample_multiline
	
	// handle both ANSI and UNICODE versions of the message
	LPTOOLTIPTEXTA pTTTA = reinterpret_cast<LPTOOLTIPTEXTA>(pNMHDR);
	LPTOOLTIPTEXTW pTTTW = reinterpret_cast<LPTOOLTIPTEXTW>(pNMHDR);

	size_t nStrLen = _tcslen(lpcszText);
	if ( nStrLen >= TIPTEXTBUFFER_SIZE )
	{
		// the 
#ifdef _UNICODE
		// Unicode environment
		if (pNMHDR->code == TTN_NEEDTEXTA)
		{
			m_astrLongTipText = toastring(lpcszText);
			pTTTA->lpszText = (PSTR)m_astrLongTipText.c_str();
		}
		else if(pNMHDR->code == TTN_NEEDTEXTW)
		{
			m_wstrLongTipText = lpcszText;
			pTTTW->lpszText = (PWSTR)m_wstrLongTipText.c_str();
		}
#else
		// Ansi environment
		if (pNMHDR->code == TTN_NEEDTEXTA)
		{
			m_astrLongTipText = lpcszText;
			pTTTA->lpszText = (PSTR)m_astrLongTipText.c_str();
		}
		else if(pNMHDR->code == TTN_NEEDTEXTW)
		{
			m_wstrLongTipText = towstring(lpcszText);
			pTTTW->lpszText = (PWSTR)m_wstrLongTipText.c_str();
		}
#endif
	}
	else
	{
		// otherwise, just do the conversion & copy
#ifdef _UNICODE
		// Unicode environment
		if (pNMHDR->code == TTN_NEEDTEXTA)
			_wcstombsz(pTTTA->szText, lpcszText, TIPTEXTBUFFER_SIZE);
		else
			lstrcpyn(pTTTW->szText, lpcszText, TIPTEXTBUFFER_SIZE);
#else
		// Ansi environment
		if (pNMHDR->code == TTN_NEEDTEXTA)
			lstrcpyn(pTTTA->szText, lpcszText, TIPTEXTBUFFER_SIZE);
		else if(pNMHDR->code == TTN_NEEDTEXTW)
		{
		#ifdef MSVC_NEW_VER
			mbstowcs_s(NULL, pTTTW->szText, TIPTEXTBUFFER_SIZE, lpcszText, _TRUNCATE);
		#else
			mbstowcs(pTTTW->szText, lpcszText, TIPTEXTBUFFER_SIZE);
		#endif // MSVC_NEW_VER
		}
#endif
	}
}

void CCustomDrawToolTipCtrl::SetParams( CCustomDrawToolTipInfo* pParams )
{
	ASSERT_VALID(this);
	
	if (pParams == NULL)
	{
		m_Params = CCustomDrawToolTipInfo();
	}
	else
	{
		m_Params = *pParams;
	}

#ifdef DERIVE_FROM_MFCTOOLTIPCTRL
	CCDToolTipCtrlBase::m_Params = m_Params;
#endif // DERIVE_FROM_MFCTOOLTIPCTRL
}

BOOL CCustomDrawToolTipCtrl::CheckDisplayToolTip( WPARAM wParam, LPARAM lParam /*= 0*/ )
{
	if ( m_DisplayIDWParam != wParam || m_DisplayIDLParam != lParam )
	{
		m_DisplayIDWParam = wParam;
		m_DisplayIDLParam = lParam;
		return TRUE;
	}
	return FALSE;
}

void CCustomDrawToolTipCtrl::SetFixedWidth( int nWidthRegular, int nWidthLargeImage )
{
#ifdef DERIVE_FROM_MFCTOOLTIPCTRL
	CCDToolTipCtrlBase::SetFixedWidth(nWidthRegular, nWidthLargeImage);
#else
	m_nFixedWidthRegular	= nWidthRegular;
	m_nFixedWidthWithImage	= nWidthLargeImage;
#endif // DERIVE_FROM_MFCTOOLTIPCTRL
}

int CCustomDrawToolTipCtrl::GetFixedWidth()
{
#ifdef DERIVE_FROM_MFCTOOLTIPCTRL
	return CCDToolTipCtrlBase::GetFixedWidth();
#else
	ASSERT_VALID(this);
	#ifdef MSVC_NEW_VER
	if (m_sizeImage.cx <= (int)(afxGlobalData.GetRibbonImageScale() * 32))
	{
		return m_nFixedWidthRegular;
	}
	else
	{
		return m_nFixedWidthWithImage;
	}
	#else
	return 0;
	#endif // MSVC_NEW_VER
#endif // DERIVE_FROM_MFCTOOLTIPCTRL
}

int CCustomDrawToolTipCtrl::OnCreate( LPCREATESTRUCT lpCreateStruct )
{
#ifdef DERIVE_FROM_MFCTOOLTIPCTRL
	return CCDToolTipCtrlBase::OnCreate(lpCreateStruct);
#else
	if (CCDToolTipCtrlBase::OnCreate(lpCreateStruct) == -1)
		return -1;
	
	ModifyStyle(WS_BORDER, 0);

	if (m_Params.m_bBalloonTooltip)
	{
		ModifyStyle(0, TTS_BALLOON);
	}
	
	return 0;
#endif // DERIVE_FROM_MFCTOOLTIPCTRL
}

BOOL CCustomDrawToolTipCtrl::OnEraseBkgnd( CDC* pDC )
{
#ifdef DERIVE_FROM_MFCTOOLTIPCTRL
	return CCDToolTipCtrlBase::OnEraseBkgnd(pDC);
#else
	CRect rect;
    GetClientRect (rect);
	
    COLORREF clrDummy;
    OnFillBackground(pDC, rect, clrDummy, clrDummy);
	
	return TRUE;
#endif // DERIVE_FROM_MFCTOOLTIPCTRL
}

void CCustomDrawToolTipCtrl::OnShow( NMHDR* pNMHDR, LRESULT* pResult )
{
	*pResult = 0;

	CWnd* pOwnerWnd = GetOwner();
	if ( pOwnerWnd )
	{
		pOwnerWnd->SendMessage(WM_CDTOOLTIPCTRL_NOTIFY, CDTOOLTIP_ONBEFORE_SHOW);
	}

	if (m_Params.m_bVislManagerTheme)
	{
#ifdef DERIVE_FROM_MFCTOOLTIPCTRL
		CMFCVisualManager::GetInstance()->GetToolTipInfo(m_Params);
#endif // DERIVE_FROM_MFCTOOLTIPCTRL
		m_Params.m_bVislManagerTheme = TRUE;
	}

	if (m_Params.m_bBalloonTooltip)
	{
		return;
	}

	CPoint ptCursor;
	::GetCursorPos(&ptCursor);

#ifdef DERIVE_FROM_MFCTOOLTIPCTRL
	GetHotButton();
#endif // DERIVE_FROM_MFCTOOLTIPCTRL

	m_sizeImage			= m_Params.m_bDrawIcon ? GetIconSize() : CSize(0, 0);
	m_ptMargin			= m_Params.m_bRoundedCorners ? CPoint(6, 4) : CPoint(4, 2);
	m_sizeDescrImage	= m_Params.m_bDrawDescrIcon ? GetDescrIconSize() : CSize(0, 0);
	m_sizeSupplementalImage	= m_Params.m_bDrawSupplementalImage ? GetSupplementalImageSize() : CSize(0, 0);

	CRect rectMargin;
	GetMargin(rectMargin);

	CRect rectDraw;
	GetClientRect(rectDraw);

	CRect rectText(rectDraw);

	CClientDC dc(this);
	CSize sizeText = OnDrawLabel(&dc, rectText, TRUE);

	int cx = sizeText.cx;
	int cy = sizeText.cy;

	CSize sizeDescr(0, 0);

	m_nLabelHeight = cy;
	if (m_sizeImage.cy > 0 && m_Params.m_bDrawIcon)
	{
		m_nLabelHeight = max(cy, m_sizeImage.cy);
	}

	cy = m_nLabelHeight;

	if ( m_Params.m_bDrawDescription && !m_strDescription.IsEmpty() )
	{
		sizeDescr = OnDrawDescription(&dc, rectText, TRUE);

		if (m_sizeDescrImage != CSize(0, 0) && m_Params.m_bDrawDescrIcon )
		{
			sizeDescr.cx += m_sizeDescrImage.cx + m_ptMargin.x;
			sizeDescr.cy = max(sizeDescr.cy, m_sizeDescrImage.cy);
		}

		if ( sizeDescr.cy <= 0)
			cy += 2 * m_ptMargin.y;
		else
		{
			cy += sizeDescr.cy + 4 * m_ptMargin.y;
			cx = max(cx, sizeDescr.cx);
		}
	}

	if (m_sizeImage.cx > 0 && m_Params.m_bDrawIcon)
	{
		cx += m_sizeImage.cx + m_ptMargin.x;
	}

	CSize sizeBody = OnDrawBody(&dc, rectDraw, TRUE);
	if (sizeBody != CSize(0, 0))
	{
		cx = max(cx, sizeBody.cx + 2 * m_ptMargin.x);
		cy += sizeBody.cy + m_ptMargin.y;
	}

	cx += 2 * m_ptMargin.x;
	cy += 2 * m_ptMargin.y;

	const int nFixedWidth = GetFixedWidth();
	if (nFixedWidth > 0 && sizeDescr != CSize(0, 0))
	{
		cx = max(cx, nFixedWidth);
	}

	CRect rectWindow;
	GetWindowRect(rectWindow);

	int x = rectWindow.left;
	int y = rectWindow.top;

	if (m_ptLocation != CPoint(-1, -1))
	{
		x = m_ptLocation.x;
		y = m_ptLocation.y;

		*pResult = 1;
	}

	CRect rectScreen;

	MONITORINFO mi;
	mi.cbSize = sizeof(MONITORINFO);
	if (GetMonitorInfo(MonitorFromPoint(rectWindow.TopLeft(), MONITOR_DEFAULTTONEAREST), &mi))
	{
		rectScreen = mi.rcWork;
	}
	else
	{
		::SystemParametersInfo(SPI_GETWORKAREA, 0, &rectScreen, 0);
	}

	int nBottom = max(ptCursor.y + cy + ::GetSystemMetrics(SM_CYCURSOR), y + cy + 2);
	if (nBottom > rectScreen.bottom)
	{
		y = ptCursor.y - cy - 1;
		
#ifdef DERIVE_FROM_MFCTOOLTIPCTRL
		if (m_pRibbonButton != NULL && m_ptLocation != CPoint(-1, -1))
		{
			ASSERT_VALID(m_pRibbonButton);

			CMFCRibbonBar* pRibbon = m_pRibbonButton->GetTopLevelRibbonBar();
			if (pRibbon->GetSafeHwnd() != NULL)
			{
				CRect rectRibbon;
				pRibbon->GetWindowRect(rectRibbon);

				y = rectRibbon.top - cy;
			}
		}
#endif // DERIVE_FROM_MFCTOOLTIPCTRL
		*pResult = 1;
	}

	if (x + cx + 2 > rectScreen.right)
	{
		if ((*pResult) == 1) // Y has been changed
		{
			x = ptCursor.x - cx - 1;
		}
		else
		{
			x = rectScreen.right - cx - 1;
			*pResult = 1;
		}
	}

// 	const CWnd* pWndInsertAfter = NULL;
// 	UINT nFlags = SWP_NOZORDER;
	const CWnd* pWndInsertAfter = &wndTopMost;
	UINT nFlags = 0;
	if ((*pResult) == 1)
	{
		SetWindowPos(pWndInsertAfter, x, y, cx, cy, nFlags | SWP_NOACTIVATE);
	}
	else
	{
		SetWindowPos(pWndInsertAfter, -1, -1, cx, cy, nFlags | SWP_NOMOVE | SWP_NOACTIVATE);
	}

	if (m_Params.m_bRoundedCorners)
	{
		CRgn rgn;
		rgn.CreateRoundRectRgn(0, 0, cx + 1, cy + 1, 4, 4);

		SetWindowRgn(rgn, FALSE);
	}
}

void CCustomDrawToolTipCtrl::OnPop( NMHDR* pNMHDR, LRESULT* pResult )
{
	CWnd* pOwnerWnd = GetOwner();
	if ( pOwnerWnd )
	{
		pOwnerWnd->SendMessage(WM_CDTOOLTIPCTRL_NOTIFY, CDTOOLTIP_ONBEFORE_POP);
	}

	if ( m_hLabelIcon )
	{
		::DestroyIcon(m_hLabelIcon);
		m_hLabelIcon		= NULL;
	}
	if ( m_hDescrIcon )
	{
		::DestroyIcon(m_hDescrIcon);
		m_hDescrIcon		= NULL;
	}
	if ( m_hSupplementalBmp )
	{
		::DeleteObject(m_hSupplementalBmp);
		m_hSupplementalBmp	= NULL;
	}

	m_strLabel.Empty();
	m_strSupplementalDescription.Empty();

#ifdef DERIVE_FROM_MFCTOOLTIPCTRL
	CCDToolTipCtrlBase::OnPop(pNMHDR, pResult);
#else
	m_strDescription.Empty();
	m_ptLocation	= CPoint(-1, -1);
	*pResult = 0;
#endif // DERIVE_FROM_MFCTOOLTIPCTRL
}

void CCustomDrawToolTipCtrl::OnPaint()
{
	if (m_Params.m_bBalloonTooltip)
	{
		CToolTipCtrl::OnPaint();
		return;
	}
	
	CPaintDC dcPaint(this); // device context for painting
	
	CSimpleMemDC memDC(dcPaint, this);
	CDC* pDC = &memDC.GetDC();
	
	CRect rect;
	GetClientRect(rect);
	
	CRect rectMargin;
	GetMargin(rectMargin);

	CRect rectDraw(rect);
	
	rectDraw.DeflateRect(rectMargin);
	rectDraw.DeflateRect(m_ptMargin.x, m_ptMargin.y);

	CRect rectText(rectDraw);
	
	COLORREF clrLine = m_Params.m_clrBorder == (COLORREF)-1 ? ::GetSysColor(COLOR_INFOTEXT) : m_Params.m_clrBorder;
	COLORREF clrText = m_Params.m_clrText == (COLORREF)-1 ? ::GetSysColor(COLOR_INFOTEXT) : m_Params.m_clrText;
	
	// Fill background:
	OnFillBackground(pDC, rect, clrText, clrLine);
	
	CPen penLine(PS_SOLID, 1, clrLine);
	CPen* pOldPen = pDC->SelectObject(&penLine);
	
	// Draw border:
	OnDrawBorder(pDC, rect, clrLine);

	int nIconHeight = 0;
	
	// Draw icon:
	if (m_sizeImage != CSize(0, 0) && m_Params.m_bDrawIcon)
	{
		CRect rectImage = rectText;
		rectImage.right = rectImage.left + m_sizeImage.cx;
		rectImage.bottom = rectImage.top + m_sizeImage.cy;
		
		OnDrawIcon(pDC, rectImage);
		
		rectText.left += m_sizeImage.cx + m_ptMargin.x;

		nIconHeight = m_sizeImage.cy;
	}
	
	pDC->SetBkMode(TRANSPARENT);
	pDC->SetTextColor(clrText);
	
	// Draw label:
	int nLabelHeight = OnDrawLabel(pDC, rectText, FALSE).cy;

	// Draw body:
	rectDraw.top += max(nLabelHeight, nIconHeight);
	CSize sizeBody = OnDrawBody(pDC, rectDraw, FALSE);
	
	// Draw separator + description:
	if (!m_strDescription.IsEmpty() && m_Params.m_bDrawDescription)
	{
		CRect rectDescr = rectDraw;
		if (sizeBody != CSize(0, 0))
		{
			rectDescr.top += m_ptMargin.y;
		}
		rectDescr.top += sizeBody.cy + 3 * m_ptMargin.y / 2;
		
		if (m_Params.m_bDrawSeparator)
		{
			OnDrawSeparator(pDC, rectDescr.left, rectDescr.right, rectDescr.top - m_ptMargin.y / 2);
		}
		
		rectDescr.top += m_ptMargin.y;

		if (m_sizeDescrImage != CSize(0, 0) && m_Params.m_bDrawDescrIcon )
		{
			CRect rectImage = rectDescr;
			rectImage.right = rectImage.left + m_sizeDescrImage.cx;
			rectImage.bottom = rectImage.top + m_sizeDescrImage.cy;
			OnDrawDescriptionIcon(pDC, rectImage);
			rectDescr.left += m_sizeDescrImage.cx + m_ptMargin.x;
		}
		OnDrawDescription(pDC, rectDescr, FALSE);
	}
	
	pDC->SelectObject(pOldPen);
}

void CCustomDrawToolTipCtrl::OnFillBackground( CDC* pDC, CRect rect, COLORREF& clrText, COLORREF& clrLine )
{
#ifdef DERIVE_FROM_MFCTOOLTIPCTRL
	CCDToolTipCtrlBase::OnFillBackground(pDC, rect, clrText, clrLine);
#else
	if (m_Params.m_clrFill == (COLORREF)-1)
	{
		::FillRect(pDC->GetSafeHdc(), rect, ::GetSysColorBrush(COLOR_INFOBK));
	}
	else
	{
		if (m_Params.m_clrFillGradient == (COLORREF)-1)
		{
			CBrush br(m_Params.m_clrFill);
			pDC->FillRect(rect, &br);
		}
		else
		{
#ifdef MSVC_NEW_VER
			CDrawingManager dm(*pDC);	
			dm.FillGradient2(rect, m_Params.m_clrFillGradient, m_Params.m_clrFill, m_Params.m_nGradientAngle == -1 ? 90 : m_Params.m_nGradientAngle);
#else
	#ifdef CUSTOMDRAW_GRADIENT
			//int nGradientAngle = m_Params.m_nGradientAngle == -1 ? 90 : m_Params.m_nGradientAngle;
			FillGradient(pDC->GetSafeHdc(), rect, m_Params.m_clrFill, m_Params.m_clrFillGradient);
	#else
			pDC->FillSolidRect(rect, m_Params.m_clrFillGradient);
	#endif // CUSTOMDRAW_GRADIENT
#endif // MSVC_NEW_VER
		}
	}
#endif // DERIVE_FROM_MFCTOOLTIPCTRL
}

CSize CCustomDrawToolTipCtrl::DrawText( CDC *pDC, 
									   const CString& strText, 
									   CRect rect, 
									   BOOL bCalcOnly, 
									   BOOL bBold /*= FALSE*/,
									   BOOL bVertCenter /*= FALSE*/,
									   BOOL bWordBreak /*= FALSE*/
									   )
{
	ASSERT_VALID(pDC);
	
	CSize sizeText(0, 0);
	
	DRAWTEXTPARAMS dtp = {sizeof(DRAWTEXTPARAMS)};
	dtp.iTabLength = m_nTextTabSize;
	
	CFont* pOldFont = SelectFont(pDC, bBold);

	UINT nFormat = DT_NOCLIP | DT_NOPREFIX | DT_TABSTOP | DT_EXPANDTABS;

	if (bWordBreak)
	{
		nFormat |= DT_WORDBREAK;
	}
	else if (strText.Find(_T('\n')) < 0) // signle line text
	{
		nFormat = DT_SINGLELINE;
		if (bVertCenter)
		{
			nFormat |= DT_VCENTER;
		}
	}
	if (bCalcOnly)
	{
		nFormat |= DT_CALCRECT;
	}

	if ( GetTextDrawer() )
		sizeText.cy = GetTextDrawer()->Draw(pDC, strText, rect, nFormat);
	else
		sizeText.cy = DrawTextEx(pDC->GetSafeHdc(), (LPTSTR)(LPCTSTR)strText, strText.GetLength(), rect, nFormat, &dtp);
	sizeText.cx = rect.Width();
	
	pDC->SelectObject(pOldFont);
	
	return sizeText;
}

CSize CCustomDrawToolTipCtrl::OnDrawLabel( CDC* pDC, CRect rect, BOOL bCalcOnly )
{
	ASSERT_VALID(pDC);
	
	CString strText(m_strLabel);
	if (strText.IsEmpty())
	{
		GetWindowText(strText);
	}
	
	BOOL bDrawDescr				= m_Params.m_bDrawDescription && !m_strDescription.IsEmpty();
	BOOL bDrawSupplementalDescr	= m_Params.m_bDrawSupplementalDescription && !m_strSupplementalDescription.IsEmpty();
	BOOL bDrawSupplementalImage	= m_Params.m_bDrawSupplementalImage && m_sizeSupplementalImage != CSize(0, 0);
	BOOL bBoldText				= m_Params.m_bBoldLabel && (bDrawDescr || bDrawSupplementalDescr || bDrawSupplementalImage);
	if (!bCalcOnly)
	{
		rect.bottom = rect.top + m_nLabelHeight;
	}
	return DrawText(pDC, strText, rect, bCalcOnly, bBoldText, TRUE);
}

void CCustomDrawToolTipCtrl::OnDrawBorder( CDC* pDC, CRect rect, COLORREF clrLine )
{
#ifdef DERIVE_FROM_MFCTOOLTIPCTRL
	CCDToolTipCtrlBase::OnDrawBorder(pDC, rect, clrLine);
#else
	if (m_Params.m_bRoundedCorners)
	{
		DrawSimpleRoundRectBorder(pDC, rect, clrLine);
	}
	else
	{
		pDC->Draw3dRect(rect, clrLine, clrLine);
	}
#endif // DERIVE_FROM_MFCTOOLTIPCTRL
}

CSize CCustomDrawToolTipCtrl::OnDrawBody( CDC* pDC, CRect rect, BOOL bCalcOnly )
{
	CRect rectDraw(rect);
	rectDraw.DeflateRect(m_ptMargin.x, m_ptMargin.y);

	CSize sizeBody(0, 0);

	if (m_sizeSupplementalImage != CSize(0, 0) && m_Params.m_bDrawSupplementalImage)
	{
		if (!bCalcOnly)
		{
			CRect rectImage = rectDraw;
			rectImage.right = rectImage.left + m_sizeSupplementalImage.cx;
			rectImage.bottom = rectImage.top + m_sizeSupplementalImage.cy;
			
			OnDrawSupplementalImage(pDC, rectImage);
		}
		sizeBody = m_sizeSupplementalImage;
	}

	// Draw separator + supplemental description:
	if (!m_strSupplementalDescription.IsEmpty() && m_Params.m_bDrawSupplementalDescription)
	{
		CRect rectDescr = rectDraw;
		int cx = 0;
		if (sizeBody.cx > 0)
		{
			cx = m_ptMargin.x;
			rectDescr.left += sizeBody.cx + cx;
		}
		
		CSize sizeDescr = OnDrawSupplementalDescription(pDC, rectDescr, bCalcOnly);
		sizeBody.cx += sizeDescr.cx + cx;
		sizeBody.cy = max(sizeBody.cy, sizeDescr.cy);
	}

	return sizeBody;
}

CSize CCustomDrawToolTipCtrl::GetDescrIconSize()
{
	if (m_hDescrIcon)
	{
		CSize sizeIcon = ::GetIconSize(m_hDescrIcon);
		return sizeIcon;
	}
	return CSize(0, 0);
}

CSize CCustomDrawToolTipCtrl::GetSupplementalImageSize()
{
	if (m_hSupplementalBmp)
	{
		CSize sizeIcon = ::GetBmpSize(m_hSupplementalBmp);
		return sizeIcon;
	}
	return CSize(0, 0);
}

BOOL CCustomDrawToolTipCtrl::OnDrawSupplementalImage( CDC* pDC, CRect rect )
{
	if (m_hSupplementalBmp)
	{
		CBitmap* pBmp = CBitmap::FromHandle(m_hSupplementalBmp);
		if ( pBmp )
		{
			CMemBitmap bmpDrawer(pDC, pBmp);
			bmpDrawer.BeginDraw(m_sizeSupplementalImage.cx, m_sizeSupplementalImage.cy, FALSE);
			pDC->BitBlt(rect.left, rect.top, m_sizeSupplementalImage.cx, m_sizeSupplementalImage.cy, &bmpDrawer.GetDC(), 0, 0, SRCCOPY);
			return TRUE;
		}
	}
	return FALSE;
}

CSize CCustomDrawToolTipCtrl::OnDrawSupplementalDescription( CDC* pDC, CRect rect, BOOL bCalcOnly )
{
	ASSERT_VALID(pDC);
	BOOL bSingleLineText = m_strSupplementalDescription.Find(_T('\n')) < 0;
	if ( bSingleLineText )
	{
		rect.right = rect.left + m_Params.m_nMaxDescrWidth;
	}
	return DrawText(pDC, m_strSupplementalDescription, rect, bCalcOnly, FALSE, FALSE, bSingleLineText);
}

void CCustomDrawToolTipCtrl::SetLabel( const CString strLabel )
{
	ASSERT_VALID(this);
	m_strLabel = strLabel;
}

void CCustomDrawToolTipCtrl::SetDescription( const CString strDescription )
{
#ifdef DERIVE_FROM_MFCTOOLTIPCTRL
	CCDToolTipCtrlBase::SetDescription(strDescription);
#else
	ASSERT_VALID(this);
	m_strDescription = strDescription;
	//m_strDescription.Replace(_T("\t"), _T("    "));
#endif // DERIVE_FROM_MFCTOOLTIPCTRL	
}

void CCustomDrawToolTipCtrl::SetSupplementalDescription( const CString strDescription )
{
	ASSERT_VALID(this);
	m_strSupplementalDescription = strDescription;
}

CSize CCustomDrawToolTipCtrl::GetIconSize()
{
	if (m_hLabelIcon)
	{
		CSize sizeIcon = ::GetIconSize(m_hLabelIcon);
		return sizeIcon;
	}
#ifdef DERIVE_FROM_MFCTOOLTIPCTRL
	return CCDToolTipCtrlBase::GetIconSize();
#else
	return CSize(0, 0);
#endif // DERIVE_FROM_MFCTOOLTIPCTRL
}

void CCustomDrawToolTipCtrl::SetLocation( CPoint pt )
{
#ifdef DERIVE_FROM_MFCTOOLTIPCTRL
	CCDToolTipCtrlBase::SetLocation( pt );
#else
	ASSERT_VALID(this);
	m_ptLocation = pt;
#endif // DERIVE_FROM_MFCTOOLTIPCTRL	
}

void CCustomDrawToolTipCtrl::SetTextTabSize( int nTabSize /*= 4*/ )
{
	ASSERT_VALID(this);
	m_nTextTabSize = nTabSize;
}

BOOL CCustomDrawToolTipCtrl::OnDrawIcon( CDC* pDC, CRect rectImage )
{
	if (m_hLabelIcon)
	{
		return ::DrawIconEx(pDC->GetSafeHdc(), rectImage.left, rectImage.top, m_hLabelIcon, m_sizeImage.cx, m_sizeImage.cy, 0, NULL, DI_NORMAL);
	}
#ifdef DERIVE_FROM_MFCTOOLTIPCTRL
	return CCDToolTipCtrlBase::OnDrawIcon( pDC, rectImage );
#else
	return FALSE;
#endif // DERIVE_FROM_MFCTOOLTIPCTRL	
}

BOOL CCustomDrawToolTipCtrl::OnDrawDescriptionIcon( CDC* pDC, CRect rect )
{
	if (m_hDescrIcon)
	{
		return ::DrawIconEx(pDC->GetSafeHdc(), rect.left, rect.top, m_hDescrIcon, m_sizeDescrImage.cx, m_sizeDescrImage.cy, 0, NULL, DI_NORMAL);
	}
	return FALSE;
}

CSize CCustomDrawToolTipCtrl::OnDrawDescription( CDC* pDC, CRect rect, BOOL bCalcOnly )
{
#ifdef DERIVE_FROM_MFCTOOLTIPCTRL
	return CCDToolTipCtrlBase::OnDrawDescription( pDC, rect, bCalcOnly );
#else
	ASSERT_VALID(pDC);
	
	if (!m_Params.m_bDrawDescription)
	{
		return CSize(0, 0);
	}

	int nFixedWidth = GetFixedWidth();
	
	if (nFixedWidth > 0 && m_sizeImage.cx <= 32)
	{
		rect.right = rect.left + nFixedWidth;
		
		if (m_sizeImage.cx > 0 && m_Params.m_bDrawIcon)
		{
			rect.right -= m_sizeImage.cx + m_ptMargin.x;
		}
	}
	else if ( m_Params.m_nMaxDescrWidth > 0)
	{
		rect.right = rect.left + m_Params.m_nMaxDescrWidth;
	}

	return DrawText(pDC, m_strDescription, rect, bCalcOnly, m_Params.m_bBoldDescription, TRUE, TRUE);
#endif // DERIVE_FROM_MFCTOOLTIPCTRL
}

void CCustomDrawToolTipCtrl::OnDrawSeparator( CDC* pDC, int x1, int x2, int y )
{
	COLORREF clrSeparator = m_Params.m_clrSeparator == (COLORREF)-1 ? RGB(158,187,221) : m_Params.m_clrBorder;;
	CPen penDark(PS_SOLID, 1, clrSeparator);
	CPen* pOldPen = pDC->SelectObject(&penDark);

	ASSERT_VALID(pDC);
	pDC->MoveTo(x1, y);
	pDC->LineTo(x2, y);

	CPen penLight(PS_SOLID, 1, RGB(255,255,255));
	pDC->SelectObject(&penLight);
	pDC->MoveTo(x1, y+1);
	pDC->LineTo(x2, y+1);

	pDC->SelectObject(pOldPen);
}

#ifndef MSVC_NEW_VER
LRESULT CCustomDrawToolTipCtrl::OnSetFont( WPARAM wParam, LPARAM lParam )
{
	BOOL bRedraw = (BOOL) LOWORD(lParam);
	
	m_hFont = (HFONT) wParam;

	if (m_fontBold.GetSafeHandle())
		m_fontBold.DeleteObject();
	if ( !m_fontBold.GetSafeHandle() )
	{
		LOGFONT lf = {0};
		GetObject(m_hFont, sizeof(LOGFONT), &lf);
		lf.lfWeight = FW_BOLD;
		m_fontBold.CreateFontIndirect(&lf);
	}
	
	if (bRedraw)
	{
		RedrawWindow();
		UpdateWindow();
	}
	
	return 0;
}
#endif // MSVC_NEW_VER

CFont* CCustomDrawToolTipCtrl::SelectFont( CDC *pDC, BOOL bBold /*= FALSE*/ )
{
#ifdef MSVC_NEW_VER
	CFont* pOldFont = (CFont*) pDC->SelectObject( bBold ? &afxGlobalData.fontBold : &afxGlobalData.fontTooltip);
#else
	ASSERT_VALID(this);
	ASSERT_VALID(pDC);
	
	CFont* pOldFont = NULL;
	
	if (m_hFont != NULL)
	{
		if (bBold)
		{
			ASSERT(m_fontBold.GetSafeHandle());
			pOldFont = pDC->SelectObject(&m_fontBold);
		}
		else
		{
			pOldFont = pDC->SelectObject(CFont::FromHandle(m_hFont));
		}
	}
	else
	{
		if (bBold)
		{
			if ( !m_fontBold.GetSafeHandle() )
			{
				HFONT hDefFont = (HFONT)GetStockObject(DEFAULT_GUI_FONT);
				LOGFONT lf = {0};
				GetObject(hDefFont, sizeof(LOGFONT), &lf);
				lf.lfWeight = FW_BOLD;
				m_fontBold.CreateFontIndirect(&lf);
			}
			pOldFont = pDC->SelectObject(&m_fontBold);
		}
		else
		{
			pOldFont = (CFont*) pDC->SelectStockObject(DEFAULT_GUI_FONT);
		}
	}
#endif // MSVC_NEW_VER
	
	return pOldFont;
}

void CCustomDrawToolTipCtrl::SetLabelIcon( HICON hIcon )
{
	ASSERT_VALID(this);
	m_hLabelIcon = hIcon;
}

void CCustomDrawToolTipCtrl::SetDescriptionIcon( HICON hIcon )
{
	ASSERT_VALID(this);
	m_hDescrIcon = hIcon;
}

void CCustomDrawToolTipCtrl::SetSupplementalImage( HBITMAP hBmp )
{
	ASSERT_VALID(this);
	m_hSupplementalBmp = hBmp;
}

/*----------------------------------------------------------------------------*/
/* CCustomDrawListCtrl
/*----------------------------------------------------------------------------*/

IMPLEMENT_DYNCREATE(CCustomDrawListCtrl, CCustomDrawListCtrlBase)

CCustomDrawListCtrl::CCustomDrawListCtrl()
	: m_bEnableCustomDraw(TRUE)
	, m_nHotItem(-1)
	, m_bIsOwnerDraw(FALSE)
	, m_bDrawHotItem(TRUE)
	, m_bMouseEventsTracked(FALSE)
	, m_bExplorerVisualStyle(FALSE)
	, m_nLockDrawCount(0)
	, m_pTextDrawer(NULL)
{
	m_bMarkSortedColumn	= TRUE;
	m_clrSortedColumn	= RGB(225,242,249);
}

CCustomDrawListCtrl::~CCustomDrawListCtrl()
{
}

BEGIN_MESSAGE_MAP(CCustomDrawListCtrl, CCustomDrawListCtrlBase)
	ON_WM_CREATE()
	ON_WM_DESTROY()
	ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, OnCustomDraw)
	ON_WM_MOUSEMOVE()
	ON_MESSAGE(WM_MOUSELEAVE, OnMouseLeave)
	ON_NOTIFY_REFLECT(LVN_COLUMNCLICK, OnHeaderClicked )
	ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTW, 0, 0xFFFF, OnToolTipText)
	ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTA, 0, 0xFFFF, OnToolTipText)
	ON_WM_SIZE()
#ifdef FIX_LISTCTRL_HILIGHT_UNSELECTED_ITEM_ISSUE
	ON_WM_SETFOCUS()
	ON_WM_KILLFOCUS()
	ON_WM_LBUTTONDOWN()
	ON_WM_RBUTTONDOWN()
#endif // FIX_LISTCTRL_HILIGHT_UNSELECTED_ITEM_ISSUE
	ON_MESSAGE(WM_CDTOOLTIPCTRL_NOTIFY, OnCustomToolTipNotify)
END_MESSAGE_MAP()

void CCustomDrawListCtrl::OnInitList()
{
#ifndef DERIVE_FROM_MFCLISTCTRL
	InitHeader();
#endif // DERIVE_FROM_MFCLISTCTRL

	SetDoubleBuffered(TRUE);

	// Disable the CToolTipCtrl of CListCtrl so it won't disturb our own tooltip-ctrl
	CToolTipCtrl* pToolTipCtrl = GetToolTips();
	if (pToolTipCtrl)
	{
		pToolTipCtrl->Activate(FALSE);
	}

	DWORD dwStyle = GetStyle();
	m_bIsOwnerDraw = (dwStyle & LVS_OWNERDRAWFIXED) == LVS_OWNERDRAWFIXED;
	
	// Enable our own tooltip-ctrl and make it show tooltip even if not having focus
	VERIFY( GetCustomDrawToolTips().Create(this, TTS_ALWAYSTIP) );
	GetCustomDrawToolTips().Activate(TRUE);

	EnableExplorerVisualStyles();
}

void CCustomDrawListCtrl::InitHeader()
{
	if (CListCtrl::GetHeaderCtrl()->GetSafeHwnd())
	{
		GetSortHeaderCtrl().Init( CListCtrl::GetHeaderCtrl()->GetSafeHwnd() );
		GetSortHeaderCtrl().SetSortable( IsSortable() );
	}
}

int CCustomDrawListCtrl::OnCreate( LPCREATESTRUCT lpCreateStruct )
{
	if (CCustomDrawListCtrlBase::OnCreate(lpCreateStruct) == -1)
		return -1;

	OnInitList();
	return 0;
}

void CCustomDrawListCtrl::PreSubclassWindow()
{
	CCustomDrawListCtrlBase::PreSubclassWindow();

	_AFX_THREAD_STATE* pThreadState = AfxGetThreadState();
	if (pThreadState->m_pWndInit == NULL)
	{
		OnInitList();
	}
}

void CCustomDrawListCtrl::EnableCustomDraw( BOOL bEnable /*= TRUE*/, BOOL bRedraw /*= FALSE*/ )
{
	m_bEnableCustomDraw = bEnable;
	if ( bRedraw )
		RedrawWindow();	
}

BOOL CCustomDrawListCtrl::IsDoubleBuffered()
{
	return (GetExtendedStyle() & LVS_EX_DOUBLEBUFFER);
}

void CCustomDrawListCtrl::SetDoubleBuffered( BOOL bSet /*= TRUE*/ )
{
	DWORD dwExtStyle = GetExtendedStyle();
	if ( bSet )
		dwExtStyle |= LVS_EX_DOUBLEBUFFER;
	else
		dwExtStyle &= ~LVS_EX_DOUBLEBUFFER;
	SetExtendedStyle(dwExtStyle);
}

BOOL CCustomDrawListCtrl::IsFullRowSelect()
{
	return (GetExtendedStyle() & LVS_EX_FULLROWSELECT);
}

void CCustomDrawListCtrl::SetFullRowSelect(BOOL bSet /*= TRUE*/)
{
	DWORD dwExtStyle = GetExtendedStyle();
	if ( bSet )
		dwExtStyle |= LVS_EX_FULLROWSELECT;
	else
		dwExtStyle &= ~LVS_EX_FULLROWSELECT;
	SetExtendedStyle(dwExtStyle);
}

BOOL CCustomDrawListCtrl::IsHeaderDragDrop()
{
	return (GetExtendedStyle() & LVS_EX_HEADERDRAGDROP);
}

void CCustomDrawListCtrl::SetHeaderDragDrop( BOOL bSet /*= TRUE*/ )
{
	DWORD dwExtStyle = GetExtendedStyle();
	if ( bSet )
		dwExtStyle |= LVS_EX_HEADERDRAGDROP;
	else
		dwExtStyle &= ~LVS_EX_HEADERDRAGDROP;
	SetExtendedStyle(dwExtStyle);
}

BOOL CCustomDrawListCtrl::IsSingleSel()
{
	return (GetStyle() & LVS_SINGLESEL);
}

void CCustomDrawListCtrl::SetSignleSel( BOOL bSet /*= TRUE*/ )
{
	DWORD dwStyle = GetStyle();
	if ( bSet )
		dwStyle |= LVS_SINGLESEL;
	else
		dwStyle &= ~LVS_SINGLESEL;
	SetWindowLong(m_hWnd, GWL_STYLE, dwStyle);
}

LRESULT CCustomDrawListCtrl::EnableExplorerVisualStyles( bool bValue /*= true*/ )
{
	if (!IsThemeEnabled())
	{
		m_bExplorerVisualStyle = false;
		return S_FALSE;
	}
	
	if (!CheckOSVersion(0x0600))
	{
		m_bExplorerVisualStyle = false;
		return S_FALSE;
	}
	
	LRESULT rc = S_FALSE;
	if (bValue)
		rc = EnableWindowTheme(GetSafeHwnd(), L"ListView", L"Explorer", NULL);
	else
		rc = EnableWindowTheme(GetSafeHwnd(), L"", L"", NULL);
	
	if (bValue && rc==S_OK)
	{
		// OBS! Focus retangle is not painted properly without double-buffering
		m_bExplorerVisualStyle = true;
//#if (_WIN32_WINNT >= 0x501)
		if (CheckOSVersion(0x501))
			SetDoubleBuffered(TRUE);
//#endif
	}
	else
	{
		m_bExplorerVisualStyle = false;
	}

	return rc;
}

#ifndef MSVC_NEW_VER
CToolTipCtrl* CCustomDrawListCtrl::GetToolTips() const
{
	ASSERT(::IsWindow(m_hWnd));
	return (CToolTipCtrl*)CWnd::FromHandle((HWND)::SendMessage(m_hWnd, LVM_GETTOOLTIPS, 0, 0L));
}

CToolTipCtrl* CCustomDrawListCtrl::SetToolTips( CToolTipCtrl* pWndTip )
{
	ASSERT(::IsWindow(m_hWnd));
	return (CToolTipCtrl*)CWnd::FromHandle((HWND)::SendMessage(m_hWnd, LVM_SETTOOLTIPS, 0, (LPARAM) pWndTip->GetSafeHwnd()));
}
#endif // MSVC_NEW_VER

#ifndef DERIVE_FROM_MFCLISTCTRL
void CCustomDrawListCtrl::EnableMarkSortedColumn( BOOL bMark /*= TRUE*/, BOOL bRedraw /*= TRUE*/ )
{
	m_bMarkSortedColumn = bMark;
	
	if (GetSafeHwnd() != NULL && bRedraw)
	{
		RedrawWindow();
	}
}
#endif // DERIVE_FROM_MFCLISTCTRL

#define DEBUG_CUSTOMDRAW

#ifdef DEBUG_CUSTOMDRAW
	#define DBGCD_TRACE	TRACE
#else
	#define DBGCD_TRACE
#endif // DEBUG_CUSTOMDRAW

#ifdef _DEBUG
CString getDrawState(UINT uDrawItemState)
{
	CString strState;
	if ( uDrawItemState & CDIS_SELECTED )
	{
		strState += " selected";
	}
	if ( uDrawItemState & CDIS_GRAYED )
	{
		strState += " grayed";
	}
	if ( uDrawItemState & CDIS_DISABLED )
	{
		strState += " disabled";
	}
	if ( uDrawItemState & CDIS_CHECKED )
	{
		strState += " checked";
	}
	if ( uDrawItemState & CDIS_FOCUS )
	{
		strState += " focus";
	}
	if ( uDrawItemState & CDIS_DEFAULT )
	{
		strState += " default";
	}
	if ( uDrawItemState & CDIS_HOT )
	{
		strState += " hot";
	}
	if ( uDrawItemState & CDIS_MARKED )
	{
		strState += " marked";
	}
	if ( uDrawItemState & CDIS_INDETERMINATE )
	{
		strState += " indeterminate";
	}
	if ( uDrawItemState == 0 )
	{
		strState += " nothing";
	}
	return strState;
}

CString getLVItemState(UINT uItemState)
{
	CString strState;
	if ( uItemState & LVIS_FOCUSED )
	{
		strState += " focused";
	}
	if ( uItemState & LVIS_SELECTED )
	{
		strState += " selected";
	}
	if ( uItemState & LVIS_CUT )
	{
		strState += " cut";
	}
	if ( uItemState & LVIS_DROPHILITED )
	{
		strState += " drophilited";
	}
	if ( uItemState & LVIS_ACTIVATING )
	{
		strState += " activating";
	}
	if ( uItemState == 0 )
	{
		strState += " nothing";
	}
	return strState;
}
#endif // _DEBUG

void CCustomDrawListCtrl::DrawItem( LPDRAWITEMSTRUCT lpDrawItemStruct )
{
	// DrawItem() is not thread safe
	// Reference:
	// http://www.codeproject.com/KB/list/selectentirerow.aspx#Readers Comments
	CMutex mutex(FALSE, "CCustomDrawListCtrl::DrawItem()");
    CSingleLock lock(&mutex);
    lock.Lock(); // Wait until signalled. 
	// Unlock takes place in destructor when function exit
	
	int nItem			= static_cast<int>( lpDrawItemStruct->itemID );
	CDC* pDC			= CDC::FromHandle(lpDrawItemStruct->hDC);
	ASSERT(pDC);

	OnDrawItem(pDC, nItem);
}

void CCustomDrawListCtrl::OnCustomDraw( NMHDR* pNMHDR, LRESULT* pResult )
{
	LPNMLVCUSTOMDRAW pNMLVCD = reinterpret_cast<LPNMLVCUSTOMDRAW>(pNMHDR);
	if ( !pNMLVCD )
	{
		ASSERT(0);	// can't fail!
		return;
	}

	DWORD& dwDrawStage	= pNMLVCD->nmcd.dwDrawStage;

	switch (dwDrawStage)
	{
	case CDDS_PREPAINT:
		if ( IsCustomDrawEnabled() )
			*pResult = CDRF_NOTIFYITEMDRAW;
		else
			*pResult = CDRF_DODEFAULT;
		break;
	case CDDS_ITEMPREPAINT:
		{
			int nItem			= static_cast<int>( pNMLVCD->nmcd.dwItemSpec );
			CDC* pDC			= CDC::FromHandle(pNMLVCD->nmcd.hdc);
			ASSERT(pDC);
			OnDrawItem(pDC, nItem);
			*pResult = CDRF_SKIPDEFAULT;
		}
		break;
	default:
		TRACE("===== CCustomDrawListCtrl::OnCustomdraw() unhandled draw stage!\n");
		*pResult = CDRF_DODEFAULT;
		break;
	}
}

CRect CCustomDrawListCtrl::GetSubItemDrawRect( int nItem, int nSubItem )
{
	CRect rcItem;
	int nArea = IsFullRowSelect() ? LVIR_BOUNDS : LVIR_LABEL;	// if it is full-row select, we need to invalidate thw whole row
	VERIFY( GetSubItemRect(nItem, nSubItem, nArea, rcItem) );
	
	if ( nItem+1 < GetItemCount() )
	{
		// this is not the last item
		UINT nState = GetItemState(nItem+1, LVIS_SELECTED|LVIS_FOCUSED);
		if ( 0 == nState )
			--rcItem.bottom;	// avoid bottom border gets covered by the grid lines
	}
	return rcItem;
}

#define TRY_DRAW_LISTCTRL_WITH_MEMDC

void CCustomDrawListCtrl::OnDrawItem( CDC* pDC, int nItem )
{
	CRect rcBounds;
	GetItemRect(nItem, rcBounds, LVIR_BOUNDS);
#ifdef TRY_DRAW_LISTCTRL_WITH_MEMDC
	CSimpleMemDC memDC(*pDC, rcBounds);
	CDC* pDrawDC	= &memDC.GetDC();
	
	CFont* pFont	= pDC->GetCurrentFont();
	CFont* pOldFont	= pDrawDC->SelectObject(pFont);
#else
	CDC* pDrawDC	= pDC;
#endif // TRY_DRAW_LISTCTRL_WITH_MEMDC

	// Erase the background first
	OnEraseBackground(pDrawDC, rcBounds, nItem);
	
	int nColCount = GetSortHeaderCtrl().GetItemCount();
	
	LVITEM lvitem		= {0};
	lvitem.mask			= LVIF_IMAGE|LVIF_STATE;
	lvitem.iItem		= nItem;
	lvitem.stateMask	= (UINT)-1;
	VERIFY( GetItem(&lvitem) );
	
	//DBGCD_TRACE("====> %d, Draw State is [%s] at tick %X\n", nItem, getLVItemState(lvitem.state), GetTickCount());
	
	for (int nCol = 0; nCol < nColCount; ++nCol)
	{
		OnDrawCell(pDrawDC, nItem, nCol, &lvitem);
	}
	
#ifdef TRY_DRAW_LISTCTRL_WITH_MEMDC
	pDrawDC->SelectObject(pOldFont);
#endif // TRY_DRAW_LISTCTRL_WITH_MEMDC
}

void CCustomDrawListCtrl::OnEraseBackground( CDC* pDC, CRect rect, int nItem )
{
	// you can also copy the background bitmap from the client window if you wish
	COLORREF clrBk = ::GetSysColor( IsWindowEnabled() ? COLOR_WINDOW : COLOR_BTNFACE );
	pDC->FillSolidRect(rect, clrBk);
}

void CCustomDrawListCtrl::OnDrawCell( CDC* pDC, int nItem, int nSubItem, LPLVITEM lpLVItem )
{
	CRect rcItem = GetSubItemDrawRect(nItem, nSubItem);

	// Fill the background first
	if ( IsWindowEnabled() )
		OnFillBackground(pDC, rcItem, nItem, nSubItem, lpLVItem);
	
	// Draw the icon
	CRect rectIcon;
	VERIFY( GetSubItemRect(nItem, nSubItem, LVIR_ICON, rectIcon) );

	BOOL bHasIcon = OnDrawIcon(pDC, rectIcon, nItem, nSubItem, lpLVItem);
	
	// Draw the text
	CRect rectLabel;
	VERIFY( GetSubItemRect(nItem, nSubItem, LVIR_LABEL, rectLabel) );
	rectLabel.left += 2;
	if ( bHasIcon )
	{
		rectLabel.left = rectIcon.right + 5;
		//rectLabel.top += 1;
	}
	OnDrawCellText(pDC, rectLabel, nItem, nSubItem, lpLVItem);
}

CSize CCustomDrawListCtrl::GetIconSize( int nItem, int nSubItem )
{
	CSize sizeIcon(0,0);
	CImageList* pImageList = GetImageList(LVSIL_SMALL);
	if (pImageList)
	{
		int cx, cy;
		ImageList_GetIconSize(pImageList->GetSafeHandle(), &cx, &cy);
		sizeIcon.cx = cx;
		sizeIcon.cy = cy;
	}
	return sizeIcon;
}

BOOL CCustomDrawListCtrl::OnDrawIcon( CDC* pDC, CRect rect, int nItem, int nSubItem, LPLVITEM lpLVItem )
{
	if ( 0 == nSubItem )
	{
		CImageList* pImageList = GetImageList(LVSIL_SMALL);
		if ( pImageList && lpLVItem->iImage >= 0 )
		{
			CSize sizeImage = GetIconSize(nItem, nSubItem);
			int ny = rect.top + (rect.Height() - sizeImage.cy) / 2;

			// Unfortunately CImageList::Draw(..ILD_TRANSPARENT) can't take care of transparent icon in 
			// expected way, I see black outline on the bitmap, so here I use DrawIconEx instead
// 			CPoint ptImage(rect.left, ny);
// 			pImageList->Draw(pDC, lpLVItem->iImage, ptImage, ILD_TRANSPARENT);

 			HICON hIcon = pImageList->ExtractIcon(lpLVItem->iImage);
 			::DrawIconEx(pDC->GetSafeHdc(), rect.left, ny, hIcon, sizeImage.cx, sizeImage.cy, 0, NULL, DI_NORMAL);
 			::DestroyIcon(hIcon);

			return TRUE;
		}
	}
	return FALSE;
}

void CCustomDrawListCtrl::OnFillBackground( CDC* pDC, CRect rect, int nItem, int nSubItem, LPLVITEM lpLVItem )
{
	UINT& nItemState = lpLVItem->state;

	CCustomDrawHelper itemDrawer;
	itemDrawer.m_bWndHasFocus		= ::GetFocus() == GetSafeHwnd();
	itemDrawer.m_bFocusItem			= ((nItemState & LVIS_FOCUSED) ? true : false);
	itemDrawer.m_bSelected			= ((nItemState & LVIS_SELECTED) ? true : false);
	itemDrawer.m_bIsHotItem			= m_bDrawHotItem && m_nHotItem == nItem;

	BOOL bFilledWithSortClr			= FALSE;

	if ( IsSortable() 
		&& m_bMarkSortedColumn 
		&& GetSortHeaderCtrl().GetSortColumn() == nSubItem
		&& !itemDrawer.m_bSelected
		&& !itemDrawer.m_bIsHotItem
		)
	{
		CRect rectColumn;
		GetSubItemRect(nItem, nSubItem, LVIR_LABEL, rectColumn);
		pDC->FillSolidRect(rectColumn, m_clrSortedColumn);
		bFilledWithSortClr = TRUE;
	}
	if ( 0 == nSubItem || (bFilledWithSortClr && itemDrawer.m_bFocusItem) )
	{
		itemDrawer.m_bDrawBorderWhenFill	= 0 == nSubItem;
		itemDrawer.DrawItemBackground(pDC, rect);
	}
}

void CCustomDrawListCtrl::OnDrawCellText( CDC* pDC, CRect rect, int nItem, int nSubItem, LPLVITEM lpLVItem )
{
	UINT& nItemState = lpLVItem->state;

	CString strLabel = GetItemText(nItem, nSubItem);
	if ( !strLabel.IsEmpty() )
	{
		LV_COLUMN lvc;
		lvc.mask = LVCF_FMT | LVCF_WIDTH;
		GetColumn(nSubItem, &lvc);

		int nOldBkMode = pDC->SetBkMode(TRANSPARENT);
		COLORREF clrTxtColor = GetItemTextColor(nItem, nSubItem, nItemState);
		COLORREF clrOldTxtColor;
		if (clrTxtColor != CLR_INVALID)
			clrOldTxtColor = pDC->SetTextColor(clrTxtColor);

		UINT nJustify = DT_LEFT;
		switch(lvc.fmt & LVCFMT_JUSTIFYMASK)
		{
		case LVCFMT_RIGHT:
			nJustify = DT_RIGHT;
			break;
		case LVCFMT_CENTER:
			nJustify = DT_CENTER;
			break;
		default:
			break;
		}

		UINT nDTFormat = nJustify|DT_END_ELLIPSIS|DT_VCENTER|DT_SINGLELINE|DT_NOPREFIX;

		if ( GetTextDrawer() )
			GetTextDrawer()->Draw(pDC, strLabel, rect, nDTFormat);
		else
			pDC->DrawText(strLabel, rect, nDTFormat);

		if (clrTxtColor != CLR_INVALID)
			pDC->SetTextColor(clrOldTxtColor);

		pDC->SetBkMode(nOldBkMode);
	}
}

COLORREF CCustomDrawListCtrl::GetItemTextColor( int nItem, int nSubItem, UINT nItemState )
{
#ifndef CUSTOMDRAW_GRADIENT
	if ( nItemState & LVIS_SELECTED && ::GetFocus() == GetSafeHwnd() && (IsFullRowSelect() || nSubItem == 0) )
	{
		return ::GetSysColor(COLOR_HIGHLIGHTTEXT);
	}
#endif // CUSTOMDRAW_GRADIENT
	return CLR_INVALID;
}

// Taken from http://www.codeproject.com/KB/list/quicklist.aspx
BOOL CCustomDrawListCtrl::OnToolTipText( UINT id, NMHDR * pNMHDR, LRESULT* pResult )
{
	UINT nID = pNMHDR->idFrom;
	
	// check if this is the automatic tooltip of the control
	if (nID == 0)
		return TRUE;	// do not allow display of automatic tooltip,
						// or our tooltip will disappear
	
	*pResult = 0;
	
	// get the mouse position
	const MSG* pMessage = GetCurrentMessage();
	ASSERT(pMessage);
	CPoint pt;
	pt = pMessage->pt;		// get the point from the message
	ScreenToClient(&pt);	// convert the point's coords to be relative to this control
	
	// see if the point falls onto a list item
	LVHITTESTINFO hitinfo = {0};
	hitinfo.pt		= pt;
	hitinfo.flags	= 0;
	hitinfo.iItem	= hitinfo.iSubItem = -1;
	
	if ( SubItemHitTest(&hitinfo) >= 0 && ShouldShowToolTipForCell(hitinfo.iItem, hitinfo.iSubItem) )
	{
		CString strToolTip = GetToolTipLabelForCell(hitinfo.iItem, hitinfo.iSubItem);
		
		if (strToolTip.IsEmpty())
		{
			strToolTip = GetItemText(hitinfo.iItem, hitinfo.iSubItem);
		}
		if (!strToolTip.IsEmpty())
		{
			GetCustomDrawToolTips().SetText(pNMHDR, strToolTip);
			GetCustomDrawToolTips().SetLabel(strToolTip);	// otherwise the tooltip can't show correct UNICODE text under ANSI environment
			return TRUE;
		}	
	}
	
	return FALSE;	// we didn't handle the message, let the 
					// framework continue propagating the message
}

void CCustomDrawListCtrl::OnMouseMove( UINT nFlags, CPoint point )
{
	CCustomDrawListCtrlBase::OnMouseMove(nFlags, point);
	
	// Find the subitem
	LVHITTESTINFO hitinfo = {0};
	hitinfo.flags	= nFlags;
	hitinfo.pt		= point;
	SubItemHitTest(&hitinfo);

	if ( m_bDrawHotItem )
	{
		if ( !m_bMouseEventsTracked )
		{
			m_bMouseEventsTracked = TRUE;
			
			TRACKMOUSEEVENT trackmouseevent;
			trackmouseevent.cbSize		= sizeof(trackmouseevent);
			trackmouseevent.dwFlags		= TME_LEAVE;
			trackmouseevent.hwndTrack	= GetSafeHwnd();
			trackmouseevent.dwHoverTime	= HOVER_DEFAULT;
			//::AFXTrackMouse(&trackmouseevent);
			_TrackMouseEvent(&trackmouseevent);
		}

		if ( m_nHotItem != hitinfo.iItem )
		{
			int nOldHotItem = m_nHotItem;
			m_nHotItem = hitinfo.iItem;
			
			LockSetRedraw(FALSE);
			SetHotItem(m_nHotItem);

			// RedrawItems produces flicker.
// 			RedrawItems(m_nHotItem, m_nHotItem);
// 			RedrawItems(nOldHotItem, nOldHotItem);
			CRect rect;
			GetItemRect(m_nHotItem, rect, LVIR_BOUNDS);
			InvalidateRect(rect, FALSE);
			GetItemRect(nOldHotItem, rect, LVIR_BOUNDS);
			InvalidateRect(rect, FALSE);

			LockSetRedraw(TRUE);
		}
	}

	if ( ShouldShowToolTipForCell(hitinfo.iItem, hitinfo.iSubItem)
		&& GetCustomDrawToolTips().CheckDisplayToolTip( (WPARAM)hitinfo.iItem, (LPARAM)hitinfo.iSubItem ) 
		)
	{		
		// Remove the old tooltip (if available)
		int nToolCount = GetCustomDrawToolTips().GetToolCount();
		//TRACE("CCustomDrawListCtrl tooltip tool count: %d\n", nToolCount);
		if (nToolCount > 0)
		{
			// Not using CToolTipCtrl::DelTool() because it redirects the messages to CListCtrl parent
			// If we call DelTool(), you will see that the return value of GetToolCount() still keep increasing!
			//GetCustomDrawToolTips().DelTool(this);
			TOOLINFO ti = {0};
			ti.cbSize = sizeof(TOOLINFO);
			ti.uFlags = TTF_IDISHWND;	// Indicate that uId is handle to a control
			ti.uId = (UINT_PTR)m_hWnd;	// Handle to the control
			ti.hwnd = m_hWnd;			// Handle to window to receive the tooltip-messages
			ti.hinst = AfxGetInstanceHandle();
			GetCustomDrawToolTips().SendMessage(TTM_DELTOOL, 0, (LPARAM) (LPTOOLINFO) &ti);

			GetCustomDrawToolTips().Activate(FALSE);
		}
		
		// Add the new tooltip (if available)
		if (hitinfo.iItem != -1 && hitinfo.iSubItem != -1)
		{
			// Not using CToolTipCtrl::AddTool() because it redirects the messages to CListCtrl parent
			//GetCustomDrawToolTips().AddTool(this);
			TOOLINFO ti = {0};
			ti.cbSize = sizeof(TOOLINFO);
			ti.uFlags = TTF_IDISHWND;	// Indicate that uId is handle to a control
			ti.uId = (UINT_PTR)m_hWnd;	// Handle to the control
			ti.hwnd = m_hWnd;			// Handle to window to receive the tooltip-messages
			ti.hinst = AfxGetInstanceHandle();
			ti.lpszText = LPSTR_TEXTCALLBACK;
			GetCustomDrawToolTips().SendMessage(TTM_ADDTOOL, 0, (LPARAM) (LPTOOLINFO) &ti);

			PreAddToolTipForCell(hitinfo.iItem, hitinfo.iSubItem);

			GetCustomDrawToolTips().Activate(TRUE);
		}
	}
}

LRESULT CCustomDrawListCtrl::OnMouseLeave( WPARAM wParam, LPARAM lParam )
{
	LRESULT result = Default();
	m_bMouseEventsTracked = FALSE;
	
	if (m_nHotItem >= 0)
	{
		int nTmpHotItem = m_nHotItem;
		m_nHotItem = -1;
		//RedrawItems(nTmpHotItem, nTmpHotItem);

		CRect rect;
		GetItemRect(nTmpHotItem, rect, LVIR_BOUNDS);
		InvalidateRect(rect, FALSE);
	}
	
	return result;
}

bool CCustomDrawListCtrl::IsSortable() const
{
	return false;
}

BOOL CCustomDrawListCtrl::OnSort( int nCol )
{
	ASSERT(IsSortable());
	return TRUE;
}

void CCustomDrawListCtrl::OnHeaderClicked( NMHDR* pNMHDR, LRESULT* pResult )
{
	// List view notification header
    NM_LISTVIEW* pNMListView = reinterpret_cast<NM_LISTVIEW*>(pNMHDR);
	
	if ( IsSortable() )
	{
		int nOldSortColumn = GetSortHeaderCtrl().GetSortColumn();
		if ( OnSort(pNMListView->iSubItem) )
		{
			GetSortHeaderCtrl().SwitchSortItem(pNMListView->iSubItem);
		}
		if (m_bMarkSortedColumn)
		{
			LockSetRedraw(FALSE);
			CRect rectClient;
			GetClientRect(rectClient);
			CRect rectColumn;
			if (nOldSortColumn >= 0)
			{
				GetSubItemRect(0, nOldSortColumn, LVIR_LABEL, rectColumn);
				rectColumn.top = rectClient.top;
				rectColumn.bottom = rectClient.bottom;
				InvalidateRect(rectColumn, FALSE);
			}
			int nNewSortColumn = GetSortHeaderCtrl().GetSortColumn();
			if (nNewSortColumn >= 0 && nNewSortColumn != nOldSortColumn)
			{
				GetSubItemRect(0, nNewSortColumn, LVIR_LABEL, rectColumn);
				rectColumn.top = rectClient.top;
				rectColumn.bottom = rectClient.bottom;
				InvalidateRect(rectColumn, FALSE);
			}
			LockSetRedraw(TRUE);
		}
	}
	
    // Return result
    *pResult = 0;
}

BOOL CCustomDrawListCtrl::PreTranslateMessage( MSG* pMsg )
{
	if (GetCustomDrawToolTips().m_hWnd)
		GetCustomDrawToolTips().RelayEvent(pMsg);
	return CCustomDrawListCtrlBase::PreTranslateMessage(pMsg);
}

BOOL CCustomDrawListCtrl::ShouldShowToolTipForCell( int nRow, int nCol )
{
	return TRUE;
}

CString CCustomDrawListCtrl::GetToolTipLabelForCell( int nRow, int nCol )
{
	return _T("");
}

void CCustomDrawListCtrl::PreAddToolTipForCell( int nRow, int nCol )
{
	// nothing to do
}

void CCustomDrawListCtrl::PreShowToolTipForCell( int nRow, int nCol )
{
	// nothing to do
}

void CCustomDrawListCtrl::PrePopToolTip()
{
	// nothing to do
}

void CCustomDrawListCtrl::OnSize( UINT nType, int cx, int cy )
{
	CCustomDrawListCtrlBase::OnSize(nType, cx, cy);
	
	if (GetSortHeaderCtrl().GetSafeHwnd() != NULL)
	{
		GetSortHeaderCtrl().RedrawWindow();
	}
}

void CCustomDrawListCtrl::OnDestroy()
{
	OnDestroyList();
	CCustomDrawListCtrlBase::OnDestroy();
}

#ifdef FIX_LISTCTRL_HILIGHT_UNSELECTED_ITEM_ISSUE
void CCustomDrawListCtrl::RepaintSelectedItems()
{
	RedrawWindow();
}

void CCustomDrawListCtrl::OnKillFocus( CWnd* pNewWnd )
{
	CCustomDrawListCtrlBase::OnKillFocus(pNewWnd);
	
	// check if we are losing focus to label edit box
	if(pNewWnd != NULL && pNewWnd->GetParent() == this)
		return;
	
	// repaint items that should change appearance
	if( m_bIsOwnerDraw && (GetStyle() & LVS_TYPEMASK) == LVS_REPORT )
		RepaintSelectedItems();
}

void CCustomDrawListCtrl::OnSetFocus( CWnd* pOldWnd )
{
	CCustomDrawListCtrlBase::OnSetFocus(pOldWnd);
	
	// check if we are getting focus from label edit box
	if(pOldWnd!=NULL && pOldWnd->GetParent()==this)
		return;
	
	// repaint items that should change appearance
	if( m_bIsOwnerDraw && (GetStyle() & LVS_TYPEMASK) == LVS_REPORT )
		RepaintSelectedItems();
}

void CCustomDrawListCtrl::OnLButtonDown( UINT nFlags, CPoint point )
{
	CCustomDrawListCtrlBase::OnLButtonDown(nFlags, point);

	// repaint items that should change appearance
	if( m_bIsOwnerDraw 
		&& (GetStyle() & LVS_TYPEMASK) == LVS_REPORT 
		&& !IsSingleSel()
		&& !SHIFT_DOWN 
		&& !CNTRL_DOWN 
		)
		RepaintSelectedItems();
}

void CCustomDrawListCtrl::OnRButtonDown( UINT nFlags, CPoint point )
{
	CCustomDrawListCtrlBase::OnRButtonDown(nFlags, point);
	
	// repaint items that should change appearance
	if( m_bIsOwnerDraw 
		&& (GetStyle() & LVS_TYPEMASK) == LVS_REPORT 
		&& !IsSingleSel()
		&& !SHIFT_DOWN 
		&& !CNTRL_DOWN 
		)
		RepaintSelectedItems();
}
#endif // FIX_LISTCTRL_HILIGHT_UNSELECTED_ITEM_ISSUE

LRESULT CCustomDrawListCtrl::OnCustomToolTipNotify( WPARAM wParam, LPARAM lParam )
{
	switch (wParam)
	{
		case CDTOOLTIP_ONBEFORE_SHOW:
			{
				int nRow = (int)GetCustomDrawToolTips().GetDisplayWParam();
				int nCol = (int)GetCustomDrawToolTips().GetDisplayLParam();
				PreShowToolTipForCell(nRow, nCol);
			}
			break;
		case CDTOOLTIP_ONBEFORE_POP:
			PrePopToolTip();
			break;
	}
	return 0;
}

/*----------------------------------------------------------------------------*/
/* CCustomDrawListBox
/*----------------------------------------------------------------------------*/

IMPLEMENT_DYNCREATE(CCustomDrawListBox, CCustomDrawListBoxBase)

CCustomDrawListBox::CCustomDrawListBox()
	: m_pImageList(NULL)
	, m_nHotItem(-1)
	, m_bMouseEventsTracked(FALSE)
	, m_bExplorerVisualStyle(FALSE)
	, m_nLockDrawCount(0)
	, m_pTextDrawer(NULL)
{
	
}

CCustomDrawListBox::~CCustomDrawListBox()
{
	
}

BEGIN_MESSAGE_MAP(CCustomDrawListBox, CCustomDrawListBoxBase)
	ON_MESSAGE(WM_DRAWITEM, OnDrawItem)
	ON_WM_DRAWITEM_REFLECT()
	ON_WM_CREATE()
	ON_WM_MOUSEMOVE()
	ON_MESSAGE(WM_MOUSELEAVE, OnMouseLeave)
	ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTW, 0, 0xFFFF, OnToolTipText)
	ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTA, 0, 0xFFFF, OnToolTipText)
	ON_MESSAGE(WM_CDTOOLTIPCTRL_NOTIFY, OnCustomToolTipNotify)
	ON_WM_SETFOCUS()
	ON_WM_KILLFOCUS()
	ON_WM_DESTROY()
END_MESSAGE_MAP()

int CCustomDrawListBox::GetItemCount()
{
	return GetCount();
}

int CCustomDrawListBox::SetItemCount( int nCount )
{
	ASSERT(::IsWindow(m_hWnd));
	return ::SendMessage(m_hWnd, LB_SETCOUNT, nCount, 0);
}

LRESULT CCustomDrawListBox::EnableExplorerVisualStyles( bool bValue /*= true*/ )
{
	if (!IsThemeEnabled())
	{
		m_bExplorerVisualStyle = false;
		return S_FALSE;
	}
	
	if (!CheckOSVersion(0x0600))
	{
		m_bExplorerVisualStyle = false;
		return S_FALSE;
	}
	
	LRESULT rc = S_FALSE;
	if (bValue)
		rc = EnableWindowTheme(GetSafeHwnd(), L"ListBox", L"Explorer", NULL);
	else
		rc = EnableWindowTheme(GetSafeHwnd(), L"", L"", NULL);
	
	m_bExplorerVisualStyle = (bValue && rc == S_OK);
	
	return rc;
}

LRESULT CCustomDrawListBox::OnDrawItem( WPARAM nIDCtl, LPARAM lpDrawItemStruct )
{
	DrawItem(reinterpret_cast<LPDRAWITEMSTRUCT>(lpDrawItemStruct));
	return 0;
}

void CCustomDrawListBox::DrawItem( LPDRAWITEMSTRUCT lpDrawItemStruct )
{
	if ( (int)lpDrawItemStruct->itemID < 0 )
		return;

	CDC* pDC = CDC::FromHandle(lpDrawItemStruct->hDC);
	int nSavedDC = pDC->SaveDC();

	CRect rectClient;
	GetClientRect(rectClient);
	
	// * Somehow lpDrawItemStruct->rcItem gives wrong value for selected item when they are scrolled outside the listbox
	// and SelectClipRgn not works in this case, so I have to use memory dc to do "clipping"
	CRect rectItem(lpDrawItemStruct->rcItem);

	CSimpleMemDC memDC(*pDC, rectItem);
	CDC* pDrawDC = &memDC.GetDC();

	CFont* pFont = pDC->GetCurrentFont();
	CFont* pOldFont = pDrawDC->SelectObject(pFont);
	
	OnFillBackground(pDrawDC, rectItem, lpDrawItemStruct);
	
	if (OnDrawIcon(pDrawDC, rectItem, lpDrawItemStruct))
	{
		int cx, cy;
		ImageList_GetIconSize(GetImageList()->GetSafeHandle(), &cx, &cy);
		rectItem.left += cx + 4;
	}

	rectItem.left += 2;	// some gap to the text
	OnDrawText(pDrawDC, rectItem, lpDrawItemStruct);

	pDrawDC->SelectObject(pOldFont);

	pDC->RestoreDC(nSavedDC);
}

void CCustomDrawListBox::MeasureItem( LPMEASUREITEMSTRUCT lpMeasureItemStruct )
{
	
}

int CCustomDrawListBox::CompareItem( LPCOMPAREITEMSTRUCT lpCompareItemStruct )
{
	return 0;
}

CImageList* CCustomDrawListBox::SetImageList( CImageList* pImageList )
{
	CImageList* pOldImageList = m_pImageList;
	m_pImageList = pImageList;
	return pOldImageList;
}

CImageList* CCustomDrawListBox::GetImageList() const
{
	return m_pImageList;
}

BOOL CCustomDrawListBox::IsComboboxList()
{
	DWORD dwStyle = GetStyle();
	BOOL bIsComboListBox = dwStyle & LBS_COMBOBOX;
	if ( !bIsComboListBox )
	{
		// Note by the way, the class name of listbox in a combobox is "ComboLBox"
		CWnd* pParentWnd = GetParent();
		if (pParentWnd)
		{
			TCHAR szClassName[20] = {0};
			GetClassName(pParentWnd->GetSafeHwnd(), szClassName, 20);
			bIsComboListBox = _tcsicmp(szClassName, _T("ComboBox")) == 0;
		}
	}
	return bIsComboListBox;
}

void CCustomDrawListBox::OnFillBackground( CDC* pDC, CRect rect, LPDRAWITEMSTRUCT lpDrawItemStruct )
{
	BOOL bIsWndEnabled = IsWindowEnabled();
	pDC->FillSolidRect(rect, ::GetSysColor(bIsWndEnabled ? COLOR_WINDOW : COLOR_BTNFACE));

	if ( !bIsWndEnabled )
		return;

	BOOL bIsComboListBox = IsComboboxList();
	
	CCustomDrawHelper itemDrawer;
	itemDrawer.m_bWndHasFocus		= bIsComboListBox || ::GetFocus() == GetSafeHwnd();
	itemDrawer.m_bIsHotItem			= ShouldDrawHotItem() && m_nHotItem == lpDrawItemStruct->itemID;
	itemDrawer.m_bFocusItem			= (lpDrawItemStruct->itemState & ODS_FOCUS) ? true : false;
	itemDrawer.m_bSelected			= (lpDrawItemStruct->itemState & ODS_SELECTED) ? true : false;
	itemDrawer.DrawItemBackground(pDC, rect);
}

BOOL CCustomDrawListBox::OnDrawIcon( CDC* pDC, CRect rect, LPDRAWITEMSTRUCT lpDrawItemStruct )
{
	if (GetImageList())	// not thread safe
	{
		int nImageIndex = GetItemIconIndex(lpDrawItemStruct->itemID);
		if (nImageIndex >= 0)
		{
#ifdef _DEBUG
			int nImgCount = GetImageList()->GetImageCount();
			ASSERT(nImageIndex < nImgCount);
#endif // _DEBUG

			CPoint ptIcon = rect.TopLeft();
			//GetImageList()->Draw(pDC, nImageIndex, ptIcon, ILD_TRANSPARENT);

			int cx, cy;
			ImageList_GetIconSize(GetImageList()->GetSafeHandle(), &cx, &cy);

			// vertical center the icon
			int ny = (rect.Height() - cy) / 2;
			ptIcon.Offset(4, ny);

			HICON hIcon = GetImageList()->ExtractIcon(nImageIndex);
			VERIFY( ::DrawIconEx(pDC->GetSafeHdc(), ptIcon.x, ptIcon.y, hIcon, cx, cy, 0, NULL, DI_NORMAL) );
 			::DestroyIcon(hIcon);
			return TRUE;
		}
	}
	return FALSE;
}

void CCustomDrawListBox::OnDrawText( CDC* pDC, CRect rect, LPDRAWITEMSTRUCT lpDrawItemStruct )
{
	CString strText = GetItemText(lpDrawItemStruct->itemID);
	int nOldBkMode = pDC->SetBkMode(TRANSPARENT);

	COLORREF clrTxtColor = GetTextColor(lpDrawItemStruct);
	COLORREF clrOldTxtColor;
	if (clrTxtColor != CLR_INVALID)
		clrOldTxtColor = pDC->SetTextColor(clrTxtColor);

	const UINT nDTFormat = DT_SINGLELINE|DT_VCENTER|DT_NOPREFIX;
	if ( GetTextDrawer() )
		GetTextDrawer()->Draw(pDC, strText, rect, nDTFormat);
	else
		pDC->DrawText(strText, strText.GetLength(), rect, nDTFormat);

	if (clrTxtColor != CLR_INVALID)
		pDC->SetTextColor(clrOldTxtColor);

	pDC->SetBkMode(nOldBkMode);
}

COLORREF CCustomDrawListBox::GetTextColor( LPDRAWITEMSTRUCT lpDIS )
{
	// Gray text does not looks good
#ifdef CUSTOMDRAW_GRADIENT
	if ( !IsWindowEnabled() )
		return ::GetSysColor(COLOR_GRAYTEXT);
#else
	if ( (lpDIS->itemState & ODS_SELECTED) && ::GetFocus() == GetSafeHwnd() )
	{
		return ::GetSysColor(COLOR_HIGHLIGHTTEXT);
	}
#endif // CUSTOMDRAW_GRADIENT
	return CLR_INVALID;
}

CString CCustomDrawListBox::GetItemText( UINT nItem )
{
	CString str;
	GetText(nItem, str);
	return str;
}

int CCustomDrawListBox::GetItemIconIndex( UINT nItem )
{
	return -1;
}

int CCustomDrawListBox::OnCreate( LPCREATESTRUCT lpCreateStruct )
{
	if (CCustomDrawListBoxBase::OnCreate(lpCreateStruct) == -1)
		return -1;
	
	OnInitListBox();
	return 0;
}

void CCustomDrawListBox::PreSubclassWindow()
{
	CCustomDrawListBoxBase::PreSubclassWindow();
	
	_AFX_THREAD_STATE* pThreadState = AfxGetThreadState();
	if (pThreadState->m_pWndInit == NULL)
	{
		OnInitListBox();
	}
}

BOOL CCustomDrawListBox::PreTranslateMessage( MSG* pMsg )
{
	if (GetCustomDrawToolTips().m_hWnd)
		GetCustomDrawToolTips().RelayEvent(pMsg);
	return CCustomDrawListBoxBase::PreTranslateMessage(pMsg);
}

BOOL CCustomDrawListBox::OnToolTipText( UINT id, NMHDR * pNMHDR, LRESULT* pResult )
{
	UINT nID = pNMHDR->idFrom;
	
	// check if this is the automatic tooltip of the control
	if (nID == 0)
		return TRUE;	// do not allow display of automatic tooltip,
						// or our tooltip will disappear
	
	*pResult = 0;
	
	// get the mouse position
	const MSG* pMessage = GetCurrentMessage();
	ASSERT(pMessage);
	CPoint pt;
	pt = pMessage->pt;		// get the point from the message
	ScreenToClient(&pt);	// convert the point's coords to be relative to this control
	
	// see if the point falls onto a list item
	BOOL bOutSide;
	UINT nItem = ItemFromPoint(pt, bOutSide);
	
	if ( !bOutSide && ShouldShowToolTipForItem(nItem) )
	{
		CString strToolTip = GetToolTipLabelForItem(nItem);
		
		if (strToolTip.IsEmpty())
		{
			strToolTip = GetItemText(nItem);
		}
		if (!strToolTip.IsEmpty())
		{
			GetCustomDrawToolTips().SetText(pNMHDR, strToolTip);
			return TRUE;
		}	
	}
	
	return FALSE;	// we didn't handle the message, let the 
					// framework continue propagating the message
}

void CCustomDrawListBox::OnMouseMove( UINT nFlags, CPoint point )
{
	CCustomDrawListBoxBase::OnMouseMove(nFlags, point);

	if ( ShouldDrawHotItem() && !m_bMouseEventsTracked)
	{
		m_bMouseEventsTracked = TRUE;
		
		TRACKMOUSEEVENT trackmouseevent;
		trackmouseevent.cbSize		= sizeof(trackmouseevent);
		trackmouseevent.dwFlags		= TME_LEAVE;
		trackmouseevent.hwndTrack	= GetSafeHwnd();
		trackmouseevent.dwHoverTime	= HOVER_DEFAULT;
		//::AFXTrackMouse(&trackmouseevent);
		_TrackMouseEvent(&trackmouseevent);
	}
	
	// Find the subitem
	BOOL bOutSide;
	UINT nItem = ItemFromPoint(point, bOutSide);
	if ( bOutSide )
	{
		nItem = -1;
	}
		
	if ( ShouldDrawHotItem() && m_nHotItem != nItem )
	{
		int nOldHotItem = m_nHotItem;
		m_nHotItem = nItem;
		
		//LockSetRedraw(FALSE);

		CRect rect;
		GetItemRect(m_nHotItem, rect);
		InvalidateRect(rect, FALSE);	// don't erase background, less flicker

		GetItemRect(nOldHotItem, rect);
		InvalidateRect(rect, FALSE);	// don't erase background, less flicker
		//LockSetRedraw(TRUE);
		UpdateWindow();
	}
	
	if ( ShouldShowToolTipForItem(nItem) && GetCustomDrawToolTips().CheckDisplayToolTip((WPARAM)nItem) )
	{		
		// Remove the old tooltip (if available)
		int nToolCount = GetCustomDrawToolTips().GetToolCount();
		//TRACE("CCustomDrawListBox tooltip tool count: %d\n", nToolCount);
		if (nToolCount > 0)
		{
			// Not using CToolTipCtrl::DelTool() because it redirects the messages to CListBox parent
			// If we call DelTool(), you will see that the return value of GetToolCount() still keep increasing!
			//GetCustomDrawToolTips().DelTool(this);
			TOOLINFO ti = {0};
			ti.cbSize = sizeof(TOOLINFO);
			ti.uFlags = TTF_IDISHWND;		// Indicate that uId is handle to a control
			ti.uId = (UINT_PTR)m_hWnd;		// Handle to the control
			ti.hwnd = m_hWnd;				// Handle to window to receive the tooltip-messages
			ti.hinst = AfxGetInstanceHandle();
			GetCustomDrawToolTips().SendMessage(TTM_DELTOOL, 0, (LPARAM) (LPTOOLINFO) &ti);
			
			GetCustomDrawToolTips().Activate(FALSE);
		}
		
		// Add the new tooltip (if available)
		if ( !bOutSide )
		{
			// Not using CToolTipCtrl::AddTool() because it redirects the messages to CListBox parent
			//GetCustomDrawToolTips().AddTool(this);
			TOOLINFO ti = {0};
			ti.cbSize = sizeof(TOOLINFO);
			ti.uFlags = TTF_IDISHWND;		// Indicate that uId is handle to a control
			ti.uId = (UINT_PTR)m_hWnd;		// Handle to the control
			ti.hwnd = m_hWnd;				// Handle to window to receive the tooltip-messages
			ti.hinst = AfxGetInstanceHandle();
			ti.lpszText = LPSTR_TEXTCALLBACK;
			GetCustomDrawToolTips().SendMessage(TTM_ADDTOOL, 0, (LPARAM) (LPTOOLINFO) &ti);
			
			PreAddToolTipForItem(nItem);
			
			GetCustomDrawToolTips().Activate(TRUE);
		}
	}
}

void CCustomDrawListBox::OnInitListBox()
{
	// Prevents the system from stripping ampersand characters from a string or terminating a string at a tab character
	VERIFY( GetCustomDrawToolTips().Create(this, TTS_ALWAYSTIP|TTS_NOPREFIX) );
	GetCustomDrawToolTips().Activate(TRUE);

	EnableExplorerVisualStyles();
}

BOOL CCustomDrawListBox::ShouldShowToolTipForItem( int nItem )
{
	return TRUE;
}

CString CCustomDrawListBox::GetToolTipLabelForItem( int nItem )
{
	return GetItemText(nItem);
}

void CCustomDrawListBox::PreAddToolTipForItem( int nItem )
{
	// nothing to do
}

void CCustomDrawListBox::PreShowToolTipForItem( int nItem )
{
	// nothing to do
}

void CCustomDrawListBox::PrePopToolTip()
{
	// nothing to do
}

LRESULT CCustomDrawListBox::OnMouseLeave( WPARAM wParam, LPARAM lParam )
{
	LRESULT result = Default();
	m_bMouseEventsTracked = FALSE;
	
	if (m_nHotItem >= 0)
	{
		CRect rect;
		GetItemRect(m_nHotItem, rect);
		m_nHotItem = -1;
		InvalidateRect(rect, FALSE);
	}
	
	return result;
}

LRESULT CCustomDrawListBox::OnCustomToolTipNotify( WPARAM wParam, LPARAM lParam )
{
	switch (wParam)
	{
	case CDTOOLTIP_ONBEFORE_SHOW:
		{
			int nItem = (int)GetCustomDrawToolTips().GetDisplayWParam();
			PreShowToolTipForItem(nItem);
		}
		break;
	case CDTOOLTIP_ONBEFORE_POP:
		PrePopToolTip();
		break;
	}
	return 0;
}

void CCustomDrawListBox::OnSetFocus( CWnd* pOldWnd )
{
	CCustomDrawListBoxBase::OnSetFocus(pOldWnd);
	
	if ( !IsComboboxList() )
	{
		RedrawWindow();
	}
}

void CCustomDrawListBox::OnKillFocus( CWnd* pNewWnd )
{
	CCustomDrawListBoxBase::OnKillFocus(pNewWnd);

	if ( !IsComboboxList() )
	{
		RedrawWindow();
	}
}

void CCustomDrawListBox::OnDestroy()
{
	OnDestroyListBox();
	CCustomDrawListBoxBase::OnDestroy();
}

/*----------------------------------------------------------------------------*/
/* CCustomDrawTreeCtrl
/*----------------------------------------------------------------------------*/
IMPLEMENT_DYNCREATE(CCustomDrawTreeCtrl, CCustomDrawTreeCtrlBase)

CCustomDrawTreeCtrl::CCustomDrawTreeCtrl()
	: m_hHotTreeItem(NULL)
	, m_bDrawHotItem(TRUE)
	, m_bCustomDraw(TRUE)
	, m_nLockDrawCount(0)
	, m_pTextDrawer(NULL)
	, m_bExplorerVisualStyle(FALSE)
	, m_bMouseEventsTracked(FALSE)
	, m_SelStyle(CDTSS_LABELRIGHT)
{
}

CCustomDrawTreeCtrl::~CCustomDrawTreeCtrl()
{
	
}

BEGIN_MESSAGE_MAP(CCustomDrawTreeCtrl, CCustomDrawTreeCtrlBase)
	ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, OnCustomDraw)
	ON_WM_MOUSEMOVE()
	ON_WM_LBUTTONDOWN()
	ON_MESSAGE(WM_MOUSELEAVE, OnMouseLeave)
	ON_WM_CREATE()
	ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTW, 0, 0xFFFF, OnToolTipText)
	ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTA, 0, 0xFFFF, OnToolTipText)
	ON_MESSAGE(WM_CDTOOLTIPCTRL_NOTIFY, OnCustomToolTipNotify)
	ON_WM_ERASEBKGND()
	ON_WM_PAINT()
#ifdef FIX_TREECTRL_SCROLL_REDRAW_ISSUE
	ON_WM_HSCROLL()
#endif // FIX_TREECTRL_SCROLL_REDRAW_ISSUE
	ON_MESSAGE(WM_STYLECHANGED, OnStyleChanged)
END_MESSAGE_MAP()

LRESULT CCustomDrawTreeCtrl::EnableExplorerVisualStyles( bool bValue /*= true*/ )
{
	if (!IsThemeEnabled())
	{
		m_bExplorerVisualStyle = false;
		return S_FALSE;
	}
	
	if (!CheckOSVersion(0x0600))
	{
		m_bExplorerVisualStyle = false;
		return S_FALSE;
	}
	
	LRESULT rc = S_FALSE;
	if (bValue)
		rc = EnableWindowTheme(GetSafeHwnd(), L"TreeView", L"Explorer", NULL);
	else
		rc = EnableWindowTheme(GetSafeHwnd(), L"", L"", NULL);
	
	m_bExplorerVisualStyle = (bValue && rc == S_OK);
	
	return rc;
}

void CCustomDrawTreeCtrl::SetDrawHotItem( BOOL bDraw /*= TRUE*/, BOOL bReDraw /*= TRUE*/ )
{
	m_bDrawHotItem = bDraw;
	if ( bReDraw )
	{
		RedrawWindow();
	}
}

void CCustomDrawTreeCtrl::EnableCustomDraw( BOOL bEnable /*= TRUE*/, BOOL bRedraw /*= FALSE*/ )
{
	m_bCustomDraw = bEnable;
	if ( bRedraw )
		RedrawWindow();
}

void CCustomDrawTreeCtrl::SetSelStyle( SelStyle selStyle )
{
	m_SelStyle = selStyle;
	RedrawSelectedItem();
}

// MSDN states: TVS_FULLROWSELECT style cannot be used in conjunction with the TVS_HASLINES style. 
// http://msdn.microsoft.com/en-us/library/bb760013(VS.85).aspx
static inline BOOL IsFullRowSelectTreeCtrl(DWORD dwStyle)
{
	return (dwStyle & TVS_FULLROWSELECT) && !(dwStyle & TVS_HASLINES);
}

BOOL CCustomDrawTreeCtrl::HasValidFullRowSelectStyle()
{
	return IsFullRowSelectTreeCtrl( GetStyle() );	
}

LRESULT CCustomDrawTreeCtrl::OnStyleChanged( WPARAM wp, LPARAM lp )
{
	int nStyleType = (int) wp;
	LPSTYLESTRUCT lpStyleStruct = (LPSTYLESTRUCT) lp;
	CCustomDrawTreeCtrlBase::OnStyleChanged(nStyleType, lpStyleStruct);
	if ( nStyleType & GWL_STYLE )
	{
		ASSERT( lpStyleStruct );
		BOOL bOldFullRowSel	= IsFullRowSelectTreeCtrl(lpStyleStruct->styleOld);
		BOOL bFullRowSel	= IsFullRowSelectTreeCtrl(lpStyleStruct->styleNew);
		if ( bFullRowSel ^ bOldFullRowSel )
		{
			SetSelStyle( bFullRowSel ? CDTSS_FULLROWSELECT : (CDTSS_FULLROWSELECT == m_SelStyle ? CDTSS_LABELRIGHT : m_SelStyle) );
		}
	}
	return 0;
}

BOOL CCustomDrawTreeCtrl::OnEraseBkgnd( CDC* pDC )
{
	//return CCustomDrawTreeCtrlBase::OnEraseBkgnd(pDC);
	return TRUE;	// flicker free!
}

void CCustomDrawTreeCtrl::OnPaint()
{
	CRect rect;
	if ( GetUpdateRect(&rect, FALSE) && !rect.IsRectEmpty() )
	{
		// double buffered drawing.
		CPaintDC dc(this);
		CSimpleMemDC memDC(dc, dc.m_ps.rcPaint);
		DefWindowProc(WM_PRINT, (WPARAM)memDC.GetDC().m_hDC, (LPARAM)(PRF_CLIENT|PRF_CHECKVISIBLE));
	}
}

void CCustomDrawTreeCtrl::OnCustomDraw( NMHDR* pNMHDR, LRESULT* pResult )
{
	LPNMTVCUSTOMDRAW pNMTVCD = reinterpret_cast<LPNMTVCUSTOMDRAW>(pNMHDR);
	if ( !pNMTVCD )
		return;
	switch (pNMTVCD->nmcd.dwDrawStage)
	{
	case CDDS_PREPAINT:
		if ( IsCustomDrawEnabled() )
			*pResult = CDRF_NOTIFYITEMDRAW;
		else
			*pResult = CDRF_DODEFAULT;
		break;
	case CDDS_ITEMPREPAINT:
		{
			// set the background and foreground color of the item 
			// to the background,
			// so you don't see the default drawing of the text
			pNMTVCD->clrTextBk = pNMTVCD->clrText = ::GetSysColor(COLOR_WINDOW);

			if ( CDTSS_FULLROWSELECT == m_SelStyle )
			{
				HTREEITEM hItem = reinterpret_cast<HTREEITEM>(pNMTVCD->nmcd.dwItemSpec);
				CDC* pDC = CDC::FromHandle(pNMTVCD->nmcd.hdc);
				
				CRect rect = GetFillRect(hItem);
				OnFillBackground(pDC, rect, hItem, pNMTVCD->nmcd.uItemState);
			}
			
			// we want to get the CDDS_ITEMPOSTPAINT notification
			*pResult = CDRF_NOTIFYPOSTPAINT;
		}
		break;
	case CDDS_ITEMPOSTPAINT:
		{
			HTREEITEM hItem = reinterpret_cast<HTREEITEM>(pNMTVCD->nmcd.dwItemSpec);
			CDC* pDC = CDC::FromHandle(pNMTVCD->nmcd.hdc);

			// draw the item
			OnDrawTreeItem(pDC, hItem, pNMTVCD->nmcd.uItemState);
			//*pResult = CDRF_DODEFAULT;
			*pResult = CDRF_SKIPDEFAULT;
		}
		break;
	default:
		*pResult = CDRF_DODEFAULT;
		break;
	}
}

CRect CCustomDrawTreeCtrl::GetFillRect( HTREEITEM hItem )
{
	CRect rcText;
	GetItemRect(hItem, rcText, TRUE);

	if ( CDTSS_LABEL == m_SelStyle )
		return rcText;

	CRect rcBounds;
	GetItemRect(hItem, &rcBounds, FALSE);

	SCROLLINFO si = {0};
	GetScrollInfo(SB_HORZ, &si, SIF_POS|SIF_RANGE);
	
	rcBounds.right = max(rcBounds.right, si.nMax - si.nPos);

	if ( m_bExplorerVisualStyle )
	{
		rcBounds.left = min(rcBounds.left, -si.nPos);
	}
	else
	{
		rcBounds.left = rcText.left;
	}

	if ( CDTSS_FULLROWSELECT == m_SelStyle )
		return rcBounds;

	ASSERT( CDTSS_LABELRIGHT == m_SelStyle );

	rcText.right = rcBounds.right;

	return rcText;
}

CRect CCustomDrawTreeCtrl::GetSelectRect( HTREEITEM hItem )
{
// 	CRect rcSel;
// 	GetItemRect(hItem, &rcSel, m_bDragSelTextOnly);
// 	return rcSel;
	return GetFillRect(hItem);
}

BOOL CCustomDrawTreeCtrl::PtInItemSelectRect( HTREEITEM hItem, CPoint pt )
{
	CRect rcSelect = GetSelectRect(hItem);
	return rcSelect.PtInRect(pt);
}

//#define DRAW_TREE_ITEM_VIA_MEMDC	// no flicker already, no need to double buffer it

void CCustomDrawTreeCtrl::OnDrawTreeItem( CDC* pDC, HTREEITEM hItem, UINT nItemState )
{
	CRect rcItem;
	GetItemRect(hItem, rcItem, TRUE);

	CRect rcFill = GetFillRect(hItem);
	rcItem.right = rcFill.right;

#ifdef DRAW_TREE_ITEM_VIA_MEMDC
	CSimpleMemDC memDC(*pDC, rcItem);
	CDC* pDrawDC = &memDC.GetDC();
#else
	CDC*& pDrawDC = pDC;
#endif // DRAW_TREE_ITEM_VIA_MEMDC

	CFont* pFont = pDC->GetCurrentFont();
	CFont* pOldFont = pDrawDC->SelectObject(pFont);

	OnFillBackground(pDrawDC, rcFill, hItem, nItemState);
	
	//rcItem.left += 5;	// makes some gap to the text.
	OnDrawText(pDrawDC, rcItem, hItem, nItemState, FALSE);

	pDrawDC->SelectObject(pOldFont);
}

void CCustomDrawTreeCtrl::OnFillBackground( CDC* pDC, CRect rect, HTREEITEM hItem, UINT nItemState )
{
	BOOL bIsWndEnabled = IsWindowEnabled();
	// Erase the background
	pDC->FillSolidRect(rect, ::GetSysColor(bIsWndEnabled ? COLOR_WINDOW : COLOR_BTNFACE));

	if ( !bIsWndEnabled )
		return;

	//rect.left += 2;	// make some gap

	CCustomDrawHelper itemDrawer;
	itemDrawer.m_bWndHasFocus		= ::GetFocus() == GetSafeHwnd();
	itemDrawer.m_bFocusItem			= (nItemState & CDIS_FOCUS) ? true : false;
	itemDrawer.m_bSelected			= (nItemState & CDIS_SELECTED) ? true : false;
	itemDrawer.m_bIsHotItem			= m_bDrawHotItem && m_hHotTreeItem == hItem;
	itemDrawer.m_bDrawBorderWhenFill	= true;
	itemDrawer.DrawItemBackground(pDC, rect);
}

CSize CCustomDrawTreeCtrl::OnDrawText( CDC* pDC, CRect rect, HTREEITEM hItem, UINT nItemState, BOOL bCalcOnly /*= FALSE*/ )
{
	CString strItem = GetItemText(hItem);
	
	COLORREF clrTxtColor;
	if (nItemState & CDIS_DISABLED)
		clrTxtColor = ::GetSysColor(COLOR_GRAYTEXT);
#ifndef CUSTOMDRAW_GRADIENT
	else if ((nItemState & CDIS_SELECTED) && ::GetFocus() == GetSafeHwnd() )
		clrTxtColor = ::GetSysColor(COLOR_HIGHLIGHTTEXT);
#endif // CUSTOMDRAW_GRADIENT
	else
		clrTxtColor = ::GetSysColor(COLOR_WINDOWTEXT);
	
	return DrawItemText(pDC, rect, hItem, strItem, clrTxtColor, bCalcOnly);
}

CSize CCustomDrawTreeCtrl::DrawItemText( CDC* pDC, CRect rect, HTREEITEM hItem, LPCTSTR lpcszText, COLORREF clrTxtColor, BOOL bCalcOnly /*= FALSE*/ )
{
	int nOldBkMode			= pDC->SetBkMode(TRANSPARENT);
	COLORREF clrOldTxtColor	= pDC->SetTextColor(clrTxtColor);
	
	DRAWTEXTPARAMS dtp = {sizeof(DRAWTEXTPARAMS)};
	dtp.iTabLength = 4;
	
	UINT nBoldState = GetItemState(hItem, TVIS_BOLD);
	BOOL bBoldText = nBoldState & TVIS_BOLD;
	
	CFont* pOldFont = NULL;
	CFont fnt;
	if ( bBoldText )
	{
		CFont* pCurFont = pDC->GetCurrentFont();
		LOGFONT lf = {0};
		pCurFont->GetLogFont(&lf);
		lf.lfWeight = FW_BOLD;	// make it bold
		
		fnt.CreateFontIndirect(&lf);
		pOldFont = pDC->SelectObject(&fnt);
	}
	
	UINT uDTFormat = DT_VCENTER|DT_SINGLELINE|DT_TABSTOP|DT_EXPANDTABS|DT_END_ELLIPSIS;
	if ( bCalcOnly )
	{
		uDTFormat |= DT_CALCRECT;
	}

	int nTextHeight;
	if ( GetTextDrawer() )
		nTextHeight = GetTextDrawer()->Draw(pDC, lpcszText, rect, uDTFormat);
	else
		nTextHeight = DrawTextEx(pDC->GetSafeHdc(), (LPTSTR)lpcszText, _tcslen(lpcszText), rect, uDTFormat, &dtp);

	CSize sizeText(rect.Width(), nTextHeight);
	
	if ( bBoldText )
	{
		pDC->SelectObject(pOldFont);
		fnt.DeleteObject();
	}
	
	pDC->SetTextColor(clrOldTxtColor);
	pDC->SetBkMode(nOldBkMode);
	return sizeText;
}

void CCustomDrawTreeCtrl::InvalidateItem( HTREEITEM hItem, BOOL bErase /*= TRUE*/ )
{
	CRect rcItem = GetFillRect(hItem);
	InvalidateRect(rcItem, bErase);
}

void CCustomDrawTreeCtrl::OnMouseMove( UINT nFlags, CPoint point )
{
	CCustomDrawTreeCtrlBase::OnMouseMove(nFlags, point);

	UINT uHitTestFlags = 0;
	HTREEITEM hItem = HitTest(point, &uHitTestFlags);

	if ( IsDrawHotItem() )
	{
		if (hItem)
		{
			CRect rcSel = GetFillRect(hItem);
			if ( !rcSel.PtInRect(point) )
				hItem = m_hHotTreeItem;
		}
		if ( hItem != m_hHotTreeItem )
		{
			if ( !m_bMouseEventsTracked )
			{
				m_bMouseEventsTracked = TRUE;
				
				TRACKMOUSEEVENT trackmouseevent;
				trackmouseevent.cbSize		= sizeof(trackmouseevent);
				trackmouseevent.dwFlags		= TME_LEAVE;
				trackmouseevent.hwndTrack	= GetSafeHwnd();
				trackmouseevent.dwHoverTime	= HOVER_DEFAULT;
				//::AFXTrackMouse(&trackmouseevent);
				_TrackMouseEvent(&trackmouseevent);
			}
			// It seems that SetRedraw() will actually produce flickering, so better do invalidate instead.
			if ( m_hHotTreeItem != NULL )
			{
				InvalidateItem(m_hHotTreeItem, TRUE);
			}
			if ( hItem != NULL )
			{
				InvalidateItem(hItem, TRUE);
			}
			m_hHotTreeItem = hItem;
			UpdateWindow();
		}
	}

	// Find the item
	if ( ShouldShowToolTipForCell(hItem, uHitTestFlags)
		&& GetCustomDrawToolTips().CheckDisplayToolTip( (WPARAM)hItem, (LPARAM)uHitTestFlags )
		)
	{
		// Remove the old tooltip (if available)
		int nToolCount = GetCustomDrawToolTips().GetToolCount();
		//TRACE("CCustomDrawTreeCtrl tooltip tool count: %d\n", nToolCount);
		if (nToolCount > 0)
		{
			// Not using CToolTipCtrl::DelTool() because it redirects the messages to CTreeCtrl parent
			// If we call DelTool(), you will see that the return value of GetToolCount() still keep increasing!
			//GetCustomDrawToolTips().DelTool(this);
			TOOLINFO ti = {0};
			ti.cbSize = sizeof(TOOLINFO);
			ti.uFlags = TTF_IDISHWND;	// Indicate that uId is handle to a control
			ti.uId = (UINT_PTR)m_hWnd;	// Handle to the control
			ti.hwnd = m_hWnd;			// Handle to window to receive the tooltip-messages
			ti.hinst = AfxGetInstanceHandle();
			GetCustomDrawToolTips().SendMessage(TTM_DELTOOL, 0, (LPARAM) (LPTOOLINFO) &ti);
			
			GetCustomDrawToolTips().Activate(FALSE);
		}
		// Not using CToolTipCtrl::AddTool() because it redirects the messages to CTreeCtrl parent
		//GetCustomDrawToolTips().AddTool(this);
		TOOLINFO ti = {0};
		ti.cbSize = sizeof(TOOLINFO);
		ti.uFlags = TTF_IDISHWND;	// Indicate that uId is handle to a control
		ti.uId = (UINT_PTR)m_hWnd;	// Handle to the control
		ti.hwnd = m_hWnd;			// Handle to window to receive the tooltip-messages
		ti.hinst = AfxGetInstanceHandle();
		ti.lpszText = LPSTR_TEXTCALLBACK;
		GetCustomDrawToolTips().SendMessage(TTM_ADDTOOL, 0, (LPARAM) (LPTOOLINFO) &ti);

		PreAddToolTipForItem(hItem, uHitTestFlags);
		GetCustomDrawToolTips().Activate(TRUE);
	}
}

LRESULT CCustomDrawTreeCtrl::OnMouseLeave( WPARAM wParam, LPARAM lParam )
{
	LRESULT result = Default();
	m_bMouseEventsTracked = FALSE;
	
	if (m_hHotTreeItem)
	{
		InvalidateItem(m_hHotTreeItem, FALSE);
		m_hHotTreeItem = NULL;
	}
	
	return result;
}

void CCustomDrawTreeCtrl::OnLButtonDown( UINT nFlags, CPoint point )
{
	if ( CDTSS_LABELRIGHT == m_SelStyle && !HasValidFullRowSelectStyle() )
	{
		UINT uHitTestFlags = 0;
		HTREEITEM hItem = HitTest(point, &uHitTestFlags);
		if ( hItem != NULL && (uHitTestFlags & TVHT_ONITEMRIGHT) )
		{
			Select(hItem, TVGN_CARET);
		}
	}
	CCustomDrawTreeCtrlBase::OnLButtonDown(nFlags, point);
}

int CCustomDrawTreeCtrl::OnCreate( LPCREATESTRUCT lpCreateStruct )
{
	if (CCustomDrawTreeCtrlBase::OnCreate(lpCreateStruct) == -1)
		return -1;
	
	OnInitTreeCtrl();
	return 0;
}

void CCustomDrawTreeCtrl::PreSubclassWindow()
{
	CCustomDrawTreeCtrlBase::PreSubclassWindow();

	_AFX_THREAD_STATE* pThreadState = AfxGetThreadState();
	if (pThreadState->m_pWndInit == NULL)
	{
		OnInitTreeCtrl();
	}
}

BOOL CCustomDrawTreeCtrl::PreTranslateMessage( MSG* pMsg )
{
	if (GetCustomDrawToolTips().m_hWnd)
		GetCustomDrawToolTips().RelayEvent(pMsg);
	return CCustomDrawTreeCtrlBase::PreTranslateMessage(pMsg);
}

void CCustomDrawTreeCtrl::OnInitTreeCtrl()
{
	// disables the automatic tooltip feature of tree view controls
	ModifyStyle(0, TVS_NOTOOLTIPS);
	
	// Enable our own tooltip-ctrl and make it show tooltip even if not having focus
	// Prevents the system from stripping ampersand characters from a string or terminating a string at a tab character
	VERIFY( GetCustomDrawToolTips().Create(this, TTS_ALWAYSTIP|TTS_NOPREFIX) );
	GetCustomDrawToolTips().Activate(TRUE);

	EnableExplorerVisualStyles(TRUE);

	if ( HasValidFullRowSelectStyle() )
	{
		m_SelStyle = CDTSS_FULLROWSELECT;
	}
}

BOOL CCustomDrawTreeCtrl::OnToolTipText( UINT id, NMHDR * pNMHDR, LRESULT * pResult )
{
	UINT nID = pNMHDR->idFrom;
	
	// check if this is the automatic tooltip of the control
	if (nID == 0)
		return TRUE;	// do not allow display of automatic tooltip,
						// or our tooltip will disappear
	
	*pResult = 0;
	
	// get the mouse position
	const MSG* pMessage = GetCurrentMessage();
	ASSERT(pMessage);
	CPoint pt;
	pt = pMessage->pt;		// get the point from the message
	ScreenToClient(&pt);	// convert the point's coords to be relative to this control
	
	// see if the point falls onto a item
	UINT uHitTestFlags = 0;
	HTREEITEM hItem = HitTest(pt, &uHitTestFlags);
	if ( hItem != NULL && (uHitTestFlags & (TVHT_ONITEM|TVHT_ONITEMRIGHT)) )
	{
		CString strToolTip = GetToolTipLabelForItem(hItem, uHitTestFlags);
		
		if ( !strToolTip.IsEmpty() )
		{
			GetCustomDrawToolTips().SetText(pNMHDR, strToolTip);
			return TRUE;
		}
		
	}
	return FALSE;	// we didn't handle the message, let the 
					// framework continue propagating the message
}

BOOL CCustomDrawTreeCtrl::ShouldShowToolTipForCell( HTREEITEM hItem, UINT uHitTestFlags )
{
	UINT uHT = TVHT_ONITEM | (CDTSS_LABEL == m_SelStyle ? 0 : TVHT_ONITEMRIGHT);
	return hItem != NULL && (uHitTestFlags & uHT);
}

CString CCustomDrawTreeCtrl::GetToolTipLabelForItem( HTREEITEM hItem, UINT uHitTestFlags )
{
	return GetItemText(hItem);
}

void CCustomDrawTreeCtrl::PreAddToolTipForItem( HTREEITEM hItem, UINT uHitTestFlags )
{
	// nothing to do
}

void CCustomDrawTreeCtrl::PreShowToolTipForItem( HTREEITEM hItem, UINT uHitTestFlags )
{
	// nothing to do
}

void CCustomDrawTreeCtrl::PrePopToolTip()
{
	// nothing to do
}

LRESULT CCustomDrawTreeCtrl::OnCustomToolTipNotify( WPARAM wParam, LPARAM lParam )
{
	switch (wParam)
	{
	case CDTOOLTIP_ONBEFORE_SHOW:
		{
			HTREEITEM hItem		= (HTREEITEM)GetCustomDrawToolTips().GetDisplayWParam();
			UINT uHitTestFlags	= (UINT)GetCustomDrawToolTips().GetDisplayLParam();
			PreShowToolTipForItem(hItem, uHitTestFlags);
		}
		break;
	case CDTOOLTIP_ONBEFORE_POP:
		PrePopToolTip();
		break;
	}
	return 0;
}

#ifdef FIX_TREECTRL_SCROLL_REDRAW_ISSUE
void CCustomDrawTreeCtrl::OnHScroll( UINT nSBCode, UINT nPos, CScrollBar* pScrollBar )
{
	CCustomDrawTreeCtrlBase::OnHScroll(nSBCode, nPos, pScrollBar);

	if ( IsDrawHotItem() && m_hHotTreeItem )
	{
		InvalidateItem(m_hHotTreeItem, FALSE);
	}

	RedrawSelectedItem();
}
#endif // FIX_TREECTRL_SCROLL_REDRAW_ISSUE

void CCustomDrawTreeCtrl::RedrawSelectedItem(BOOL bErase /*= FALSE*/)
{
	HTREEITEM hSelItem = GetSelectedItem();
	if ( hSelItem )
	{
		InvalidateItem(hSelItem, FALSE);
	}
}

/*----------------------------------------------------------------------------*/
/* class CTriCheckStateTreeCtrl
/*----------------------------------------------------------------------------*/

IMPLEMENT_DYNCREATE(CTriCheckStateTreeCtrl, CTriCheckStateTreeCtrlBase)

#ifndef STATEIMAGEMASKTOINDEX
	#define STATEIMAGEMASKTOINDEX(i) ((i) >> 12)
#endif // STATEIMAGEMASKTOINDEX

#define TVICON_W	13
#define TVICON_H	13

CTriCheckStateTreeCtrl::CTriCheckStateTreeCtrl()
	: m_uClickHitTestFlags(0)
{
	
}

CTriCheckStateTreeCtrl::~CTriCheckStateTreeCtrl()
{
}


BEGIN_MESSAGE_MAP(CTriCheckStateTreeCtrl, CTriCheckStateTreeCtrlBase)
	//{{AFX_MSG_MAP(CTriCheckStateTreeCtrl)
	ON_WM_LBUTTONDOWN()
	ON_WM_LBUTTONDBLCLK()
	ON_NOTIFY_REFLECT(NM_CLICK, OnClickItem)
	ON_NOTIFY_REFLECT(TVN_KEYDOWN, OnKeydown)
	ON_WM_KEYDOWN()
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

void CTriCheckStateTreeCtrl::OnInitTreeCtrl()
{
	CTriCheckStateTreeCtrlBase::OnInitTreeCtrl();
	if ( GetSafeHwnd() )
	{
		VERIFY( InitCheckBoxImage() );
		SetImageList(&m_ChkBoxImgList, TVSIL_STATE);
	}
}

//#define CUSTOMDRAW_CHECKBOX

BOOL CTriCheckStateTreeCtrl::InitCheckBoxImage()
{
	int cx = TVICON_W;	//GetSystemMetrics(SM_CXICON);
	int cy = TVICON_H;	//GetSystemMetrics(SM_CYICON);

	CMemBitmap bitmapDrawer;
	if ( !bitmapDrawer.BeginDraw(cx * 4, cy) )
	{
		return FALSE;
	}
	CDC& dcMemory = bitmapDrawer.GetDC();

	CRect rect(0, 0, cx, cy);

	HTHEME hTheme = OOpenThemeData(m_hWnd, VSCLASS_BUTTON);
	if ( hTheme )
	{
		rect.OffsetRect(cx, 0);

		const int stateIds[] = {CBS_UNCHECKEDNORMAL, CBS_MIXEDNORMAL, CBS_CHECKEDNORMAL};
		for (int ii = TTCS_UNCHECKED; ii <= TTCS_CHECKED; ++ii)
		{
			ODrawThemeBackground(hTheme, dcMemory, BP_CHECKBOX, stateIds[ii-TTCS_UNCHECKED], rect, NULL);
			rect.OffsetRect(cx, 0);
		}
		OCloseThemeData(hTheme);
	}
	else
	{
		CDC& dcMemory = bitmapDrawer.GetDC();
		
		CRect rect(0, 0, cx, cy);
		CBrush brBorder(RGB(28,81,128));
		for (int ii = TTCS_NONE; ii <= TTCS_CHECKED; ++ii)
		{
			// Fill the background first
		#ifdef CUSTOMDRAW_GRADIENT
			FillGradient(dcMemory.GetSafeHdc(), rect, RGB(223, 222, 215), RGB(255, 255, 255), GFT_DIAG_TL_BR);
		#else
			dcMemory.FillSolidRect(rect, RGB(255,255,255));
		#endif // CUSTOMDRAW_GRADIENT
			// Draw the border
			dcMemory.FrameRect(rect, &brBorder);
			rect.OffsetRect(cx, 0);
		}
		
		// Draw the icon for partial checked button
		rect.left = cx * TTCS_PARTIALCHECKED;
		rect.right = rect.left + cx;
		CRect rectPartial(rect);
		rectPartial.DeflateRect(3, 3);
		dcMemory.FillSolidRect(rectPartial, RGB(115, 193, 114));
		
		// Draw the icon for normal checked button
		rect.left = cx * TTCS_CHECKED;
		rect.right = rect.left + cx;
		POINT ptsTick[3] = {
			{rect.left+3, rect.top+6},
			{rect.left+5, rect.top+8},
			{rect.left+9, rect.top+4}
		};
		
		// Draw the tick
		CPen penTick(PS_SOLID, 3, RGB(33, 161, 33));
		CPen* pOldPen = dcMemory.SelectObject(&penTick);
		
		dcMemory.Polyline(ptsTick, 3);
		
		dcMemory.SelectObject(pOldPen);
	}
	
	// Finish the bitmap
	bitmapDrawer.EndDraw();
	
	m_ChkBoxImgList.Create(cx, cy, ILC_COLOR24, 0, 1);
	int nBmpIndex = m_ChkBoxImgList.Add( bitmapDrawer.GetBitmapPtr(), (CBitmap*)NULL );
	ASSERT(nBmpIndex >= 0);

#ifdef _DEBUG
	int nImgCount = m_ChkBoxImgList.GetImageCount();
	ASSERT(nImgCount > 0);
#endif // _DEBUG
	return TRUE;
}

/////////////////////////////////////////////////////////////////////////////
// CTriCheckStateTreeCtrl message handlers

void CTriCheckStateTreeCtrl::OnClickItem(NMHDR* pNMHDR, LRESULT* pResult) 
{
	if (m_uClickHitTestFlags & TVHT_ONITEMSTATEICON)
		*pResult = 1;	// Mark message as handled and suppress default handling,
						// otherwise the original tree control would modify the check state.
	else
		*pResult = 0;
}

void CTriCheckStateTreeCtrl::OnLButtonDown(UINT nFlags, CPoint point)
{
	HTREEITEM hItem = HitTest(point, &m_uClickHitTestFlags);
	if ( hItem != NULL && (m_uClickHitTestFlags & TVHT_ONITEMSTATEICON) )
	{
		OnClickItemStateIcon(hItem);
	}
	
	CTriCheckStateTreeCtrlBase::OnLButtonDown(nFlags, point);
}

void CTriCheckStateTreeCtrl::OnLButtonDblClk( UINT nFlags, CPoint point )
{
	HTREEITEM hItem = HitTest(point, &m_uClickHitTestFlags);
	if ( hItem != NULL && (m_uClickHitTestFlags & (TVHT_ONITEMRIGHT|TVHT_ONITEMLABEL)) )
	{
		OnDblClkItem(hItem);
	}
	
	CTriCheckStateTreeCtrlBase::OnLButtonDblClk(nFlags, point);
}

void CTriCheckStateTreeCtrl::OnClickItemStateIcon( HTREEITEM hItem )
{
	TriCheckState tcs = GetCheckState(hItem);
	if (tcs != TTCS_NONE)
		ToggleCheck(hItem);
}

void CTriCheckStateTreeCtrl::ToggleCheckSelectedItem()
{
	HTREEITEM hItem = GetSelectedItem();
	if ( hItem != NULL )
	{
		TriCheckState tcs = GetCheckState(hItem);
		if (tcs != TTCS_NONE)
		{
			ToggleCheck(hItem);
		}
	}
}

void CTriCheckStateTreeCtrl::DeleteSelectedItem()
{
	HTREEITEM hItem = GetSelectedItem();
	if ( hItem != NULL )
	{
		DeleteItem(hItem);
	}
}

void CTriCheckStateTreeCtrl::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) 
{
 	if (VK_SPACE == nChar)
 	{
		ToggleCheckSelectedItem();
	}
	else if ( VK_DELETE == nChar )
	{
		DeleteSelectedItem();
	}
	else
		CTriCheckStateTreeCtrlBase::OnKeyDown(nChar, nRepCnt, nFlags);
}

void CTriCheckStateTreeCtrl::OnKeydown(NMHDR* pNMHDR, LRESULT* pResult) 
{
	TV_KEYDOWN* pTVKeyDown = reinterpret_cast<TV_KEYDOWN*>(pNMHDR);
	
	*pResult = 0;
}

BOOL CTriCheckStateTreeCtrl::SetItemState(HTREEITEM hItem, UINT nState, UINT nStateMask, BOOL bSearchChild /*= TRUE*/)
{
	if ( (nStateMask & TVIS_STATEIMAGEMASK) && !CanCheckItem(hItem) )
		return FALSE;
	BOOL bReturn = CTriCheckStateTreeCtrlBase::SetItemState( hItem, nState, nStateMask );

	if ( nStateMask & TVIS_STATEIMAGEMASK )
	{
		TriCheckState iState = (TriCheckState)STATEIMAGEMASKTOINDEX(nState);
		if (iState != TTCS_NONE)
		{
			if (bSearchChild)
			{
				TravelCheckChildren(hItem, iState);
			}
			TravelCheckSiblingAndParent(hItem, iState);
		}
	}
	return bReturn;
}

BOOL CTriCheckStateTreeCtrl::SetCheckState( HTREEITEM hItem, TriCheckState tcs /*= TTCS_CHECKED*/ )
{
	return SetItemState(hItem, INDEXTOSTATEIMAGEMASK(tcs), TVIS_STATEIMAGEMASK);
}

TriCheckState CTriCheckStateTreeCtrl::GetCheckState( HTREEITEM hItem ) const
{
	return (TriCheckState)STATEIMAGEMASKTOINDEX(GetItemState( hItem, TVIS_STATEIMAGEMASK ));
}

BOOL CTriCheckStateTreeCtrl::SetCheck( HTREEITEM hItem, BOOL bCheck /*= TRUE*/ )
{
	return SetCheckState(hItem, bCheck ? TTCS_CHECKED : TTCS_UNCHECKED);
}

BOOL CTriCheckStateTreeCtrl::GetCheck( HTREEITEM hItem ) const
{
	TriCheckState tcs = GetCheckState(hItem);
	return TTCS_CHECKED == tcs || TTCS_PARTIALCHECKED == tcs;
}

BOOL CTriCheckStateTreeCtrl::DeleteItem( HTREEITEM hItem )
{
	HTREEITEM hParentItem = GetParentItem(hItem);
	BOOL bRet = CTriCheckStateTreeCtrlBase::DeleteItem(hItem);
	if ( GetDeleteParentIfLastChild() )
	{
		while ( hParentItem != NULL )
		{
			HTREEITEM hSiblingItem = GetChildItem(hParentItem);
			if (NULL == hSiblingItem)
			{
				// The parent item has no child left
				HTREEITEM hTmpParentItem = hParentItem;
				hParentItem = GetParentItem(hParentItem);
				CTriCheckStateTreeCtrlBase::DeleteItem(hTmpParentItem);
			}
			else
				break;
		}
	}
	if ( hParentItem != NULL )
	{
		TriCheckState tcs = GetCheckState(hParentItem);
		if ( TTCS_PARTIALCHECKED == tcs )
		{
			HTREEITEM hChildItem = GetChildItem(hParentItem);
			ASSERT(hChildItem != NULL);	// it must has been deleted by the above code!
			tcs = GetCheckState(hChildItem);
			TravelCheckSiblingAndParent(hChildItem, tcs);
		}
	}
	return bRet;
}

void CTriCheckStateTreeCtrl::TravelCheckChildren(HTREEITEM hItem, TriCheckState nState)
{
	HTREEITEM hChildItem = GetChildItem(hItem);
	while (hChildItem != NULL)
	{
		TriCheckState nChildState = GetCheckState(hChildItem);
		if ( nChildState != TTCS_NONE && CanCheckItem(hChildItem) )
		{
			CTriCheckStateTreeCtrlBase::SetItemState( hChildItem, INDEXTOSTATEIMAGEMASK(nState), TVIS_STATEIMAGEMASK );
		}
		// recursively check its children
		TravelCheckChildren(hChildItem, nState);
		hChildItem = GetNextSiblingItem(hChildItem);
	}
}

void CTriCheckStateTreeCtrl::TravelCheckSiblingAndParent(HTREEITEM hItem, TriCheckState nState)
{
	HTREEITEM hParentItem = GetParentItem(hItem);
	if ( NULL == hParentItem )
		return;
	// Check the status of all the sibling node
	HTREEITEM hSiblingItem = GetChildItem(hParentItem);
	while (hSiblingItem != NULL)
	{
		TriCheckState nSiblingState = GetCheckState(hSiblingItem);
		// If any of them is different with hItem, we have to do partial check for all of its parents.
		if ( nSiblingState != nState && nSiblingState != TTCS_NONE)
		{
			while (hParentItem != NULL)
			{
				TriCheckState nParentState = GetCheckState( hParentItem );
				if ( nParentState != TTCS_NONE && CanCheckItem(hParentItem) )
				{
					CTriCheckStateTreeCtrlBase::SetItemState( hParentItem, INDEXTOSTATEIMAGEMASK(TTCS_PARTIALCHECKED), TVIS_STATEIMAGEMASK );
				}
				hParentItem = GetParentItem(hParentItem);
			}
			return;	// Done.
		}
		hSiblingItem = GetNextSiblingItem(hSiblingItem);
	}
	// If we come to here, that means we still need to check the parent's sibling
	TriCheckState nParentState = GetCheckState( hParentItem );
	if ( nParentState != TTCS_NONE && CanCheckItem(hParentItem) )
	{
		CTriCheckStateTreeCtrlBase::SetItemState( hParentItem, INDEXTOSTATEIMAGEMASK(nState), TVIS_STATEIMAGEMASK );
	}
	TravelCheckSiblingAndParent(hParentItem, nState);
}

void CTriCheckStateTreeCtrl::OnDblClkItem( HTREEITEM hItem )
{
	TRACE("CTriCheckStateTreeCtrl::OnDblClkItem\n");
}

BOOL CTriCheckStateTreeCtrl::CanCheckItem( HTREEITEM hItem )
{
	return TRUE;
}

/*----------------------------------------------------------------------------*/
/* CMultiSelTriCheckTreeCtrl
/*----------------------------------------------------------------------------*/

#ifndef SPI_GETFLATMENU
	#define SPI_GETFLATMENU	0x1022	// Determines whether native User menus have flat menu appearance. 
									// The pvParam parameter must point to a BOOL variable that returns
									// TRUE if the flat menu appearance is set, or FALSE otherwise. 
#endif

#ifndef COLOR_MENUHILIGHT 
	#define COLOR_MENUHILIGHT 29	// The color used to highlight menu items when the menu appears as
									// a flat menu (see SystemParametersInfo). The highlighted menu item
									// is outlined with COLOR_HIGHLIGHT.
#endif

// Don't bother why this coded like this
static COLORREF GetSelectionFillColor(BYTE byPercent, COLORREF clrHiLi)
{
	BYTE byRVal = GetRValue(clrHiLi) + byPercent * (255 - GetRValue(clrHiLi)) / 100;
	BYTE byGVal = GetGValue(clrHiLi) + byPercent * (255 - GetGValue(clrHiLi)) / 100;
	BYTE byBVal = GetBValue(clrHiLi) + byPercent * (255 - GetBValue(clrHiLi)) / 100;
	return RGB(byRVal, byGVal, byBVal);
}

static COLORREF GetSelectionFillColor()
{
	BOOL bFlatMenu = TRUE;
	SystemParametersInfo(SPI_GETFLATMENU, 0, &bFlatMenu, 0);
	if (bFlatMenu)
	{
		return ::GetSysColor(COLOR_MENUHILIGHT);
	}
	COLORREF clrHiLight = ::GetSysColor(COLOR_HIGHLIGHT);	// Color of item(s) selected in a control.
	return GetSelectionFillColor(60, clrHiLight);
}

#define HasShiftFlag()				(nFlags & MK_SHIFT)
#define HasCtrlFlag()				(nFlags & MK_CONTROL)
#define HasCtrlOrShiftFlag()		(nFlags & (MK_CONTROL|MK_SHIFT))

COLORREF CMultiSelTriCheckTreeCtrl::s_clrSelFill = GetSelectionFillColor();

CMultiSelTriCheckTreeCtrl::CMultiSelTriCheckTreeCtrl()
{
	m_rcSelBox.SetRectEmpty();
	m_bPendingDragSel	= FALSE;
	m_bDuringDragSel	= FALSE;
	m_bMultiSel			= TRUE;
	
	m_clrSelBorder		= RGB_SELECTION_BORDER;
	m_clrSelFill		= s_clrSelFill;
	m_hSelectAnchor		= NULL;
	m_bEmulated			= FALSE;
}

CMultiSelTriCheckTreeCtrl::~CMultiSelTriCheckTreeCtrl()
{
	
}

BEGIN_MESSAGE_MAP(CMultiSelTriCheckTreeCtrl, CMultiSelTriCheckTreeCtrlBase)
	ON_WM_PAINT()
	ON_WM_MOUSEMOVE()
	ON_WM_LBUTTONDOWN()
	ON_WM_LBUTTONUP()
	ON_WM_RBUTTONDOWN()
	ON_WM_RBUTTONUP()
	ON_WM_SETFOCUS()
	ON_WM_KILLFOCUS()
	ON_WM_KEYDOWN()
	ON_WM_CANCELMODE()
END_MESSAGE_MAP()

#ifdef _DEBUG
	//#define DEBUG_SELECTION_UPDATE_BOX
#endif // _DEBUG

void CMultiSelTriCheckTreeCtrl::OnPaint()
{
	CRect rcUpdate;
	if ( GetUpdateRect(&rcUpdate, FALSE) && !rcUpdate.IsRectEmpty() )
	{
		// double buffered drawing.
		CPaintDC dc(this);
		
		//CRect rcPaint(rcItem);
		CRect rcPaint(dc.m_ps.rcPaint);
		CSimpleMemDC memDCBk(dc, rcPaint);
		DefWindowProc(WM_PRINT, (WPARAM)memDCBk.GetDC().m_hDC, (LPARAM)(PRF_CLIENT|PRF_CHECKVISIBLE));
		
		if ( IsMultiSelectable() && m_bDuringDragSel )
		{
			CRect rcSelUpdate, rcSel(m_rcSelBox);
			rcSel.NormalizeRect();
			rcPaint.NormalizeRect();
			rcSelUpdate.IntersectRect(rcSel, rcPaint);
			if ( !rcSelUpdate.IsRectEmpty() )
			{
				CMemBitmap bmpFill(&dc);
				if ( bmpFill.BeginDraw(rcSelUpdate.Size()) )
				{
					CDC& dcSel = bmpFill.GetDC();
					CDC& dcBack = memDCBk.GetDC();
					CRect rcFill;
					rcFill.left		= 0;
					rcFill.top		= 0;
					rcFill.right	= rcSel.Width();
					rcFill.bottom	= rcSel.Height();
					
					dcSel.FillSolidRect(rcFill, m_clrSelFill);
					
					BLENDFUNCTION bf;
					bf.BlendFlags			= 0;
					bf.AlphaFormat			= 0;
					bf.BlendOp				= AC_SRC_OVER;
					bf.SourceConstantAlpha	= ALPHA_SELECTION_FILL;
					::AlphaBlend(dcBack, rcSelUpdate.left, rcSelUpdate.top, rcSelUpdate.Width(), rcSelUpdate.Height(), 
						dcSel, 0, 0, rcSelUpdate.Width(), rcSelUpdate.Height(), bf);
					
					CBrush brush(m_clrSelBorder);
					dcBack.FrameRect(rcSel, &brush);

#ifdef DEBUG_SELECTION_UPDATE_BOX
					CString strText;
					strText.Format(_T("%d %d  %d %d  - %d %d  %d %d  - %d %d  %d %d [%u]"), 
						m_rcSelBox.left, m_rcSelBox.top, m_rcSelBox.right, m_rcSelBox.bottom,
						rcSelUpdate.left, rcSelUpdate.top, rcSelUpdate.right, rcSelUpdate.bottom,
						dc.m_ps.rcPaint.left, dc.m_ps.rcPaint.top, dc.m_ps.rcPaint.right, dc.m_ps.rcPaint.bottom,
						//rcUpdate.left, rcUpdate.top, rcUpdate.right, rcUpdate.bottom,
						GetTickCount());
					GetParent()->SetWindowText(strText);
#endif // DEBUG_SELECTION_UPDATE_BOX
				}
			}
		}
	}
}

void CMultiSelTriCheckTreeCtrl::OnLButtonDown( UINT nFlags, CPoint point )
{
	if ( IsMultiSelectable() )
	{
		if ( OnButtonDown(TRUE, nFlags, point) )
			return;	// has been properly handled with this message.
	}
	CMultiSelTriCheckTreeCtrlBase::OnLButtonDown(nFlags, point);
}

void CMultiSelTriCheckTreeCtrl::OnRButtonDown( UINT nFlags, CPoint point )
{
	if ( IsMultiSelectable() )
	{
		if ( OnButtonDown(FALSE, nFlags, point) )
			return;	// has been properly handled with this message.
	}
	CMultiSelTriCheckTreeCtrlBase::OnRButtonDown(nFlags, point);
}

#define TVHT_CLICKABLE		(TVHT_ONITEMBUTTON|TVHT_ONITEM)

BOOL CMultiSelTriCheckTreeCtrl::OnButtonDown( BOOL bLeft, UINT nFlags, CPoint point )
{
	ASSERT( IsMultiSelectable() );

	if (m_bPendingDragSel)
	{
		// The user is trying to hold down both the two mouse buttons at the same time...
		ASSERT( !m_bLeftBtnDragSel ^ !bLeft );
		if ( HasCapture() )
			::ReleaseCapture();
		if ( !m_bDuringDragSel && !HasCtrlOrShiftFlag() )
			SelectAll(FALSE);
		OnEndDragSelection();
		return FALSE;
	}

	ASSERT( !HasCapture() );
	
	UINT uHitTestFlags = 0;
	HTREEITEM hItem = HitTest(point, &uHitTestFlags);
	
	if ( NULL == hItem || !(uHitTestFlags & TVHT_CLICKABLE) )
	{
		ASSERT( m_itemsInSelBox.empty() );
		// set the top left point as the anchor point.
		m_rcSelBox.left		= point.x;
		m_rcSelBox.top		= point.y;
		m_bPendingDragSel	= TRUE;
		m_bLeftBtnDragSel	= bLeft;

		if (::GetFocus() != m_hWnd)
			::SetFocus(m_hWnd);
		::SetCapture(m_hWnd);
		return TRUE;	// We have done with this message
	}
	else
	{
		ASSERT( hItem );
		if ( !(uHitTestFlags & (TVHT_ONITEMBUTTON|TVHT_ONITEMSTATEICON)) )
		{
			DoPreSelection(hItem, bLeft, nFlags);
			//DoAction(hItem, bLeft, nFlags, point);
			return TRUE;
		}
	}
	return FALSE;
}

void CMultiSelTriCheckTreeCtrl::OnLButtonUp( UINT nFlags, CPoint point )
{
	if ( !OnButtonUp(TRUE, nFlags, point) )
		CMultiSelTriCheckTreeCtrlBase::OnLButtonUp(nFlags, point);
}

void CMultiSelTriCheckTreeCtrl::OnRButtonUp( UINT nFlags, CPoint point )
{
	if ( !OnButtonUp(FALSE, nFlags, point) )
		CMultiSelTriCheckTreeCtrlBase::OnRButtonUp(nFlags, point);
}

BOOL CMultiSelTriCheckTreeCtrl::OnButtonUp( BOOL bLeft, UINT nFlags, CPoint point )
{
	UINT uHitTestFlags = 0;
	HTREEITEM hItem = HitTest(point, &uHitTestFlags);
	if ( m_bPendingDragSel )
	{
		if ( HasCapture() )
			::ReleaseCapture();
		if (hItem)
		{
			NMTREEVIEW nmtv;
			nmtv.hdr.hwndFrom = m_hWnd;
			nmtv.hdr.idFrom = ::GetDlgCtrlID(m_hWnd);
			nmtv.itemNew.mask = TVIF_HANDLE|TVIF_PARAM;
			nmtv.itemNew.hItem = hItem;
			nmtv.itemNew.lParam = GetItemData(hItem);
			nmtv.hdr.code = m_bLeftBtnDragSel ? NM_CLICK : NM_RCLICK;
			_SendNotify(&nmtv.hdr);
		}
	}
	if ( !m_bDuringDragSel && bLeft && hItem )
	{
		BOOL bInItemSelRect = PtInItemSelectRect(hItem, point);
		if ( !HasCtrlOrShiftFlag() )
		{
			SelectAllIgnore(FALSE, hItem);
			//SelectItem(hItem);
			if ( bInItemSelRect )
			{
				SetItemState(hItem, TVIS_FOCUSED|TVIS_SELECTED, TVIS_FOCUSED|TVIS_SELECTED);
				m_hSelectAnchor = hItem;
			}
		}
		else
		{
			if ( bInItemSelRect )
			{
				if ( HasShiftFlag() )
				{
					if ( !(uHitTestFlags & TVHT_CLICKABLE) )
					{
						if (!m_hSelectAnchor)
							m_hSelectAnchor = GetSelectedItem();	//focus
						/*
						#ifdef _DEBUG
						CString strAnchorText = GetItemText(m_hSelectAnchor);
						CString strTargetText = GetItemText(hItem);
						TRACE("\t====== Selection from [%s] to [%s]\n", strAnchorText, strTargetText);
						#endif // _DEBUG
						*/
						SelectRange(m_hSelectAnchor, hItem, !HasCtrlFlag());
						//SelectItem(hItem);
						SetItemState(hItem, TVIS_FOCUSED, TVIS_FOCUSED);	//focus changes to last clicked

						if ( m_hSelectAnchor != hItem )
							SetItemState(m_hSelectAnchor, TVIS_SELECTED, TVIS_SELECTED);
					}
				}
				else
				{
					UINT nState = TVIS_SELECTED;
					if ( HasCtrlFlag() )
						nState ^= (GetItemState(hItem, TVIS_SELECTED) & TVIS_SELECTED);
					else
						SelectAllIgnore(FALSE, hItem);

					HTREEITEM hSelItem = GetSelectedItem();

					SetItemState(hItem, TVIS_FOCUSED|nState, TVIS_FOCUSED|TVIS_SELECTED);
					m_hSelectAnchor = hItem;

// 					if ( hSelItem && hSelItem != hItem )
// 						SetItemState( hSelItem, TVIS_SELECTED, TVIS_SELECTED);
				}
			}
		}
	}

	if ( m_bPendingDragSel )
	{
		OnEndDragSelection();
	}
	if ( ::GetFocus() != m_hWnd )
		::SetFocus(m_hWnd);
	return TRUE;
}

void CMultiSelTriCheckTreeCtrl::OnMouseMove( UINT nFlags, CPoint point )
{
	if ( IsMultiSelectable() && m_bPendingDragSel )
	{
		if ( !HasCapture() )
			::SetCapture(m_hWnd);
		m_bDuringDragSel = TRUE;
		
		CRect rcClient;
		GetClientRect(rcClient);

		CPoint ptScroll;
		SCROLLINFO si = {0};
		GetScrollInfo(SB_HORZ, &si, SIF_POS);
		ptScroll.x = si.nPos;
		GetScrollInfo(SB_VERT, &si, SIF_POS);
		ptScroll.y = si.nPos;

		// Do the scrolling
		if (point.y < rcClient.top)
			SendMessage(WM_VSCROLL, SB_LINEUP);
		else if (point.y >= rcClient.bottom)
			SendMessage(WM_VSCROLL, SB_LINEDOWN);
		if (point.x < rcClient.left)
			SendMessage(WM_HSCROLL, SB_LINELEFT);
		else if (point.x >= rcClient.right)
			SendMessage(WM_HSCROLL, SB_LINERIGHT);

		CPoint ptNewScroll;
		GetScrollInfo(SB_HORZ, &si, SIF_POS);
		ptNewScroll.x = si.nPos;
		GetScrollInfo(SB_VERT, &si, SIF_POS);
		ptNewScroll.y = si.nPos;

		if ( ptScroll != ptNewScroll )
		{
			ptScroll -= ptNewScroll;
			UINT nItemHeight = GetItemHeight();
			int nVOffset = ptScroll.y * nItemHeight;
			m_rcSelBox.OffsetRect(ptScroll.x, nVOffset);
		}
		CRect rcOldSelBox	= m_rcSelBox;

		m_rcSelBox.right	= max(rcClient.left, point.x);
		m_rcSelBox.bottom	= max(rcClient.top, point.y);

		m_rcSelBox.right	= min(rcClient.right, m_rcSelBox.right);
		m_rcSelBox.bottom	= min(rcClient.bottom, m_rcSelBox.bottom);

		CRect rcNewSelBox(m_rcSelBox), rcUpdateSelBox;
		rcOldSelBox.NormalizeRect();
		rcNewSelBox.NormalizeRect();

		// Now we are going to redraw the "dirty" area of the selection,
		// so we need to define what a "dirty" area is.
		if ( rcOldSelBox != rcNewSelBox )
		{
			UpdateDragSelection(nFlags);
			// The simplest solution is to calculate the dirty area by combining the two rectangle
			// If we want to minimize the dirty area, we may consider doing some tricky calculation
			// to make sure that only the area that is required to be redraw become dirty.
			rcUpdateSelBox.UnionRect(rcOldSelBox, rcNewSelBox);
			InvalidateRect(rcUpdateSelBox);
			//Invalidate();	// better not call this since it will redraw everything which is not necessary
			UpdateWindow();
		}
		return;	// we have done with this message.
	}

	CMultiSelTriCheckTreeCtrlBase::OnMouseMove(nFlags, point);
}

void CMultiSelTriCheckTreeCtrl::OnSetFocus( CWnd* pOldWnd )
{
	CMultiSelTriCheckTreeCtrlBase::OnSetFocus(pOldWnd);
	if ( IsMultiSelectable() )
	{
		HTREEITEM hItem = GetFirstSelectedItem();
		while (hItem)
		{
			InvalidateItem(hItem, FALSE);
			hItem = GetNextSelectedItem(hItem);
		}
		UpdateWindow();
	}
}

void CMultiSelTriCheckTreeCtrl::OnKillFocus( CWnd* pNewWnd )
{
	CMultiSelTriCheckTreeCtrlBase::OnKillFocus(pNewWnd);
	
	if ( m_bPendingDragSel )
	{
		if ( HasCapture() )
			::ReleaseCapture();
		OnEndDragSelection();
	}

	if ( IsMultiSelectable() )
	{
		HTREEITEM hItem = GetFirstSelectedItem();
		while (hItem)
		{
			InvalidateItem(hItem, FALSE);
			hItem = GetNextSelectedItem(hItem);
		}
		UpdateWindow();
	}
}

void CMultiSelTriCheckTreeCtrl::OnCancelMode()
{
	CMultiSelTriCheckTreeCtrlBase::OnCancelMode();
	if ( m_bPendingDragSel )
	{
		if ( HasCapture() )
			::ReleaseCapture();
		OnEndDragSelection();
	}
}

void CMultiSelTriCheckTreeCtrl::OnEndDragSelection()
{
	if (m_bPendingDragSel)
	{
		//TRACE("---------CMultiSelTriCheckTreeCtrl::OnEndDragSelection\n");
		m_bPendingDragSel = m_bDuringDragSel = FALSE;
		InvalidateRect(m_rcSelBox);
		UpdateWindow();
		m_rcSelBox.SetRectEmpty();

		m_itemsInSelBox.clear();
	}
}

void CMultiSelTriCheckTreeCtrl::DoPreSelection( HTREEITEM hItem, BOOL bLeft, UINT nFlags )
{
	if (::GetFocus() != m_hWnd)
		::SetFocus(m_hWnd);
	if (bLeft)
	{
		//if shift key down, select immediately
		if ( HasShiftFlag() )
		{
			if (!m_hSelectAnchor)
				m_hSelectAnchor = GetSelectedItem();	//focus
			/*
			#ifdef _DEBUG
			CString strAnchorText = GetItemText(m_hSelectAnchor);
			CString strTargetText = GetItemText(hItem);
			TRACE("\t====== Selection from [%s] to [%s]\n", strAnchorText, strTargetText);
			#endif // _DEBUG
			*/
			SelectRange(m_hSelectAnchor, hItem, !HasCtrlFlag());
			//SelectItem(hItem);
			SetItemState(hItem, TVIS_FOCUSED, TVIS_FOCUSED);	//focus changes to last clicked

			if ( m_hSelectAnchor != hItem )
				SetItemState(m_hSelectAnchor, TVIS_SELECTED, TVIS_SELECTED);
		}
		else
		{
			// if ctrl was down, then the selection is delayed until
			// mouse up, otherwise select the one item
			if ( !HasCtrlFlag() )
			{
				if ( !IsSelected(hItem) )
					SelectAllIgnore(FALSE, hItem);
				SetItemState(hItem, TVIS_SELECTED|TVIS_FOCUSED, TVIS_SELECTED|TVIS_FOCUSED);
			}
			//m_hSelectAnchor = NULL;	//reset when a non-shift operation occurs
			m_hSelectAnchor = hItem;
		}
	}
	else
	{
		// right mouse
		if (HasCtrlOrShiftFlag())
		{
			if ( !HasShiftFlag() )
				m_hSelectAnchor = hItem;
		}
		else
		{
			if ( !IsSelected(hItem) )
				SelectAllIgnore(FALSE, hItem);
			SetItemState(hItem, TVIS_SELECTED|TVIS_FOCUSED, TVIS_SELECTED|TVIS_FOCUSED);
		}
	}
}
/*
#ifndef GET_X_LPARAM
	#define GET_X_LPARAM(lParam)    ((int)(short)LOWORD(lParam))
#endif

#ifndef GET_Y_LPARAM
	#define GET_Y_LPARAM(lParam)    ((int)(short)HIWORD(lParam))
#endif

void CMultiSelTriCheckTreeCtrl::DoAction( HTREEITEM hItem, BOOL bLeft, UINT nFlags, CPoint point )
{
	::SetCapture(m_hWnd);
	ASSERT(::GetCapture() == m_hWnd);
	
	MSG msg;
	UINT nDone = 0;
	CPoint pt;
	CSize sizeDrag(::GetSystemMetrics(SM_CXDRAG), ::GetSystemMetrics(SM_CYDRAG));
	
	while (!nDone && ::GetMessage(&msg, NULL, 0, 0))
	{
		if (::GetCapture() != m_hWnd)
			break;
		
		switch (msg.message)
		{
		case WM_MOUSEMOVE:
			pt.x = GET_X_LPARAM(msg.lParam);
			pt.y = GET_Y_LPARAM(msg.lParam);
			if ((abs(pt.x - point.x) > sizeDrag.cx)
				|| ((abs(pt.y - point.y) > sizeDrag.cy)) )
				nDone = 2;
			//because we exit loop, button up will still be dispatched
			// which means WM_CONTEXTMENU will be sent after TVN_BEGINRDRAG
			// - this is the same behaviour as original tree
			break;
			
		case WM_LBUTTONUP:
		case WM_RBUTTONUP:
			nDone = 1;
			break;
			
		default:
			::DispatchMessage(&msg);
			break;
		}
	}
	
	::ReleaseCapture();
	ASSERT(::GetCapture() != m_hWnd);
	
	//construct tree notification info
	NMTREEVIEW nmtv;
	nmtv.hdr.hwndFrom = m_hWnd;
	nmtv.hdr.idFrom = ::GetDlgCtrlID(m_hWnd);
	nmtv.itemNew.mask = TVIF_HANDLE|TVIF_PARAM;
	nmtv.itemNew.hItem = hItem;
	nmtv.itemNew.lParam = GetItemData(hItem);
	DWORD dwStyle = GetStyle();
	
	if (nDone == 1)
	{
		//click
		if (!HasShiftFlag() && bLeft)
		{
			UINT nState = TVIS_SELECTED;
			if ( HasCtrlFlag() )
				nState ^= (GetItemState(hItem, TVIS_SELECTED) & TVIS_SELECTED);
			else
				SelectAllIgnore(FALSE, hItem);
			SetItemState(hItem, TVIS_FOCUSED|nState, TVIS_FOCUSED|TVIS_SELECTED);
		}
		if (::GetFocus() != m_hWnd)
			::SetFocus(m_hWnd);
		nmtv.hdr.code = bLeft ? NM_CLICK : NM_RCLICK;
		_SendNotify(&nmtv.hdr);
	}
	else if (nDone == 2)
	{
		//drag
		SetItemState(hItem, TVIS_FOCUSED|TVIS_SELECTED, TVIS_FOCUSED|TVIS_SELECTED);
		if (!(dwStyle & TVS_DISABLEDRAGDROP))
		{
			nmtv.hdr.code = bLeft ? TVN_BEGINDRAG : TVN_BEGINRDRAG;
			nmtv.ptDrag = point;
			_SendNotify(&nmtv.hdr);
		}
	}
}
*/

BOOL CMultiSelTriCheckTreeCtrl::PreTranslateMessage( MSG* pMsg )
{
	if ( WM_KEYDOWN == pMsg->message )
	{
		if (VK_ESCAPE == pMsg->wParam)
		{
			if (m_bPendingDragSel)
			{
				if ( HasCapture() )
					::ReleaseCapture();
				SelectAll(FALSE);
				OnEndDragSelection();
			}
		}
	}
	return CMultiSelTriCheckTreeCtrlBase::PreTranslateMessage(pMsg);
}

void CMultiSelTriCheckTreeCtrl::OnKeyDown( UINT nChar, UINT nRepCnt, UINT nFlags )
{
	if ( !IsMultiSelectable() )
	{
		CMultiSelTriCheckTreeCtrlBase::OnKeyDown(nChar, nRepCnt, nFlags);
		return;
	}
	
	const BOOL bCtrl	= CNTRL_DOWN;
	const BOOL bShift	= SHIFT_DOWN;
	
	BOOL bDir = FALSE;
	HTREEITEM hSel = NULL;
	switch (nChar)
	{
	case VK_UP:
		bDir = TRUE;
	case VK_DOWN:
		//common
		hSel = GetSelectedItem();
		if (!m_hSelectAnchor)
			m_hSelectAnchor = hSel;
		
		if (!bCtrl && !bShift) {
			m_hSelectAnchor = NULL;	//reset
			SelectAll(FALSE);
		}
		break;
	}
	
	CMultiSelTriCheckTreeCtrlBase::OnKeyDown(nChar, nRepCnt, nFlags);
	if (!hSel || (!bCtrl && !bShift) )
		return;
	
	HTREEITEM hNext = bDir ? GetPrevVisibleItem(hSel) : GetNextVisibleItem(hSel);
	if (!hNext)
		hNext = hSel;
	if (bShift)
		SelectRange(m_hSelectAnchor, hNext, TRUE);
	else if (bCtrl)
		SetItemState(hNext, TVIS_FOCUSED, TVIS_FOCUSED);
}

/*
    Operations on each item

    =============================================================================================================================
    Key Pressed                    |          Inside Selection Box              |            Outside Selection Box
    =============================================================================================================================
         No                        |              Select item                   |                Deselect item
    =============================================================================================================================
                 | In itemlist     |                 NOP                        |  Toggle selection & remove item from itemlist
    Control key  |---------------------------------------------------------------------------------------------------------------
                 | Not in itemlist | Toggle selection & add item into itemlist  |                    NOP
    =============================================================================================================================
                 | In itemlist     |                 NOP                        |  Unselect item & remove item from itemlist
    Shift key    |---------------------------------------------------------------------------------------------------------------
                 | Not in itemlist | Select item & add item into itemlist       |                    NOP
    =============================================================================================================================
*/

void CMultiSelTriCheckTreeCtrl::UpdateDragSelection(UINT nFlags)
{
	CRect rcItem, rcSel(m_rcSelBox);
	
	HTREEITEM hItem = GetRootItem();
	while (hItem)
	{
		rcItem = GetSelectRect(hItem);
		rcItem.NormalizeRect();
		rcSel.NormalizeRect();

		BOOL bInSelBox		= rcItem.IntersectRect(rcItem, rcSel);

		BOOL bSetSelected	= TRUE;
		BOOL bNowSelected	= bInSelBox;

		if ( HasCtrlOrShiftFlag() )
		{
			TreeItemList::iterator itItem = std::find( m_itemsInSelBox.begin(), m_itemsInSelBox.end(), hItem);
			BOOL bInList	= itItem != m_itemsInSelBox.end();
			BOOL bSelected	= IsSelected(hItem);

			if ( bInList ^ bInSelBox )
			{
				if ( HasCtrlFlag() )
					bNowSelected = !bSelected;
				if ( bInSelBox )
					m_itemsInSelBox.push_back(hItem);
				else
					m_itemsInSelBox.erase(itItem);
			}
			else
			{
				bSetSelected = FALSE;
			}
		}

		if (bSetSelected)
		{
			SetItemState(hItem, bNowSelected ? TVIS_SELECTED : 0, TVIS_SELECTED);
		}
		hItem = GetNextVisibleItem(hItem);
	}
}

size_t CMultiSelTriCheckTreeCtrl::GetSelectedCount() const
{
	size_t nCount = 0;
	HTREEITEM hItem = GetFirstSelectedItem();
	while (hItem)
	{
		++nCount;
		hItem = GetNextSelectedItem(hItem);
	}
	return nCount;
}

HTREEITEM CMultiSelTriCheckTreeCtrl::GetFirstSelectedItem() const
{
	HTREEITEM hItem = GetRootItem();
	while (hItem)
	{
		if ( IsSelected(hItem) )
			break;
		hItem = GetNextVisibleItem(hItem);
	}
	ASSERT( !hItem || IsSelected(hItem) );
	return hItem;
}

HTREEITEM CMultiSelTriCheckTreeCtrl::GetNextSelectedItem( HTREEITEM hItem ) const
{
	hItem = GetNextVisibleItem(hItem);
	while (hItem)
	{
		if ( IsSelected(hItem) )
			break;
		hItem = GetNextVisibleItem(hItem);
	}
	return hItem;
}

HTREEITEM CMultiSelTriCheckTreeCtrl::GetPrevSelectedItem( HTREEITEM hItem ) const
{
	hItem = GetPrevVisibleItem(hItem);
	while (hItem)
	{
		if ( IsSelected(hItem) )
			break;
		hItem = GetPrevVisibleItem(hItem);
	}
	return hItem;
}

void CMultiSelTriCheckTreeCtrl::SelectAll( BOOL bSelect /*= TRUE*/ )
{
	UINT nState = bSelect ? TVIS_SELECTED : 0;
	HTREEITEM hItem = GetRootItem();
	while (hItem)
	{
		if ( !IsSelected(hItem) ^ !bSelect )
			SetItemState(hItem, nState, TVIS_SELECTED);
		hItem = GetNextVisibleItem(hItem);
	}
}

void CMultiSelTriCheckTreeCtrl::SelectRange( HTREEITEM hFirst, HTREEITEM hLast, BOOL bOnly /*= TRUE*/ )
{
	//locate (and select) either first or last
	// (so order is arbitrary)
	HTREEITEM hItem = GetRootItem();
	while (hItem)
	{
		if ((hItem == hFirst) || (hItem == hLast))
		{
			if (hFirst != hLast)
			{
				if (!IsSelected(hItem))
					SetItemState(hItem, TVIS_SELECTED, TVIS_SELECTED);
				hItem = GetNextVisibleItem(hItem);
			}
			break;
		}
		
		if (bOnly && IsSelected(hItem))
			SetItemState(hItem, 0, TVIS_SELECTED);
		hItem = GetNextVisibleItem(hItem);
	}
	//select rest of range
	while (hItem)
	{
		if (!IsSelected(hItem))
			SetItemState(hItem, TVIS_SELECTED, TVIS_SELECTED);
		if ((hItem == hFirst) || (hItem == hLast))
		{
			hItem = GetNextVisibleItem(hItem);
			break;
		}
		hItem = GetNextVisibleItem(hItem);
	}
	if (!bOnly)
		return;
	while (hItem)
	{
		if (IsSelected(hItem))
			SetItemState(hItem, 0, TVIS_SELECTED);
		hItem = GetNextVisibleItem(hItem);
	}
}

void CMultiSelTriCheckTreeCtrl::SelectAllIgnore( BOOL bSelect, HTREEITEM hIgnore )
{
	// special case to avoid multiple notifications for
	// the same item
	UINT nState = bSelect ? TVIS_SELECTED : 0;
	HTREEITEM hItem = GetRootItem();
	while (hItem)
	{
		if (hItem != hIgnore)
		{
			if ( !IsSelected(hItem) ^ !bSelect )
				SetItemState(hItem, nState, TVIS_SELECTED);
		}
		hItem = GetNextVisibleItem(hItem);
	}
}

void CMultiSelTriCheckTreeCtrl::SetMultiSelectable( BOOL bMultiSel /*= TRUE*/ )
{
	m_bMultiSel = bMultiSel;
	if ( !m_bMultiSel )
	{
		HTREEITEM hItem = GetSelectedItem();
		if (hItem && !IsSelected(hItem))
			hItem = NULL;
		SelectAllIgnore(FALSE, hItem);
		if (hItem)
			SelectItem(hItem);
	}
}

void CMultiSelTriCheckTreeCtrl::RedrawSelectedItem(BOOL bErase /*= FALSE*/)
{
	if ( IsMultiSelectable() )
	{
		HTREEITEM hItem = GetRootItem();
		while (hItem)
		{
			if ( IsSelected(hItem) )
			{
				InvalidateItem(hItem, FALSE);
			}
			hItem = GetNextVisibleItem(hItem);
		}
	}
	CMultiSelTriCheckTreeCtrlBase::RedrawSelectedItem();
}

void CMultiSelTriCheckTreeCtrl::SetSelectedItemCheckState( TriCheckState tcs )
{
	HTREEITEM hItem = GetFirstSelectedItem();
	while (hItem)
	{
		SetCheckState(hItem, tcs);
		hItem = GetNextSelectedItem(hItem);
	}
}

void CMultiSelTriCheckTreeCtrl::OnClickItemStateIcon( HTREEITEM hItem )
{
	if ( IsMultiSelectable() && IsSelected(hItem) && !CNTRL_DOWN )
	{
		TriCheckState tcs = GetCheckState(hItem);
		if (tcs != TTCS_NONE)
			SetSelectedItemCheckState(TTCS_CHECKED == tcs ? TTCS_UNCHECKED : TTCS_CHECKED);
	}
	else
		CMultiSelTriCheckTreeCtrlBase::OnClickItemStateIcon(hItem);
}

void CMultiSelTriCheckTreeCtrl::ToggleCheckSelectedItem()
{
	if ( IsMultiSelectable() )
	{
		BOOL bHasAnyCheckedItem = FALSE;
		HTREEITEM hItem = GetFirstSelectedItem();
		while (hItem)
		{
			if ( GetCheck(hItem) )
			{
				bHasAnyCheckedItem = TRUE;
				break;
			}
			hItem = GetNextSelectedItem(hItem);
		}

		SetSelectedItemCheckState(bHasAnyCheckedItem ? TTCS_UNCHECKED : TTCS_CHECKED);
	}
	else
		CMultiSelTriCheckTreeCtrlBase::ToggleCheckSelectedItem();
}

void CMultiSelTriCheckTreeCtrl::DeleteSelectedItem()
{
	if ( IsMultiSelectable() )
	{
		// I can not think of a better solution right now, maybe this can be
		// improved later, if I have the time :)
		TreeItemList selItems;
		GetSelectedList(selItems);
		for (TreeItemList::const_iterator iter = selItems.begin(); iter != selItems.end(); ++iter)
		{
			DeleteItem(*iter);
		}
	}
	else
		CMultiSelTriCheckTreeCtrlBase::DeleteSelectedItem();
}

void CMultiSelTriCheckTreeCtrl::GetSelectedList( TreeItemList& selectedList ) const
{
	selectedList.clear();
	HTREEITEM hItem = GetFirstSelectedItem();
	while (hItem)
	{
		selectedList.push_back(hItem);
		hItem = GetNextSelectedItem(hItem);
	}
}

BOOL CMultiSelTriCheckTreeCtrl::SetItemState( HTREEITEM hItem, UINT nState, UINT nStateMask )
{
	ASSERT(hItem);

	if ( !IsMultiSelectable() )
		return CMultiSelTriCheckTreeCtrlBase::SetItemState(hItem, nState, nStateMask);

	HTREEITEM hFocus = GetSelectedItem();		//current focus
	BOOL bWasFocus = (hFocus == hItem);
	BOOL bFocusWasSel = hFocus && IsSelected(hFocus);	//selection state of current focus
	BOOL bWasSel = IsSelected(hItem);		//select state of acting item

	UINT nS = nState & ~TVIS_FOCUSED;
	UINT nSM = nStateMask & ~TVIS_FOCUSED;

	if (nStateMask & TVIS_FOCUSED)
	{
		//wanted to affect focus
		if (nState & TVIS_FOCUSED)
		{
			//wanted to set focus
			if (!bWasFocus && bFocusWasSel)
			{
				//because SelectItem would de-select the current 'real' selection
				// (the one with focus), need to make the tree ctrl think there is
				// no 'real' selection but still keep the the old item selected
				//it has to be done before the SelectItem call because
				// otherwise the TVN_SELCHANGING/ED notification handlers
				// wouldn't be able to get the proper list of selected items
				CMultiSelTriCheckTreeCtrlBase::SelectItem(NULL);	//will cause notify, but can be taken as focus change
				CMultiSelTriCheckTreeCtrlBase::SetItemState(hFocus, TVIS_SELECTED, TVIS_SELECTED);
				UpdateWindow();
			}
			if (!CMultiSelTriCheckTreeCtrlBase::SelectItem(hItem))	//set focus (will consequently select, if not already focused)
				return FALSE;
			if (nStateMask & TVIS_SELECTED)
			{
				//wanted to affect select state
				if (nState & TVIS_SELECTED)
				{
					//wanted to select, already done if wasn't focused
					if (!bWasFocus || bFocusWasSel)
					{
						nS &= ~TVIS_SELECTED;
						nSM &= ~TVIS_SELECTED;
					}
				}
				//else wanted to clear, base call will do that
			}
			else
			{
				//didn't want to affect select state
				if (!bWasSel)
				{
					//it wasn't previously selected, let base clear (correct)
					nS &= ~TVIS_SELECTED;
					nSM |= TVIS_SELECTED;
				}
				//else was already selected, no harm done
			}
		}
		else
		{
			//wanted to clear focus
			if (bWasFocus)
			{
				//it had the focus
				CMultiSelTriCheckTreeCtrlBase::SelectItem(NULL);	//clear focus
				if (!(nStateMask & TVIS_SELECTED))
				{
					//didn't want to affect select state
					if (bWasSel)
					{
						//it was selected, so restore
						ASSERT( !(nS & TVIS_SELECTED) );
						ASSERT( !(nSM & TVIS_SELECTED) );
						//set state here, to avoid double-notify
						CMultiSelTriCheckTreeCtrlBase::SetItemState(hItem, TVIS_SELECTED, TVIS_SELECTED);
						//let base do other states
					}
				}
				else if (nState & TVIS_SELECTED)
				{
					//wanted to select (but clear focus)
					if (bWasSel)
					{
						//if was selected, restore
						CMultiSelTriCheckTreeCtrlBase::SetItemState(hItem, TVIS_SELECTED, TVIS_SELECTED);
					}
					//don't want to notify, default did it
					nS &= ~TVIS_SELECTED;
					nSM &= ~TVIS_SELECTED;
				}
			}
		}
	}

	if (!nSM)
		return TRUE;	//no other states to alter

	if (nSM & TVIS_SELECTED)
	{
		//still need to alter selection state
		NMTREEVIEW nmtv;
		nmtv.hdr.hwndFrom = m_hWnd;
		nmtv.hdr.idFrom = ::GetDlgCtrlID(m_hWnd);
		nmtv.hdr.code = TVN_SELCHANGING;
		nmtv.itemOld.mask = nmtv.itemNew.mask = 0;
		nmtv.itemOld.hItem = nmtv.itemNew.hItem = NULL;
		TVITEM& item = (nS & TVIS_SELECTED) ? nmtv.itemNew : nmtv.itemOld;
		item.mask = TVIF_HANDLE|TVIF_PARAM;
		item.hItem = hItem;
		item.lParam = GetItemData(hItem);

		if (_SendNotify(&nmtv.hdr))
			return FALSE;	//sel-changing stopped
		VERIFY( CMultiSelTriCheckTreeCtrlBase::SetItemState(hItem, nS, nSM) );
		nmtv.hdr.code = TVN_SELCHANGED;
		_SendNotify(&nmtv.hdr);
		nS &= ~TVIS_SELECTED;
		nSM &= ~TVIS_SELECTED;
	}
	if (!nSM)
		return TRUE;
	return CMultiSelTriCheckTreeCtrlBase::SetItemState(hItem, nS, nSM);
}

UINT CMultiSelTriCheckTreeCtrl::GetItemState( HTREEITEM hItem, UINT nStateMask ) const
{
	UINT nState = CMultiSelTriCheckTreeCtrlBase::GetItemState(hItem, nStateMask & ~TVIS_FOCUSED);
	if (nStateMask & TVIS_FOCUSED)
		if (GetSelectedItem() == hItem)
			nState |= TVIS_FOCUSED;
	return nState;
}

/////////////////////////////////////////////////////////////////////////////
// _SendNotify
// - helper to distinguish between default control generated notifications
//   and this classes emulated ones (so can tell if focus or select notify)
BOOL CMultiSelTriCheckTreeCtrl::_SendNotify( LPNMHDR pNMHDR )
{
	ASSERT(::GetParent(m_hWnd));	//never expected this
	
	BOOL b = m_bEmulated;
	m_bEmulated = TRUE;
	BOOL bRes = ::SendMessage(::GetParent(m_hWnd), WM_NOTIFY, (WPARAM)pNMHDR->idFrom, (LPARAM)pNMHDR);
	m_bEmulated = b;
	return bRes;
}

/*----------------------------------------------------------------------------*/
/* CIconWnd
/*----------------------------------------------------------------------------*/
CIconWnd::CIconWnd()
	: m_hIcon(NULL)
{
}

CIconWnd::~CIconWnd()
{
	DestroyIcon();
}

#ifdef HYBRID_ICONEDIT
BEGIN_MESSAGE_MAP(CIconWnd, CStatic)
	ON_WM_PAINT()
	ON_WM_ERASEBKGND()
END_MESSAGE_MAP()

void CIconWnd::OnPaint()
{
	CPaintDC dc( this );
	CSimpleMemDC memDC(dc, this);
	CDC* pDC = &memDC.GetDC();
	if( m_hIcon )
	{
		CRect rect;
		GetClientRect( &rect );

		// Clearing the background
		pDC->FillSolidRect( rect, GetSysColor(COLOR_WINDOW) );

		// Drawing the icon
		DrawIcon(pDC);
	}

}

BOOL CIconWnd::OnEraseBkgnd( CDC* )
{
	return TRUE;
}
#endif // HYBRID_ICONEDIT

void CIconWnd::SetIcon(HICON hIcon, BOOL redraw /*= TRUE*/, BOOL bAutoDestroy /*= TRUE*/)
{
	DestroyIcon();
	
	m_hIcon = hIcon;
	
	m_bAutoDestroyIcon = bAutoDestroy;
	
	if(redraw)
		RedrawWindow();
}

void CIconWnd::SetIcon(UINT id, BOOL redraw /*= TRUE*/)
{
	DestroyIcon();
	
	m_hIcon = (HICON)::LoadImage(
		AfxGetResourceHandle(),
		MAKEINTRESOURCE(id),
		IMAGE_ICON,
		16,
		16,
		LR_DEFAULTCOLOR|LR_LOADTRANSPARENT);
	
	ASSERT(m_hIcon != NULL);
	
	// icon was loaded internally
	m_bAutoDestroyIcon = true;
	
	if(redraw)
		RedrawWindow();
}

void CIconWnd::DrawIcon( CDC* pDC )
{
	if( m_hIcon )
	{
		// Drawing the icon
		int width = GetSystemMetrics( SM_CXSMICON );
		int height = GetSystemMetrics( SM_CYSMICON );
		
		::DrawIconEx( 
			pDC->GetSafeHdc(), 
			1, 
			1,
			m_hIcon, 
			width, 
			height, 
			0, 
			NULL, 
			DI_NORMAL);
	}
}

void CIconWnd::DestroyIcon()
{
	// if icon was loaded internally, destroy it
	if(m_bAutoDestroyIcon && m_hIcon != NULL)
		::DestroyIcon(m_hIcon);
}

BOOL CIconWnd::CreateIcon( CWnd* pParent )
{
	ASSERT(pParent);
	CRect rect;
	pParent->GetClientRect(rect);
	rect.right = GetSystemMetrics( SM_CXSMICON );
	return Create("", WS_CHILD | WS_VISIBLE, rect, pParent, 1);
}

IMPLEMENT_DYNAMIC(CIconEdit, CIconEditBase)

CIconEdit::CIconEdit()
{
}

CIconEdit::~CIconEdit()
{
	
}


BEGIN_MESSAGE_MAP(CIconEdit, CIconEditBase)
	ON_MESSAGE(WM_SETFONT, OnSetFont)
	ON_WM_ERASEBKGND()
	ON_WM_SIZE()
	ON_WM_KEYDOWN()
	ON_WM_SETCURSOR()
END_MESSAGE_MAP()

void CIconEdit::PreSubclassWindow() 
{
	RecalcLayout();
	CIconEditBase::PreSubclassWindow();
}

void CIconEdit::SetIcon(HICON hIcon, BOOL redraw /*= TRUE*/, BOOL bAutoDestroy /*= TRUE*/)
{
	CreateIcon();
	m_iconWnd.SetIcon(hIcon, redraw, bAutoDestroy);

	RecalcLayout();
}

void CIconEdit::SetIcon(UINT id, BOOL redraw /*= TRUE*/)
{
	CreateIcon();
	m_iconWnd.SetIcon(id, redraw);

	RecalcLayout();
}

void CIconEdit::RecalcLayout()
{
	if (m_iconWnd.m_hIcon)
	{
		int width = GetSystemMetrics( SM_CXSMICON );
		DWORD dwMargins = GetMargins();
		SetMargins(width, HIWORD(dwMargins));

		CRect rectEditArea;
		GetClientRect(rectEditArea);
		rectEditArea.left = width + 4;
		SetRect(rectEditArea);
	}
}

void CIconEdit::OnSize(UINT nType, int cx, int cy)
{
	CIconEditBase::OnSize(nType, cx, cy);

	RecalcLayout();
}

LRESULT CIconEdit::OnSetFont(WPARAM wParam, LPARAM lParam)
{
	DefWindowProc(WM_SETFONT, wParam, lParam);

	RecalcLayout();

	return 0;
}

void CIconEdit::DrawIcon( CDC* pDC )
{
	m_iconWnd.DrawIcon(pDC);
}

BOOL CIconEdit::OnEraseBkgnd( CDC* pDC )
{
	DrawIcon(pDC);
	return TRUE;
}

void CIconEdit::OnKeyDown( UINT nChar, UINT nRepCnt, UINT nFlags )
{
	//this will draw the background again
	//so that the button will be drawn if the text exists
	
	InvalidateRect(NULL, FALSE);
	
	CIconEditBase::OnKeyDown(nChar, nRepCnt, nFlags);
}

BOOL CIconEdit::OnSetCursor( CWnd* pWnd, UINT nHitTest, UINT message )
{
	CPoint pntCursor;
	GetCursorPos(&pntCursor);
	ScreenToClient(&pntCursor);
	//if mouse is not in the edit area then
	//show arrow cursor
	CRect rectEditArea;
	GetRect(rectEditArea);
	if (!rectEditArea.PtInRect(pntCursor))
	{
		SetCursor(AfxGetApp()->LoadStandardCursor(MAKEINTRESOURCE(IDC_ARROW)));
		return TRUE;
	}
	
	return CIconEditBase::OnSetCursor(pWnd, nHitTest, message);
}

void CIconEdit::CreateIcon()
{
	if( !m_iconWnd.GetSafeHwnd() )
	{
		m_iconWnd.CreateIcon(this);
	}
}

/*----------------------------------------------------------------------------*/
/* CComboListBox
/*----------------------------------------------------------------------------*/

IMPLEMENT_DYNCREATE(CComboListBox, CComboListBoxBase)

CComboListBox::CComboListBox()
	: m_pComboBox(NULL)
{
	
}

CComboListBox::~CComboListBox()
{
	
}

BEGIN_MESSAGE_MAP(CComboListBox, CComboListBoxBase)
	ON_MESSAGE(WM_CDTOOLTIPCTRL_NOTIFY, OnCustomToolTipNotify)
END_MESSAGE_MAP()

LRESULT CComboListBox::OnCustomToolTipNotify( WPARAM wParam, LPARAM lParam )
{
	if ( IsComboboxList() && GetBuddyComboBox() )
	{
		return GetBuddyComboBox()->SendMessage(WM_CDTOOLTIPCTRL_NOTIFY, wParam, lParam);
	}
	else
		return CComboListBoxBase::OnCustomToolTipNotify(wParam, lParam);
}

/////////////////////////////////////////////////////////////////////////////
// CCustomDrawComboBox

IMPLEMENT_DYNCREATE(CCustomDrawComboBox, CCustomDrawComboBoxBase)

CCustomDrawComboBox::CCustomDrawComboBox()
	: m_hComboEdit(NULL)
	, m_hComboList(NULL)
{
}

CCustomDrawComboBox::~CCustomDrawComboBox()
{
}


BEGIN_MESSAGE_MAP(CCustomDrawComboBox, CCustomDrawComboBoxBase)
	ON_WM_DESTROY()
	ON_WM_CREATE()
	ON_MESSAGE(WM_CDTOOLTIPCTRL_NOTIFY, OnCustomToolTipNotify)
#ifdef SUBCLASS_LISTBOX_EDIT_IN_CTLCOLOR
	ON_WM_CTLCOLOR()
#endif // SUBCLASS_LISTBOX_EDIT_IN_CTLCOLOR
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CCustomDrawComboBox message handlers

void CCustomDrawComboBox::PreSubclassWindow()
{
	CCustomDrawComboBoxBase::PreSubclassWindow();

	_AFX_THREAD_STATE* pThreadState = AfxGetThreadState();
	if (pThreadState->m_pWndInit == NULL)
	{
		OnInitCombo();
	}
}

int CCustomDrawComboBox::OnCreate( LPCREATESTRUCT lpCreateStruct )
{
	if (CCustomDrawComboBoxBase::OnCreate(lpCreateStruct) == -1)
		return -1;
	OnInitCombo();
	return 0;
}

BOOL CCustomDrawComboBox::OnInitCombo()
{
	COMBOBOXINFO cbi = {sizeof(COMBOBOXINFO)};
	if ( ::OGetComboBoxInfo(GetSafeHwnd(), &cbi) )
	{
		m_hComboEdit = cbi.hwndItem;
		m_hComboList = cbi.hwndList;
		
#ifndef SUBCLASS_LISTBOX_EDIT_IN_CTLCOLOR
		GetComboListBox().SubclassWindow(m_hComboList);
		GetComboEdit().SubclassWindow(m_hComboEdit);
#endif // SUBCLASS_LISTBOX_EDIT_IN_CTLCOLOR
		
		//EnableWindowTheme(GetSafeHwnd(), L" ", L" ");
	}

	GetComboListBox().SetBuddyComboBox(this);
	SetExtendedUI(TRUE);	// Pressing the DOWN ARROW key displays the list box
	return TRUE;
}

void CCustomDrawComboBox::OnDestroyCombo()
{
	if ( GetComboEdit().GetSafeHwnd() )
	{
		GetComboEdit().UnsubclassWindow();
	}
	if (GetComboListBox().GetSafeHwnd())
	{
		GetComboListBox().UnsubclassWindow();
	}
}

int CCustomDrawComboBox::GetItemCount()
{
#ifdef USE_HOOK_CHANGE_LISTBOX_STYLE
	return GetComboListBox().m_hWnd ? GetComboListBox().GetItemCount() : 0;
#else
	return GetCount();
#endif // USE_HOOK_CHANGE_LISTBOX_STYLE
}

void CCustomDrawComboBox::SetItemCount( int nCount )
{
	if (!GetComboListBox().m_hWnd)
		return;
#ifdef USE_HOOK_CHANGE_LISTBOX_STYLE
	GetComboListBox().SetItemCount(nCount);

	SetDroppedVisibleItemCount(nCount > MAX_VISIBLE_COMBOLISTITEM_COUNT ? MAX_VISIBLE_COMBOLISTITEM_COUNT : nCount);
#else
	int nCurCount	= GetCount();
	int nExtraCount = nCurCount - nCount;
	if ( nExtraCount > 0 )
	{
		for (int ii = 0; ii < nExtraCount; ++ii)
			DeleteString(0);
	}
	else if ( nExtraCount < 0 )
	{
		for (int ii = 0; ii > nExtraCount; --ii)
			AddString(_T(""));
	}
#endif // USE_HOOK_CHANGE_LISTBOX_STYLE
}


#ifdef USE_HOOK_CHANGE_LISTBOX_STYLE
HHOOK	g_cbtHook = NULL;

LRESULT CALLBACK ComboListCbtCreationHook(int code, WPARAM wParam, LPARAM lParam)
{
	if ( HCBT_CREATEWND == code )
	{
		ASSERT(lParam != NULL);
		LPCREATESTRUCT lpcs = ((LPCBT_CREATEWND)lParam)->lpcs;
		ASSERT(lpcs != NULL);

		// get class name of the window that is being created
		// GetClassName is a more reliable API
		// http://support.microsoft.com/kb/106079/
		TCHAR szClassName[20] = {0};

		HWND hWnd		= reinterpret_cast<HWND>(wParam);
		GetClassName(hWnd, szClassName, 20);

		// Kind of like hacking, the class name of the listbox somehow is not "ListBox" at this moment.
		if ( _tcsicmp(szClassName, _T("ComboLBox")) == 0
			|| _tcsicmp(szClassName, _T("ListBox")) == 0
			)
		{
			lpcs->style &= ~(LBS_HASSTRINGS|LBS_SORT);
			lpcs->style |= LBS_OWNERDRAWFIXED|LBS_NODATA;
			SetWindowLong(hWnd, GWL_STYLE, lpcs->style);
		}
	}
	LRESULT lResult = CallNextHookEx(g_cbtHook, code, wParam, lParam);
	return lResult;
}
#endif // USE_HOOK_CHANGE_LISTBOX_STYLE

BOOL CCustomDrawComboBox::Create( DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID, BOOL bVirtual /*= FALSE*/ )
{
	if (bVirtual)
	{
		dwStyle &= ~CBS_SORT;
		dwStyle |= CBS_OWNERDRAWFIXED|WS_VSCROLL;

		// LBS_NODATA can NOT be set dynamically during runtime, it has to be set BEFORE the listbox
		// is created, so here I use the CBT hook to modify the dwStyle at creation.
	#ifdef USE_HOOK_CHANGE_LISTBOX_STYLE
		ASSERT(NULL == g_cbtHook);
		g_cbtHook = ::SetWindowsHookEx(WH_CBT, ComboListCbtCreationHook, NULL, ::GetCurrentThreadId());
	#endif // USE_HOOK_CHANGE_LISTBOX_STYLE
	}
	
	BOOL bRet = CCustomDrawComboBoxBase::Create(dwStyle, rect, pParentWnd, nID);
	
	if ( bVirtual )
	{
#ifdef USE_HOOK_CHANGE_LISTBOX_STYLE
		::UnhookWindowsHookEx(g_cbtHook);
		g_cbtHook	= NULL;
#endif // USE_HOOK_CHANGE_LISTBOX_STYLE
	}

	return bRet;
}

BOOL CCustomDrawComboBox::CreateFromCtrl( CWnd* pParent, int nID, BOOL bVirtual /*= FALSE*/, DWORD dwStyleAdd /*= 0*/ )
{
	if (!pParent || !pParent->GetSafeHwnd())
		return FALSE;
	CWnd *pCtrl = pParent->GetDlgItem(nID);
	if (!pCtrl)
		return FALSE;

	CFont* pFont = pCtrl->GetFont();
	LOGFONT lf = {0};
	if (pFont)
	{
		pFont->GetLogFont(&lf);
	}

	UINT style = ::GetWindowLong(pCtrl->GetSafeHwnd(), GWL_STYLE);
	CRect controlRect;
	pCtrl->GetWindowRect(controlRect);
	pParent->ScreenToClient(controlRect);
	pCtrl->DestroyWindow();

	BOOL bRet = Create(style | dwStyleAdd, controlRect, pParent, nID, bVirtual);

	m_font.CreateFontIndirect(&lf);
	SetFont(&m_font);
	
	return bRet;
}

void CCustomDrawComboBox::OnDestroy()
{
	//TRACE("CCustomDrawComboBox::OnDestroy()\n");
	OnDestroyCombo();
	CCustomDrawComboBoxBase::OnDestroy();
}

CString CCustomDrawComboBox::GetItemText( UINT nItem )
{
	if (GetComboListBox().GetSafeHwnd())
	{
		return GetComboListBox().GetItemText(nItem);
	}
	return _T("");
}

int CCustomDrawComboBox::GetItemIconIndex( UINT nItem )
{
	if (GetComboListBox().GetSafeHwnd())
	{
		return GetComboListBox().GetItemIconIndex(nItem);
	}
	return -1;
}

void CCustomDrawComboBox::DrawItem( LPDRAWITEMSTRUCT lpDIS )
{
	if (GetComboListBox().GetSafeHwnd())
	{
		GetComboListBox().DrawItem(lpDIS);
	}
}

void CCustomDrawComboBox::MeasureItem( LPMEASUREITEMSTRUCT lpMeasureItemStruct )
{
	if (GetComboListBox().GetSafeHwnd())
	{
		GetComboListBox().MeasureItem(lpMeasureItemStruct);
	}
}

int CCustomDrawComboBox::CompareItem( LPCOMPAREITEMSTRUCT lpCompareItemStruct )
{
	if (GetComboListBox().GetSafeHwnd())
	{
		return GetComboListBox().CompareItem(lpCompareItemStruct);
	}
	return 0;
}

void CCustomDrawComboBox::SetDroppedVisibleItemCount( int nCount )
{
	if (GetComboListBox().GetSafeHwnd())
	{
		SetComboboxDroppedVisibleItemCount(GetSafeHwnd(), nCount);
	}
}

void CCustomDrawComboBox::SetTextOfItem( int nIndex )
{
	if ( nIndex >= 0 )
	{
		CString strText = GetItemText(nIndex);
		SetWindowText(strText);
		GetParent()->PostMessage(WM_COMMAND, MAKEWPARAM(GetDlgCtrlID(), CBN_EDITCHANGE), (LPARAM)GetSafeHwnd());
	}
}

void CCustomDrawComboBox::PreAddToolTipForItem( int nItem )
{
	// nothing to do
}

void CCustomDrawComboBox::PreShowToolTipForItem( int nItem )
{
	// nothing to do
}

void CCustomDrawComboBox::PrePopToolTip()
{
	// nothing to do
}

LRESULT CCustomDrawComboBox::OnCustomToolTipNotify( WPARAM wParam, LPARAM lParam )
{
	switch (wParam)
	{
	case CDTOOLTIP_ONBEFORE_SHOW:
		{
			int nItem = (int)GetCustomDrawToolTips().GetDisplayWParam();
			PreShowToolTipForItem(nItem);
		}
		break;
	case CDTOOLTIP_ONBEFORE_POP:
		PrePopToolTip();
		break;
	}
	return 0;
}

#ifdef SUBCLASS_LISTBOX_EDIT_IN_CTLCOLOR
HBRUSH CCustomDrawComboBox::OnCtlColor( CDC* pDC, CWnd* pWnd, UINT nCtlColor )
{
	if ( CTLCOLOR_EDIT == nCtlColor )
	{
		if ( GetComboEdit().GetSafeHwnd() == NULL )
		{
			ASSERT( pWnd->GetSafeHwnd() == m_hComboEdit );
			GetComboEdit().SubclassWindow(pWnd->GetSafeHwnd());
		}
	}
	else if ( CTLCOLOR_LISTBOX == nCtlColor )
	{
		if ( GetComboListBox().GetSafeHwnd() == NULL )
		{
			ASSERT( pWnd->GetSafeHwnd() == m_hComboList );
			GetComboListBox().SubclassWindow(pWnd->GetSafeHwnd());
		}
	}
	return CCustomDrawComboBoxBase::OnCtlColor(pDC, pWnd, nCtlColor);
}
#endif // SUBCLASS_LISTBOX_EDIT_IN_CTLCOLOR

/*----------------------------------------------------------------------------*/
/* CXEditPrompt
/*----------------------------------------------------------------------------*/
IMPLEMENT_DYNCREATE(CXEditPrompt, CXEditPromptBase)

BEGIN_MESSAGE_MAP(CXEditPrompt, CXEditPromptBase)
	ON_WM_CREATE()
	ON_WM_CTLCOLOR_REFLECT()
	ON_WM_KEYDOWN()
	ON_WM_SETFOCUS()
	ON_WM_KILLFOCUS()
	ON_WM_LBUTTONDOWN()
	ON_WM_MBUTTONDOWN()
	ON_WM_RBUTTONDOWN()
END_MESSAGE_MAP()

///////////////////////////////////////////////////////////////////////////////
// ctor
CXEditPrompt::CXEditPrompt()
	:	m_bShowCueBanner(TRUE)
	, m_strPromptText(_T("<Enter text here>"))
	, m_crPromptColor(RGB(119,121,118))	// RAL 9023 (Pearl dark gray)
										// see http://www.highplains.net/pixelcolor.html
	, m_dwCueBannerAlign(ES_CENTER)
{
	m_crBkColor = GetSysColor(COLOR_WINDOW);
}

///////////////////////////////////////////////////////////////////////////////
// dtor
CXEditPrompt::~CXEditPrompt()
{
	m_brush.DeleteObject();
	m_robrush.DeleteObject();
}

int CXEditPrompt::OnCreate( LPCREATESTRUCT lpCreateStruct )
{
	if (CXEditPromptBase::OnCreate(lpCreateStruct) == -1)
		return -1;
	OnInitEdit();
	return 0;
}

///////////////////////////////////////////////////////////////////////////////
// PreSubclassWindow
void CXEditPrompt::PreSubclassWindow()
{
	CXEditPromptBase::PreSubclassWindow();
	_AFX_THREAD_STATE* pThreadState = AfxGetThreadState();
	if (pThreadState->m_pWndInit == NULL)
	{
		OnInitEdit();
	}
}

BOOL CXEditPrompt::OnInitEdit()
{
	m_brush.CreateSolidBrush(GetSysColor(COLOR_WINDOW));
	
	m_robrush.CreateSolidBrush(GetSysColor(COLOR_BTNFACE));
	EnableCueBanner();
	
	//SetWindowText(m_strPromptText);
	SetSel(-1, 0);		// get rid of standard highlighting
	return TRUE;
}

///////////////////////////////////////////////////////////////////////////////
// Reset
void CXEditPrompt::Reset()
{
	EnableCueBanner();

	SetWindowText(m_strPromptText);
	SetSel(m_strPromptText.GetLength(), m_strPromptText.GetLength());
	RedrawWindow();
}

///////////////////////////////////////////////////////////////////////////////
// SetPromptColor
void CXEditPrompt::SetPromptColor(COLORREF crText)
{
	m_crPromptColor = crText; 
	//if (m_bFirstTime)
	//	RedrawWindow();
}

///////////////////////////////////////////////////////////////////////////////
// SetPromptText
void CXEditPrompt::SetPromptText(LPCTSTR lpszPrompt)
{ 
	m_strPromptText = lpszPrompt;
	if (m_strPromptText.IsEmpty())
	{
		EnableCueBanner(FALSE);
	}
	if (m_bShowCueBanner)
		SetWindowText(m_strPromptText);
}

///////////////////////////////////////////////////////////////////////////////
// OnSetFocus
void CXEditPrompt::OnSetFocus(CWnd* pOldWnd) 
{
	CXEditPromptBase::OnSetFocus(pOldWnd);
	
	if (m_bShowCueBanner)
	{
		// get rid of standard highlighting
		SetSel(m_strPromptText.GetLength(), m_strPromptText.GetLength());
	}
}

///////////////////////////////////////////////////////////////////////////////
// CtlColor
HBRUSH CXEditPrompt::CtlColor(CDC* pDC, UINT /*nCtlColor*/) 
{
	if ( !IsWindowEnabled() )
		return NULL;
	if (m_bShowCueBanner)
	{
		pDC->SetTextColor(m_crPromptColor);
	}
	if ( GetStyle() & ES_READONLY )
	{
		pDC->SetBkColor(GetSysColor(COLOR_BTNFACE));
		return m_robrush;
	}
	pDC->SetBkColor(m_crBkColor);
	return m_brush;		// setting text color will have no effect unless 
						// we return a valid brush
}

///////////////////////////////////////////////////////////////////////////////
// OnKeyDown
void CXEditPrompt::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) 
{
	if (m_bShowCueBanner)
	{
		// key down includes keys like shift and ctrl
		EnableCueBanner(FALSE);
		SetWindowText(_T(""));
	}
	
	CXEditPromptBase::OnKeyDown(nChar, nRepCnt, nFlags);
}

///////////////////////////////////////////////////////////////////////////////
// OnLButtonDown
void CXEditPrompt::OnLButtonDown(UINT nFlags, CPoint point) 
{
	if (m_bShowCueBanner)
	{
		EnableCueBanner(FALSE);
		SetWindowText(_T(""));
	}
	
	CXEditPromptBase::OnLButtonDown(nFlags, point);
}

///////////////////////////////////////////////////////////////////////////////
// OnMButtonDown
void CXEditPrompt::OnMButtonDown(UINT nFlags, CPoint point) 
{
	if (m_bShowCueBanner)
	{
		EnableCueBanner(FALSE);
		SetWindowText(_T(""));
	}
	
	CXEditPromptBase::OnMButtonDown(nFlags, point);
}

///////////////////////////////////////////////////////////////////////////////
// OnRButtonDown
void CXEditPrompt::OnRButtonDown(UINT nFlags, CPoint point) 
{
	if (m_bShowCueBanner)
	{
		EnableCueBanner(FALSE);
		SetWindowText(_T(""));
	}
	
	CXEditPromptBase::OnRButtonDown(nFlags, point);
}

///////////////////////////////////////////////////////////////////////////////
// GetWindowText
int CXEditPrompt::GetWindowText(LPTSTR lpszStringBuf, int nMaxCount) const
{
	if (m_bShowCueBanner)
	{
		if (lpszStringBuf && (nMaxCount > 0))
			lpszStringBuf[0] = _T('\0');
		return 0;
	}

	return CXEditPromptBase::GetWindowText(lpszStringBuf, nMaxCount);
}

///////////////////////////////////////////////////////////////////////////////
// GetWindowText
void CXEditPrompt::GetWindowText(CString& rString) const
{
	if (m_bShowCueBanner)
		rString = _T("");
	else
		CXEditPromptBase::GetWindowText(rString);
}

///////////////////////////////////////////////////////////////////////////////
// SetWindowText
void CXEditPrompt::SetWindowText(LPCTSTR lpszString)
{
	// the control is being initialized, just ignore
	if (m_bShowCueBanner && (lpszString[0] == _T('\0')))
		return;

	// if this is not prompt string, then no need to prompt
	if (lpszString && 
		(m_strPromptText.Compare(lpszString) != 0))
		EnableCueBanner(FALSE);

	CXEditPromptBase::SetWindowText(lpszString);
}

///////////////////////////////////////////////////////////////////////////////
// DefWindowProc
LRESULT CXEditPrompt::DefWindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
	if (message == WM_SETTEXT)
	{
		TCHAR *cp = (TCHAR *) lParam;

		if (!cp)
			return TRUE;

		// the control is being initialized, just ignore
		if (m_bShowCueBanner && (cp[0] == _T('\0')))
			return TRUE;

		// if this is not prompt string, then no need to prompt
		if (m_strPromptText.Compare(cp) != 0)
			EnableCueBanner(FALSE);
	}
	else if (message == WM_GETTEXT)
	{
		if (m_bShowCueBanner)
		{
			TCHAR *cp = (TCHAR *) lParam;

			if (cp && (wParam != 0))
				*cp = _T('\0');

			return 0;
		}
	}
	else if ( WM_GETTEXTLENGTH == message )
	{
		if ( m_bShowCueBanner )
			return 0;
	}

	return CXEditPromptBase::DefWindowProc(message, wParam, lParam);
}

void CXEditPrompt::SetBKColor( COLORREF crBK )
{
	m_crBkColor = crBK;
	RedrawWindow();
}

void CXEditPrompt::OnKillFocus(CWnd* pOldWnd)
{
	CXEditPromptBase::OnKillFocus(pOldWnd);
	if (!m_bShowCueBanner)
	{
		CString strWndText;
		GetWindowText(strWndText);
		if (strWndText.IsEmpty())
		{
			EnableCueBanner();
			SetWindowText(m_strPromptText);
		}
	}
}

int CXEditPrompt::GetWindowTextLength() const
{
	if (m_bShowCueBanner)
		return 0;
	return CXEditPromptBase::GetWindowTextLength();
}

void CXEditPrompt::SetCueBannerAlign( DWORD val )
{
	m_dwCueBannerAlign = val;
	if ( m_bShowCueBanner )
		UpdateStyleForCueBanner();
}

/*----------------------------------------------------------------------------*/
/* CFilterEdit
/*----------------------------------------------------------------------------*/
IMPLEMENT_DYNCREATE(CFilterEdit, CXEditPrompt)

CFilterEdit::CFilterEdit(CWnd* pBuddyWnd /*= NULL*/)
:m_pBuddyWnd(pBuddyWnd)
{
	
}

CFilterEdit::~CFilterEdit()
{
	
}

BOOL CFilterEdit::PreTranslateMessage( MSG* pMsg )
{
	if ( pMsg->message == WM_KEYDOWN )
	{
		switch ( pMsg->wParam )
		{
		case VK_DOWN:
		case VK_UP:
		case VK_PRIOR:
		case VK_NEXT:
			{
				CWnd* pWnd = GetFocus();
				if ( pWnd && pWnd == this && m_pBuddyWnd )
				{
// 					CDialog* pDlg = (CDialog*)GetParent();
// 					ASSERT(m_pBuddyWnd);
// 					SetDialogFocus(pDlg->GetSafeHwnd(), m_pBuddyWnd->GetSafeHwnd());
					m_pBuddyWnd->SetFocus();
					m_pBuddyWnd->SendMessage(WM_KEYDOWN, pMsg->wParam, pMsg->lParam);
					return TRUE;
				}
			}
			break;
		default:
			break;
		}
	}
	return CXEditPrompt::PreTranslateMessage(pMsg);
}

void CFilterEdit::SetBuddyWindow( CWnd* pBuddyWnd )
{
	m_pBuddyWnd = pBuddyWnd;
}

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 Code Project Open License (CPOL)


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

Comments and Discussions