Click here to Skip to main content
15,885,760 members
Articles / Desktop Programming / MFC

CListCtrlExt and CListViewExt controls

Rate me:
Please Sign up or sign in to vote.
4.78/5 (22 votes)
20 Feb 2012CPOL5 min read 82.8K   8.7K   84  
An enhanced list control based on the standard CListCtrl MFC class.
// ListViewExt.cpp : implementation of the CListViewExt class
//

#include "stdafx.h"
#include "ListViewExt.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

#ifndef HDF_SORTDOWN
#define HDF_SORTDOWN				0x0200
#endif
#ifndef HDF_SORTUP
#define HDF_SORTUP					0x0400
#endif
/////////////////////////////////////////////////////////////////////////////
// CListViewExt

CListViewExt::EditorInfo::EditorInfo()
	:m_pfnInitEditor(NULL)
	,m_pfnEndEditor(NULL)
	,m_pWnd(NULL)
	,m_bReadOnly(FALSE)
{
}

CListViewExt::EditorInfo::EditorInfo(PFNEDITORCALLBACK pfnInitEditor, PFNEDITORCALLBACK pfnEndEditor, CWnd *pWnd)
	:m_pfnInitEditor(pfnInitEditor)
	,m_pfnEndEditor(pfnEndEditor)
	,m_pWnd(pWnd)
	,m_bReadOnly(FALSE)
{
}

CListViewExt::CellInfo::CellInfo(int nColumn)
	:m_clrBack(-1)
	,m_clrText(-1)
	,m_dwUserData(NULL)
	,m_nColumn(nColumn)
{
}

CListViewExt::CellInfo::CellInfo(int nColumn, COLORREF clrBack, COLORREF clrText, DWORD_PTR dwUserData)
	:m_clrBack(clrBack)
	,m_clrText(clrText)
	,m_dwUserData(dwUserData)
	,m_nColumn(nColumn)
{
}

CListViewExt::CellInfo::CellInfo(int nColumn, EditorInfo eiEditor, COLORREF clrBack, COLORREF clrText, DWORD_PTR dwUserData)
	:m_clrBack(clrBack)
	,m_clrText(clrText)
	,m_dwUserData(dwUserData)
	,m_eiEditor(eiEditor)
	,m_nColumn(nColumn)
{
}

CListViewExt::CellInfo::CellInfo(int nColumn, EditorInfo eiEditor, DWORD_PTR dwUserData)
	:m_clrBack(-1)
	,m_clrText(-1)
	,m_dwUserData(dwUserData)
	,m_eiEditor(eiEditor)
	,m_nColumn(nColumn)
{
}

CListViewExt::ColumnInfo::ColumnInfo(int nColumn)
	:m_eiEditor()
	,m_clrBack(-1)
	,m_clrText(-1)
	,m_nColumn(nColumn)
	,m_eSort(None)
	,m_eCompare(NotSet)
	,m_fnCompare(NULL)
{
}

CListViewExt::ColumnInfo::ColumnInfo(int nColumn, PFNEDITORCALLBACK pfnInitEditor, PFNEDITORCALLBACK pfnEndEditor, CWnd *pWnd)
	:m_eiEditor(pfnInitEditor, pfnEndEditor, pWnd)
	,m_nColumn(nColumn)
	,m_clrBack(-1)
	,m_clrText(-1)
	,m_eSort(None)
	,m_eCompare(NotSet)
	,m_fnCompare(NULL)
{
}

CListViewExt::ItemData::ItemData(DWORD_PTR dwUserData)
	:m_clrBack(0xFFFFFFFF)
	,m_clrText(0xFFFFFFFF)
	,m_dwUserData(dwUserData)
{
}

CListViewExt::ItemData::ItemData(COLORREF clrBack, COLORREF clrText, DWORD_PTR dwUserData)
	:m_clrBack(clrBack)
	,m_clrText(clrText)
	,m_dwUserData(dwUserData)
{
}

CListViewExt::ItemData::~ItemData()
{
	while(m_aCellInfo.GetSize() > 0)
	{
		CellInfo *pInfo = (CellInfo*)m_aCellInfo.GetAt(0);
		m_aCellInfo.RemoveAt(0);
		delete pInfo;
	}
}

// CListViewExt
IMPLEMENT_DYNAMIC(CListViewExt, CListView)

CListViewExt::CListViewExt()
	:m_pEditor(NULL)
	,m_hAccel(NULL)
	,m_nEditingRow(-1)
	,m_MsgHook()
	,m_nRow(-1)
	,m_nColumn(-1)
	,m_nSortColumn(-1)
	,m_fnCompare(NULL)
	,m_dwSortData(NULL)
	,m_bColumnSort(FALSE)
	,m_bGrid(FALSE)
	,m_nFocusCell(-1)
	,m_nLastSearchCell(-1)
	,m_nLastSearchRow(-1)
	,m_nEditingColumn(-1)
	,m_bHandleDelete(FALSE)
	,m_pHeaderCtrl(NULL)
{
	m_pHeaderCtrl = new CHeaderCtrlExt;
}

CListViewExt::~CListViewExt()
{
	DeleteAllItemsData();
	DeleteAllColumnInfo();
	delete m_pHeaderCtrl;
}


BEGIN_MESSAGE_MAP(CListViewExt, CListView)
	//{{AFX_MSG_MAP(CListViewExt)
	ON_WM_CHAR()
	ON_WM_KEYDOWN()
	ON_WM_LBUTTONDOWN()
	ON_WM_RBUTTONDOWN()
	ON_NOTIFY(HDN_ENDDRAG, 0, OnHdnEnddrag)
	ON_NOTIFY(NM_RCLICK, 0, OnNmRclickHeader)
	ON_NOTIFY(HDN_DIVIDERDBLCLICK, 0, OnHdnDividerdblclick)
	ON_NOTIFY_REFLECT_EX(NM_DBLCLK, CListViewExt::OnNMDblclk)
	ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, CListViewExt::OnNMCustomdraw)
	ON_NOTIFY_REFLECT(LVN_COLUMNCLICK, CListViewExt::OnColumnclick)
	//}}AFX_MSG_MAP
	ON_MESSAGE(LVM_FINDITEM, OnFindItem)
	ON_MESSAGE(LVM_INSERTITEM, OnInsertItem)
	ON_MESSAGE(LVM_DELETEITEM, OnDeleteItem)
	ON_MESSAGE(LVM_INSERTCOLUMN, OnInsertColumn)
	ON_MESSAGE(LVM_DELETECOLUMN, OnDeleteColumn)
	ON_MESSAGE(LVM_DELETEALLITEMS,OnDeleteAllItems)
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CListViewExt diagnostics

#ifdef _DEBUG
void CListViewExt::AssertValid() const
{
	CListView::AssertValid();
}

void CListViewExt::Dump(CDumpContext& dc) const
{
	CListView::Dump(dc);
}
#endif //_DEBUG

/////////////////////////////////////////////////////////////////////////////
// CListViewExt message handlers

void CListViewExt::OnInitialUpdate() 
{
	CListView::OnInitialUpdate();

	// TODO: Add your specialized code here and/or call the base class

	DWORD dwOldStyle = GetStyle();
	ModifyStyle(LVS_TYPEMASK, LVS_REPORT);
	::SetWindowLong(m_hWnd, GWL_STYLE, dwOldStyle);

	if(m_pHeaderCtrl->m_hWnd == NULL)
	{
		HWND hWnd = (HWND)::SendMessage(m_hWnd,LVM_GETHEADER,0,0);
		m_pHeaderCtrl->SubclassWindow(hWnd);

		m_clrDefBack = GetListCtrl().GetTextBkColor() | 0xFF000000;
		m_clrDefText = GetListCtrl().GetTextColor();
	}
}

BOOL CListViewExt::PreTranslateMessage(MSG* pMsg)
{
	if((m_hAccel && GetParent() && GetFocus() == this && ::TranslateAccelerator(GetParent()->m_hWnd, m_hAccel, pMsg)))return TRUE;

	if(pMsg->message == WM_KEYDOWN && (pMsg->wParam == VK_ESCAPE || pMsg->wParam == VK_RETURN))
	{
		HideEditor();
		GetListCtrl().SetItemState(GetListCtrl().GetNextItem(-1, LVNI_FOCUSED), LVIS_SELECTED, LVIS_SELECTED);
	}

	return CListView::PreTranslateMessage(pMsg);
}

LRESULT CListViewExt::OnInsertColumn(WPARAM wParam, LPARAM lParam)
{
	int nPos = (int)Default(); // call default control procedure

	LPLVCOLUMN pCol = (LPLVCOLUMN)lParam;
	CHeaderCtrlExt::CItemData* pData = new CHeaderCtrlExt::CItemData(pCol->cx, TRUE, TRUE);
	m_pHeaderCtrl->SetItemData((int)wParam, (DWORD_PTR)pData);

	return nPos;
}

LRESULT CListViewExt::OnDeleteColumn(WPARAM wParam, LPARAM lParam)
{
	CHeaderCtrlExt::CItemData* pData = (CHeaderCtrlExt::CItemData*)m_pHeaderCtrl->GetItemData((int)wParam);
	if(pData)delete pData;

	return Default();
}

LRESULT CListViewExt::OnInsertItem(WPARAM wParam, LPARAM lParam)
{
	int nPos = (int)Default();
	LPLVITEM pItem = (LPLVITEM)lParam;
	SetItemUserData(nPos, pItem->iItem);

	return nPos;
}

LRESULT CListViewExt::OnDeleteItem(WPARAM wParam, LPARAM lParam)
{
	DeleteItemData((int)wParam);

	return Default();
}

LRESULT CListViewExt::OnDeleteAllItems(WPARAM wParam, LPARAM lParam)
{
	DeleteAllItemsData();

	return Default();
}

LRESULT CListViewExt::OnFindItem(WPARAM wParam, LPARAM lParam)
{
	int nPos = (int)Default();
	LVFINDINFO* pLVFindItem = (LVFINDINFO*)lParam;
	if(pLVFindItem->flags & LVIF_PARAM)
	{
		int nStart = (int)wParam;
		int nCount = GetListCtrl().GetItemCount();
		for(int i = nStart + 1;i < nCount;++i)
		{
			if(pLVFindItem->lParam == (int)GetItemUserData(i))
			{
				nPos = i;
				break;
			}
		}
	}

	return nPos;
}

BOOL CListViewExt::EnsureSubItemVisible(int nItem, int nSubItem, CRect* pRect)
{
	BOOL bReturn = GetListCtrl().EnsureVisible(nItem, FALSE);
	CRect rect,rcList;
	GetClientRect(&rcList);
	GetListCtrl().GetSubItemRect(nItem, nSubItem, LVIR_LABEL, rect);
	if(rect.right > rcList.Width())GetListCtrl().Scroll(CSize(rect.Width() > rcList.Width() ? rect.left : rect.right - rcList.Width(),0));
	if(rect.left < 0)GetListCtrl().Scroll(CSize(rect.left));
	if(pRect)
	{
		GetListCtrl().GetSubItemRect(nItem, nSubItem, LVIR_LABEL, rect);
		rect.right = min(rect.right, rcList.Width() - 4);
		*pRect = rect;
	}

	return bReturn;
}

int CListViewExt::GetColumnCount()
{
	if(m_pHeaderCtrl)return m_pHeaderCtrl->GetItemCount();

	int i = 0;
	LVCOLUMN col;
	col.mask = LVCF_WIDTH;
	while(GetListCtrl().GetColumn(i++, &col));

	return i;
}

BOOL CListViewExt::AddItem(int nItemIndex, int nSubItemIndex, LPCTSTR lpszItemText, int nImageIndex)
{
	LV_ITEM lvItem;
	lvItem.mask = LVIF_TEXT;
	lvItem.iItem = nItemIndex;
	lvItem.iSubItem = nSubItemIndex;
	lvItem.pszText = (LPTSTR)lpszItemText;
	if(nImageIndex != -1)
	{
		lvItem.mask |= LVIF_IMAGE;
		lvItem.iImage |= LVIF_IMAGE;
	}

	if(nSubItemIndex == 0)return GetListCtrl().InsertItem(&lvItem);

	return GetListCtrl().SetItem(&lvItem);
}

void CListViewExt::SelectItem(int nItem, BOOL bSelect)
{
	int nIndex = -1;
	if(nItem >= 0)GetListCtrl().SetItemState(nItem, (bSelect ? LVIS_SELECTED: 0), LVIS_SELECTED);
	else
	{
		while((nIndex = GetListCtrl().GetNextItem(nIndex, bSelect ? LVNI_ALL : LVNI_SELECTED)) >= 0)
		{
			GetListCtrl().SetItemState(nIndex, (bSelect ? LVIS_SELECTED: 0), LVIS_SELECTED);
		}
	}
}

BOOL CListViewExt::Reset()
{
	return (GetListCtrl().DeleteAllItems() && DeleteAllColumns());
}

void CListViewExt::DeleteSelectedItems()
{
	int nItem = -1;
	while((nItem = GetListCtrl().GetNextItem(-1, LVNI_SELECTED)) >= 0)
		GetListCtrl().DeleteItem(nItem);
}

void CListViewExt::HandleDeleteKey(BOOL bHandle)
{
	m_bHandleDelete = bHandle;
}

DWORD_PTR CListViewExt::GetItemUserData(int nItem) const
{
	ItemData* pData = (ItemData*)GetItemDataInternal(nItem);
	if(! pData)return NULL;

	return pData->m_dwUserData;
}

DWORD_PTR CListViewExt::GetItemDataInternal(int nItem) const
{
	return GetListCtrl().GetItemData(nItem);
}

BOOL CListViewExt::SetItemUserData(int nItem, DWORD_PTR dwData)
{
	ItemData* pData = (ItemData*)GetItemDataInternal(nItem);

	if(pData)pData->m_dwUserData = dwData;
	else
	{
		pData = new ItemData(dwData);
		m_aItemData.Add(pData);
	}

	return GetListCtrl().SetItemData(nItem, (DWORD_PTR)pData);
}

int CListViewExt::GetItemIndexFromData(DWORD_PTR dwData)
{
	LVFINDINFO find;
	find.flags = LVFI_PARAM;
	find.lParam = dwData;

	return GetListCtrl().FindItem(&find);
}

BOOL CListViewExt::SetCellData(int nItem, int nSubItem, DWORD_PTR dwData)
{
	if(nItem < 0 || nItem >= GetListCtrl().GetItemCount() || nSubItem < 0 || nSubItem >= GetColumnCount())return FALSE;

	CellInfo* pCellInfo = GetCellInfo(nItem, nSubItem);
	if(! pCellInfo)
	{ 
		pCellInfo = new CellInfo(nSubItem);
		ItemData* pData = (ItemData*)GetItemDataInternal(nItem);
		if(! pData)
		{
			SetItemUserData(nItem, 0);
			pData = (ItemData*)GetItemDataInternal(nItem);
		}
		pData->m_aCellInfo.Add(pCellInfo);
	}
	pCellInfo->m_dwUserData = dwData;

	return TRUE;
}

DWORD_PTR CListViewExt::GetCellData(int nItem, int nSubItem)
{
	CellInfo* pCellInfo = GetCellInfo(nItem, nSubItem);

	if(pCellInfo)return pCellInfo->m_dwUserData;
	else return 0;
}

BOOL CListViewExt::DeleteAllColumns()
{
	while(GetListCtrl().DeleteColumn(0));

	return(GetColumnCount() == 0);
}

BOOL CListViewExt::DeleteAllItemsData()
{
	while(m_aItemData.GetSize() > 0)
	{
		ItemData* pData = (ItemData*)m_aItemData.GetAt(0);
		if(pData)delete pData;
		m_aItemData.RemoveAt(0);
	}

	return TRUE;
}

BOOL CListViewExt::DeleteItemData(int nItem)
{
	if(nItem < 0 || nItem > GetListCtrl().GetItemCount())return FALSE;

	ItemData* pData = (ItemData*)GetItemUserData(nItem);
	INT_PTR nCount = m_aItemData.GetSize();

	for(INT_PTR i = 0; i < nCount && pData;++i)
	{
		if(m_aItemData.GetAt(i) == pData)
		{
			m_aItemData.RemoveAt(i);
			break;
		}
	}

	if(pData)delete pData;

	return TRUE;
}	

CListViewExt::ColumnInfo* CListViewExt::GetColumnInfo(int nColumn)
{
	INT_PTR nCount = m_aColumnInfo.GetSize();
	for(INT_PTR i = 0;i < nCount;++i)
	{
		ColumnInfo* pColInfo = (ColumnInfo*)m_aColumnInfo.GetAt(i);
		if(pColInfo->m_nColumn == nColumn)return pColInfo;
	}

	return NULL;
}

CListViewExt::CellInfo* CListViewExt::GetCellInfo(int nItem, int nSubItem)
{
	ItemData* pData = (ItemData*)GetItemDataInternal(nItem);
	if(pData == NULL)return NULL;
	INT_PTR nCount = pData->m_aCellInfo.GetSize();
	for(INT_PTR i = 0;i < nCount;++i)
	{
		CellInfo* pInfo = (CellInfo*)pData->m_aCellInfo.GetAt(i);
		if(pInfo && pInfo->m_nColumn == nSubItem)return pInfo;
	}

	return NULL;
}

BOOL CListViewExt::DeleteAllColumnInfo()
{
	while(m_aColumnInfo.GetSize() > 0)
	{
		ColumnInfo* pData = (ColumnInfo*)m_aColumnInfo.GetAt(0);
		if(pData)delete pData;
		m_aColumnInfo.RemoveAt(0);
	}

	return TRUE;
}

BOOL CListViewExt::DeleteColumnInfo(int nColumn)
{
	if(nColumn < 0 || nColumn > GetColumnCount())return FALSE;

	INT_PTR nCount = m_aColumnInfo.GetSize();
	ColumnInfo* pData = (ColumnInfo*)GetColumnInfo(nColumn);

	for(INT_PTR i = 0; i < nCount && pData;++i)
	{
		if(m_aColumnInfo.GetAt(i) == pData)
		{
			m_aColumnInfo.RemoveAt(i);
			break;
		}
	}

	if(pData)delete pData;

	return TRUE;
}

void CListViewExt::OnNMCustomdraw(NMHDR* pNMHDR, LRESULT* pResult)
{
	LPNMLVCUSTOMDRAW lplvcd = reinterpret_cast<LPNMLVCUSTOMDRAW>(pNMHDR);

	*pResult = 0;
	int iCol = lplvcd->iSubItem;
	int iRow = lplvcd->nmcd.dwItemSpec;
	int nRowItemData = (int)lplvcd->nmcd.lItemlParam;
	CListCtrl& ListCtrl = GetListCtrl();

	switch(lplvcd->nmcd.dwDrawStage)
	{
		case CDDS_PREPAINT:
			*pResult = CDRF_NOTIFYSUBITEMDRAW;          // ask for subitem notifications.
			break;
		case CDDS_ITEMPREPAINT:
			*pResult = CDRF_NOTIFYSUBITEMDRAW;
			if(lplvcd->nmcd.uItemState & CDIS_FOCUS)
			{
				// If drawing focus row, then remove focus state and request to draw it later
				//	- Row paint request can come twice, with and without focus flag
				//	- Only respond to the one with focus flag, else DrawFocusRect XOR will cause solid or blank focus-rectangle
				if(ListCtrl.GetNextItem(-1, LVNI_FOCUSED) == iRow)
				{
					if(m_nFocusCell >= 0)
					{
						// We want to draw a cell-focus-rectangle instead of row-focus-rectangle
						lplvcd->nmcd.uItemState &= ~CDIS_FOCUS;
						*pResult |= CDRF_NOTIFYPOSTPAINT;
					}
					else
					{
						// Avoid bug where bottom of focus rectangle is missing when using grid-lines
						//	- Draw the focus-rectangle for the entire row (explicit)
						lplvcd->nmcd.uItemState &= ~CDIS_FOCUS;
						*pResult |= CDRF_NOTIFYPOSTPAINT;
					}
				}
			}
			break;
		case CDDS_ITEMPREPAINT | CDDS_SUBITEM:
			{
				COLORREF clrBack = 0xFFFFFFFF;
				COLORREF clrText = 0xFFFFFFFF;
				*pResult = CDRF_DODEFAULT;
				CellInfo *pCell = GetCellInfo(iRow, iCol);
				if(pCell)
				{
					clrBack = pCell->m_clrBack;
					clrText = pCell->m_clrText;
				}
				if(clrBack == 0xFFFFFFFF && clrText == 0xFFFFFFFF)
				{
					ItemData* pData = (ItemData*)GetItemDataInternal(iRow);
					if(pData)
					{
						clrBack = pData->m_clrBack;
						clrText = pData->m_clrText;
					}
				}
				if(clrBack == 0xFFFFFFFF && clrText == 0xFFFFFFFF)
				{
					ColumnInfo* pInfo = GetColumnInfo(iCol);
					if(pInfo)
					{						
						clrBack = pInfo->m_clrBack;
						clrText = pInfo->m_clrText;
					}
				}
				if(clrBack != 0xFFFFFFFF)
				{
					lplvcd->clrTextBk = clrBack;					
					*pResult  = CDRF_NEWFONT;
				}
				else
				{
					if(clrBack != m_clrDefBack)
					{
						lplvcd->clrTextBk = m_clrDefBack;				
						*pResult  = CDRF_NEWFONT;
					}
				}
				if(clrText != 0xFFFFFFFF)
				{					
					lplvcd->clrText = clrText;					
					*pResult  = CDRF_NEWFONT;
				}				
				else
				{
					if(clrText != m_clrDefText)
					{
						lplvcd->clrText = m_clrDefText;		
						*pResult  = CDRF_NEWFONT;
					}
				}
				// Remove the selection color for the focus cell, to make it easier to see focus
				if(lplvcd->nmcd.uItemState & CDIS_SELECTED && m_nFocusCell == iCol && ListCtrl.GetNextItem(-1, LVNI_FOCUSED) == iRow)
				{
					lplvcd->nmcd.uItemState &= ~CDIS_SELECTED;
				}
			}
			break;
		case CDDS_ITEMPOSTPAINT:
			if(m_bGrid && (ListCtrl.GetExtendedStyle() & LVS_EX_FULLROWSELECT))
			{
				if(ListCtrl.GetNextItem(-1, LVNI_FOCUSED) != iRow)break;
				// Perform the drawing of the focus rectangle
				if(m_nFocusCell >= 0)
				{
					// Draw the focus-rectangle for a single-cell
					CRect rcHighlight;
					CDC* pDC = CDC::FromHandle(lplvcd->nmcd.hdc);
					VERIFY(GetCellRect(iRow, m_nFocusCell, rcHighlight));
					// Adjust rectangle according to grid-lines
					int cxborder = ::GetSystemMetrics(SM_CXBORDER);
					// Columns after the first visible column, has to take account of left-grid-border
					if(m_pHeaderCtrl->OrderToIndex(m_nFocusCell) != 0)rcHighlight.left += cxborder;
					rcHighlight.bottom -= cxborder;
					pDC->DrawFocusRect(rcHighlight);
				}
				else
				{
					// Avoid bug where bottom of focus rectangle is missing when using grid-lines
					//	- Draw the focus-rectangle for the entire row (explicit)
					CRect rcHighlight;
					CDC* pDC = CDC::FromHandle(lplvcd->nmcd.hdc);
					// Using LVIR_BOUNDS to get the entire row-rectangle
					VERIFY(ListCtrl.GetItemRect(iRow, rcHighlight, LVIR_BOUNDS));
					int cxborder = ::GetSystemMetrics(SM_CXBORDER);
					rcHighlight.bottom -= cxborder;
					pDC->DrawFocusRect(rcHighlight);
				}
			}
			break;
		default:
			*pResult = CDRF_DODEFAULT;
			break;
	}
}

void CListViewExt::OnColumnclick(NMHDR* pNMHDR, LRESULT* pResult)
{
	NM_LISTVIEW* phdr = reinterpret_cast<NM_LISTVIEW*>(pNMHDR);
	SortOnColumn(phdr->iSubItem, TRUE);
	*pResult = 0;
}

BOOL CListViewExt::OnNMDblclk(NMHDR* pNMHDR, LRESULT* pResult)
{
	if(m_bGrid && 
		(GetListCtrl().GetStyle() & LVS_TYPEMASK) == LVS_REPORT && 
		(GetListCtrl().GetExtendedStyle() & LVS_EX_FULLROWSELECT))
	{
		NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;
		if(pNMListView)
		{
			int nItem = pNMListView->iItem, nSubItem = pNMListView->iSubItem;
			*pResult = DisplayEditor(nItem, nSubItem);
		}
	}

	return *pResult;
}

void CListViewExt::OnLButtonDown(UINT nFlags, CPoint point)
{
	if(! m_bGrid 
		|| (GetListCtrl().GetStyle() & LVS_TYPEMASK) != LVS_REPORT 
		|| ! (GetListCtrl().GetExtendedStyle() & LVS_EX_FULLROWSELECT))
	{
		CListView::OnLButtonDown(nFlags, point);
		return;
	}

	// Find out what subitem was clicked
	LVHITTESTINFO hitinfo = {0};
	hitinfo.pt = point;
	hitinfo.flags = nFlags;
	GetListCtrl().SubItemHitTest(&hitinfo);

	// Update the focused cell before calling CListCtrl::OnLButtonDown()
	// as it might cause a row-repaint
	m_nFocusCell = hitinfo.iSubItem;
	CListView::OnLButtonDown(nFlags, point);

	// CListCtrl::OnLButtonDown() doesn't always cause a row-repaint
	// call our own method to ensure the row is repainted
	UpdateFocusCell(hitinfo.iSubItem);
}

void CListViewExt::OnRButtonDown(UINT nFlags, CPoint point)
{
	if(! m_bGrid 
		|| (GetListCtrl().GetStyle() & LVS_TYPEMASK) != LVS_REPORT 
		|| ! (GetListCtrl().GetExtendedStyle() & LVS_EX_FULLROWSELECT))
	{
		CListView::OnRButtonDown(nFlags, point);
		return;
	}

	// Find out what subitem was clicked
	LVHITTESTINFO hitinfo = {0};
	hitinfo.flags = nFlags;
	hitinfo.pt = point;
	GetListCtrl().SubItemHitTest(&hitinfo);

	// If not right-clicking on an actual row, then don't update focus cell
	if(hitinfo.iItem == -1)
	{
		CListView::OnRButtonDown(nFlags, point);
		return;
	}

	// Update the focused cell before calling CListCtrl::OnLButtonDown()
	// as it might cause a row-repaint
	m_nFocusCell = hitinfo.iSubItem;
	CListView::OnRButtonDown(nFlags, point);

	// CListCtrl::OnLButtonDown() doesn't always cause a row-repaint
	// call our own method to ensure the row is repainted
	UpdateFocusCell(hitinfo.iSubItem);
}

void CListViewExt::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
	// Catch event before the parent listctrl gets it to avoid extra scrolling
	//	- OBS! This can also prevent the key-events to reach LVN_KEYDOWN handlers

	if(! m_bGrid 
		|| (GetListCtrl().GetStyle() & LVS_TYPEMASK) != LVS_REPORT 
		|| ! (GetListCtrl().GetExtendedStyle() & LVS_EX_FULLROWSELECT))
	{
		CListView::OnKeyDown(nChar, nRepCnt, nFlags);
		return;
	}

	switch(nChar)
	{
		case VK_RIGHT:	
			MoveFocusCell(TRUE);
			return;	// Do not allow scroll
		case VK_LEFT:
			MoveFocusCell(FALSE);
			return;	// Do not allow scroll
		case 0x41:	// CTRL+A (Select all rows)
			if(GetKeyState(VK_CONTROL) < 0)
			{
				if(! (GetListCtrl().GetStyle() & LVS_SINGLESEL))
				{
					GetListCtrl().SetItemState(-1, LVIS_SELECTED, LVIS_SELECTED);
				}
			}
			break;
		case VK_F2:
			DisplayEditor(GetListCtrl().GetNextItem(-1, LVNI_SELECTED),m_nFocusCell);
			break;
	}

	CListView::OnKeyDown(nChar, nRepCnt, nFlags);
}
// Keyboard search with subitems
void CListViewExt::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
{
	if(m_nFocusCell <= 0)
	{
		CListView::OnChar(nChar, nRepCnt, nFlags);
		return;
	}

	if(GetKeyState(VK_CONTROL) < 0)
	{
		m_sLastSearchString = _T("");
		return;
	}

	// No input within 2 seconds, resets the search
	if(m_tLastSearchTime.GetCurrentTime() >= (m_tLastSearchTime + 2) && m_sLastSearchString.GetLength() > 0)m_sLastSearchString = _T("");

	// Changing cells, resets the search
	if(m_nLastSearchCell != m_nFocusCell)m_sLastSearchString = _T("");

	// Changing rows, resets the search
	if(m_nLastSearchRow != GetListCtrl().GetNextItem(-1, LVNI_FOCUSED))m_sLastSearchString = _T("");

	m_nLastSearchCell = m_nFocusCell;
	m_tLastSearchTime = m_tLastSearchTime.GetCurrentTime();

	if(m_sLastSearchString.GetLength() == 1 && (UINT)m_sLastSearchString.GetAt(0) == nChar)
	{
		// When the same first character is entered again,
		// then just repeat the search
	}
	else m_sLastSearchString += (TCHAR)nChar;

	int nRow = GetListCtrl().GetNextItem(-1, LVNI_FOCUSED);
	if(nRow < 0)nRow = 0;
	int nCol = m_nFocusCell;
	if(nCol < 0)nCol = m_pHeaderCtrl->OrderToIndex(0);
	int nRowCount = GetListCtrl().GetItemCount();

	// Perform the search loop twice
	//	- First search from current position down to bottom
	//	- Then search from top to current position
	for(int j = 0;j < 2;++j)
	{
		for(int i = nRow + 1;i < nRowCount;++i)
		{
			CString cellText = GetListCtrl().GetItemText(i, nCol);
			if(cellText.GetLength() >= m_sLastSearchString.GetLength())
			{
				cellText = cellText.Left(m_sLastSearchString.GetLength());
				if(cellText.CompareNoCase(m_sLastSearchString) == 0)
				{
					// De-select all other rows
					GetListCtrl().SetItemState(-1, 0, LVIS_SELECTED);
					// Select row found
					GetListCtrl().SetItemState(i, LVIS_SELECTED, LVIS_SELECTED);
					// Focus row found
					GetListCtrl().SetItemState(i, LVIS_FOCUSED, LVIS_FOCUSED);	
					// Scroll to row found
					GetListCtrl().EnsureVisible(i, FALSE);			
					m_nLastSearchRow = i;
					return;
				}
			}
		}
		nRowCount = nRow;
		nRow = -1;
	}
}

void CListViewExt::OnNmRclickHeader(NMHDR* pNMHDR, LRESULT* pResult)
{
	LPNMHEADER phdr = reinterpret_cast<LPNMHEADER>(pNMHDR);

	// TODO: Add your control notification handler code here

	CPoint point;
	GetCursorPos(&point);

	int nHeaderItemCount = m_pHeaderCtrl->GetItemCount();
	int nHeaderRemovableItemCount = m_pHeaderCtrl->GetRemovableItemCount();

	CMenu menu;
	if(nHeaderItemCount > nHeaderRemovableItemCount && 
		menu.CreatePopupMenu())
	{
		const int TEXT_LEN = 64;
		const TCHAR TEXT_TAIL[] = _T("...");
		TCHAR szText[TEXT_LEN + sizeof(TEXT_TAIL) - 1];
		HDITEM hdi;
		hdi.mask = HDI_TEXT;
		hdi.pszText = szText;
		hdi.cchTextMax = TEXT_LEN;
		int nCount = m_pHeaderCtrl->GetItemCount();
		for(int i = 0;i < nCount;++i)
		{
			if(! m_pHeaderCtrl->GetItem(i, &hdi))return;
			if(hdi.cchTextMax == TEXT_LEN - 1)_tcscat(szText, TEXT_TAIL);
			UINT nFlags = MF_STRING;
			if(! m_pHeaderCtrl->GetRemovable(i))nFlags |= MF_GRAYED | MF_CHECKED;
			if(m_pHeaderCtrl->GetVisible(i))nFlags |= MF_CHECKED;
			if(! menu.AppendMenu(nFlags, i + 1, szText))return;
		}
		UINT nIndex = menu.TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON | TPM_RETURNCMD, point.x, point.y, this);
		if(nIndex > 0)
		{
			nIndex--;
			BOOL bVisible = m_pHeaderCtrl->GetVisible(nIndex);
			m_pHeaderCtrl->SetVisible(nIndex,! bVisible);
		}
	}

	*pResult = 0;
}

void CListViewExt::OnHdnDividerdblclick(NMHDR* pNMHDR, LRESULT* pResult)
{
	LPNMHEADER phdr = reinterpret_cast<LPNMHEADER>(pNMHDR);

	// TODO: Add your control notification handler code here

	*pResult = 1;

	int nItem = m_pHeaderCtrl->FindVisibleItem(phdr->iItem);
	if(nItem >= 0)
	{
		int nCount = GetListCtrl().GetItemCount();
		if(nCount > 0)
		{
			CString sTemp;
			int nMaxWidth = 0;
			CClientDC dc(this);
			CFont* pOldFont = dc.SelectObject(GetFont());
			for(int i = 0;i < nCount;++i)
			{
				sTemp = GetListCtrl().GetItemText(i, nItem);
				int w = GetListCtrl().GetStringWidth(sTemp);
				nMaxWidth = max(nMaxWidth, w + 12);
			}
			CImageList* pImgList = GetListCtrl().GetImageList(LVSIL_SMALL);
			if(pImgList != NULL && nItem == 0)
			{
				int cx, cy;
				ImageList_GetIconSize(pImgList->m_hImageList, &cx, &cy);
				nMaxWidth += cx;
			}
			GetListCtrl().SetColumnWidth(nItem, nMaxWidth);
			dc.SelectObject(pOldFont);
		}
	}
}

void CListViewExt::OnHdnEnddrag(NMHDR* pNMHDR, LRESULT* pResult)
{
	LPNMHEADER phdr = reinterpret_cast<LPNMHEADER>(pNMHDR);

	// TODO: Add your control notification handler code here

	m_pHeaderCtrl->PostMessage(WM_HDN_ENDDRAG, 0, 0L);
	*pResult = 0;
}

int CALLBACK CListViewExt::CompareProc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
{
	int nSort = 0;
	int nCompare = 0;
	ColumnInfo* pColInfo;
	CListViewExt* This = reinterpret_cast<CListViewExt*>(lParamSort);

	if(! This)return 0;
	if(! (This->m_nSortColumn < 0 || This->m_nSortColumn >= This->GetColumnCount()))
	{
		pColInfo = This->GetColumnInfo(This->m_nSortColumn);
		if(pColInfo && (pColInfo->m_eSort & SortBits))
		{
			nSort = pColInfo->m_eSort & Ascending ? 1 : -1 ;
			if(! This->m_fnCompare && pColInfo->m_fnCompare)This->m_fnCompare = pColInfo->m_fnCompare;
		}
	}
	if(This->m_fnCompare && This->m_fnCompare != &CListViewExt::CompareProc)
	{
		ItemData* pD1 = reinterpret_cast<ItemData*>(lParam1);
		ItemData* pD2 = reinterpret_cast<ItemData*>(lParam2);
		if(pD1)lParam1 = pD1->m_dwUserData;
		if(pD2)lParam2 = pD2->m_dwUserData;
		nCompare = This->m_fnCompare(lParam1, lParam2, This->m_dwSortData ? This->m_dwSortData : This->m_nSortColumn);
		if(! This->m_dwSortData && nSort)return nCompare * nSort;
	}
	if(! nSort)return 0;

	int nLeft = This->GetItemIndexFromData(lParam1);
	int nRight = This->GetItemIndexFromData(lParam2);

	if(nLeft < 0) nLeft = lParam1;
	if(nRight < 0) nRight = lParam2;
	int nCount = This->GetListCtrl().GetItemCount();
	if(nLeft < 0 || nRight < 0 || nLeft >= nCount || nRight >= nCount)return 0;
	nCompare = Compare(nLeft, nRight, lParamSort);

	return nCompare * nSort;
}

BOOL CListViewExt::SortItems(PFNLVCOMPARE pfnCompare, DWORD_PTR dwData)
{
	int nCount = GetListCtrl().GetItemCount();
	DWORD_PTR dwEditingItemData = 0;
	if(m_nEditingRow >= 0 && m_nEditingRow < nCount)dwEditingItemData = GetItemDataInternal(m_nEditingRow);
	CString dbg;
	dbg.Format(_T("\nBefore : %d"), m_nEditingRow);
	OutputDebugString(dbg);
	m_fnCompare = pfnCompare;
	m_dwSortData = dwData;
	BOOL bReturn = GetListCtrl().SortItems(CompareProc, (DWORD_PTR)this);
	m_fnCompare = NULL;
	m_dwSortData = NULL;
	if(dwEditingItemData)m_nEditingRow = GetItemIndexFromData(dwEditingItemData);
	dbg.Format(_T("\nAfter : %d"), m_nEditingRow);
	OutputDebugString(dbg);

	return bReturn;
}

BOOL CListViewExt::SortOnColumn(int nColumn, BOOL bChangeOrder)
{
	ColumnInfo* pColInfo;
	if((pColInfo = GetColumnInfo(nColumn)) && (pColInfo->m_eSort & SortBits))
	{
		if(pColInfo->m_eSort & Auto)
		{
			pColInfo->m_eSort =(Sort)((pColInfo->m_eSort & (Ascending | Descending)) ? pColInfo->m_eSort : pColInfo->m_eSort | Descending);
			if(bChangeOrder)pColInfo->m_eSort = (Sort)(pColInfo->m_eSort ^ (Ascending | Descending));
		}
		HDITEM hd;
		hd.mask = HDI_FORMAT;
		m_pHeaderCtrl->GetItem(m_nSortColumn, &hd);
		hd.fmt = hd.fmt & ~(HDF_SORTDOWN | HDF_SORTUP);
		m_pHeaderCtrl->SetItem(m_nSortColumn, &hd);
		m_nSortColumn = nColumn;
		GetListCtrl().SortItems(CompareProc, (DWORD_PTR)this);
		m_pHeaderCtrl->GetItem(nColumn, &hd);
		hd.fmt = hd.fmt & ~(HDF_SORTDOWN | HDF_SORTUP);
		hd.fmt |= pColInfo->m_eSort & Ascending ? HDF_SORTUP : HDF_SORTDOWN;
		m_pHeaderCtrl->SetItem(nColumn, &hd);
		//	to store of which column and direction was clicked
		m_bColumnSort = pColInfo->m_eSort & Ascending ? TRUE : FALSE;
		return TRUE;
	}

	return FALSE;
}

void CListViewExt::SetColumnSorting(int nColumn, Sort eSort, PFNLVCOMPARE fnCallBack)
{
	if(nColumn < 0 || nColumn >= GetColumnCount() || ! (eSort & SortBits))return;

	ColumnInfo* pInfo = (ColumnInfo*)GetColumnInfo(nColumn);
	if(! pInfo)SetColumnEditor(nColumn, (CWnd*)NULL);
	pInfo = (ColumnInfo*)GetColumnInfo(nColumn);
	if(pInfo)
	{
		pInfo->m_eSort = eSort;
		pInfo->m_eCompare = NotSet;
		pInfo->m_fnCompare = fnCallBack;
	}
}

int CListViewExt::Compare(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
{
	CListViewExt* This = (CListViewExt*)lParamSort;
	if(! This || This->m_nSortColumn < 0 || This->m_nSortColumn >= This->GetColumnCount())return 0;
	int nSubItem = This->m_nSortColumn;
	ColumnInfo* pInfo = This->GetColumnInfo(nSubItem);
	if(! pInfo)return 0;

	CString sLeft = This->GetListCtrl().GetItemText(lParam1, nSubItem);
	CString sRight = This->GetListCtrl().GetItemText(lParam2, nSubItem);

	switch(pInfo->m_eCompare)
	{
	case Int:
		return CompareInt(sLeft, sRight);
	case Double:
		return CompareDouble(sLeft, sRight);
	case StringNoCase:
		return CompareStringNoCase(sLeft, sRight);
	case StringNumber:
		return CompareNumberString(sLeft, sRight);
	case StringNumberNoCase:
		return CompareNumberStringNoCase(sLeft, sRight);
	case String:
		return CompareString(sLeft, sRight);
	case Date:
		return CompareDate(sLeft, sRight);
	case NotSet:
		return 0;
	default:
		return CompareString(sLeft, sRight);
	}

	return CompareString(sLeft, sRight);
}

int CListViewExt::CompareInt(LPCSTR pLeftText, LPCSTR pRightText)
{
	return (int)(atol(pLeftText) - atol(pRightText));
}

int CListViewExt::CompareDouble(LPCSTR pLeftText, LPCSTR pRightText)
{
	return (int)(atof(pLeftText) - atof(pRightText));
}

int CListViewExt::CompareString(LPCSTR pLeftText, LPCSTR pRightText)
{
	return CString(pLeftText).Compare(pRightText);
}

int CListViewExt::CompareNumberString(LPCSTR pLeftText, LPCSTR pRightText)
{
	LONGLONG l1 = atol(pLeftText);
	LONGLONG l2 = atol(pRightText);

	if(l1 && l2 && (l1 - l2))
	{
		CString sTemp1, sTemp2;
		sTemp1.Format(_T("%ld"), l1);
		sTemp2.Format(_T("%ld"), l2);
		CString left(pLeftText);
		CString right(pRightText);
		if(sTemp1.GetLength() == left.GetLength() && sTemp2.GetLength() == right.GetLength())return (int)(l1 - l2);
	}

	return CString(pLeftText).Compare(pRightText);
}

int CListViewExt::CompareNumberStringNoCase(LPCSTR pLeftText, LPCSTR pRightText)
{
	LONGLONG l1 = atol(pLeftText);
	LONGLONG l2 = atol(pRightText);

	if(l1 && l2 && (l1 - l2))
	{
		CString sTemp1, sTemp2;
		sTemp1.Format(_T("%ld"),l1);
		sTemp2.Format(_T("%ld"),l2);
		CString left(pLeftText);
		CString right(pRightText);
		if(sTemp1.GetLength() == left.GetLength() && sTemp2.GetLength() == right.GetLength())return (int)(l1 - l2);
	}

	return CString(pLeftText).CompareNoCase(pRightText);
}

int CListViewExt::CompareStringNoCase(LPCSTR pLeftText, LPCSTR pRightText)
{
	return CString(pLeftText).CompareNoCase(pRightText);
}

int CListViewExt::CompareDate(LPCSTR pLeftText, LPCSTR pRightText)
{
	COleDateTime dL, dR;
	dL.ParseDateTime(pLeftText);
	dR.ParseDateTime(pRightText);

	return (dL == dR ? 0 : (dL < dR ? -1 : 1));
}

void CListViewExt::SetColumnSorting(int nColumn, Sort eSort, Comparer eComparer)
{
	if(nColumn < 0 || nColumn >= GetColumnCount() || ! (eSort & SortBits))return;

	ColumnInfo* pInfo = (ColumnInfo*)GetColumnInfo(nColumn);
	if(! pInfo)SetColumnEditor(nColumn, (CWnd*)NULL);
	pInfo = (ColumnInfo*)GetColumnInfo(nColumn);
	if(pInfo)
	{
		pInfo->m_eSort = eSort;
		pInfo->m_eCompare = eComparer;
		pInfo->m_fnCompare = NULL;
	}
}

void CListViewExt::SetColumnReadOnly(int nColumn, BOOL bReadOnly)
{
	if(nColumn < 0 || nColumn >= GetColumnCount())return;

	ColumnInfo* pInfo = (ColumnInfo*)GetColumnInfo(nColumn);
	if(! pInfo)SetColumnEditor(nColumn, (CWnd*)NULL);
	pInfo = (ColumnInfo*)GetColumnInfo(nColumn);
	if(pInfo)pInfo->m_eiEditor.m_bReadOnly = bReadOnly;
}

void CListViewExt::SetCellReadOnly(int nRow, int nColumn, BOOL bReadOnly )
{
	if(nRow < 0 || nRow >= GetListCtrl().GetItemCount() || nColumn < 0 || nColumn >= GetColumnCount())return;

	CellInfo* pInfo = (CellInfo*)GetCellInfo(nRow, nColumn);
	if(! pInfo)SetCellEditor(nRow, nColumn, (CWnd*)NULL);
	pInfo = (CellInfo*)GetCellInfo(nRow, nColumn);
	if(pInfo)pInfo->m_eiEditor.m_bReadOnly = bReadOnly;
}

void CListViewExt::SetRowReadOnly(int nRow, BOOL bReadOnly)
{
	if(nRow < 0 || nRow >= GetListCtrl().GetItemCount())return;

	ItemData* pInfo = (ItemData*)GetItemDataInternal(nRow);
	if(! pInfo)SetItemUserData(nRow, 0);
	pInfo = (ItemData*)GetItemDataInternal(nRow);
	if(pInfo)pInfo->m_eiEditor.m_bReadOnly = bReadOnly;
}

BOOL CListViewExt::IsColumnReadOnly(int nColumn)
{
	if(nColumn < 0 || nColumn >= GetColumnCount())return FALSE;

	ColumnInfo *pInfo = (ColumnInfo*)GetColumnInfo(nColumn);
	if(pInfo)return pInfo->m_eiEditor.m_bReadOnly;

	return FALSE;
}

BOOL CListViewExt::IsColumnHidden(int nColumn)
{
	return ! m_pHeaderCtrl->GetVisible(nColumn);
}

BOOL CListViewExt::IsRowReadOnly(int nRow)
{
	if(nRow < 0 || nRow >= GetListCtrl().GetItemCount())return FALSE;

	ItemData* pInfo = (ItemData*)GetItemDataInternal(nRow);
	if(pInfo)return pInfo->m_eiEditor.m_bReadOnly;

	return FALSE;
}

BOOL CListViewExt::IsCellReadOnly(int nRow, int nColumn)
{
	if(nRow < 0 || nRow >= GetListCtrl().GetItemCount() || nColumn < 0 || nColumn >= GetColumnCount())return FALSE;

	CellInfo* pInfo = (CellInfo*)GetCellInfo(nRow, nColumn);
	if(pInfo)return pInfo->m_eiEditor.m_bReadOnly;
	else return (IsRowReadOnly(nRow) || IsColumnReadOnly(nColumn));
}

void CListViewExt::SetRowColors(int nItem, COLORREF clrBk, COLORREF clrText)
{
	ItemData* pData = (ItemData*)GetItemDataInternal(nItem);
	if(! pData)SetItemUserData(nItem, 0);
	pData = (ItemData*)GetItemDataInternal(nItem);
	if(! pData)return;

	pData->m_clrText = clrText;
	pData->m_clrBack = clrBk;
	GetListCtrl().Update(nItem);
}

void CListViewExt::SetColumnColors(int nColumn, COLORREF clrBack, COLORREF clrText)
{
	if(nColumn < 0 || nColumn >= GetColumnCount())return;

	ColumnInfo* pColInfo = GetColumnInfo(nColumn);
	if(! pColInfo)
	{ 
		pColInfo = new ColumnInfo(nColumn);  
		m_aColumnInfo.Add(pColInfo);
	}

	if(pColInfo)
	{
		pColInfo->m_clrBack = clrBack;
		pColInfo->m_clrText = clrText;
	}
}

void CListViewExt::SetCellColors(int nRow, int nColumn, COLORREF clrBack, COLORREF clrText)
{
	if(nRow < 0 || nRow >= GetListCtrl().GetItemCount() || nColumn < 0 || nColumn >= GetColumnCount())return;

	CellInfo* pCellInfo = GetCellInfo(nRow, nColumn);
	if(! pCellInfo)SetCellData(nRow, nColumn, 0);

	pCellInfo = GetCellInfo(nRow, nColumn);
	if(pCellInfo)
	{
		pCellInfo->m_clrBack = clrBack;
		pCellInfo->m_clrText = clrText;
	}
}

void CListViewExt::SetColumnEditor(int nColumn, PFNEDITORCALLBACK pfnInitEditor, PFNEDITORCALLBACK pfnEndEditor, CWnd* pWnd)
{
	ColumnInfo* pColInfo = GetColumnInfo(nColumn);
	if(! pColInfo)
	{ 
		pColInfo = new ColumnInfo(nColumn);
		m_aColumnInfo.Add(pColInfo);
	}
	if(pColInfo)
	{
		pColInfo->m_eiEditor.m_pfnInitEditor = pfnInitEditor;
		pColInfo->m_eiEditor.m_pfnEndEditor = pfnEndEditor;
		pColInfo->m_eiEditor.m_pWnd = pWnd;
	}
}

void CListViewExt::SetColumnEditor(int nColumn, CWnd* pWnd)
{
	SetColumnEditor(nColumn, NULL, NULL, pWnd);
}

void CListViewExt::SetRowEditor(int nRow, CWnd* pWnd)
{
	SetRowEditor(nRow, NULL, NULL, pWnd);
}

void CListViewExt::SetRowEditor(int nRow, PFNEDITORCALLBACK pfnInitEditor, PFNEDITORCALLBACK pfnEndEditor, CWnd* pWnd)
{
	ItemData* pData = (ItemData*)GetItemDataInternal(nRow);
	if(! pData)
	{ 
		SetItemUserData(nRow, 0);
		pData = (ItemData*)GetItemDataInternal(nRow);
	}
	if(pData)
	{
		pData->m_eiEditor.m_pfnInitEditor = pfnInitEditor;
		pData->m_eiEditor.m_pfnEndEditor = pfnEndEditor;
		pData->m_eiEditor.m_pWnd = pWnd;
	}
}

BOOL CListViewExt::DisplayEditor(int nItem, int nSubItem)
{
	int nCount = GetListCtrl().GetItemCount();
	DWORD_PTR dwEditingItemData = 0;
	if(nItem >= 0 && nItem < nCount)dwEditingItemData = GetItemDataInternal(nItem);
	HideEditor();
	if(dwEditingItemData)nItem = GetItemIndexFromData(dwEditingItemData);
	if(nItem < 0 || nItem > nCount || nSubItem < 0 || nSubItem > GetColumnCount()
		|| IsColumnReadOnly(nSubItem) || IsRowReadOnly(nItem) || IsCellReadOnly(nItem, nSubItem) || IsColumnHidden(nSubItem))return FALSE;
	CRect rectSubItem;
//	GetSubItemRect(nItem, nSubItem, LVIR_LABEL, rectSubItem);
	EnsureSubItemVisible(nItem, nSubItem, &rectSubItem);

	CellInfo* pCellInfo = GetCellInfo(nItem, nSubItem);
	ColumnInfo* pColInfo = GetColumnInfo(nSubItem);
	ItemData* pRowInfo = (ItemData*)GetItemDataInternal(nItem);

	BOOL bReadOnly = FALSE;
	m_pEditor = &m_eiDefEditor;
	if(pColInfo && ! (bReadOnly |= pColInfo->m_eiEditor.m_bReadOnly) && pColInfo->m_eiEditor.IsSet())m_pEditor = &pColInfo->m_eiEditor;
	if(pRowInfo && ! (bReadOnly |= pRowInfo->m_eiEditor.m_bReadOnly) && pRowInfo->m_eiEditor.IsSet())m_pEditor = &pRowInfo->m_eiEditor;
	if(pCellInfo && ! (bReadOnly |= pCellInfo->m_eiEditor.m_bReadOnly) && pCellInfo->m_eiEditor.IsSet())m_pEditor = &pCellInfo->m_eiEditor;
	if(bReadOnly || ! m_pEditor || ! m_pEditor->IsSet() || m_pEditor->m_bReadOnly)
	{
		m_pEditor = NULL;
		return FALSE;
	}

	m_nEditingRow = nItem;
	m_nEditingColumn = nSubItem;
	m_nRow = nItem;
	m_nColumn = nSubItem;
	CString sText =  GetListCtrl().GetItemText(nItem, nSubItem);
	if(m_pEditor->m_pfnInitEditor)m_pEditor->m_pfnInitEditor(&m_pEditor->m_pWnd, nItem, nSubItem, sText, GetItemUserData(nItem), this, TRUE);

	if(! m_pEditor->m_pWnd)return FALSE;
	SelectItem(-1, FALSE);
	if(! m_pEditor->m_pfnInitEditor)m_pEditor->m_pWnd->SetWindowText(sText);

	m_pEditor->m_pWnd->SetParent(this);
	m_pEditor->m_pWnd->SetOwner(this);

	m_pEditor->m_pWnd->SetWindowPos(NULL, rectSubItem.left, rectSubItem.top, rectSubItem.Width(), rectSubItem.Height(), SWP_SHOWWINDOW);
	m_pEditor->m_pWnd->ShowWindow(SW_SHOW);
	m_pEditor->m_pWnd->SetFocus();

	m_MsgHook.Attach(m_pEditor->m_pWnd->m_hWnd, this->m_hWnd);

	return TRUE;
}

void CListViewExt::HideEditor(BOOL bUpdate)
{
	CSingleLock lock(&m_oLock, TRUE);

	if(lock.IsLocked() && m_MsgHook.Detach())
	{	
		if(m_pEditor && m_pEditor->m_pWnd)
		{
			m_pEditor->m_pWnd->ShowWindow(SW_HIDE);
			CString sText;
			DWORD_PTR dwData = 0;
			if(GetListCtrl().GetItemCount() > m_nEditingRow)
			{
				sText =  GetListCtrl().GetItemText(m_nEditingRow, m_nEditingColumn);
				dwData = GetItemUserData(m_nEditingRow);
			}
			else bUpdate = FALSE;
			if(m_pEditor->m_pfnEndEditor)bUpdate = m_pEditor->m_pfnEndEditor(&m_pEditor->m_pWnd, m_nEditingRow, m_nEditingColumn, sText, dwData, this, bUpdate);
			else m_pEditor->m_pWnd->GetWindowText(sText);
			if(bUpdate)GetListCtrl().SetItemText(m_nEditingRow, m_nEditingColumn, sText);
			if(GetListCtrl().GetItemCount() > m_nEditingRow)GetListCtrl().Update(m_nEditingRow);
			if(bUpdate == -1)SortOnColumn(m_nEditingColumn);
			m_pEditor = NULL;
		}
	}

	lock.Unlock();
}

void CListViewExt::SetCellEditor(int nRow, int nColumn, PFNEDITORCALLBACK pfnInitEditor, PFNEDITORCALLBACK pfnEndEditor, CWnd* pWnd)
{
	if(nRow < 0 || nRow >= GetListCtrl().GetItemCount() || nColumn < 0 || nColumn >= GetColumnCount())return;

	CellInfo* pCellInfo = GetCellInfo(nRow, nColumn);
	if(! pCellInfo)
	{ 
		SetCellData(nRow, nColumn, 0);
		pCellInfo = (CellInfo*)GetCellInfo(nRow, nColumn);
	}

	if(pCellInfo)
	{
		pCellInfo->m_eiEditor.m_pfnInitEditor = pfnInitEditor;
		pCellInfo->m_eiEditor.m_pfnEndEditor = pfnEndEditor;
		pCellInfo->m_eiEditor.m_pWnd = pWnd;
	}
}

void CListViewExt::SetCellEditor(int nRow, int nColumn, CWnd* pWnd)
{
	SetCellEditor(nRow, nColumn, NULL, NULL, pWnd);
}

void CListViewExt::SetDefaultEditor(PFNEDITORCALLBACK pfnInitEditor, PFNEDITORCALLBACK pfnEndEditor, CWnd* pWnd)
{
	m_eiDefEditor.m_pfnInitEditor = pfnInitEditor;
	m_eiDefEditor.m_pfnEndEditor = pfnEndEditor;
	m_eiDefEditor.m_pWnd = pWnd;
}

void CListViewExt::SetDefaultEditor(CWnd* pWnd)
{
	SetDefaultEditor(NULL, NULL, pWnd);
}

BOOL CListViewExt::SaveState(LPCTSTR lpszListName)
{
	CWinApp* pApp = (CWinApp*)AfxGetApp();

	int nCount = m_pHeaderCtrl->GetItemCount();
	pApp->WriteProfileInt(lpszListName,_T("ColumnCount"),nCount);

	int* nColOrder = new int[nCount];
	m_pHeaderCtrl->GetOrderArray(nColOrder,nCount);
	pApp->WriteProfileBinary(lpszListName,_T("ColumnOrder"),(BYTE*)nColOrder,sizeof(int) * nCount);
	delete []nColOrder;

	int* nColWidth = new int[nCount];
	m_pHeaderCtrl->GetWidthArray(nColWidth,nCount);
	pApp->WriteProfileBinary(lpszListName,_T("ColumnWidth"),(BYTE*)nColWidth,sizeof(int) * nCount);
	delete []nColWidth;

	int* nColVisible = new int[nCount];
	m_pHeaderCtrl->GetVisibleArray(nColVisible,nCount);
	pApp->WriteProfileBinary(lpszListName,_T("ColumnVisible"),(BYTE*)nColVisible,sizeof(int) * nCount);
	delete []nColVisible;

	pApp->WriteProfileInt(lpszListName,_T("ColSortNum"),m_nSortColumn);
	pApp->WriteProfileInt(lpszListName,_T("ColSortDir"),m_bColumnSort);

	return TRUE;
}

BOOL CListViewExt::RestoreState(LPCTSTR lpszListName)
{
	CWinApp* pApp = (CWinApp*)AfxGetApp();

	int nCount = pApp->GetProfileInt(lpszListName,_T("ColumnCount"), 0);
	if(nCount != m_pHeaderCtrl->GetItemCount())return FALSE;

	UINT nBytes;
	BOOL bReturn = FALSE;
	int* nColOrder = NULL;
	if((pApp->GetProfileBinary(lpszListName,_T("ColumnOrder"),(BYTE**)&nColOrder,&nBytes) && nBytes == sizeof(int) * nCount))
	{
		bReturn = VerifyOrderArray(nColOrder,nCount) && m_pHeaderCtrl->SetOrderArray(nCount,nColOrder);
		ASSERT(bReturn);
	}
	delete []nColOrder;
	if(! bReturn)return FALSE;

	bReturn = FALSE;
	int* nColWidth = NULL;
	if((pApp->GetProfileBinary(lpszListName,_T("ColumnWidth"),(BYTE**)&nColWidth,&nBytes) && nBytes == sizeof(int) * nCount))
		bReturn = m_pHeaderCtrl->SetWidthArray(nCount,nColWidth);
	delete []nColWidth;
	if(! bReturn)return FALSE;

	bReturn = FALSE;
	int* nColVisible = NULL;
	if((pApp->GetProfileBinary(lpszListName,_T("ColumnVisible"),(BYTE**)&nColVisible,&nBytes) && nBytes == sizeof(int) * nCount))
		bReturn = m_pHeaderCtrl->SetVisibleArray(nCount,nColVisible);
	delete []nColVisible;

	int nColumnSort = pApp->GetProfileInt(lpszListName,_T("ColSortNum"),0);
	BOOL bColumnSort = pApp->GetProfileInt(lpszListName,_T("ColSortDir"),0);
	SortOnColumn(nColumnSort,bColumnSort);

	return bReturn;
}

BOOL CListViewExt::VerifyOrderArray(int* piArray, int nCount)
{
	for(int i = 0;i < nCount;++i)
	{
		if(! (piArray[i] >= 0 && piArray[i] <= nCount - 1))return FALSE;
		// compare with items after current one
		for(int j = i + 1;j < nCount;++j)
		{
			if(piArray[i] == piArray[j])return FALSE;
		}
	}

	return TRUE;
}

void CListViewExt::UpdateFocusCell(int nCol)
{
	m_nFocusCell = nCol;	// Update focus cell before starting re-draw
	int nFocusRow = GetListCtrl().GetNextItem(-1, LVNI_FOCUSED);

	if(nFocusRow >= 0)
	{
		CRect itemRect;
		VERIFY(GetListCtrl().GetItemRect(nFocusRow, itemRect, LVIR_BOUNDS));
		InvalidateRect(itemRect);
		UpdateWindow();
	}
}

void CListViewExt::MoveFocusCell(BOOL bRight)
{
	if(GetListCtrl().GetItemCount() <= 0)
	{
		m_nFocusCell = -1;	// Entire row selected
		return;
	}

	if(m_nFocusCell == -1)
	{
		// Entire row already selected
		if(bRight)
		{
			// Change to the first column in the current order
			m_nFocusCell = m_pHeaderCtrl->OrderToIndex(0);
		}
	}
	else
	{
		// Convert focus-cell to order index
		int nOrderIndex = -1;
		for(int i = 0;i < m_pHeaderCtrl->GetItemCount();++i)
		{
			int nCol = m_pHeaderCtrl->OrderToIndex(i);
			if(nCol == m_nFocusCell)
			{
				nOrderIndex = i;
				break;
			}
		}
		// Move to the following column
		if(bRight)nOrderIndex++;
		else if(nOrderIndex > 0)nOrderIndex--;
		// Convert order-index to focus cell
		if(nOrderIndex >= 0 && nOrderIndex < m_pHeaderCtrl->GetItemCount())
		{
			int nCol = m_pHeaderCtrl->OrderToIndex(nOrderIndex);
			if(! IsColumnHidden(nCol))m_nFocusCell = nCol;
		}
	}
	// Ensure the column is visible
	if(m_nFocusCell >= 0)
	{
		VERIFY(EnsureColumnVisible(m_nFocusCell, FALSE));
	}

	UpdateFocusCell(m_nFocusCell);
}

BOOL CListViewExt::EnsureColumnVisible(int nCol, BOOL bPartialOK)
{
	if(nCol < 0 || nCol >= m_pHeaderCtrl->GetItemCount())return FALSE;

	CRect rcHeader;
	int nScrollX = 0,nOffset = GetScrollPos(SB_HORZ);
	if(m_pHeaderCtrl->GetItemRect(nCol, rcHeader) == FALSE)return FALSE;

	CRect rcClient;
	GetClientRect(&rcClient);

	if(bPartialOK)
	{
		if((rcHeader.left - nOffset < rcClient.right) && (rcHeader.right - nOffset > 0))
		{
			return TRUE;
		}
	}
	if((rcHeader.Width() > rcClient.Width()) || (rcHeader.left - nOffset < 0))
	{
		nScrollX = rcHeader.left - nOffset;
	}
	else
	{
		if(rcHeader.right - nOffset > rcClient.right)
		{
			nScrollX = rcHeader.right - nOffset - rcClient.right;
		}
	}

	if(nScrollX != 0)
	{
		CSize size(nScrollX, 0);
		if(GetListCtrl().Scroll(size) == FALSE)return FALSE;
	}

	return TRUE;
}

BOOL CListViewExt::GetCellRect(int nRow, int nCol, CRect& rect)
{
	// Find the top and bottom of the cell-rectangle
	CRect rowRect,colRect;
	if(GetListCtrl().GetItemRect(nRow, rowRect, LVIR_BOUNDS) == FALSE || 
		m_pHeaderCtrl->GetItemRect(nCol, colRect) == FALSE)return FALSE;

	// Adjust for scrolling
	colRect.left -= GetScrollPos(SB_HORZ);
	colRect.right -= GetScrollPos(SB_HORZ);

	rect.left = colRect.left;
	rect.top = rowRect.top;
	rect.right = colRect.right;
	rect.bottom = rowRect.bottom;

	return TRUE;
}

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
Romania Romania
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions