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

Understanding CDockablePane

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

#include "StdAfx.h"
#include "CustomDrawCommon.h"
#include "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, _T("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;
}

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 Apache License, Version 2.0


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

Comments and Discussions