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

CGridCtrl 1.5

Rate me:
Please Sign up or sign in to vote.
3.95/5 (19 votes)
17 Jun 20042 min read 132.5K   6.4K   41  
This article presents a tiny grid control derived from the standard list control
//-------------------------------------------------------------------
//
//  $Id: CGridCtrl.cpp,v 1.12 2004/06/16 23:50:36 miezoo Exp $
//  Author: Marius Negrutiu (18.05.2004 21:13:01)
//  mailto://negrutiu@as.ro
//
//-------------------------------------------------------------------

#include "stdafx.h"
#include "CGridCtrl.h"

IMPLEMENT_DYNAMIC(CGridCtrl, CListCtrl)

#define MiddleInt(i1, i2)	(((i1) + (i2)) / 2)
#define RHEIGHT(pRect)		((pRect)->bottom - (pRect)->top)
#define RWIDTH(pRect)		((pRect)->right - (pRect)->left)
#define DC					mDraw.cd->nmcd.hdc

// ids
#define		IDC_INPLACEEDIT	10				// in-place editor id
#define		ID_TOOLTIP		11				// incremental-search tooltip id
#define		ID_TIMER		1				// incremental search timer id
#define		EN_FINISH		(WM_USER + 1)	// message from in-place editors to the list view
#define		CB_SUICIDE		(WM_USER + 2)	// message sent to inplace combo boxes

// inplace editors status
#define INPLACE_OK			0
#define INPLACE_CANCEL		1	// the inplace editor was canceled (escape pressed)

// inplace editor type
#define INPLACE_EDIT		1
#define INPLACE_COMBO		2

// custom drawing settings
#define COMBO_BTNWIDTH		14
#define CUSTOM_BTNWIDTH		14
#define CHECK_BTNWIDTH		14
#define CHECK_MARKWIDTH		11

// initialize statics
WNDPROC CGridCtrl::mOldChildProc = NULL;
WNDPROC CGridCtrl::mOldComboEditProc = NULL;
WNDPROC CGridCtrl::mOldComboListProc = NULL;
HWND CGridCtrl::mComboLBox = NULL;
bool CGridCtrl::mComboLBoxClicked = false;

#pragma warning(push)
#pragma warning(disable: 4100)

//
//	[static] CGridCtrl::EditProcedure
//
INT_PTR CALLBACK
CGridCtrl::EditBoxProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
{
	switch (msg)
	{
		case WM_GETDLGCODE:
			return DLGC_WANTALLKEYS;

		case WM_CHAR:
		{
			if (wp == VK_ESCAPE)
			{
				// mark	the	editbox	as beeing canceled
				SetWindowLong(hwnd,	GWL_USERDATA, MAKELONG(INPLACE_CANCEL, INPLACE_EDIT));
				goto _kill;
			}

			if (wp == VK_RETURN)
			{
				// mark	the	editbox	as NOT beeing canceled
				SetWindowLong(hwnd,	GWL_USERDATA, MAKELONG(INPLACE_OK, INPLACE_EDIT));

			_kill:
				// inform the parent that it should destroy this editbox
				::PostMessage(::GetParent(hwnd), WM_COMMAND, MAKELONG(IDC_INPLACEEDIT, EN_FINISH), (LPARAM)hwnd);
				return FALSE;
			}
			break;
		}
	}

	return CallWindowProc(CGridCtrl::mOldChildProc, hwnd, msg, wp, lp);
}

//
//	[static] CGridCtrl::ComboBoxProc
//
INT_PTR CALLBACK
CGridCtrl::ComboBoxProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
{
	switch (msg)
	{
		case WM_GETDLGCODE:
			return DLGC_WANTALLKEYS;

		case WM_COMMAND:
			// (asynchronously) finish inplace edit if the user clicked the dropped list
			if (HIWORD(wp) == LBN_SELCHANGE && mComboLBoxClicked)
				::PostMessage(hwnd, EN_FINISH, VK_RETURN, 0);
			break;

		case WM_CHAR:
		case EN_FINISH:
		{
			HWND ComboEx = ::GetParent(hwnd);

			if (wp == VK_ESCAPE)
			{
				// mark	the	combo box as beeing canceled
				::SetWindowLong(ComboEx, GWL_USERDATA, MAKELONG(INPLACE_CANCEL, INPLACE_COMBO));
				goto _kill;
			}

			if (wp == VK_RETURN)
			{
				// mark	the	combo box as NOT beeing canceled
				SetWindowLong(ComboEx, GWL_USERDATA, MAKELONG(INPLACE_OK, INPLACE_COMBO));

			_kill:
				// inform the parent that it should destroy this combo
				::PostMessage(::GetParent(ComboEx), WM_COMMAND, MAKELONG(IDC_INPLACEEDIT, EN_FINISH), (LPARAM)ComboEx);
				return FALSE;
			}
			break;
		}

		case WM_CTLCOLORLISTBOX:
			// subclass the ComboLBox
			if (mComboLBox != (HWND)lp)
			{
				mComboLBox = (HWND)lp;
				mOldComboListProc = (WNDPROC)(UINT64)::SetWindowLong(mComboLBox, GWL_WNDPROC, (LONG)(UINT64)ComboListProc);
			}
			break;

		case CB_SUICIDE:
			// destroy its parent (ComboBoxEx)
			::DestroyWindow(::GetParent(hwnd));
			return TRUE;
	}

	return CallWindowProc(mOldChildProc, hwnd, msg, wp, lp);
}

//
//	[static] CGridCtrl::ComboEditProc
//
INT_PTR CALLBACK
CGridCtrl::ComboEditProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
{
	switch (msg)
	{
		case WM_GETDLGCODE:
			return DLGC_WANTALLKEYS;

		case WM_KEYDOWN:
		{
			switch (wp)
			{
				//case VK_TAB: 
				case VK_ESCAPE:
				case VK_RETURN:
				{
					HWND Combo = ::GetParent(hwnd);
					::PostMessage(Combo, EN_FINISH, wp, 0);
					return 0;
				}
			}
			break;
		}

		case WM_KEYUP:
		case WM_CHAR:
		{
			switch (wp)
			{
				//case VK_TAB:
				case VK_ESCAPE:
				case VK_RETURN:
					return 0;
			}
		}
	}

	// Call the original window procedure for default processing. 
	return CallWindowProc(mOldComboEditProc, hwnd, msg, wp, lp);
}

//
//	[static] CGridCtrl::ComboListProc
//
INT_PTR CALLBACK
CGridCtrl::ComboListProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
{
	switch (msg)
	{
		case WM_LBUTTONDOWN:
		{
			// remember that the user clicked the ComboLBox
			mComboLBoxClicked = true;
			break;
		}

		case WM_DESTROY:
			mComboLBox = NULL;
			mComboLBoxClicked = false;
			break;
	}

	// Call the original window procedure for default processing. 
	return ::CallWindowProc(mOldComboListProc, hwnd, msg, wp, lp);
}

//
//	CGridCtrl::CGridCtrl
//
CGridCtrl::CGridCtrl()
:	mEditLen(DEFAULT_EDIT_LIMIT),
	mBuffer(NULL),
	mBufferLen(0),
	mRow(-1),
	mCol(0),
	mDestroying(false),
	mEditCol(-1),
	mEditRow(-1),
	mIncSearch(true),
	mIncSearchTip(NULL),
	mIncSearchTimer(DEFAULT_TIMER_INCSEARCH)
{
	// alloc string buffer
	SetStringBufferLen(DEFAULT_BUFFER_LENGTH);
}

//
//	CGridCtrl::~CGridCtrl
//
CGridCtrl::~CGridCtrl()
{
	if (mBuffer)
		delete [] mBuffer;
}

//
//	[virtual] CGridCtrl::PreSubclassWindow
//
void CGridCtrl::PreSubclassWindow(void)
{
	// modify control style
	LONG style = GetWindowLong(m_hWnd, GWL_STYLE);
	style &= ~(	LVS_ICON | LVS_SMALLICON | LVS_LIST |	// set to "Report" view
				LVS_EDITLABELS);						// disable the default "edit labels"

	style |=	LVS_REPORT |							// set to "Report" view
				WS_CLIPCHILDREN;						// avoid flickering when resizing columns

	SetWindowLong(m_hWnd, GWL_STYLE, style);

	// modify control extended style
	int ExStyle =	LVS_EX_SUBITEMIMAGES |				// allow subitem icons
//					LVS_EX_GRIDLINES |
					LVS_EX_FULLROWSELECT;

	::SendMessage(m_hWnd, LVM_SETEXTENDEDLISTVIEWSTYLE, ExStyle, ExStyle);

	// cache colors
	CacheVisuals();

	//
	// create the incremental-search tooltip
	// NOTE: it will be automatically destroyed
	//
	mIncSearchTip = CreateWindowEx(	WS_EX_TOPMOST,
									TOOLTIPS_CLASS,
									NULL,
									WS_POPUP | TTS_NOPREFIX,
									CW_USEDEFAULT,
									CW_USEDEFAULT,
									CW_USEDEFAULT,
									CW_USEDEFAULT,
									m_hWnd,
									NULL,
									(HINSTANCE)(UINT64)GetWindowLong(m_hWnd, GWL_HINSTANCE),
									NULL);
	ASSERT(mIncSearchTip);

	// INITIALIZE MEMBERS OF THE TOOLINFO STRUCTURE
	TOOLINFO ti;
	ti.cbSize = sizeof(TOOLINFO);
	ti.uFlags = TTF_TRACK;
	ti.hwnd = m_hWnd;
	ti.hinst = (HINSTANCE)(UINT64)GetWindowLong(m_hWnd, GWL_HINSTANCE);
	ti.uId = ID_TOOLTIP;
	ti.lpszText = NULL;

	// register our listctrl with the tooltip
	::SendMessage(mIncSearchTip, TTM_ADDTOOL, 0, (LPARAM) (LPTOOLINFO)&ti);
}

//
//	CGridCtrl::SetStringBufferLen
//
void CGridCtrl::SetStringBufferLen(UINT16 Len)
{
	if (Len != mBufferLen)
	{
		if (mBuffer)
		{
			delete [] mBuffer;
			mBufferLen = 0;
		}

		mBuffer = new TCHAR[Len];
		ASSERT(mBuffer);
		if (mBuffer)
			mBufferLen = Len;
	}
}

//
//	CGridCtrl::OnLButtonDown
//
void CGridCtrl::OnLButtonDown(UINT nFlags, CPoint point)
{
	int Row, Col;
	RECT rect;

	// destroy any in-place editor
	if (mEditRow != -1)
	{
		SetFocus();
		// do not perform the default action. in case of multiedit the selection
		// must not change, in order to apply the new data
		return;
	}

	// test which cell was clicked
	bool EditorOn = false;
	if (CellHitTest(point, Row, Col, &rect))
		EditorOn = OnCellLBtnDown(Row, Col, &rect, point);

	// perform default action (only if no inplace editors are on - multiedit stuff)
	if (!EditorOn)
		CListCtrl::OnLButtonDown(nFlags, point);
}

//
//	CGridCtrl::OnLClick
//
BOOL CGridCtrl::OnLClick(NMHDR* hdr, LRESULT* res)
{
	// default message handle
	Default();

	int Row, Col;
	RECT rect;

	NMITEMACTIVATE *ia = (NMITEMACTIVATE*)hdr;
	CPoint point(ia->ptAction);

	// test which cell was clicked
	if (CellHitTest(point, Row, Col, &rect))
		OnCellLBtnUp(Row, Col, &rect, point);

	return FALSE;
}

//
//	CGridCtrl::OnLButtonDblClk
//
void CGridCtrl::OnLButtonDblClk(UINT nFlags, CPoint point)
{
	// perform default action
	CListCtrl::OnLButtonDblClk(nFlags, point);

	// test which cell was clicked
	int Row, Col;
	RECT rect;
	if (CellHitTest(point, Row, Col, &rect))
		OnCellDblClick(Row, Col, &rect);
}

//
//	CGridCtrl::OnRButtonDown
//
void CGridCtrl::OnRButtonDown(UINT nFlags, CPoint point)
{
	int Row, Col;
	RECT rect;

	// destroy any in-place editor
	if (mEditRow != -1)
	{
		SetFocus();
		// do not perform the default action. in case of multiedit the selection
		// must not change, in order to apply the new data
		return;
	}

	// test which cell was clicked
	if (CellHitTest(point, Row, Col, &rect))
		OnCellRBtnDown(Row, Col, &rect, point);

	// perform default action
	CListCtrl::OnRButtonDown(nFlags, point);
}

//
//	CGridCtrl::OnRClick
//
BOOL CGridCtrl::OnRClick(NMHDR* hdr, LRESULT* res)
{
	// default message handle
	Default();

	int Row, Col;
	RECT rect;

	NMITEMACTIVATE *ia = (NMITEMACTIVATE*)hdr;
	CPoint point(ia->ptAction);

	// test which cell was clicked
	if (CellHitTest(point, Row, Col, &rect))
		OnCellRBtnUp(Row, Col, &rect, point);

	return FALSE;
}

//
//	CGridCtrl::OnKeyDown
//
void CGridCtrl::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
	switch (nChar)
	{
		case VK_UP:
			SelectCell(mRow - 1, mCol);
			break;

		case VK_DOWN:
			SelectCell(mRow + 1, mCol);
			break;

		case VK_LEFT:
			if (SelectCell(mRow, mCol - 1, true))
				DeselectAll();
			return;

		case VK_RIGHT:
			if (SelectCell(mRow, mCol + 1, true))
				DeselectAll();
			return;

		case VK_HOME:
			if (HIBYTE(GetKeyState(VK_CONTROL)))
			{
				CListCtrl::OnKeyDown(nChar, nRepCnt, nFlags);

				// update focused row
				int OldRow = mRow;
				mRow = GetFocused();

				// redraw old row (for clearing the focus)
				if (OldRow != mRow)
					RedrawItem(OldRow);

				// redraw new row (for painting the focus);
				RedrawItems(mRow, false);
				return;
			}
			else
			{
				// jump to the first cell (in line)
				if (SelectCell(mRow, 0, true))
					DeselectAll();
			}
			return;

		case VK_END:
			if (HIBYTE(GetKeyState(VK_CONTROL)))
			{
				CListCtrl::OnKeyDown(nChar, nRepCnt, nFlags);

				// update focused row
				int OldRow = mRow;
				mRow = GetFocused();

				// redraw old row (for clearing the focus)
				if (OldRow != mRow)
					RedrawItem(OldRow);

				// redraw new row (for painting the focus);
				RedrawItem(mRow, false);

				return;
			}
			else
			{
				// jump to the last cell (in line)
				if (SelectCell(mRow, GetColumnCount() - 1, true))
					DeselectAll();
			}
			return;

		case VK_PRIOR:
		case VK_NEXT:
		{
			CListCtrl::OnKeyDown(nChar, nRepCnt, nFlags);

			// update focused row
			int OldRow = mRow;
			mRow = GetFocused();

			// redraw old row (for clearing the focus)
			if (OldRow != mRow)
				RedrawItem(OldRow);

			// redraw new row (for drawing the focus)
			RedrawItem(mRow, false);

			return;
		}

		case VK_SPACE:
		{
			if (mIncSearch && !mIncSearchString.IsEmpty())
			{
				OnIncrementalSearch((TCHAR)nChar);
				break;
			}

			EditCell(mRow, mCol);
			return;
		}

		case VK_F2:
		{
			EditCell(mRow, mCol);
			return;
		}
	}

	CListCtrl::OnKeyDown(nChar, nRepCnt, nFlags);
}

//
//	CGridCtrl::OnChar
//
void CGridCtrl::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
{
	if (mIncSearch &&
		nChar != VK_SPACE &&
		_istprint(nChar))
	{
		OnIncrementalSearch((TCHAR)nChar);
	}
}

//
//	CGridCtrl::EditCell
//
bool CGridCtrl::EditCell(	IN int Row,
							IN int Col,
							IN const RECT* CellRect OPTIONAL)
{
	// ensure cell visibility
	VERIFY(EnsureVisible(Row, FALSE));
	if (EnsureColVisible(Col) != 0)
		CellRect = NULL;

	// cell rectangle
	RECT rect;
	if (CellRect)
		::CopyRect(&rect, CellRect);
	else
		GetCellRect(Row, Col, rect);

	// make sure the cell rect does not exceed the listview
	ValidateCellRect(&rect);

	// handle each cell type
	CellType type = OnGetCellType(Row, Col);
	switch (type)
	{
		case CELL_REGULAR:
			break;

		case CELL_CHECKBOXON:
		case CELL_CHECKBOXOFF:
		{
			if (GetSelectedCount() == 1)
			{
				// reverse check state
				OnCheckBoxClick(Row, Col);
				return true;
			}
			else
			{
				// reverse check state of all selected cells
				int Index;
				CellType ct;

				POSITION pos = GetFirstSelectedItemPosition();
				while (pos)
				{
					Index = GetNextSelectedItem(pos);

					ct = OnGetCellType(Index, Col);
					if (ct == type)
						OnCheckBoxClick(Index, Col);
				}
				return true;
			}
		}

		case CELL_EDITBOX:
		{
			LVITEM item;
			item.mask = LVIF_IMAGE;
			item.iItem = Row;
			item.iSubItem = Col;
			GetItem(&item);

			// jump over the icon
			if (item.iImage >= 0 && GetImageList(LVSIL_SMALL))
				rect.left += 16;
			return CreateInPlaceEdit(Row, Col, &rect);
		}

		case CELL_COMBOBOX:
			return CreateInPlaceCombo(Row, Col, &rect, false);

		case CELL_EDITCOMBOBOX:
			return CreateInPlaceCombo(Row, Col, &rect, true);

		case CELL_CUSTOMEDITOR:
			OnCustomCellClick(Row, Col);
			return true;
	}
	return false;
}

//
//	[virtual] CGridCtrl::OnCellLBtnDown
//
bool CGridCtrl::OnCellLBtnDown(	IN int Row,
								IN int Col,
								IN const RECT *CellRect,
								IN const CPoint& Point)
{
	// reset the selection if the column was changed
	if (mCol != Col)
	{
		int temp = mCol;
		mCol = Col;
		DeselectAll();
		mCol = temp;
	}

	// select the clicked cell
	if (!SelectCell(Row, Col, mRow == Row) &&
		!HIBYTE(GetKeyState(VK_CONTROL)) &&
		!HIBYTE(GetKeyState(VK_SHIFT)))
	{
		// the cell was already selected. edit it!
		return EditCell(Row, Col, CellRect);
	}

	// no inplace editor was created
	return false;
}

//
//	[virtual] CGridCtrl::OnCellLBtnUp
//
void CGridCtrl::OnCellLBtnUp(	IN int Row,
								IN int Col,
								IN const RECT *CellRect,
								IN const CPoint& Point)
{
	// no action
}

//
//	[virtual] CGridCtrl::OnCellDblClick
//
void CGridCtrl::OnCellDblClick(IN int Row, IN int Col, IN const RECT* CellRect)
{
	// enter edit mode
	EditCell(Row, Col);
}

//
//	[virtual] CGridCtrl::OnCellRBtnDown
//
void CGridCtrl::OnCellRBtnDown(	IN int Row,
								IN int Col,
								IN const RECT *CellRect,
								IN const CPoint& Point)
{
	// select the clicked cell
	if (!HIBYTE(GetKeyState(VK_CONTROL)) && !HIBYTE(GetKeyState(VK_SHIFT)))
	{
		if (Col != mCol)
		{
			DeselectAll();
			SelectCell(Row, Col, true);
		}
		else
			SelectCell(Row, Col);
	}
}

//
//	[virtual] CGridCtrl::OnCellRBtnUp
//
void CGridCtrl::OnCellRBtnUp(	IN int Row,
								IN int Col,
								IN const RECT *CellRect,
								IN const CPoint& Point)
{
	// no action
}

//
//	[virtual] CGridCtrl::OnCustomCellClick
//
void CGridCtrl::OnCustomCellClick(int Row, int Col)
{
	// no action
}

//
//	[virtual] CGridCtrl::OnCheckBoxClick
//
void CGridCtrl::OnCheckBoxClick(int Row, int Col)
{
	// paint the new check state
	RedrawItem(Row);
}

//
//	[virtual] CGridCtrl::OnGetComboBoxItems
//
void CGridCtrl::OnGetComboBoxItems(	IN  int Row,
									IN  int Col,
									OUT bool& HasImages,
									OUT CStringList& Items,
									OUT CArray<int, int>& Images)
{
	// no action
}

//
//	CGridCtrl::OnEndInPlaceEdit
//
bool CGridCtrl::OnEndInPlaceEdit(	IN int Row,
									IN int Col,
									IN bool Canceled,
									IN const TCHAR* Text,
									IN int Image)
{
	if (!Canceled)
	{
		if (GetSelectedCount() == 1)
		{
			// apply text
			SetItemText(Row, Col, Text);

			// apply image
			if (Image != -1 && GetImageList(LVSIL_SMALL))
				SetItem(Row, Col, LVIF_IMAGE, NULL, Image, 0, 0, 0);
		}
		else
		{
			CellType type = OnGetCellType(Row, Col), ct;
			ASSERT(type != CELL_REGULAR);

			int Index;
			POSITION pos = GetFirstSelectedItemPosition();
			while (pos)
			{
				Index = GetNextSelectedItem(pos);

				ct = OnGetCellType(Index, Col);
				if (type == ct)
				{
					// apply text
					SetItemText(Index, Col, Text);

					// apply image
					if (Image != -1 && GetImageList(LVSIL_SMALL))
						SetItem(Index, Col, LVIF_IMAGE, NULL, Image, 0, 0, 0);
				}
			}
		}
	}

	// allow the in-place editor be destroyed
	return true;
}

//
//	[virtual] CGridCtrl::OnCommand
//
BOOL CGridCtrl::OnCommand(IN WPARAM wp, IN LPARAM lp)
{
	// destroy in-place editor
	if (LOWORD(wp) == IDC_INPLACEEDIT)
	{
		unsigned short msg = HIWORD(wp);
		if (msg	== EN_KILLFOCUS	||
			msg	== EN_FINISH ||
			msg == CBN_KILLFOCUS)
		{
			static bool AlreadyValidating = false;

			// avoid reentrance
			if (AlreadyValidating) return TRUE;
			AlreadyValidating = true;

			// get inplace control data
			int Image = -1;
			long UsrData = GetWindowLong((HWND)lp, GWL_USERDATA);
			bool Canceled = (LOWORD(UsrData) == INPLACE_CANCEL);
			bool ComboBox = (HIWORD(UsrData) == INPLACE_COMBO);

			if (ComboBox)
			{
				// get selected index
				int Index = CB_ERR;

				HWND edit = (HWND)::SendMessage((HWND)lp, CBEM_GETEDITCONTROL, 0, 0);
				if (edit)
				{
					int len = (int)::SendMessage(edit, WM_GETTEXTLENGTH, 0, 0) + 1;
					ASSERT(len <= mBufferLen);
					::SendMessage(edit, WM_GETTEXT, len, (LPARAM)mBuffer);

					Index = (int)::SendMessage((HWND)lp, CB_FINDSTRINGEXACT, (WPARAM)-1, (LPARAM)mBuffer);
				}
				else
				{
					Index = (int)::SendMessage((HWND)lp, CB_GETCURSEL, 0, 0);
				}

				// get item text and image index
				COMBOBOXEXITEM item;
				item.mask = CBEIF_TEXT | CBEIF_IMAGE;
				item.iItem = Index;
				item.pszText = mBuffer;
				item.cchTextMax = mBufferLen;
				::SendMessage((HWND)lp, CBEM_GETITEM, 0, (LPARAM)&item);

				Image = (Index != CB_ERR ? item.iImage : -1);
			}
			else
			{
				::GetWindowText((HWND)lp, mBuffer, mBufferLen);
			}

			// trigger the event
			if (OnEndInPlaceEdit(mEditRow, mEditCol, Canceled, mBuffer, Image))
			{
				// kill	the inplace editor
				mEditRow = mEditCol = -1;
				if (ComboBox)
				{
					// we cannot destroy the ComboBoxEx here, because its message
					// processing is pending.
					// instead, we schedule its destruction by posting a
					// 'suicide' message to its ComboBox child (which is
					// subclassed by us).
					// see CGridCtrl::ComboBoxProc for details
					HWND combo = (HWND)::SendMessage((HWND)lp, CBEM_GETCOMBOCONTROL, 0, 0);
					ASSERT(combo);
					VERIFY(::PostMessage(combo, CB_SUICIDE, 0, 0));
				}
				else
				{
					// kill'em all
					::DestroyWindow((HWND)lp);
				}
				SetFocus();
			}
			else
			{
				// do not kill the inplace editor
				::SetFocus((HWND)lp);
			}

			AlreadyValidating = false;
			return TRUE;
		}
	}

	return CListCtrl::OnCommand(wp, lp);
}

//
//	[virtual] CGridCtrl::OnGetCellColors
//
bool CGridCtrl::OnGetCellColors(	IN  int Row,
									IN  int Col,
									OUT COLORREF& TextColor,
									OUT COLORREF& BkColor,
									OUT UINT8& TextStyle) const
{
	// use default colors
	return false;
}

//
//	[virtual] CGridCtrl::OnGetCellType
//
CellType CGridCtrl::OnGetCellType(IN int Row, IN int Col)
{
	// regular cell, by default
	return CELL_REGULAR;
}

//
//	CGridCtrl::OnSetFocus
//
void CGridCtrl::OnSetFocus(CWnd* pOldWnd)
{
	CListCtrl::OnSetFocus(pOldWnd);
	RedrawItem(mRow);
}

//
//	CGridCtrl::OnKillFocus
//
void CGridCtrl::OnKillFocus(CWnd* pNewWnd)
{
	CListCtrl::OnKillFocus(pNewWnd);
	RedrawItem(mRow);
}

//
//	[virtual] CGridCtrl::CreateInPlaceEdit
//
bool CGridCtrl::CreateInPlaceEdit(	IN int Row,
									IN int Col,
									IN const RECT *CellRect)
{
	// create an editbox
	HWND edit =	CreateWindow(	TEXT("EDIT"),
								NULL,				// no window title
								WS_CHILD | WS_VISIBLE |	ES_LEFT	| WS_BORDER	| ES_AUTOHSCROLL,
								CellRect->left,
								CellRect->top,
								RWIDTH(CellRect),
								RHEIGHT(CellRect),
								m_hWnd,				// parent window
								(HMENU)IDC_INPLACEEDIT,	// control ID
								(HINSTANCE)(UINT64)GetWindowLong(m_hWnd, GWL_HINSTANCE),
								NULL);				// pointer not needed

	if (!edit) return false;

	// subclass	editbox
	mOldChildProc = (WNDPROC)(UINT64)SetWindowLong(edit, GWL_WNDPROC, (LONG)(UINT64)EditBoxProc);

	// init	edit window
	::SetWindowLong(edit, GWL_USERDATA, MAKELONG(INPLACE_OK, INPLACE_EDIT));
	::SendMessage(edit, WM_SETFONT, (WPARAM)GetStockObject(DEFAULT_GUI_FONT), FALSE);
	::SetFocus(edit);
	::SendMessage(edit, EM_LIMITTEXT, mEditLen, 0);

	// set txt
	GetItemText(Row, Col, mBuffer, mBufferLen);
	::SendMessage(edit, WM_SETTEXT, 0, (LPARAM)mBuffer);
	::SendMessage(edit, EM_SETSEL, 0, -1);

	// turn on the child editor
	mEditRow = Row, mEditCol = Col;

	return true;
}

//
//	[virtual] CGridCtrl::CreateInPlaceCombo
//
bool CGridCtrl::CreateInPlaceCombo(	IN int Row,
									IN int Col,
									IN const RECT *CellRect,
									IN bool Editable)
{
	// get combo box items
	bool HasImages = false;
	CStringList Items;
	CArray<int, int> Images;
	OnGetComboBoxItems(Row, Col, HasImages, Items, Images);
	ASSERT(!HasImages || (HasImages && (Items.GetCount() == Images.GetSize())));

	// create an editbox
	HWND ComboEx = CreateWindowEx(	0,
									WC_COMBOBOXEX,
									NULL,				// no window title
									WS_CHILD | WS_VISIBLE |	ES_LEFT	| WS_BORDER	| CBS_AUTOHSCROLL | (Editable ? CBS_DROPDOWN : CBS_DROPDOWNLIST),
									CellRect->left,
									CellRect->top - 1,
									0,
									204,				// dropped-down list height
									m_hWnd,				// parent window
									(HMENU)IDC_INPLACEEDIT,	// control ID
									(HINSTANCE)(UINT64)GetWindowLong(m_hWnd, GWL_HINSTANCE),
									NULL);				// pointer not needed

	if (!ComboEx) return false;

	// set the image list
	if (HasImages && GetImageList(LVSIL_SMALL))
		::SendMessage(ComboEx, CBEM_SETIMAGELIST, 0, (LPARAM)GetImageList(LVSIL_SMALL)->m_hImageList);

	// resize the combo box to its final dimensions
	::SendMessage(ComboEx, CB_SETITEMHEIGHT, (WPARAM)-1, RHEIGHT(CellRect) - 4);	// adjust this value if the combo is too small
	::SetWindowPos(	ComboEx,
					NULL,
					CellRect->left, CellRect->top - 1,
					RWIDTH(CellRect), 0,
					SWP_NOACTIVATE);

	// subclass	combo box
	HWND ComboBox = (HWND)::SendMessage(ComboEx, CBEM_GETCOMBOCONTROL, 0, 0);
	mOldChildProc = (WNDPROC)(UINT64)SetWindowLong(ComboBox, GWL_WNDPROC, (LONG)(UINT64)ComboBoxProc);

	// subclass its edit box, too (for intercepting ENTER and ESCAPE)
	HWND edit = (HWND)::SendMessage(ComboEx, CBEM_GETEDITCONTROL, 0, 0);
	if (Editable)
	{
		ASSERT(edit);
		mOldComboEditProc = (WNDPROC)(UINT64)SetWindowLong(edit, GWL_WNDPROC, (LONG)(UINT64)ComboEditProc);
		::SendMessage(edit, EM_LIMITTEXT, mEditLen, 0);
	}

	// init	the combo box
	::SetWindowLong(ComboEx, GWL_USERDATA, MAKELONG(INPLACE_OK, INPLACE_COMBO));
	//::SendMessage(ComboBox, WM_SETFONT, (WPARAM)GetStockObject(DEFAULT_GUI_FONT), FALSE);
	::SetFocus(ComboEx);
	::SendMessage(ComboEx, CB_SETDROPPEDWIDTH, 70, 0);
	
	// populate the combo box
	COMBOBOXEXITEM cbei;
	cbei.mask = CBEIF_TEXT | (HasImages ? CBEIF_IMAGE | CBEIF_SELECTEDIMAGE : 0);

	int i = 0;
	for (POSITION pos = Items.GetHeadPosition(); pos != NULL; i++)
	{
		if (HasImages)
		{
			cbei.iImage = Images[i];
			cbei.iSelectedImage = Images[i];
		}

		cbei.iItem = i;
		cbei.pszText = Items.GetNext(pos).GetBuffer(0);
		::SendMessage(ComboEx, CBEM_INSERTITEM, 0, (LPARAM)&cbei);
	}

	// set initial text/selection
	GetItemText(Row, Col, mBuffer, mBufferLen);
	int index = (int)::SendMessage(ComboEx, CB_FINDSTRINGEXACT, (WPARAM)-1, (LPARAM)mBuffer);
	if (index != CB_ERR)
		::SendMessage(ComboEx, CB_SETCURSEL, index, 0);
	else if (Editable)
	{
		::SendMessage(edit, WM_SETTEXT, 0, (LPARAM)mBuffer);
		::SendMessage(edit, EM_SETSEL, 0, -1);
	}

	// drop it down!
	::SendMessage(ComboEx, CB_SHOWDROPDOWN, TRUE, 0);

	// turn on the child editor
	mEditRow = Row, mEditCol = Col;

	return true;
}

//
//	CGridCtrl::EnsureColVisible
//
int	CGridCtrl::EnsureColVisible(IN int Col)
{
	ASSERT(IsWindow(m_hWnd));
	RECT rect;
	
	// get X of	the	first column
	if (!GetItemRect(0, &rect, LVIR_BOUNDS)) return 0;
	int	x =	rect.left;
	
	// get client rect
	GetClientRect(&rect);

	// get X of	our	column
	for	(int c = 0;	c <	Col; c++)
		x += GetColumnWidth(c);
		
	// get our column's	width
	int	w =	GetColumnWidth(Col);

	// is the column already visible?
	if (x >= rect.left && x	+ w	<= rect.right)
		return 0;

	// ensure visibility
	int	scroll_to_start	= x	- rect.left;
	int	scroll_to_end =	x +	w -	rect.right;
	if (w >	RHEIGHT(&rect) ||						// if the column is	larger than	the	view, always scroll	to its start position
		abs(scroll_to_start) < abs(scroll_to_end))	// optimal scroll (to column start or end position)
	{
		Scroll(CSize(scroll_to_start, 0));
		return scroll_to_start;
	}
	else
	{
		Scroll(CSize(scroll_to_end, 0));
		return scroll_to_end;
	}
}

//
//	CGridCtrl::CellHitTest
//
BOOL CGridCtrl::CellHitTest(IN	const CPoint& Pt,
							OUT	int &Row,
							OUT	int	&Col,
							OUT	RECT *Rect OPTIONAL)
{
	ASSERT(IsWindow(m_hWnd));

	// detect which	item was hit
	int	top	= GetTopIndex();
	int	btm	= min(top + GetCountPerPage(), GetItemCount());
								
	RECT _rect = {0, 0, 0, 0};
	for	(Row = top; Row < btm; Row++)
	{
		GetItemRect(Row, &_rect, LVIR_BOUNDS);
		if ((Pt.y >= _rect.top) && (Pt.y < _rect.bottom))
			break;
	}

	// exit	if no item was hit
	if (Row >= btm) return(FALSE);

	// exit	if no column was hit
	if (Pt.x > _rect.right) return(FALSE);

	// detect which	column was hit
	Col = 0;
	LV_COLUMN lvc;
	lvc.mask = LVCF_WIDTH;
	while (GetColumn(Col, &lvc))
	{
		if (Pt.x < _rect.left + lvc.cx)
		{
			_rect.right	= _rect.left + lvc.cx;
			break;
		}

		_rect.left += lvc.cx, Col++;
	}

	// return cell rectangle
	if (Rect)
		CopyRect(Rect, &_rect);
	
	return(TRUE);
}

//
//	CGridCtrl::ValidateCellRect
//
void CGridCtrl::ValidateCellRect(IN OUT RECT* rect)
{
	ASSERT(rect);
	ASSERT(IsWindow(m_hWnd));

	RECT client, r;
	GetClientRect(&client);
	CopyRect(&r, rect);

	IntersectRect(rect, &client, &r);
}

//
//	CGridCtrl::GetCellRect
//
bool CGridCtrl::GetCellRect(IN  int Row,
							IN  int Col,
							OUT RECT& Rect)
{
	// valid arguments?
	ASSERT(IsWindow(m_hWnd));
	ASSERT(Row >= 0 && Row < GetItemCount() && Col >= 0 && Col < GetColumnCount());

	// treat "LVS_EX_GRIDLINES"
	UINT8 spacing = HasGridLines() ? (UINT8)1 : (UINT8)0;

	// get the whole item rect
	if (!GetItemRect(Row, &Rect, LVIR_BOUNDS))
		return false;

	// set left/right
	Rect.left = Rect.right = 0;
	for (int c = 0; c <= Col; c++)
	{
		Rect.left = Rect.right;
		Rect.right += GetColumnWidth(c);
	}

	Rect.left += spacing;
	Rect.bottom -= spacing;

	::OffsetRect(&Rect, -GetScrollPos(SB_HORZ), 0);

	return true;
}

//
//	CGridCtrl::GetFocused
//
int CGridCtrl::GetFocused() const
{
/*	POSITION pos = GetFirstSelectedItemPosition();
	if (!pos) return -1;

	int Index;
	while (pos)
	{
		Index = GetNextSelectedItem(pos);
		if (GetItemState(Index, LVIS_FOCUSED) != 0)
			return Index;
	}*/
	int n = GetItemCount();
	for (int i = 0; i < n; i++)
		if (GetItemState(i, LVIS_FOCUSED) != 0)
			return i;
	return -1;
}

//
//	CGridCtrl::SelectCell
//
bool CGridCtrl::SelectCell(IN int Row, IN int Col, IN bool DoRedraw)
{
	if (Row == mRow &&
		Col == mCol &&
		GetItemState(Row, LVIS_SELECTED) != 0) return false;
	
	if (Row < 0 || Row >= GetItemCount() ||
		Col < 0 || Col >= GetColumnCount()
		)
		return false;

	// backup current row
	int OldCurRow = mRow;

	// update current cell
	mRow = Row;
	mCol = Col;

	// ensure cell visibility
	VERIFY(EnsureVisible(Row, FALSE));
	EnsureColVisible(Col);

	if (DoRedraw)
	{
		// redraw old cell (for clearing its focus)
		RedrawItem(OldCurRow);

		// redraw current cell (for paining its focus)
		if (Row != OldCurRow)
			RedrawItem(Row, false);
	}

	return true;
}

//
//	CGridCtrl::DeselectAll
//
void CGridCtrl::DeselectAll()
{
	int Index;
	POSITION pos = GetFirstSelectedItemPosition();
	while (pos)
	{
		Index = GetNextSelectedItem(pos);
		if (Index != mRow)
			SetItemState(Index, 0, LVIS_SELECTED);
	}
	SetSelectionMark(mRow);
	SetItemState(mRow, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED);
}

//
//	CGridCtrl::OnCustomDraw
//
BOOL CGridCtrl::OnCustomDraw(IN NMHDR* Header, OUT LRESULT* Result)
{
	static LVITEM		Item;
	static RECT			ItemRect, ClientRect;
	static CImageList*	il = NULL;
	static int			CellOffset, ScrollPos;
	static UINT8		Spacing;
	static int			Row, Col;

	mDraw.cd = reinterpret_cast<NMLVCUSTOMDRAW*>(Header);

	switch(mDraw.cd->nmcd.dwDrawStage)
	{
		case CDDS_PREPAINT:
			// request ITEM notification
			*Result = CDRF_NOTIFYITEMDRAW;

			// cache the small image list
			il = GetImageList(LVSIL_SMALL);

			// treat "LVS_EX_GRIDLINES"
			Spacing = (HasGridLines() ? 1 : 0);

			// prepare LVITEM
			Item.pszText = mBuffer;
			Item.cchTextMax = mBufferLen;
			Item.mask = LVIF_IMAGE | LVIF_TEXT;

			// painting preparations
			GetClientRect(&ClientRect);
			mDraw.CellText = mBuffer;
			mDraw.GridLines = HasGridLines();

			::SetBkMode(DC, TRANSPARENT);
			
			break;

		case CDDS_ITEMPREPAINT:
			// request SUBITEM notification
			*Result = CDRF_NOTIFYSUBITEMDRAW;

			// cache item rect
			GetItemRect(ROW, &ItemRect, LVIR_BOUNDS);

			// init
			CellOffset = 0;
			ScrollPos = GetScrollPos(SB_HORZ);
			Item.iItem = ROW;

			break;

		case CDDS_ITEMPREPAINT | CDDS_SUBITEM:
		{
			// it might happen that the first subitem drawn has a wrong index (>0) (double click on column header to automatically resize)
			// in this case the index will be corrected (windows bug??)
			if (CellOffset == 0 && COL != 0)
				COL = 0;

			mDraw.Icon = -1;
			Row = ROW;
			Col = COL;

			static LVCOLUMN lvc;
			lvc.mask = LVCF_FMT;
			GetColumn(Col, &lvc);
			mDraw.Justify = (UINT8)(lvc.fmt & LVCFMT_JUSTIFYMASK);

			// compute cell rect (not using GetCellRect() for extra performance)
			mDraw.Rect.top = ItemRect.top;
			mDraw.Rect.bottom = ItemRect.bottom;

			mDraw.Rect.left = CellOffset - ScrollPos;
			CellOffset += GetColumnWidth(Col);

			if (Col > 0)
				mDraw.Rect.left += Spacing;
			mDraw.Rect.bottom -= Spacing;
			mDraw.Rect.right = CellOffset - ScrollPos;

			// do not draw the cell if it's not visible
			if (mDraw.Rect.right < ClientRect.left ||
				mDraw.Rect.left > ClientRect.right)
			{
				*Result = CDRF_SKIPDEFAULT;
				break;
			}

			// get cell info
			Item.iSubItem = Col;
			GetItem(&Item);
			mDraw.Type = OnGetCellType(Row, Col);

			// draw the icon
			if (mDraw.Type != CELL_SEPARATOR && il && Item.iImage >= 0)
			{
				mDraw.Icon = Item.iImage;

				// erase icon background
				mDraw.Rect.right = mDraw.Rect.left + 17;
				::FillRect(DC, &mDraw.Rect, mDraw.BrushNormalFill);

				// draw the icon
				ImageList_Draw(	il->m_hImageList,
								Item.iImage,
								DC,
								mDraw.Rect.left,
								mDraw.Rect.top,
								ILD_TRANSPARENT);

				// update remaining cell rect (jump over the icon)
				mDraw.Rect.left += 17;
			}
			mDraw.Rect.right = CellOffset - ScrollPos;

			// draw the label
			if (RWIDTH(&mDraw.Rect) > 1)
			{
				mDraw.Focused = (Row == mRow && Col == mCol && GetFocus() == this);
				mDraw.Selected = (Col == mCol && GetItemState(Row, LVIS_SELECTED) != 0);
				switch (mDraw.Type)
				{
					case CELL_REGULAR:
						DrawRegularCell();
						break;

					case CELL_EDITBOX:
						DrawEditBoxCell();
						break;

					case CELL_COMBOBOX:
					case CELL_EDITCOMBOBOX:
						DrawComboBoxCell();
						break;

					case CELL_CHECKBOXON:
					case CELL_CHECKBOXOFF:
						DrawCheckBoxCell();
						break;

					case CELL_CUSTOMEDITOR:
						DrawCustomCell();
						break;

					case CELL_SEPARATOR:
						DrawSeparatorCell();
						break;
				}

				*Result = CDRF_SKIPDEFAULT;
				break;
			}
		}

		default:
			*Result = CDRF_DODEFAULT;
	}

	return TRUE;
}

//
//	CGridCtrl::GenerateFont
//
void CGridCtrl::GenerateFont(IN UINT8 Style)
{
	// check whether the font is already cached
	if (mDraw.CustomFontStyle == Style && mDraw.CustomFont != NULL)
		return;

	// destroy previous cache font
	if (mDraw.CustomFont)
		::DeleteObject(mDraw.CustomFont);

	// read current font's metrics
	static TEXTMETRIC metrix;
	GetTextMetrics(DC, &metrix);

	static LOGFONT lf;
	::GetTextFace(DC, LF_FACESIZE, lf.lfFaceName);
	//_tcscpy(lf.lfFaceName, TEXT("Tahoma"));
	lf.lfCharSet = metrix.tmCharSet;
	lf.lfClipPrecision = CLIP_DEFAULT_PRECIS;
	lf.lfEscapement = 0;
	lf.lfHeight = metrix.tmHeight;
	lf.lfItalic = (Style & FONT_ITALIC) != 0 ? TRUE : FALSE;
	lf.lfOrientation = 0;
	lf.lfOutPrecision = OUT_DEFAULT_PRECIS;
	lf.lfPitchAndFamily = metrix.tmPitchAndFamily;
	lf.lfQuality = DEFAULT_QUALITY;//PROOF_QUALITY;
	lf.lfStrikeOut = (Style & FONT_STRIKEOUT) != 0 ? TRUE : FALSE;
	lf.lfUnderline = (Style & FONT_UNDERLINE) != 0 ? TRUE : FALSE;
	lf.lfWeight = (Style & FONT_BOLD) != 0 ? FW_BOLD : FW_NORMAL;
	lf.lfWidth = 0;

	mDraw.CustomFont = CreateFontIndirect(&lf);
	ASSERT(mDraw.CustomFont);
	mDraw.CustomFontStyle = Style;
}

//
//	[virtual] CGridCtrl::DrawRegularCell
//
void __fastcall CGridCtrl::DrawRegularCell()
{
	// query for local colors
	static COLORREF clText, clBk, clDefBk;
	static UINT8 TxtStyle;
	static HFONT oldf;
	static HBRUSH brush;
	static bool KillBrush;

	TxtStyle = 0, oldf = NULL, KillBrush = false;

	// prepare default colors
	if (mDraw.Selected)
	{
		brush = mDraw.BrushSelectedFill;
		clDefBk = clBk = mDraw.ColorSelectedFill;
		clText = mDraw.ColorSelText;
	}
	else
	{
		brush = mDraw.BrushNormalFill;
		clDefBk = clBk = mDraw.ColorNormalFill;
		clText = mDraw.ColorText;
	}

	// query for color override
	if (OnGetCellColors(ROW, COL, clText, clBk, TxtStyle))
	{
		// brush
		if (clBk != clDefBk)
		{
			brush = ::CreateSolidBrush(clBk);
			KillBrush = true;
		}

		// font
		if (TxtStyle != 0)
		{
			GenerateFont(TxtStyle);
			oldf = (HFONT)::SelectObject(DC, mDraw.CustomFont);
		}
	}

	// background
	::FillRect(DC, &mDraw.Rect, brush);
	if (KillBrush) ::DeleteObject(brush);

	// text
	::SetTextColor(DC, clText);

	// focus
	if (mDraw.Focused)
	{
		::SetBkColor(DC, clBk);
		DrawFocusRect(DC, &mDraw.Rect);
	}

	// text
	::InflateRect(&mDraw.Rect, -2, 0);
	::DrawText(DC, mDraw.CellText, (int)_tcslen(mDraw.CellText), &mDraw.Rect, DT_SINGLELINE | DT_END_ELLIPSIS | DT_VCENTER | (mDraw.Justify & LVCFMT_RIGHT ? DT_RIGHT : (mDraw.Justify & LVCFMT_CENTER ? DT_CENTER : DT_LEFT)));
	if (oldf) ::SelectObject(DC, oldf);
}

//
//	[virtual] CGridCtrl::DrawEditBoxCell
//
void __fastcall CGridCtrl::DrawEditBoxCell()
{
	if (!mDraw.GridLines)
	{
/*		if (mDraw.Icon == -1)
			mDraw.Rect.left--;
		mDraw.Rect.top--;*/

		mDraw.Rect.bottom--;
		mDraw.Rect.right--;

		static RECT r;
		::SetRect(&r, mDraw.Rect.left, mDraw.Rect.bottom, mDraw.Rect.right, mDraw.Rect.bottom);
		::FrameRect(DC, &r, mDraw.BrushNormalFill);
		::SetRect(&r, mDraw.Rect.right, mDraw.Rect.top - 1, mDraw.Rect.right, mDraw.Rect.bottom + 2);
		::FrameRect(DC, &r, mDraw.BrushNormalFill);
	}

	// frame
	::FrameRect(DC, &mDraw.Rect, mDraw.BrushFrame);
	::InflateRect(&mDraw.Rect, -1, -1);

	// query for local colors
	static COLORREF clText, clBk, clDefBk;
	static UINT8 TxtStyle;
	static HFONT oldf;
	static HBRUSH brush;
	static bool KillBrush;

	TxtStyle = 0, oldf = NULL, KillBrush = false;

	// prepare default colors
	if (mDraw.Selected)
	{
		brush = mDraw.BrushSelectedFill;
		clDefBk = clBk = mDraw.ColorSelectedFill;
		clText = mDraw.ColorSelText;
	}
	else
	{
		brush = mDraw.BrushNormalFill;
		clDefBk = clBk = mDraw.ColorNormalFill;
		clText = mDraw.ColorText;
	}

	// query for color override
	if (OnGetCellColors(ROW, COL, clText, clBk, TxtStyle))
	{
		// brush
		if (clBk != clDefBk)
		{
			brush = ::CreateSolidBrush(clBk);
			KillBrush = true;
		}

		// font
		if (TxtStyle != 0)
		{
			GenerateFont(TxtStyle);
			oldf = (HFONT)::SelectObject(DC, mDraw.CustomFont);
		}
	}

	// background
	::FillRect(DC, &mDraw.Rect, brush);
	if (KillBrush) ::DeleteObject(brush);

	// text
	::SetTextColor(DC, clText);

	// focus
	if (mDraw.Focused)
	{
		::SetBkColor(DC, clBk);
		DrawFocusRect(DC, &mDraw.Rect);
	}

	// text
	::InflateRect(&mDraw.Rect, -2, 0);
	::DrawText(DC, mDraw.CellText, (int)_tcslen(mDraw.CellText), &mDraw.Rect, DT_SINGLELINE | DT_END_ELLIPSIS | DT_VCENTER | (mDraw.Justify & LVCFMT_RIGHT ? DT_RIGHT : (mDraw.Justify & LVCFMT_CENTER ? DT_CENTER : DT_LEFT)));
	if (oldf) ::SelectObject(DC, oldf);
}

//
//	[virtual] CGridCtrl::DrawComboBoxCell
//
void __fastcall CGridCtrl::DrawComboBoxCell()
{
	if (!mDraw.GridLines)
	{
/*		if (mDraw.Icon == -1)
			mDraw.Rect.left--;
		mDraw.Rect.top--;*/

		mDraw.Rect.bottom--;
		mDraw.Rect.right--;

		static RECT r;
		::SetRect(&r, mDraw.Rect.left, mDraw.Rect.bottom, mDraw.Rect.right, mDraw.Rect.bottom);
		::FrameRect(DC, &r, mDraw.BrushNormalFill);
		::SetRect(&r, mDraw.Rect.right, mDraw.Rect.top - 1, mDraw.Rect.right, mDraw.Rect.bottom + 2);
		::FrameRect(DC, &r, mDraw.BrushNormalFill);
	}

	mDraw.Rect.right -= COMBO_BTNWIDTH;

	// combo button
	int MinOffset = mDraw.Rect.right < mDraw.Rect.left ? mDraw.Rect.left - mDraw.Rect.right : 0;
	::BitBlt(	DC,
				mDraw.Rect.right + MinOffset, mDraw.Rect.top,
				COMBO_BTNWIDTH, RHEIGHT(&mDraw.Rect),
				mDraw.ComboDC,
				MinOffset, 0,
				SRCCOPY);

	// is there room for text?
	if (MinOffset > 0) return;

	// frame
	::FrameRect(DC, &mDraw.Rect, mDraw.BrushFrame);
	::InflateRect(&mDraw.Rect, -1, -1);

	// query for local colors
	static COLORREF clText, clBk, clDefBk;
	static UINT8 TxtStyle;
	static HFONT oldf;
	static HBRUSH brush;
	static bool KillBrush;

	TxtStyle = 0, oldf = NULL, KillBrush = false;

	// prepare default colors
	if (mDraw.Selected)
	{
		brush = mDraw.BrushSelectedFill;
		clDefBk = clBk = mDraw.ColorSelectedFill;
		clText = mDraw.ColorSelText;
	}
	else
	{
		brush = mDraw.BrushNormalFill;
		clDefBk = clBk = mDraw.ColorNormalFill;
		clText = mDraw.ColorText;
	}

	// query for color override
	if (OnGetCellColors(ROW, COL, clText, clBk, TxtStyle))
	{
		// brush
		if (clBk != clDefBk)
		{
			brush = ::CreateSolidBrush(clBk);
			KillBrush = true;
		}

		// font
		if (TxtStyle)
		{
			GenerateFont(TxtStyle);
			oldf = (HFONT)::SelectObject(DC, mDraw.CustomFont);
		}
	}

	// background
	::FillRect(DC, &mDraw.Rect, brush);
	if (KillBrush) ::DeleteObject(brush);

	// text
	::SetTextColor(DC, clText);

	// focus
	if (mDraw.Focused)
	{
		::SetBkColor(DC, clBk);
		DrawFocusRect(DC, &mDraw.Rect);
	}

	// text
	::InflateRect(&mDraw.Rect, -2, 0);
	::DrawText(DC, mDraw.CellText, (int)_tcslen(mDraw.CellText), &mDraw.Rect, DT_SINGLELINE | DT_END_ELLIPSIS | DT_VCENTER | (mDraw.Justify & LVCFMT_RIGHT ? DT_RIGHT : (mDraw.Justify & LVCFMT_CENTER ? DT_CENTER : DT_LEFT)));
	if (oldf) ::SelectObject(DC, oldf);
}

//
//	[virtual] CGridCtrl::DrawCheckBoxCell
//
void __fastcall CGridCtrl::DrawCheckBoxCell()
{
	// check box mark
	::BitBlt(	DC,
				mDraw.Rect.left, mDraw.Rect.top,
				CHECK_BTNWIDTH, RHEIGHT(&mDraw.Rect),
				mDraw.Type == CELL_CHECKBOXON ? mDraw.CheckOnDC : mDraw.CheckOffDC,
				0, 0,
				SRCCOPY);

	mDraw.Rect.left += CHECK_BTNWIDTH;

	// query for local colors
	static COLORREF clText, clBk, clDefBk;
	static UINT8 TxtStyle;
	static HFONT oldf;
	static HBRUSH brush;
	static bool KillBrush;

	TxtStyle = 0, oldf = NULL, KillBrush = false;

	// prepare default colors
	if (mDraw.Selected)
	{
		brush = mDraw.BrushSelectedFill;
		clDefBk = clBk = mDraw.ColorSelectedFill;
		clText = mDraw.ColorSelText;
	}
	else
	{
		brush = mDraw.BrushNormalFill;
		clDefBk = clBk = mDraw.ColorNormalFill;
		clText = mDraw.ColorText;
	}

	// query for color override
	if (OnGetCellColors(ROW, COL, clText, clBk, TxtStyle))
	{
		// brush
		if (clBk != clDefBk)
		{
			brush = ::CreateSolidBrush(clBk);
			KillBrush = true;
		}

		// font
		if (TxtStyle)
		{
			GenerateFont(TxtStyle);
			oldf = (HFONT)::SelectObject(DC, mDraw.CustomFont);
		}
	}

	// background
	::FillRect(DC, &mDraw.Rect, brush);
	if (KillBrush) ::DeleteObject(brush);

	// text
	::SetTextColor(DC, clText);

	// focus
	if (mDraw.Focused)
	{
		::SetBkColor(DC, clBk);
		DrawFocusRect(DC, &mDraw.Rect);
	}

	// text
	::InflateRect(&mDraw.Rect, -2, 0);
	::DrawText(DC, mDraw.CellText, (int)_tcslen(mDraw.CellText), &mDraw.Rect, DT_SINGLELINE | DT_END_ELLIPSIS | DT_VCENTER | (mDraw.Justify & LVCFMT_RIGHT ? DT_RIGHT : (mDraw.Justify & LVCFMT_CENTER ? DT_CENTER : DT_LEFT)));
	if (oldf) ::SelectObject(DC, oldf);
}

//
//	[virtual] CGridCtrl::DrawCustomCell
//
void __fastcall CGridCtrl::DrawCustomCell()
{
	if (!mDraw.GridLines)
	{
/*		if (mDraw.Icon == -1)
			mDraw.Rect.left--;
		mDraw.Rect.top--;*/

		mDraw.Rect.bottom--;
		mDraw.Rect.right--;

		static RECT r;
		::SetRect(&r, mDraw.Rect.left, mDraw.Rect.bottom, mDraw.Rect.right, mDraw.Rect.bottom);
		::FrameRect(DC, &r, mDraw.BrushNormalFill);
		::SetRect(&r, mDraw.Rect.right, mDraw.Rect.top - 1, mDraw.Rect.right, mDraw.Rect.bottom + 2);
		::FrameRect(DC, &r, mDraw.BrushNormalFill);
	}

	mDraw.Rect.right -= CUSTOM_BTNWIDTH;

	// ellipsis button
	int MinOffset = mDraw.Rect.right < mDraw.Rect.left ? mDraw.Rect.left - mDraw.Rect.right : 0;
	::BitBlt(	DC,
				mDraw.Rect.right + MinOffset, mDraw.Rect.top,
				CUSTOM_BTNWIDTH, RHEIGHT(&mDraw.Rect),
				mDraw.CustomDC,
				MinOffset, 0,
				SRCCOPY);

	// is there room for text?
	if (MinOffset > 0) return;

	// frame
	::FrameRect(DC, &mDraw.Rect, mDraw.BrushFrame);
	::InflateRect(&mDraw.Rect, -1, -1);

	// query for local colors
	static COLORREF clText, clBk, clDefBk;
	static UINT8 TxtStyle;
	static HFONT oldf;
	static HBRUSH brush;
	static bool KillBrush;

	TxtStyle = 0, oldf = NULL, KillBrush = false;

	// prepare default colors
	if (mDraw.Selected)
	{
		brush = mDraw.BrushSelectedFill;
		clDefBk = clBk = mDraw.ColorSelectedFill;
		clText = mDraw.ColorSelText;
	}
	else
	{
		brush = mDraw.BrushNormalFill;
		clDefBk = clBk = mDraw.ColorNormalFill;
		clText = mDraw.ColorText;
	}

	// query for color override
	if (OnGetCellColors(ROW, COL, clText, clBk, TxtStyle))
	{
		// brush
		if (clBk != clDefBk)
		{
			brush = ::CreateSolidBrush(clBk);
			KillBrush = true;
		}

		// font
		if (TxtStyle)
		{
			GenerateFont(TxtStyle);
			oldf = (HFONT)::SelectObject(DC, mDraw.CustomFont);
		}
	}

	// background
	::FillRect(DC, &mDraw.Rect, brush);
	if (KillBrush) ::DeleteObject(brush);

	// text
	::SetTextColor(DC, clText);

	// focus
	if (mDraw.Focused)
	{
		::SetBkColor(DC, clBk);
		DrawFocusRect(DC, &mDraw.Rect);
	}

	// text
	::InflateRect(&mDraw.Rect, -2, 0);
	::DrawText(DC, mDraw.CellText, (int)_tcslen(mDraw.CellText), &mDraw.Rect, DT_SINGLELINE | DT_END_ELLIPSIS | DT_VCENTER | (mDraw.Justify & LVCFMT_RIGHT ? DT_RIGHT : (mDraw.Justify & LVCFMT_CENTER ? DT_CENTER : DT_LEFT)));
	if (oldf) ::SelectObject(DC, oldf);
}

//
//	[virtual]
//
void CGridCtrl::DrawSeparatorCell()
{
	COLORREF BkColor, TextColor = mDraw.ColorText;
	UINT8 TextStyle = 0;

	BkColor = mDraw.ColorNormalFill;
	HBRUSH brush = mDraw.BrushNormalFill;
	bool KillBrush = false;

	if (OnGetCellColors(ROW, COL, TextColor, BkColor, TextStyle) &&
		BkColor != mDraw.ColorNormalFill)
	{
		brush = ::CreateSolidBrush(BkColor);
		KillBrush = true;
	}

	::FillRect(DC, &mDraw.Rect, brush);
	if (KillBrush) ::DeleteObject(brush);

	HPEN pen = ::CreatePen(PS_SOLID, 0, TextColor);
	HPEN oldp = (HPEN)::SelectObject(DC, pen);
	int y = MiddleInt(mDraw.Rect.top, mDraw.Rect.bottom);
	::MoveToEx(DC, mDraw.Rect.left, y, NULL);
	::LineTo(DC, mDraw.Rect.right, y);
	::DeleteObject(::SelectObject(DC, oldp));
}

//
//	CGridCtrl::OnEraseBkgnd
//
BOOL CGridCtrl::OnEraseBkgnd(CDC *dc)
{
	static RECT rect, clrect;
	int Items = GetItemCount();

	if (Items > 0)
	{
		GetClientRect(&clrect);

		GetItemRect(GetTopIndex(), &rect, LVIR_BOUNDS);
		if (rect.top > 0)
		{
			rect.bottom = rect.top;
			rect.top = 0;
			rect.left = clrect.left;
			rect.right = clrect.right;

			::FillRect(dc->m_hDC, &rect, mDraw.BrushNormalFill);
		}

		if (GetTopIndex() + GetCountPerPage() >= Items)		// is the last item visible?
		{
			// draw the (whitespace) area NOT covered by items
			GetItemRect(GetItemCount() - 1, &rect, LVIR_BOUNDS);
			int Bottom = rect.bottom;

			GetClientRect(&rect);
			rect.top = Bottom;
			::FillRect(dc->m_hDC, &rect, GetSysColorBrush(COLOR_BTNFACE));
		}
	}
	return TRUE;
}

//
//	CGridCtrl::OnSysColorChange
//
void CGridCtrl::OnSysColorChange()
{
	CListCtrl::OnSysColorChange();
	if (mDraw.UseDefaultColors)
		CacheVisuals();
}

//
//	CGridCtrl::CacheVisuals
//
void CGridCtrl::CacheVisuals()
{
	RECT rect, TempRect;
	CClientDC dc(this);

	// kill previous cached values
	mDraw.KillObjects();

	// bitmap height
	int Height = (GetImageList(LVSIL_SMALL) ? 16 : 13);
	int Width;

	//
	// generate combo box button
	//
	Width = COMBO_BTNWIDTH;
	mDraw.ComboDC = ::CreateCompatibleDC(dc);
	mDraw.ComboBmp = ::CreateCompatibleBitmap(dc, Width, Height);
	mDraw.DefaultStockBmp = (HBITMAP)::SelectObject(mDraw.ComboDC, mDraw.ComboBmp);

	// background
	::SetRect(&rect, 0, 0, Width, Height);
	::FillRect(mDraw.ComboDC, &rect, mDraw.BrushButtonFill);

	// frame
	rect.left--;
	::FrameRect(mDraw.ComboDC, &rect, mDraw.BrushFrame);
	rect.left++;

	// arrow
	TempRect.left = MiddleInt(rect.left, rect.right) - 3;
	TempRect.top = MiddleInt(rect.top, rect.bottom) - 1;
	TempRect.right = TempRect.left + 5;
	TempRect.bottom = TempRect.top + 1;

	for (char i = 0; i < 3; i++)
	{
		::FrameRect(mDraw.ComboDC, &TempRect, mDraw.BrushFrame);
		TempRect.left++, TempRect.top++, TempRect.bottom++, TempRect.right--;
	}

	//
	// generate elipsis button
	//
	Width = CUSTOM_BTNWIDTH;
	mDraw.CustomDC = ::CreateCompatibleDC(dc);
	mDraw.CustomBmp = ::CreateCompatibleBitmap(dc, Width, Height);
	::SelectObject(mDraw.CustomDC, mDraw.CustomBmp);

	// background
	::SetRect(&rect, 0, 0, Width, Height);
	::FillRect(mDraw.CustomDC, &rect, mDraw.BrushButtonFill);

	// frame
	rect.left--;
	::FrameRect(mDraw.CustomDC, &rect, mDraw.BrushFrame);
	rect.left++;

	// ellipsis
	TempRect.left = MiddleInt(rect.left, rect.right) - 5;
	TempRect.top = rect.bottom - 5;
	TempRect.right = TempRect.left + 2;
	TempRect.bottom = TempRect.top + 2;
	for (i = 0; i < 3; i++)
	{
		::FillRect(mDraw.CustomDC, &TempRect, mDraw.BrushFrame);
		::OffsetRect(&TempRect, 3, 0);
	}

	//
	//	generate check box marks
	//
	Width = CHECK_BTNWIDTH;
	::SetRect(&rect, 0, 0, Width, Height);
	rect.bottom++;

	mDraw.CheckOffDC = ::CreateCompatibleDC(dc);
	mDraw.CheckOffBmp = ::CreateCompatibleBitmap(dc, Width, Height + 1);
	::SelectObject(mDraw.CheckOffDC, mDraw.CheckOffBmp);

	mDraw.CheckOnDC = ::CreateCompatibleDC(dc);
	mDraw.CheckOnBmp = ::CreateCompatibleBitmap(dc, Width, Height + 1);
	::SelectObject(mDraw.CheckOnDC, mDraw.CheckOnBmp);

	// background
	::FillRect(mDraw.CheckOffDC, &rect, mDraw.BrushNormalFill);

	// draw button check frame
	TempRect.left = rect.left + (RWIDTH(&rect) - CHECK_MARKWIDTH) / 2;
	TempRect.top = rect.top + (RHEIGHT(&rect) - CHECK_MARKWIDTH) / 2;
	TempRect.right = TempRect.left + CHECK_MARKWIDTH;
	TempRect.bottom = TempRect.top + CHECK_MARKWIDTH;
//	::FillRect(mDraw.CheckOffDC, &TempRect, mDraw.BrushButtonFill);
	::FrameRect(mDraw.CheckOffDC, &TempRect, mDraw.BrushFrame);

	rect.bottom--;

	// draw check mark
	::BitBlt(	mDraw.CheckOnDC,
				0, 0, Width, Height + 1,
				mDraw.CheckOffDC,
				0, 0,
				SRCCOPY);

	HPEN ShadowPen = ::CreatePen(PS_SOLID, 0, mDraw.ColorFrame);
	HPEN oldp = (HPEN)::SelectObject(mDraw.CheckOnDC, ShadowPen);

	POINT pt;
	pt.x = MiddleInt(TempRect.left, TempRect.right) - 3;
	pt.y = MiddleInt(TempRect.top, TempRect.bottom) - 1;

	for (i = 0; i < 3; i++)
	{
		::MoveToEx(mDraw.CheckOnDC, pt.x, pt.y, NULL);
		::LineTo(mDraw.CheckOnDC, pt.x + 2, pt.y + 2);
		::LineTo(mDraw.CheckOnDC, pt.x + 2 + 5, pt.y + 2 - 5);

		pt.y++;
	}

	::SelectObject(mDraw.CheckOnDC, oldp);
	::DeleteObject(ShadowPen);

	// set flag
	mDraw.Initialized = true;
}

//
//	CGridCtrl::OnSetImageList
//
LRESULT	CGridCtrl::OnSetImageList(WPARAM wp, LPARAM lp)
{
	LRESULT ret = Default();

	if (!mDestroying && wp == LVSIL_SMALL)
	{
		// some cached items depend on the image list, so they
		// must be update...
		CacheVisuals();
	}

	return ret;
}

//
//	CGridCtrl::OnDestroy
//
void CGridCtrl::OnDestroy()
{
	// set "Destroying" flag in order to avoid recaching
	mDestroying = true;

	CListCtrl::OnDestroy();
}

//
//	[virtual] CGridCtrl::OnIncrementalSearch
//
void CGridCtrl::OnIncrementalSearch(TCHAR ch)
{
	ASSERT(mIncSearch);
	CString sTip;
	int n;

	// string overflow?
	if (mIncSearchString.GetLength() >= mBufferLen)
	{
		MessageBeep((UINT)-1);
		goto _exit;
	}

	// update incremental string
	mIncSearchString += ch;

	// get window position
	RECT rect;
	GetWindowRect(&rect);

	// update the tooltip
	sTip = mIncSearchString + TEXT("_");
	TOOLINFO ti;
	ti.cbSize = sizeof(ti);
	ti.lpszText = sTip.GetBuffer(0);
	ti.hwnd = m_hWnd;
	ti.uId = ID_TOOLTIP;
	::SendMessage(mIncSearchTip, TTM_UPDATETIPTEXT, 0, (LPARAM)&ti);
	::SendMessage(mIncSearchTip, TTM_TRACKPOSITION, 0, (LPARAM)MAKELONG(rect.left + 5, rect.top + 5));
	::SendMessage(mIncSearchTip, TTM_TRACKACTIVATE, TRUE, (LPARAM)&ti);

	// find the item that matches the key
	LVITEM item;
	item.mask = LVIF_TEXT;
	item.pszText = mBuffer;
	item.cchTextMax = mBufferLen;
	item.iSubItem = mCol;

	n = GetItemCount();
	for (item.iItem = mRow; item.iItem < n; item.iItem++)
	{
		GetItem(&item);
		if (_tcsnicmp(mBuffer, mIncSearchString.GetBuffer(0), mIncSearchString.GetLength()) == 0)
		{
			if (SelectCell(item.iItem, mCol))
				DeselectAll();
			goto _exit;
		}
	}

	// continue searching from the 1st item
	for (item.iItem = 0; item.iItem < mRow; item.iItem++)
	{
		GetItem(&item);
		if (_tcsnicmp(mBuffer, mIncSearchString.GetBuffer(0), mIncSearchString.GetLength()) == 0)
		{
			if (!SelectCell(item.iItem, mCol))
				DeselectAll();
			goto _exit;
		}
	}

_exit:

	SetTimer(ID_TIMER, mIncSearchTimer, NULL);
}

//
//	CGridCtrl::OnTimer
//
void CGridCtrl::OnTimer(UINT_PTR nIDEvent)
{
	if (nIDEvent == ID_TIMER)
	{
		mIncSearchString.Empty();

		// hide the incremental-search tooltip
		TOOLINFO ti;
		ti.cbSize = sizeof(ti);
		ti.hwnd = m_hWnd;
		ti.uId = ID_TOOLTIP;
		::SendMessage(mIncSearchTip, TTM_TRACKACTIVATE, FALSE, (LPARAM)&ti);

		// stop incremental-search timer
		KillTimer(ID_TIMER);

		return;
	}
	CListCtrl::OnTimer(nIDEvent);
}

//
//	CGridCtrl::RedrawItem
//
void CGridCtrl::RedrawItem(	IN int Row,
							IN bool CheckVisibility)
{
	if (CheckVisibility)
	{
		int Top = GetTopIndex();
		if (Row < Top || Row > (Top + GetCountPerPage()))
			return;
	}

	RedrawItems(Row, Row);
}

//
//	MESSAGE_MAP
//
BEGIN_MESSAGE_MAP(CGridCtrl, CListCtrl)
	ON_NOTIFY_REFLECT_EX(NM_CUSTOMDRAW, OnCustomDraw)
	ON_WM_ERASEBKGND()
	ON_WM_LBUTTONDOWN()
	ON_NOTIFY_REFLECT_EX(NM_CLICK, OnLClick)
	ON_WM_LBUTTONDBLCLK()
	ON_WM_RBUTTONDOWN()
	ON_NOTIFY_REFLECT_EX(NM_RCLICK, OnRClick)
	ON_WM_KEYDOWN()
	ON_WM_CHAR()
	ON_WM_SETFOCUS()
	ON_WM_KILLFOCUS()
	ON_WM_SYSCOLORCHANGE()
	ON_MESSAGE(LVM_SETIMAGELIST, OnSetImageList)
	ON_WM_DESTROY()
	ON_WM_TIMER()
END_MESSAGE_MAP()

#pragma warning(pop)

//-------------------------------------------------------------------
//	HISTORY
//-------------------------------------------------------------------
//
//	$Log: CGridCtrl.cpp,v $
//	Revision 1.12  2004/06/16 23:50:36  miezoo
//	- vc6 compilable
//	
//	Revision 1.11  2004/06/16 18:20:58  miezoo
//	- mouse related notifications
//	- new events for button-ups
//	
//	Revision 1.10  2004/06/16 18:04:44  miezoo
//	- selection behavior related
//	
//	Revision 1.9  2004/06/14 08:18:20  miezoo
//	- version [1.5]
//	- bug fix: asynchronous inplace combo box destruction
//	- optimized inplace edit workflow
//	- right-click stuff
//	- inplace combo boxes are dropped down by default
//	- bug fix: CellHitTest corrected
//	- bug fix: drawing very small cells corrected
//	- bug fix: focus rect is correctly painted
//	
//	Revision 1.8  2004/06/09 17:23:28  miezoo
//	- bug fix: initialize string buffer length
//	
//	Revision 1.7  2004/06/09 17:01:32  miezoo
//	- version [1.4]
//	- separator cells
//	- adjustable inplace combo box height
//	
//	Revision 1.6  2004/06/08 22:00:08  miezoo
//	- OnCellClick: repaint the item when the row doesn't change
//	
//	Revision 1.5  2004/06/08 19:48:44  miezoo
//	- version [1.3]
//	- on-demand cell type
//	- colors and font styles
//	- auto cell resize bug fix
//	- USE_DEFAULT_ERASEBKGND vanished
//	
//	Revision 1.4  2004/06/06 23:25:20  miezoo
//	- check-box-mark painting corrected
//	
//	Revision 1.3  2004/06/06 23:14:07  miezoo
//	- column justification is correct handled
//	- incremental search timer
//	
//	Revision 1.2  2004/06/06 12:57:23  miezoo
//	- SetStringBufferLen(...) added
//	- mIncSearch flag added
//	
//	Revision 1.1  2004/06/06 11:49:06  miezoo
//	- Initial revision
//	
//-------------------------------------------------------------------

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

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

License

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

A list of licenses authors might use can be found here


Written By
Web Developer
Romania Romania
- C/C++ Programming
- Windows Device Driver programming (DDK)
- Low-level networking programmer (firewalls, routers, sniffers...)
- AutoCAD ObjectARX/DBX programming
- ADT Object Modeling Framework (OMF) programmer

Comments and Discussions