Click here to Skip to main content
15,884,099 members
Articles / Desktop Programming / MFC

An MFC-CListCtrl derived class that allows other ‘controls’ to be inserted into a particular cell

Rate me:
Please Sign up or sign in to vote.
4.92/5 (54 votes)
5 Jan 2014CPOL12 min read 167.4K   13.9K   160  
A class derived from CListCtrl that allows edit controls, combo boxes, check boxes, date pickers, and color pickers to be inserted into or removed from particular cells extremely easily. The inserted 'controls' are not CWnd-derived.
// ConfigListCtrl.cpp : implementation file
//

#include "stdafx.h"
#include "ConfigListCtrl.h"
#include "CellCtrls\ListCtrlCellWnd.h"
#include "CellCtrls\CellButton.h"

// CConfigListCtrl

IMPLEMENT_DYNAMIC(CConfigListCtrl, CListCtrl)

CConfigListCtrl::CConfigListCtrl() : m_bInit(FALSE), m_bCaretExists(FALSE), m_bTabbedIn(FALSE), m_bKeyUp(FALSE), m_bIsEnabled(TRUE)
{
}

CConfigListCtrl::~CConfigListCtrl()
{
	ForEachCellCtrl(&CConfigListCtrl::DeleteCellCtrl);
}

BEGIN_MESSAGE_MAP(CConfigListCtrl, CListCtrl)
	ON_WM_CHAR()
	ON_WM_HSCROLL()
	ON_WM_KEYDOWN()
	ON_WM_KEYUP()
	ON_WM_KILLFOCUS()
	ON_WM_LBUTTONDOWN()
//	ON_WM_LBUTTONUP()
	ON_WM_MOUSEMOVE()
	ON_WM_MOUSEWHEEL()
	ON_WM_RBUTTONDOWN()
	ON_WM_SETFOCUS()
	ON_WM_VSCROLL()
	ON_MESSAGE(WM_CELL_CTRL_MSG, OnCellCtrlMsg)
	ON_WM_MEASUREITEM_REFLECT()
	ON_WM_THEMECHANGED()
	ON_WM_ENABLE()
	ON_WM_LBUTTONUP()
	ON_WM_TIMER()
END_MESSAGE_MAP()

// CConfigListCtrl message handlers
void CConfigListCtrl::DeleteCellCtrl(DWORD dwLocation, CCellCtrl *pCellCtrl)
{
	delete pCellCtrl;
}

void CConfigListCtrl::OnInactiveResizeColumn(DWORD dwLocation, CCellCtrl *pCellCtrl)
{
	WORD	a,b;
	CRect	rcCtrl;

	a = HIWORD(dwLocation);
	b = LOWORD(dwLocation);
	GetSubItemRect(a, b, LVIR_LABEL, rcCtrl);

	pCellCtrl->OnInactiveResizeColumn(rcCtrl);
}

void CConfigListCtrl::ForEachCellCtrl(OpOnCellCtrl OpOnCtrl)
{
	DWORD				dwLocation;
	CCellCtrl			*pCellCtrl;

	POSITION Pos = m_CtrlMap.GetStartPosition();

	while (Pos)
	{
		m_CtrlMap.GetNextAssoc(Pos, dwLocation, pCellCtrl);
		(this->*OpOnCtrl)(dwLocation, pCellCtrl);		
	}
}

void CConfigListCtrl::DeActivateCell()
{
	m_ActiveCell.m_lItem = m_ActiveCell.m_lSubItem = -1;
	if (m_ActiveCell.m_pCellCtrl)
		m_ActiveCell.m_pCellCtrl->OnKillActive();
	m_ActiveCell.m_pCellCtrl = NULL;
}

void CConfigListCtrl::ShiftItemsRight(int nCol)
{
	if (nCol) // Only required when nCol = 0. Done automatically otherwise
		return;

	int LastCol = GetHeaderCtrl()->GetItemCount() - 1;

	while (nCol < LastCol)
	{
		for (int Row = 0; Row < GetItemCount(); Row++)
			SetItem(Row, LastCol, GetItemText(Row, LastCol - 1));

		LastCol--;
	}

	if (nCol < GetHeaderCtrl()->GetItemCount() - 1)
		for (int Row = 0; Row < GetItemCount(); Row++)
			SetItem(Row, nCol, _T(""));
}

void CConfigListCtrl::RemoveCtrl(int nItem, int nSubItem)
{
	CCellCtrl				*pCellCtrl;

  	if (m_CtrlMap.Lookup(GetMapKey(nItem, nSubItem), pCellCtrl))
	{
		m_CtrlMap.RemoveKey(GetMapKey(nItem, nSubItem));

		if (m_ActiveCell.m_pCellCtrl == pCellCtrl)
			DeActivateCell();

		delete pCellCtrl; 

		m_CtrlSet.Remove(GetMapKey(nItem, nSubItem));
	}
}

void CConfigListCtrl::RemoveCtrlRow(int nItem)
{
	for (int nSubItem = 0; nSubItem < GetHeaderCtrl()->GetItemCount(); nSubItem++)
		RemoveCtrl(nItem, nSubItem);

	IncrCtrlMapRow(nItem, FALSE);
	m_CtrlSet.IncrementHIWORD(nItem, FALSE);

	Invalidate();
}

void CConfigListCtrl::RemoveCtrlColumn(int nSubItem)
{
	for (int nItem = 0; nItem < GetItemCount(); nItem++)
		RemoveCtrl(nItem, nSubItem);

	IncrCtrlMapColumn(nSubItem, FALSE);
	m_CtrlSet.IncrementLOWORD(nSubItem, FALSE);
}

void CConfigListCtrl::RemoveAllCtrls()
{
	ForEachCellCtrl(&CConfigListCtrl::DeleteCellCtrl);
}

void CConfigListCtrl::InsertCtrl(int nItem, int nSubItem, CCellCtrl *pCellCtrl)
{
  	ASSERT(pCellCtrl);

	RemoveCtrl(nItem, nSubItem);
	m_CtrlMap.SetAt(GetMapKey(nItem, nSubItem), pCellCtrl);
	m_CtrlSet.Insert(GetMapKey(nItem, nSubItem));
} 

int CConfigListCtrl::InsertItem(int nItem, CCellCtrl *pCellCtrl, LPCTSTR lpszDefaultTextValue /* = "\0"*/)
{
	InsertBookmark(nItem);
	ASSERT(pCellCtrl);

	InsertCtrl(nItem, 0, pCellCtrl);
	
	pCellCtrl->Initialize(m_hWnd, &m_bIsEnabled, lpszDefaultTextValue);

	return CListCtrl::InsertItem(nItem, _T("\0"));
}

BOOL CConfigListCtrl::SetItem(int nItem, int nSubItem, LPCTSTR lpszItem)
{
	LV_ITEM lvi;
	lvi.mask = LVIF_TEXT | LVIS_SELECTED;
	lvi.iItem = nItem;
	lvi.iSubItem = nSubItem;
	lvi.pszText = (LPTSTR)lpszItem;

	return CListCtrl::SetItem(&lvi);
}

BOOL CConfigListCtrl::SetItem(int nItem, int nSubItem, CCellCtrl *pCellCtrl, LPCTSTR lpszDefaultTextValue /* = "\0"*/)
{
	ASSERT(pCellCtrl);

	InsertCtrl(nItem, nSubItem, pCellCtrl);
	pCellCtrl->Initialize(m_hWnd, &m_bIsEnabled, lpszDefaultTextValue);

	return SetItem(nItem, nSubItem, pCellCtrl->GetSelectedTextValue());
}

DWORD IncrLOWORD(DWORD dwLocation, DWORD dwCompVal, BOOL incr)
{
	if (LOWORD(dwLocation) >= dwCompVal)
		dwLocation = MAKELONG(LOWORD(dwLocation) + ((incr)? 1 : -1), HIWORD(dwLocation));

	return dwLocation;
}

DWORD IncrHIWORD(DWORD dwLocation, DWORD dwCompVal, BOOL incr)
{
	if (HIWORD(dwLocation) >= dwCompVal)
		dwLocation = MAKELONG(LOWORD(dwLocation), HIWORD(dwLocation) + ((incr)? 1 : -1));

	return dwLocation;
}

void CConfigListCtrl::CopyMaps(const CCellMap &SourceMap, CCellMap &DestMap, DWORD dwCompVal /*= 0*/, BOOL Incr /*= TRUE*/,
																			DWORD (*pFunc)(DWORD, DWORD, BOOL) /*= NULL*/)
{
	DWORD				dwLocation;
	CCellCtrl			*pCellCtrl;

	POSITION Pos = SourceMap.GetStartPosition();
	DestMap.RemoveAll();	

	while (Pos)
	{
		SourceMap.GetNextAssoc(Pos, dwLocation, pCellCtrl);
		
		if (pFunc)
			dwLocation = (*pFunc)(dwLocation, dwCompVal, Incr);

		DestMap.SetAt(dwLocation, pCellCtrl);
	}
}

void CConfigListCtrl::IncrCtrlMapRow(WORD wRow, BOOL incr /*= TRUE*/)
{
	CCellMap			CtrlMap;
	CopyMaps(m_CtrlMap, CtrlMap, wRow, incr, IncrHIWORD);
	CopyMaps(CtrlMap, m_CtrlMap);
}

void CConfigListCtrl::IncrCtrlMapColumn(WORD wCol, BOOL incr /*= TRUE*/)
{
	CCellMap			CtrlMap;
	CopyMaps(m_CtrlMap, CtrlMap, wCol, incr, IncrLOWORD);
	CopyMaps(CtrlMap, m_CtrlMap);
}

void CConfigListCtrl::InsertBookmark(WORD nItem)
{
	IncrCtrlMapRow(nItem);
	m_CtrlSet.IncrementHIWORD(nItem);
}

void CConfigListCtrl::InsertColumn(WORD nItem)
{
	IncrCtrlMapColumn(nItem);
	m_CtrlSet.IncrementLOWORD(nItem);
}

void CConfigListCtrl::PreSubclassWindow() 
{
	CHeaderCtrl* pHeaderCtrl = GetHeaderCtrl();

	if (pHeaderCtrl)
	{
		VERIFY(m_HeaderCtrl.SubclassWindow(pHeaderCtrl->m_hWnd));
	}

	m_bIsEnabled = this->IsWindowEnabled();
	
	CListCtrl::PreSubclassWindow();
}

void CConfigListCtrl::DrawItemText(CDC *pDC, int nItem, int nSubItem, const LPRECT	prcBounding, UINT uiItemState)
{
	CBrush							Brush;
	RECT							rcTxt;
	CString							strText;

	if (!IsWindowEnabled())
	{
		Brush.CreateSolidBrush(GetSysColor(COLOR_BTNFACE));
		pDC->SetTextColor(GetSysColor(COLOR_WINDOWTEXT)); // Text in a disabled CListCtrl stays black, so not using COLOR_GRAYTEXT
	}
	else if (uiItemState & (ODS_FOCUS | ODS_SELECTED))
	{
		Brush.CreateSolidBrush(GetSysColor(COLOR_HIGHLIGHT));
		pDC->SetTextColor(GetSysColor(COLOR_HIGHLIGHTTEXT));
	}
	else
	{
		Brush.CreateSolidBrush(GetSysColor(COLOR_WINDOW));
		pDC->SetTextColor(GetSysColor(COLOR_WINDOWTEXT));
	}
	strText = GetItemText(nItem, nSubItem);
	pDC->FillRect(prcBounding, &Brush);
	
	CopyRect(&rcTxt, prcBounding);
	rcTxt.top += ::GetSystemMetrics(SM_CYFIXEDFRAME);

	pDC->DrawText(strText, &rcTxt, DT_LEFT | DT_BOTTOM | DT_END_ELLIPSIS);

	Brush.DeleteObject();
}

void CConfigListCtrl::CreateCaret()
{
	if (!m_bCaretExists)
	{
		LONG lHeight = ::GetSystemMetrics(SM_CYVTHUMB) + 2*::GetSystemMetrics(SM_CYEDGE) - 2*::GetSystemMetrics(SM_CYFIXEDFRAME);
		CreateSolidCaret(1, lHeight);
		m_bCaretExists = TRUE;
	}
}

void CConfigListCtrl::DeleteCaret()
{
	if (m_bCaretExists)
	{
		::DestroyCaret();
		m_bCaretExists = FALSE;
	}
}

void CConfigListCtrl::OnDrawInitialize(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
	CDC								DC;
	CRect							rc;
	int								iSavedDC;

	DC.Attach(lpDrawItemStruct->hDC);
	iSavedDC = DC.SaveDC();
	
	GetWindowRect(&rc);

	WINDOWPOS wp;
	wp.hwnd = this->m_hWnd;
	wp.cx = rc.Width() ;
	wp.cy = rc.Height() ;
	wp.flags = SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOZORDER;

	SendMessage(WM_WINDOWPOSCHANGED, 0, reinterpret_cast<LPARAM> (&wp));

	DC.RestoreDC(iSavedDC);
	DC.Detach();
}

void CConfigListCtrl::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
	CDC								DC;
	RECT							&rcItem = lpDrawItemStruct->rcItem;
	int								iSavedDC;
	UINT							&uiItemID = lpDrawItemStruct->itemID,
									&uiItemState = lpDrawItemStruct->itemState;
	LONG							lCol;
	RECT							rcCtrl;
	CCellCtrl						*pCellCtrl;

	DC.Attach(lpDrawItemStruct->hDC);
	iSavedDC = DC.SaveDC();

	if (!m_bInit)
	{
		OnDrawInitialize(lpDrawItemStruct); 
		m_bInit = TRUE;
	}

	for (lCol = 0; lCol < GetHeaderCtrl()->GetItemCount(); lCol++)
	{
		GetSubItemRect(uiItemID, lCol, LVIR_LABEL, (CRect &)rcCtrl);

		if (m_CtrlMap.Lookup(GetMapKey(uiItemID, lCol), pCellCtrl))
			pCellCtrl->DrawCtrl(&DC, &rcCtrl, uiItemState);
		else
			DrawItemText(&DC, uiItemID, lCol, &rcCtrl, uiItemState);
	}
	DC.RestoreDC(iSavedDC);
	DC.Detach();
}

void CConfigListCtrl::MeasureItem(LPMEASUREITEMSTRUCT lpMeasureItemStruct)
{
	lpMeasureItemStruct->itemHeight = ::GetSystemMetrics(SM_CYVTHUMB) + 2*::GetSystemMetrics(SM_CYEDGE);
}

void CConfigListCtrl::InvalidateActiveCellRect()
{
//	CRect ref;
//	GetSubItemRect(m_ActiveCell.m_lItem, m_ActiveCell.m_lSubItem, LVIR_BOUNDS, ref);
//	InvalidateRect(ref, FALSE);
	// Only redraw text area to avoid potential flicking of frame:
	InvalidateRect(&GetItemCellCtrl(m_ActiveCell.m_lItem, m_ActiveCell.m_lSubItem)->GetTextRect(), TRUE);
	SetItem(m_ActiveCell.m_lItem, m_ActiveCell.m_lSubItem, m_ActiveCell.m_pCellCtrl->GetSelectedTextValue());
}

BOOL CConfigListCtrl::IsOnCellCtrl(int iItem, int iSubItem, CCellCtrl **ppCellCtrl) const
{
	BOOL bIsOnCtrl = FALSE;

//	if ((DWORD)iItem < m_dwBookmarkCount && (DWORD)iSubItem < m_dwColumnCount)
		if (m_CtrlMap.Lookup(GetMapKey(iItem, iSubItem), *ppCellCtrl))
			bIsOnCtrl = TRUE;
			
	return bIsOnCtrl;
}

CCellCtrl *CConfigListCtrl::GetItemCellCtrl(int iItem, int iSubItem) const
{
	BOOL bIsOnCtrl = FALSE;
	CCellCtrl *pCellCtrl = 0;

//	if ((DWORD)iItem < m_dwBookmarkCount && (DWORD)iSubItem < m_dwColumnCount)
		if (!m_CtrlMap.Lookup(GetMapKey(iItem, iSubItem), pCellCtrl))
			pCellCtrl = 0;
			
	return pCellCtrl;
}

void CConfigListCtrl::OnResizeColumn()
{
//	Invalidate(FALSE); // causes flicking.
	RECT rc;
	GetWindowRect(&rc);
	InvalidateRect(&rc, FALSE);

	ForEachCellCtrl(&CConfigListCtrl::OnInactiveResizeColumn);

	if (m_ActiveCell.m_lItem != -1 && m_ActiveCell.m_lSubItem != -1)
		m_ActiveCell.m_pCellCtrl->OnResizeColumn();
}

void CConfigListCtrl::OnHeaderLButtonDown(UINT nFlags, CPoint point)
{
	SetFocus(); // If don't put this, control does not have focus: caret is on wrong ctrl. 
}

void CConfigListCtrl::SetColumnMinWidth(int nCol)
// Set the column to the minimum wisth required so that all items in this column can be fully
// diplayed.
{
	int							nItem = 0,
								nWidth,
								nMinWidth = 0;
	CCellCtrl					*pCellCtrl;

// WARNING: GetNextItem always returns 0 if only one row.
// AS A RESULT CANNOT BE USED.	

	for (nItem = 0; nItem < GetItemCount(); nItem++)
	{
		if (m_CtrlMap.Lookup(GetMapKey(nItem, nCol), pCellCtrl))
			nWidth = pCellCtrl->GetMinWidth();
		else
			nWidth = GetStringWidth(GetItemText(nItem, nCol) + _T("i"));
	
		if (nWidth > nMinWidth)
			nMinWidth = nWidth;
	}
	if (!nCol) nMinWidth += GetSystemMetrics(SM_CXEDGE); // First column has a small indentation

	SetColumnWidth(nCol, nMinWidth);
	ForEachCellCtrl(&CConfigListCtrl::OnInactiveResizeColumn); // Remember: column will probably have been resized
}

BOOL CConfigListCtrl::OnCommand(WPARAM wParam, LPARAM lParam) 
{
	BOOL bCall;
	if (m_ActiveCell.m_lItem != -1 && m_ActiveCell.m_lSubItem != -1)
		bCall = m_ActiveCell.m_pCellCtrl->OnCommand(wParam, lParam);

	return (bCall)? CListCtrl::OnCommand(wParam, lParam) : TRUE;
}

void CConfigListCtrl::OnParentMove(int x, int y)
{
	if (m_ActiveCell.m_lItem != -1 && m_ActiveCell.m_lSubItem != -1)
		m_ActiveCell.m_pCellCtrl->OnParentMove(x, y);
}

void CConfigListCtrl::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
{
	BOOL bCall = TRUE;
	if (m_ActiveCell.m_lItem != -1 && m_ActiveCell.m_lSubItem != -1)
	{
		bCall = m_ActiveCell.m_pCellCtrl->OnChar(nChar, nRepCnt, nFlags);
		InvalidateActiveCellRect();
		SetItem(m_ActiveCell.m_lItem, m_ActiveCell.m_lSubItem, m_ActiveCell.m_pCellCtrl->GetSelectedTextValue());
	}
	// Never call CListCtrl::OnChar: reason:
	// If do so, will automatically highlight a row in which first character of first column is same as typed character.
	// No notification received, and m_ActiveCell remains the active cell, so display is inconsistent.   
	//if (bCall)
	//	CListCtrl::OnChar(nChar, nRepCnt, nFlags);
}

void CConfigListCtrl::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
	BOOL bCall = TRUE;
	if (m_ActiveCell.m_lItem != -1 && m_ActiveCell.m_lSubItem != -1)
		m_ActiveCell.m_pCellCtrl->OnHScroll(nSBCode, nPos, pScrollBar);
	
	if (bCall)
		CListCtrl::OnHScroll(nSBCode, nPos, pScrollBar);
}

void CConfigListCtrl::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
	if (m_ActiveCell.m_lItem != -1 && m_ActiveCell.m_lSubItem != -1)
	{
		m_ActiveCell.m_pCellCtrl->OnKeyDown(nChar, nRepCnt, nFlags);
		InvalidateActiveCellRect();
	}
	else
		CListCtrl::OnKeyDown(nChar, nRepCnt, nFlags);
}

void CConfigListCtrl::OnKeyUp(UINT nChar, UINT nRepCnt, UINT nFlags)
{
	BOOL bCall = TRUE;
	if (m_ActiveCell.m_lItem != -1 && m_ActiveCell.m_lSubItem != -1)
	{
		bCall = m_ActiveCell.m_pCellCtrl->OnKeyUp(nChar, nRepCnt, nFlags);
	//	InvalidateActiveCellRect();
	}
	if (bCall) 	
		CListCtrl::OnKeyUp(nChar, nRepCnt, nFlags);
}

void CConfigListCtrl::OnKillFocus(CWnd* pNewWnd)
{
	CListCtrl::OnKillFocus(pNewWnd);

	if (m_ActiveCell.m_lItem != -1 && m_ActiveCell.m_lSubItem != -1)
	{
		CListCtrlCellWnd *pListCtrlCellWnd = dynamic_cast<CListCtrlCellWnd *>(pNewWnd);

		// The code below is unfortunately needed when open a color picker and press 'enter' with 'Other'
		// button selected. Works with both CMFCColorDialog and CColorDialog.
		if (!pListCtrlCellWnd)
		{
			pNewWnd = pNewWnd->GetParentOwner();

			if (pNewWnd)
				pListCtrlCellWnd = dynamic_cast<CListCtrlCellWnd *>(pNewWnd->GetParent());
		}

		if (!pListCtrlCellWnd)
		{
			m_ActiveCell.m_pCellCtrl->OnKillActive();
			m_ActiveCell.m_pCellCtrl->OnKillFocus(pNewWnd);
			InvalidateActiveCellRect();
			m_ActiveCell.m_lItem = -1;
			m_ActiveCell.m_lSubItem = -1;
			m_ActiveCell.m_pCellCtrl = NULL;
		}
	}
	DeleteCaret();
}

void CConfigListCtrl::OnLButtonDown(UINT nFlags, CPoint point)
{
	BOOL bCall = TRUE;
	LVHITTESTINFO Info;
	CCellCtrl *pCellCtrl = NULL;
	BOOL bCaretExisted = m_bCaretExists;
	RECT rcSubItem;
	CWnd *pNewFocusedWin = NULL;

	Info.flags = 0;
	Info.pt = point;

	int iItem = SubItemHitTest(&Info);

	if (IsOnCellCtrl(Info.iItem, Info.iSubItem, &pCellCtrl) && !(pCellCtrl->GetStyle() & CLCS_DISABLED))
	{
		m_ActiveCell.m_lItem = Info.iItem;
		m_ActiveCell.m_lSubItem = Info.iSubItem;

		if (m_ActiveCell.m_pCellCtrl && m_ActiveCell.m_pCellCtrl != pCellCtrl)
			m_ActiveCell.m_pCellCtrl->OnKillActive();

		m_ActiveCell.m_pCellCtrl = pCellCtrl;

// The bounding rect for a combobox can be inaccurate without couple of lines below.
// 	Try commenting these and: use mousewheel up and down several time, then click 
//	on combo to see.
		GetSubItemRect(Info.iItem, Info.iSubItem, LVIR_LABEL, (CRect &)rcSubItem);
		pCellCtrl->SetWindowRect(&rcSubItem);

		SetFocus(); // only get focus when left mouse button is up otherwise
		
		bCall = pCellCtrl->OnLButtonDown(nFlags, point);
		pNewFocusedWin = GetFocus();
		SetItem(m_ActiveCell.m_lItem, m_ActiveCell.m_lSubItem, m_ActiveCell.m_pCellCtrl->GetSelectedTextValue());

		if (m_bCaretExists)
			pCellCtrl->OnCaretHandlingLButtonDown(nFlags, point);
	}
	else
		DeActivateCell();

//	this->Invalidate(FALSE); // Have to put this line, otherwise change of state of a cell is slow to show up.
	// Does not seem necessary anymore - might have been before.

	if (bCall)
		CListCtrl::OnLButtonDown(nFlags, point); // setfocus is called in here: m_bCaretExists is always true after this line

	if (pCellCtrl && !bCaretExisted)
		pCellCtrl->OnCaretHandlingLButtonDown(nFlags, point);

	if (pNewFocusedWin)
		::SetFocus(pNewFocusedWin->m_hWnd);
}

void CConfigListCtrl::InvalidateHotRect()
{
	RECT rcSubItem;
	GetSubItemRect(m_HotCell.m_lItem, m_HotCell.m_lSubItem, LVIR_LABEL, (CRect &)rcSubItem);
	InvalidateRect(&rcSubItem, FALSE);
}

void CConfigListCtrl::OnMouseMove(UINT nFlags, CPoint point)
{
	if (IsAppThemed())
	{
		CCellCtrl *pCellCtrl = NULL;
		LVHITTESTINFO Info;

		Info.flags = 0;
		Info.pt = point;

		int iItem = SubItemHitTest(&Info);

		if (IsOnCellCtrl(Info.iItem, Info.iSubItem, &pCellCtrl))
		{
			if (m_HotCell.m_pCellCtrl != pCellCtrl)
			{
				if (m_HotCell.m_pCellCtrl)
				{
					m_HotCell.m_pCellCtrl->OnKillHot();
					InvalidateHotRect();
				}
				m_HotCell.Assign(pCellCtrl, Info.iItem, Info.iSubItem);
				m_HotCell.m_pCellCtrl->OnSetHot();
				InvalidateHotRect();
			}
			pCellCtrl->OnHotMouseMove(nFlags, point);
		}
		else if (m_HotCell.m_pCellCtrl)
		{
			m_HotCell.m_pCellCtrl->OnKillHot();
			InvalidateHotRect();
			m_HotCell.Assign(NULL, -1, -1);
		}
	}

	BOOL bCall = TRUE;
	if (m_ActiveCell.m_lItem != -1 && m_ActiveCell.m_lSubItem != -1)
		bCall = m_ActiveCell.m_pCellCtrl->OnMouseMove(nFlags, point);
	
	if (bCall) CListCtrl::OnMouseMove(nFlags, point);
}

BOOL CConfigListCtrl::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
{
	BOOL bCall = TRUE;
	if (m_ActiveCell.m_lItem != -1 && m_ActiveCell.m_lSubItem != -1)
		bCall = m_ActiveCell.m_pCellCtrl->OnMouseWheel(nFlags, zDelta, pt);
	
	return (bCall)? CListCtrl::OnMouseWheel(nFlags, zDelta, pt) : TRUE;
}

void CConfigListCtrl::OnRButtonDown(UINT nFlags, CPoint point)
{
	BOOL bCall = TRUE;
	if (m_ActiveCell.m_lItem != -1 && m_ActiveCell.m_lSubItem != -1)
		bCall = m_ActiveCell.m_pCellCtrl->OnRButtonDown(nFlags, point);
	
	if (bCall) CListCtrl::OnRButtonDown(nFlags, point);
}

void CConfigListCtrl::OnSetFocus(CWnd* pOldWnd)
{
	CListCtrl::OnSetFocus(pOldWnd);
	CreateCaret();

	if (m_ActiveCell.m_lItem != -1 && m_ActiveCell.m_lSubItem != -1)
		m_ActiveCell.m_pCellCtrl->OnSetFocus(pOldWnd);
}

void CConfigListCtrl::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
	BOOL bCall = TRUE;
	if (m_ActiveCell.m_lItem != -1 && m_ActiveCell.m_lSubItem != -1)
		bCall = m_ActiveCell.m_pCellCtrl->OnVScroll(nSBCode, nPos, pScrollBar);
	
	if (bCall)
		CListCtrl::OnVScroll(nSBCode, nPos, pScrollBar);
}

void CConfigListCtrl::OnLButtonUp(UINT nFlags, CPoint point)
{
	BOOL bCall = TRUE;
	if (m_ActiveCell.m_lItem != -1 && m_ActiveCell.m_lSubItem != -1)
		bCall = m_ActiveCell.m_pCellCtrl->OnLButtonUp(nFlags, point);
	
	if (bCall)
		CListCtrl::OnLButtonUp(nFlags, point);
}

void CConfigListCtrl::OnTimer(UINT_PTR nIDEvent)
{
	BOOL bCall = TRUE;
	if (m_ActiveCell.m_lItem != -1 && m_ActiveCell.m_lSubItem != -1)
		bCall = m_ActiveCell.m_pCellCtrl->OnTimer(nIDEvent);
	
	if (bCall)
		CListCtrl::OnTimer(nIDEvent);
}

LRESULT CConfigListCtrl::OnCellCtrlMsg(WPARAM wParam, LPARAM lParam)
{
	if (m_ActiveCell.m_lItem != -1 && m_ActiveCell.m_lSubItem != -1)
	{
		m_ActiveCell.m_pCellCtrl->OnCellCtrlMsg(wParam, lParam);
		SetItem(m_ActiveCell.m_lItem, m_ActiveCell.m_lSubItem, m_ActiveCell.m_pCellCtrl->GetSelectedTextValue());
	}
	return TRUE; 
}

void CConfigListCtrl::OnEscape(DWORD dwLocation, CCellCtrl *pCellCtrl)
{
	CCellButton *pButton = dynamic_cast<CCellButton *>(pCellCtrl);

	if (pButton)
		pButton->OnEscape();
}

void CConfigListCtrl::OnEnter(DWORD dwLocation, CCellCtrl *pCellCtrl)
{
	CCellButton *pButton = dynamic_cast<CCellButton *>(pCellCtrl);

	if (pButton)
		pButton->OnEnter();
}

BOOL CConfigListCtrl::OnEnterEsc(WPARAM param)
{
	if (param == VK_ESCAPE) 
		ForEachCellCtrl(&CConfigListCtrl::OnEscape);
	else if (param == VK_RETURN)
		ForEachCellCtrl(&CConfigListCtrl::OnEnter);
	return TRUE;
}

void CConfigListCtrl::ActivateCellCtrl(DWORD dw)
{
	CCellCtrl *pCellCtrl;
	if (m_CtrlMap.Lookup(dw, pCellCtrl))
	{
		SetItemState(-1, 0, LVIS_SELECTED|LVIS_FOCUSED);
		m_ActiveCell.m_pCellCtrl = pCellCtrl;
		m_ActiveCell.m_lItem = HIWORD(dw);
		m_ActiveCell.m_lSubItem = LOWORD(dw);
		m_ActiveCell.m_pCellCtrl->OnSetActive();
		SetItemState(m_ActiveCell.m_lItem, LVIS_SELECTED, LVIS_SELECTED|LVIS_FOCUSED);
		RECT rcSubItem;
		GetSubItemRect(m_ActiveCell.m_lItem, m_ActiveCell.m_lSubItem, LVIR_LABEL, (CRect &)rcSubItem);
		pCellCtrl->SetWindowRect(&rcSubItem);
		pCellCtrl->OnCaretHandlingLButtonDown(0, CPoint(0,0));
	}
}

DWORD CConfigListCtrl::GetNextEnabled(DWORD dw)
{
	CCellCtrl *pCellCtrl = NULL;

	while (m_CtrlMap.Lookup(dw, pCellCtrl) && pCellCtrl &&
						pCellCtrl->GetStyle() & CLCS_DISABLED && dw != m_CtrlSet.Last())
		dw = m_CtrlSet.Next(dw);

	return dw;
}

DWORD CConfigListCtrl::GetPrevEnabled(DWORD dw)
{
	CCellCtrl *pCellCtrl = NULL;

	while (m_CtrlMap.Lookup(dw, pCellCtrl) && pCellCtrl &&
						pCellCtrl->GetStyle() & CLCS_DISABLED && dw != m_CtrlSet.First())
		dw = m_CtrlSet.Prev(dw);

	return dw;
}

BOOL CConfigListCtrl::PreTranslateMessage(MSG* pMsg)
{
	if(pMsg->message==WM_KEYUP) 
		m_bKeyUp = TRUE;
	else if (pMsg->wParam == VK_TAB && m_bKeyUp)
	{ 
		m_bKeyUp = FALSE;

		if (GetKeyState(VK_SHIFT) & KEY_PRESSED)
		{
			if (!m_bTabbedIn)
			{
				m_bTabbedIn = TRUE;
				ActivateCellCtrl(GetPrevEnabled(m_CtrlSet.Last()));
				return TRUE;
			}
			else if (m_ActiveCell.m_lItem != -1 && m_ActiveCell.m_lSubItem != -1 && 
				GetMapKey(m_ActiveCell.m_lItem, m_ActiveCell.m_lSubItem) != m_CtrlSet.First())
			{
				DWORD dw = GetPrevEnabled(m_CtrlSet.Prev(GetMapKey(m_ActiveCell.m_lItem, m_ActiveCell.m_lSubItem)));

				CCellCtrl *pCellCtrl = NULL;
				
				m_CtrlMap.Lookup(dw, pCellCtrl);

				if (pCellCtrl && pCellCtrl->GetStyle() & CLCS_DISABLED)
					m_bTabbedIn = FALSE;
				else
				{
					m_ActiveCell.m_pCellCtrl->OnKillActive();
					ActivateCellCtrl(dw);
					return TRUE;
				}
			}
			else
				m_bTabbedIn = FALSE;	
		}
		else
		{
			if (!m_bTabbedIn)
			{
				m_bTabbedIn = TRUE;
				ActivateCellCtrl(GetNextEnabled(m_CtrlSet.First()));
				return TRUE;
			}
			else if (m_ActiveCell.m_lItem != -1 && m_ActiveCell.m_lSubItem != -1 && 
				GetMapKey(m_ActiveCell.m_lItem, m_ActiveCell.m_lSubItem) != m_CtrlSet.Last())
			{
				DWORD dw = GetNextEnabled(m_CtrlSet.Next(GetMapKey(m_ActiveCell.m_lItem, m_ActiveCell.m_lSubItem)));

				CCellCtrl *pCellCtrl = NULL;
				
				m_CtrlMap.Lookup(dw, pCellCtrl);

				if (pCellCtrl && pCellCtrl->GetStyle() & CLCS_DISABLED)
					m_bTabbedIn = FALSE;
				else
				{
					m_ActiveCell.m_pCellCtrl->OnKillActive();
					ActivateCellCtrl(dw);
					return TRUE;
				}
			}
			else
				m_bTabbedIn = FALSE;
		}
	}
	else if (pMsg->wParam == VK_TAB)
	{ 
		m_bKeyUp = TRUE;
		m_bTabbedIn = TRUE;
		return PreTranslateMessage(pMsg);
	}
	return CListCtrl::PreTranslateMessage(pMsg);
}

void CConfigListCtrl::OnThemeChanged(DWORD dwLocation, CCellCtrl *pCellCtrl)
{
	pCellCtrl->OnThemeChanged();
}

LRESULT CConfigListCtrl::OnThemeChanged()
{
	// This feature requires Windows XP or greater.
	// The symbol _WIN32_WINNT must be >= 0x0501.
	ForEachCellCtrl(&CConfigListCtrl::OnThemeChanged);

	// When theme changes, MeasureItem never seems to be called again, as it should. 
	// Not found a correct solution so heigth is correctly recomputed after a theme change...
	// Not too bad, as user won't change theme while this application is open that often...
	// Still annoying!

	return 1;
}

void CConfigListCtrl::OnEnable(BOOL bEnable)
{
	CListCtrl::OnEnable(bEnable);

	m_bIsEnabled = this->IsWindowEnabled();
}

void CConfigListCtrl::EnableCtrl(int iItem, int iSubItem, BOOL Enable /*= TRUE*/)
{
	CCellCtrl *pCellCtrl;

	if (IsOnCellCtrl(iItem, iSubItem, &pCellCtrl))
	{
		if (Enable)
			pCellCtrl->AddToStyle(CLCS_DISABLED);
		else
			pCellCtrl->RemoveFromStyle(CLCS_DISABLED);

		CRect rcCtrl;
		GetSubItemRect(iItem, iSubItem, LVIR_LABEL, rcCtrl);
		InvalidateRect(&rcCtrl, FALSE);
	}
}

BOOL CConfigListCtrl::IsCtrlEnabled(int iItem, int iSubItem)
{
	BOOL Enabled = m_bIsEnabled;
	CCellCtrl *pCellCtrl;

	if (IsOnCellCtrl(iItem, iSubItem, &pCellCtrl))
		Enabled = pCellCtrl->GetStyle() & CLCS_DISABLED;

	return Enabled;
}

BOOL CConfigListCtrl::SetItemValueFromCtrl(const CCellCtrl *pCellCtrl, const CString &strValue)
{
	DWORD				dwLocation;
	CCellCtrl			*pSearchCellCtrl;
	BOOL				bFound = FALSE;

	POSITION Pos = m_CtrlMap.GetStartPosition();

	while (Pos && !bFound)
	{
		m_CtrlMap.GetNextAssoc(Pos, dwLocation, pSearchCellCtrl);

		if (pSearchCellCtrl == pCellCtrl)
		{
			SetItem(HIWORD(dwLocation), LOWORD(dwLocation), strValue);
			bFound = FALSE;
		}
	}
	return bFound;
}

void CConfigListCtrl::OnSizing(UINT fwSide, LPRECT pRect)
{
	if (m_ActiveCell.m_lItem != -1 && m_ActiveCell.m_lSubItem != -1)
	{
		CCellButton *pButton = dynamic_cast<CCellButton *>(m_ActiveCell.m_pCellCtrl);

		if (pButton)
			pButton->OnEscape();
	}
}

BOOL  CConfigListCtrl::SetCtrlAlignment(int iItem, int iSubItem, Alignment align)
{
	CCellCtrl *pCellCtrl;

	if (IsOnCellCtrl(iItem, iSubItem, &pCellCtrl))
	{
		switch(align)
		{
			case Center:
				pCellCtrl->AddToStyle(CLCS_ALIGN_CENTER);
				pCellCtrl->RemoveFromStyle(CLCS_ALIGN_RIGHT);
			break;

			case Right:
				pCellCtrl->RemoveFromStyle(CLCS_ALIGN_CENTER);
				pCellCtrl->AddToStyle(CLCS_ALIGN_RIGHT);
			break;

			case Left:
			default:
				pCellCtrl->RemoveFromStyle(CLCS_ALIGN_CENTER);
				pCellCtrl->RemoveFromStyle(CLCS_ALIGN_RIGHT);
		}
		InvalidateRect(&pCellCtrl->GetModDrawAreaRect(), FALSE);
		return TRUE;
	}
	return FALSE;
}

BOOL  CConfigListCtrl::GetCtrlAlignment(int iItem, int iSubItem, Alignment &align)
{
	CCellCtrl *pCellCtrl;
	align = Left;

	if (IsOnCellCtrl(iItem, iSubItem, &pCellCtrl))
	{
		if (pCellCtrl->GetStyle() & CLCS_ALIGN_CENTER)
			align = Center;
		else if (pCellCtrl->GetStyle() & CLCS_ALIGN_RIGHT)
			align = Right;

		return TRUE;
	}
	return FALSE;
}

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

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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


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

Comments and Discussions