www.pudn.com > CReportCtrl_Demo.zip > ReportCtrl.cpp


/////////////////////////////////////////////////////////////////////////////// 
// ReportCtrl.cpp 
// 
// CReportCtrl, a CListCtrl derived class that is specialized on "Report View" 
// style.  
// 
// Features: 
// 
// 1, Item sorting by clicking on column header. 
// 2, Sub-item text edit. 
// 3, Item repositioning. 
// 4, Customizing checkbox styles, including "single" and "disabled". 
// 5, Sending a message to parent window when user clicks on a checkbox. 
// 6, Convenient item insertion, deletion, moving, and sub-item text changing. 
// 7, Sub-item images and color 
// 8, And much more...  
// 
// This code may be used in compiled form in any way you desire. This file may be 
// redistributed unmodified by any means PROVIDING it is not sold for profit without 
// the authors written consent, and providing that this notice and the authors name  
// is included. If the source code in  this file is used in any commercial application  
// then acknowledgement must be made to the author of this file . 
// 
// This file is provided "as is" with no expressed or implied warranty. 
// 
// Written by Bin Liu (abinn32@yahoo.com) 
// 
// History 
// 
// Nov. 26, 2003 - Initial release. 
// Dec. 03, 2003 - Fixed a bug in "EndEdit" where item text were not preperly committed. 
//               - Completed the implementation of the "Sort-Separator" feature. 
// Jan. 01, 2004 - Fixed a bug in "SetItemData". 
//               - Added message "WM_EDIT_COMMITTED" which is sent to the parent window 
//                 when an item text editing is committed. 
//               - Fixed a bug in "SetItemText"(double type). 
//               - Fixed a bug where item sorting does not work properly when there 
//                 are multiple CReportCtrl objects on same window. 
// 
/////////////////////////////////////////////////////////////////////////////// 
 
#include "stdafx.h" 
#include "ReportCtrl.h" 
#include  
 
#ifdef _DEBUG 
#define new DEBUG_NEW 
#undef THIS_FILE 
static char THIS_FILE[] = __FILE__; 
#endif 
 
// Below styles MUST be present in a report ctrl 
#define MUST_STYLE			(WS_CHILD | WS_VISIBLE | WS_HSCROLL | WS_VSCROLL | LVS_REPORT) 
#define MUST_EX_STYLE		(LVS_EX_FULLROWSELECT | LVS_EX_SUBITEMIMAGES) 
 
// Below styles MUST NOT be present in a report ctrl 
#define MUST_NOT_STYLE		(LVS_EDITLABELS | LVS_ICON | LVS_SMALLICON | LVS_LIST | LVS_NOSCROLL) 
#define MUST_NOT_EX_STYLE	(0) 
 
struct ROWINFO 
{ 
	DWORD dwData; 
	DWORD dwStates; 
	CArray aImages; 
	CStringArray aTexts; 
}; 
 
////////////////////////////////////////////////////////////////////////// 
// CItemData class is used for store extra information 
////////////////////////////////////////////////////////////////////////// 
class CItemData 
{ 
public: 
	CItemData() { dwData = 0; } 
	void InsertColumn(int nColumn); 
	void DeleteColumn(int nColumn); 
	DWORD dwData; // The actual 32-bit user data stores here 
	CArray aTextColors; // Sub item text colors 
	CArray aBkColors; // Sub item backgroud colors 
}; 
 
void CItemData::InsertColumn(int nColumn) 
{ 
	aTextColors.InsertAt(nColumn, ::GetSysColor(COLOR_WINDOWTEXT)); 
	aBkColors.InsertAt(nColumn, ::GetSysColor(COLOR_WINDOW)); 
} 
 
void CItemData::DeleteColumn(int nColumn) 
{ 
	aTextColors.RemoveAt(nColumn); 
	aBkColors.RemoveAt(nColumn); 
} 
 
/////////////////////////////////////////////////////////////////////// 
// A set of functions used for item text format determining 
/////////////////////////////////////////////////////////////////////// 
namespace _ITEM_COMPARE_FUNCS 
{ 
	BOOL _IsDecNumber(const CString& str, double& f); 
	int _DecNumberCompare(double f1, double f2); 
	BOOL _IsHexNumber(const CString& str, DWORD& dw); 
	int _HexNumberCompare(DWORD dw1, DWORD dw2); 
	BOOL _IsDate(const CString& str, COleDateTime& date); 
	int _DateCompare(const COleDateTime& date1, const COleDateTime& date2); 
}; 
 
BOOL _ITEM_COMPARE_FUNCS::_IsDecNumber(const CString& str, double& f) 
{ 
	if (str.IsEmpty()) 
		return FALSE; 
 
	LPTSTR p; 
	f = _tcstod(str, &p); 
	return (*p == _T('\0') || (*p == _T('%') && p[1] == _T('\0'))); 
} 
 
int _ITEM_COMPARE_FUNCS::_DecNumberCompare(double f1, double f2) 
{ 
	if(f1 < f2) 
		return -1; 
	 
	if(f1 > f2) 
		return 1; 
 
	return 0; 
} 
 
BOOL _ITEM_COMPARE_FUNCS::_IsHexNumber(const CString& str, DWORD& dw) 
{ 
	if (str.IsEmpty()) 
		return FALSE; 
 
	LPTSTR p; 
	dw = _tcstoul(str, &p, 16); 
	return *p == _T('\0'); 
} 
 
int _ITEM_COMPARE_FUNCS::_HexNumberCompare(DWORD dw1, DWORD dw2) 
{ 
	if (dw1 > dw2) 
		return 1; 
 
	if (dw1 < dw2) 
		return -1; 
 
	return 0; 
} 
 
BOOL _ITEM_COMPARE_FUNCS::_IsDate(const CString& str, COleDateTime& date) 
{ 
	return date.ParseDateTime(str); 
} 
 
int _ITEM_COMPARE_FUNCS::_DateCompare(const COleDateTime& date1, const COleDateTime& date2) 
{ 
	if (date1 < date2) 
		return -1; 
 
	if (date1 > date2) 
		return 1; 
 
	return 0; 
} 
 
///////////////////////////////////////////////////////////////////////////// 
// CReportCtrl Implementation 
///////////////////////////////////////////////////////////////////////////// 
CReportCtrl::CReportCtrl(): m_pWndEdit(NULL) 
{ 
	m_pWndEdit = new CEdit; 
	VERIFY(m_pWndEdit != NULL); 
	m_pszSeparator = NULL; 
	m_bAllowEdit = FALSE; 
	m_ptEditting.x = -1; 
	m_ptEditting.y = -1; 
	m_nChkStyle = RC_CHKBOX_NORMAL; 
	m_dwPrevEditFmt = ES_LEFT; 
	m_nSortCol = -1; 
	m_bSortAscending = TRUE; 
} 
 
CReportCtrl::~CReportCtrl() 
{	 
	if (m_pszSeparator != NULL) 
		delete [] m_pszSeparator; 
 
	if (m_pWndEdit != NULL) 
		delete m_pWndEdit; 
} 
 
 
BEGIN_MESSAGE_MAP(CReportCtrl, CListCtrl) 
	//{{AFX_MSG_MAP(CReportCtrl) 
	ON_NOTIFY_REFLECT(LVN_COLUMNCLICK, OnColumnclick) 
	ON_WM_LBUTTONDOWN() 
	ON_WM_CREATE() 
	ON_WM_DESTROY() 
	ON_WM_LBUTTONDBLCLK() 
	ON_WM_MBUTTONDOWN() 
	ON_WM_MBUTTONDBLCLK() 
	ON_WM_RBUTTONDOWN() 
	ON_WM_RBUTTONDBLCLK() 
	ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, OnCustomDraw) 
	//}}AFX_MSG_MAP 
END_MESSAGE_MAP() 
 
///////////////////////////////////////////////////////////////////////////// 
// CReportCtrl message handlers 
void CReportCtrl::OnCustomDraw(NMHDR* pNMHDR, LRESULT* pResult)  
{ 
	LPNMLVCUSTOMDRAW lplvcd = (LPNMLVCUSTOMDRAW)pNMHDR; 
 
	if (lplvcd->nmcd.dwDrawStage == CDDS_PREPAINT) 
	{ 
	    *pResult = CDRF_NOTIFYITEMDRAW; 
	} 
    else if (lplvcd->nmcd.dwDrawStage == CDDS_ITEMPREPAINT) 
	{ 
	    *pResult = CDRF_NOTIFYSUBITEMDRAW; 
	} 
    else if (lplvcd->nmcd.dwDrawStage == (CDDS_ITEMPREPAINT | CDDS_SUBITEM)) 
    { 
		CItemData* p = (CItemData*)(CListCtrl::GetItemData(lplvcd->nmcd.dwItemSpec)); 
		ASSERT(p != NULL); 
		ASSERT(lplvcd->iSubItem >= 0 && lplvcd->iSubItem < p->aTextColors.GetSize()); 
		lplvcd->clrText = p->aTextColors[lplvcd->iSubItem]; 
		lplvcd->clrTextBk = p->aBkColors[lplvcd->iSubItem]; 
		*pResult = CDRF_DODEFAULT; 
	} 
} 
 
void CReportCtrl::OnColumnclick(NMHDR* pNMHDR, LRESULT* pResult)  
{ 
	NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR; 
	// TODO: Add your control notification handler code here 
	const int COL = pNMListView->iSubItem; 
	SortItems(COL, COL == m_nSortCol ? !m_bSortAscending : TRUE); 
	*pResult = 0; 
} 
 
void CReportCtrl::OnLButtonDown(UINT nFlags, CPoint point)  
{ 
	// TODO: Add your message handler code here and/or call default 
	_MouseClkMonitor(WM_LBUTTONDOWN, nFlags, point, TRUE); 
} 
 
void CReportCtrl::OnLButtonDblClk(UINT nFlags, CPoint point)  
{ 
	// TODO: Add your message handler code here and/or call default 
	_MouseClkMonitor(WM_LBUTTONDBLCLK, nFlags, point, TRUE); 
} 
 
void CReportCtrl::OnMButtonDown(UINT nFlags, CPoint point)  
{ 
	// TODO: Add your message handler code here and/or call default 
	_MouseClkMonitor(WM_MBUTTONDOWN, nFlags, point, FALSE); 
} 
 
void CReportCtrl::OnMButtonDblClk(UINT nFlags, CPoint point)  
{ 
	// TODO: Add your message handler code here and/or call default 
	_MouseClkMonitor(WM_MBUTTONDBLCLK, nFlags, point, FALSE); 
} 
 
void CReportCtrl::OnRButtonDown(UINT nFlags, CPoint point)  
{ 
	// TODO: Add your message handler code here and/or call default 
	_MouseClkMonitor(WM_RBUTTONDOWN, nFlags, point, FALSE); 
} 
 
void CReportCtrl::OnRButtonDblClk(UINT nFlags, CPoint point)  
{ 
	// TODO: Add your message handler code here and/or call default 
	_MouseClkMonitor(WM_RBUTTONDBLCLK, nFlags, point, FALSE); 
} 
 
int CReportCtrl::OnCreate(LPCREATESTRUCT lpCreateStruct)  
{ 
	lpCreateStruct->style &= ~MUST_NOT_STYLE; 
	lpCreateStruct->style |= MUST_STYLE; 
	if (CListCtrl::OnCreate(lpCreateStruct) == -1) 
		return -1; 
	 
	// TODO: Add your specialized creation code here 
	SetExtendedStyle(GetExtendedStyle()); 
	ASSERT(GetHeaderCtrl() != NULL); 
	return 0; 
} 
 
void CReportCtrl::OnDestroy()  
{ 
	DeleteAllItems(); 
	m_pWndEdit->DestroyWindow(); 
	m_imgList.DeleteImageList(); 
	m_headerImgList.DeleteImageList(); 
	CListCtrl::OnDestroy(); 
	// TODO: Add your message handler code here	 
} 
 
BOOL CReportCtrl::_IsValidIndex(int nIndex) const 
{ 
	return nIndex >= 0 && nIndex < CListCtrl::GetItemCount(); 
} 
 
// the heading text is in the format of "text,width,format;text,width,format;..." 
BOOL CReportCtrl::SetColumnHeader(const CString& strHeadings) 
{ 
	DeleteAllItems(); 
	DeleteAllColumns(); 
	EndEdit(TRUE); 
	BOOL bInserted = FALSE; 
	CStringArray aLong, aShort; 
	_StringSplit(strHeadings, aLong, _T(';')); 
	for (int i = 0; i < aLong.GetSize(); i++) 
	{ 
		_StringSplit(aLong[i], aShort, _T(',')); 
		if (aShort.GetSize() > 0) 
		{ 
			const int WIDTH = aShort.GetSize() > 1 ? _ttoi(aShort[1]) : 100; 
			int nFormat = aShort.GetSize() > 2 ? _ttoi(aShort[2]) : 0; 
 
			if (nFormat == 1) 
				nFormat = LVCFMT_CENTER; 
			else if (nFormat == 2) 
				nFormat = LVCFMT_RIGHT; 
			else 
				nFormat = LVCFMT_LEFT; 
 
			bInserted |= (InsertColumn(GetColumnCount(), aShort[0], nFormat, WIDTH) >= 0); 
		} 
	} 
	return bInserted; 
} 
 
int CReportCtrl::InsertItem(int nIndex, LPCTSTR lpText) 
{	 
	EndEdit(TRUE); 
	_UnsetSortedColumn(); 
	const int IDX = CListCtrl::InsertItem(nIndex, lpText); 
	if (IDX >= 0) 
		_AllocItemMemory(IDX); 
	return IDX; 
} 
 
BOOL CReportCtrl::DeleteItem(int nItem, BOOL bSelectNextItem) 
{ 
	EndEdit(m_ptEditting.x != nItem); 
	 
	if (bSelectNextItem) 
		SetItemStates(nItem + 1, RC_ITEM_SELECTED); 
 
	_FreeItemMemory(nItem); 
	return CListCtrl::DeleteItem(nItem); 
} 
 
int CReportCtrl::DeleteAllItems(DWORD dwStates) 
{ 
	EndEdit(FALSE); 
	int nItemCount = CListCtrl::GetItemCount(); 
	if (dwStates & RC_ITEM_ALL) 
	{ 
		LockWindowUpdate(); 
		for (int i = 0; i < nItemCount; i++) 
			_FreeItemMemory(i); 
		CListCtrl::DeleteAllItems(); 
		UnlockWindowUpdate(); 
		return nItemCount;		 
	} 
	 
	int nDelCount = 0; 
	LockWindowUpdate(); 
 
	for (int i = 0; i < nItemCount; i++) 
	{ 
		if (ExamItemStates(i, dwStates)) 
		{ 
			DeleteItem(i--); 
			nItemCount--; 
			nDelCount++; 
		} 
	}	 
	UnlockWindowUpdate(); 
	return nDelCount; 
} 
 
void CReportCtrl::SortItems(int nColumn, BOOL bAscending) 
{ 
	EndEdit(TRUE); 
	if (nColumn < 0 || nColumn >= GetColumnCount() || !IsSortable()) 
		return;	 
 
	// do the sorting	 
	m_nSortCol = nColumn; 
	m_bSortAscending = bAscending; 
	 
	BOOL bEnd = FALSE; 
	int nSep1 = -1; 
	int nSep2 = _FindSeparator(-1, nColumn); 
	do 
	{ 
		if (nSep2 < 0) 
		{ 
			nSep2 = GetItemCount(); 
			bEnd = TRUE; 
		} 
		_PartialSort(nSep1 + 1, nSep2 - 1); 
		nSep1 = _FindSeparator(nSep2 - 1, nColumn); 
		nSep2 = _FindSeparator(nSep1, nColumn); 
	} while (!bEnd && nSep1 >= 0); 
 
	GetParent()->SendMessage(WM_ITEM_SORTED, (WPARAM)m_nSortCol, (LPARAM)m_bSortAscending); 
} 
 
BOOL CReportCtrl::SetItemText(int nItem, int nSubItem, LPCTSTR lpText) 
{ 
	EndEdit(TRUE); 
	_UnsetSortedColumn(); 
	return CListCtrl::SetItemText(nItem, nSubItem, lpText); 
} 
 
int CReportCtrl::GetColumnCount() const 
{ 
	return GetHeaderCtrl()->GetItemCount(); 
} 
 
int CReportCtrl::InsertItem(const LVITEM *pItem) 
{ 
	EndEdit(TRUE); 
	_UnsetSortedColumn(); 
	const int IDX = CListCtrl::InsertItem(pItem); 
	if (IDX >= 0) 
		_AllocItemMemory(IDX); 
	return IDX; 
} 
 
int CReportCtrl::InsertItem(int nItem, LPCTSTR lpItem, int nImage) 
{	 
	EndEdit(TRUE); 
	_UnsetSortedColumn(); 
	const int IDX = CListCtrl::InsertItem(nItem, lpItem, nImage); 
	if (IDX >= 0) 
		_AllocItemMemory(IDX); 
	return IDX; 
} 
 
int CReportCtrl::InsertItem(UINT nMask, int nItem, LPCTSTR lpItem, UINT nState, UINT nStateMask, int nImage, LPARAM lParam) 
{ 
	EndEdit(TRUE); 
	_UnsetSortedColumn(); 
	const int IDX = CListCtrl::InsertItem(nMask, nItem, lpItem, nState, nStateMask, nImage, lParam); 
	if (IDX >= 0) 
		_AllocItemMemory(IDX); 
	return IDX; 
} 
 
void CReportCtrl::SetGridLines(BOOL bSet) 
{ 
	DWORD dwStype = GetExtendedStyle(); 
	if (bSet) 
		dwStype |= LVS_EX_GRIDLINES; 
	else 
		dwStype &= ~LVS_EX_GRIDLINES; 
 
	SetExtendedStyle(dwStype);	 
} 
 
int CReportCtrl::InsertColumn(int nCol, const LVCOLUMN *pColumn) 
{ 
	EndEdit(TRUE); 
	const int IDX = CListCtrl::InsertColumn(nCol, pColumn); 
	if (IDX >= 0) 
		_UpdateColumn(IDX, TRUE); 
	return IDX; 
} 
 
int CReportCtrl::InsertColumn(int nCol, LPCTSTR lpColumnHeading, int nFormat, int nWidth, int nSubItem) 
{ 
	EndEdit(TRUE); 
	const int IDX = CListCtrl::InsertColumn(nCol, lpColumnHeading, nFormat, nWidth, nSubItem); 
	if (IDX >= 0) 
		_UpdateColumn(IDX, TRUE); 
	return IDX; 
} 
 
BOOL CReportCtrl::DeleteColumn(int nCol) 
{ 
	EndEdit(TRUE); 
	const BOOL RES = CListCtrl::DeleteColumn(nCol); 
	if (RES) 
		_UpdateColumn(nCol, FALSE); 
	return RES; 
} 
 
void CReportCtrl::SetCheckboxeStyle(int nStyle) 
{ 
	m_nChkStyle = nStyle; 
	EndEdit(TRUE); 
	DWORD dwStype = GetExtendedStyle(); 
	if (nStyle == 0) 
	{ 
		dwStype &= ~LVS_EX_CHECKBOXES;		 
	} 
	else 
	{ 
		dwStype |= LVS_EX_CHECKBOXES; 
		_EnsureSingleCheck(-1); 
	} 
 
	SetExtendedStyle(dwStype); 
} 
 
int CReportCtrl::MoveTo(int nItem, int nNewPosition) 
{ 
	if (!_IsValidIndex(nItem)) 
		return -1; 
 
	EndEdit(TRUE); 
	const int ITEMS = CListCtrl::GetItemCount(); 
	nNewPosition = max(0, nNewPosition); 
	nNewPosition  = min(ITEMS - 1, nNewPosition); 
 
	if (nItem == nNewPosition) 
		return nNewPosition; 
 
	_UnsetSortedColumn(); 
 
	// Backup all states and attributes 
	const int COLS = GetColumnCount(); 
	const DWORD STATES = GetItemStates(nItem); 
	const DWORD DATA = CListCtrl::GetItemData(nItem); 
	CArray aImages; 
	CStringArray aTexts; 
	aImages.SetSize(COLS); 
	aTexts.SetSize(COLS); 
	for (int i = 0; i < COLS; i++) 
	{ 
		aImages[i] = GetItemImage(nItem, i); 
		aTexts[i] = GetItemText(nItem, i); 
	}	 
 
	// Delete the item 
	CListCtrl::DeleteItem(nItem); 
 
	// Insert a new item to the new position 
	const int IDX = CListCtrl::InsertItem(nNewPosition, ""); 
 
	// Restore all states & attributes to the newly inserted item 
	for (int j = 0; j < COLS; j++) 
	{ 
		CListCtrl::SetItemText(IDX, j, aTexts[j]); 
		SetItemImage(IDX, j, aImages[j]); 
	} 
	CListCtrl::SetItemData(IDX, DATA); 
	SetItemStates(IDX, STATES); 
	return IDX; 
} 
 
int CReportCtrl::MoveUp(int nItem, int nCount) 
{	 
	return MoveTo(nItem, nItem - nCount); 
} 
 
BOOL CReportCtrl::MoveDown(int nItem, int nCount) 
{ 
	return MoveTo(nItem, nItem + nCount); 
} 
 
BOOL CReportCtrl::SwapItems(int nItem1, int nItem2) 
{ 
	EndEdit(TRUE); 
	if (!_IsValidIndex(nItem1) || !_IsValidIndex(nItem2)) 
		return FALSE; 
 
	if (nItem1 == nItem2) 
		return TRUE; 
 
	_UnsetSortedColumn(); 
 
	// record previous states first 
	const DWORD STATES1 = GetItemStates(nItem1); 
	const DWORD STATES2 = GetItemStates(nItem2); 
	const DWORD DATA1 = CListCtrl::GetItemData(nItem1); 
	const DWORD DATA2 = CListCtrl::GetItemData(nItem2); 
 
	// swap item texts and images 
	for (int i = 0; i < GetColumnCount(); i++) 
	{ 
		CString str = GetItemText(nItem1, i); 
		CListCtrl::SetItemText(nItem1, i, GetItemText(nItem2, i)); 
		CListCtrl::SetItemText(nItem2, i, str); 
		UINT nImg = GetItemImage(nItem1, i); 
		SetItemImage(nItem1, i, GetItemImage(nItem2, i)); 
		SetItemImage(nItem2, i, nImg); 
	} 
	 
	// swap item data 
	CListCtrl::SetItemData(nItem1, DATA2); 
	CListCtrl::SetItemData(nItem2, DATA1); 
 
	// restore states 
	SetItemStates(nItem1, STATES2); 
	SetItemStates(nItem2, STATES1); 
 
	return TRUE; 
} 
 
int CReportCtrl::MoveToTop(int nItem) 
{ 
	return MoveTo(nItem, -1); 
} 
 
int CReportCtrl::MoveToBottom(int nItem) 
{ 
	return MoveTo(nItem, INT_MAX); 
} 
 
BOOL CReportCtrl::SetItemText(int nItem, int nSubItem, TCHAR val) 
{ 
	return SetItemText(nItem, nSubItem, CString(val)); 
} 
 
BOOL CReportCtrl::SetItemText(int nItem, int nSubItem, INT val) 
{	 
	return SetItemText(nItem, nSubItem, (LONG)val); 
} 
 
BOOL CReportCtrl::SetItemText(int nItem, int nSubItem, UINT val) 
{ 
	return SetItemText(nItem, nSubItem, (ULONG)val); 
} 
 
BOOL CReportCtrl::SetItemText(int nItem, int nSubItem, DOUBLE val, int nPrecision) 
{ 
	CString sText, sFmt; 
	 
	if (nPrecision >= 0) 
	{ 
		sFmt.Format(_T("%%.%df"), nPrecision); 
		sText.Format(sFmt, val); 
	} 
	else 
	{ 
		sText.Format(_T("%f"), val); 
		sText.TrimRight(_T('0')); 
		sText.TrimRight(_T('.')); 
	} 
	 
	return SetItemText(nItem, nSubItem, sText); 
} 
 
BOOL CReportCtrl::SetItemText(int nItem, int nSubItem, const COleDateTime &dateTime, DWORD dwFlags) 
{ 
	return SetItemText(nItem, nSubItem, dateTime.GetStatus() == COleDateTime::valid ? dateTime.Format(dwFlags) : _T("")); 
} 
 
BOOL CReportCtrl::SetItemText(int nItem, int nSubItem, LONG val) 
{ 
	CString str; 
	str.Format(_T("%d"), val); 
	return SetItemText(nItem, nSubItem, str); 
} 
 
BOOL CReportCtrl::SetItemText(int nItem, int nSubItem, ULONG val) 
{ 
	CString str; 
	str.Format(_T("%u"), val); 
	return SetItemText(nItem, nSubItem, str); 
} 
 
BOOL CReportCtrl::DeleteAllColumns() 
{ 
	while (GetColumnCount() > 0) 
		DeleteColumn(0);		 
	return TRUE; 
} 
 
LPCTSTR CReportCtrl::GetSortSeparator() const 
{ 
	return m_pszSeparator; 
} 
 
void CReportCtrl::SetSortSeparator(LPCTSTR lpSortSeparator) 
{ 
	if (m_pszSeparator != NULL) 
	{ 
		delete [] m_pszSeparator; 
		m_pszSeparator = NULL; 
	} 
 
	if (lpSortSeparator != NULL) 
		m_pszSeparator = _tcsdup(lpSortSeparator); 
} 
 
int CReportCtrl::InsertItemEx(int nItem, LPCTSTR lpText, ...) 
{ 
	EndEdit(TRUE); 
	_UnsetSortedColumn(); 
	const int IDX = CListCtrl::InsertItem(nItem, lpText); 
	if (IDX < 0) 
		return -1; 
 
 	va_list list; 
	va_start(list, lpText); 
 
	for(int iColumn = 1; iColumn < GetColumnCount(); iColumn++) 
	{ 
		LPCTSTR lp = va_arg(list, LPCTSTR); 
		if (lp != NULL) 
		{ 
			CListCtrl::SetItemText(IDX, iColumn, lp); 
		} 
		else 
		{ 
			break; 
		} 
	} 
 
	va_end(list); 
 
	if (IDX >= 0) 
		_AllocItemMemory(IDX); 
 
	return IDX; 
} 
 
BOOL CReportCtrl::_IsEditVisible() const 
{ 
	return m_bAllowEdit 
		&& m_ptEditting.x >= 0 && m_ptEditting.x < CListCtrl::GetItemCount() 
		&& m_ptEditting.y >= 0 && m_ptEditting.y < GetColumnCount(); 
} 
 
void CReportCtrl::SetEditable(BOOL bSet) 
{	 
	if (!bSet) 
		EndEdit(TRUE); 
	m_bAllowEdit = bSet; 
} 
 
BOOL CReportCtrl::IsEditable() const 
{ 
	return m_bAllowEdit; 
} 
 
BOOL CReportCtrl::_ItemCheckMonitor(int nIndex, BOOL bBefore, BOOL bAfter, UINT nMsg) 
{ 
	if (!_IsValidIndex(nIndex) 
		|| m_nChkStyle == RC_CHKBOX_NONE 
		|| (bBefore && bAfter) 
		|| (!bBefore && !bAfter)) 
	{ 
		return FALSE; 
	} 
 
	if (m_nChkStyle == RC_CHKBOX_SINGLE) 
	{ 
		// Only one item can be checked 
		if (!bBefore && bAfter) 
		{ 
			SetAllItemStates(RC_ITEM_CHECKED, RC_ITEM_UNCHECKED); // uncheck all 
			SetItemStates(nIndex, RC_ITEM_CHECKED); 
		}		 
	} 
	else if (m_nChkStyle == RC_CHKBOX_DISABLED) 
	{ 
		// Cannot change the item checked/unchecked states by mouse clicks 
		SetItemStates(nIndex, bBefore ? RC_ITEM_CHECKED : RC_ITEM_UNCHECKED); 
		return TRUE; 
	}	 
 
	SetAllItemStates(RC_ITEM_SELECTED, RC_ITEM_UNSELECTED); // unselect all 
	SetItemStates(nIndex, RC_ITEM_SELECTED | RC_ITEM_FOCUSED); 
	GetParent()->SendMessage(WM_ON_CHKBOX, (WPARAM)nIndex, (LPARAM)nMsg); 
	return TRUE; 
} 
 
int CReportCtrl::GetCheckboxStyle() const 
{ 
	return m_nChkStyle; 
} 
 
void CReportCtrl::_StringSplit(const CString &str, CStringArray &arr, TCHAR chDelimitior) 
{ 
	int nStart = 0, nEnd = 0; 
	arr.RemoveAll(); 
 
	while (nEnd < str.GetLength()) 
	{ 
		// determine the paragraph ("xxx,xxx,xxx;") 
		nEnd = str.Find(chDelimitior, nStart); 
		if( nEnd == -1 ) 
		{ 
			// reached the end of string 
			nEnd = str.GetLength(); 
		} 
 
		CString s = str.Mid(nStart, nEnd - nStart); 
		if (!s.IsEmpty()) 
			arr.Add(s); 
 
		nStart = nEnd + 1; 
	} 
} 
 
BOOL CReportCtrl::SetSortable(BOOL bSet) 
{ 
	if (!HasColumnHeader()) 
		return FALSE; 
 
	LONG lStyle = ::GetWindowLong(GetHeaderCtrl()->GetSafeHwnd(), GWL_STYLE); 
	if (bSet) 
	{ 
		lStyle |= HDS_BUTTONS; 
	} 
	else 
	{ 
		lStyle &= ~HDS_BUTTONS; 
		_UnsetSortedColumn(); 
	} 
	::SetWindowLong(GetHeaderCtrl()->GetSafeHwnd(), GWL_STYLE, lStyle); 
	CListCtrl::GetHeaderCtrl()->RedrawWindow(); 
	return TRUE; 
} 
 
DWORD CReportCtrl::SetExtendedStyle(DWORD dwNewStyle) 
{ 
	dwNewStyle &= ~MUST_NOT_EX_STYLE; 
	dwNewStyle |= MUST_EX_STYLE; 
	return CListCtrl::SetExtendedStyle(dwNewStyle); 
} 
 
BOOL CReportCtrl::PreCreateWindow(CREATESTRUCT& cs)  
{ 
	// TODO: Add your specialized code here and/or call the base class 
	cs.style &= ~MUST_NOT_STYLE; 
	cs.style |= MUST_STYLE; 
	return CListCtrl::PreCreateWindow(cs); 
} 
 
BOOL CReportCtrl::PreTranslateMessage(MSG* pMsg)  
{ 
	// TODO: Add your specialized code here and/or call the base class 
	if (pMsg->message == WM_KEYDOWN) 
	{ 
		if (!_IsEditVisible()) 
		{ 
			// disable user from check/uncheck the checkboxes using space key 
			// things get nasty if the user is HOLDING the space bar down 
			if (pMsg->wParam == VK_SPACE) 
				return TRUE; 
		} 
		else 
		{ 
			POINT pt = m_ptEditting; 
			switch (pMsg->wParam) 
			{ 
			case VK_ESCAPE: // Cancel edit 
				EndEdit(FALSE); 
				return TRUE; 
 
			case VK_RETURN: // Commit edit 
				EndEdit(TRUE); 
				return TRUE; 
 
			case VK_TAB: // switch edit sub items 
				if (pt.y == GetColumnCount() - 1) 
					pt.y = 0; 
				else 
					pt.y++; 
				EndEdit(TRUE); 
				StartEdit(pt.x, pt.y); 
				return TRUE; 
 
			case VK_UP: // edit upper item 
				if (pt.x > 0) 
				{ 
					pt.x--; 
					EndEdit(TRUE); 
					StartEdit(pt.x, pt.y); 
					EnsureVisible(pt.x, FALSE); 
					return TRUE; 
				} 
				break; 
				 
			case VK_DOWN: // edit lower item 
				if (pt.x < CListCtrl::GetItemCount() - 1) 
				{ 
					pt.x++; 
					EndEdit(TRUE); 
					StartEdit(pt.x, pt.y); 
					EnsureVisible(pt.x, FALSE); 
					return TRUE; 
				} 
				break;			 
 
			default: 
				break;				 
			} 
		} 
	} 
 
	return CListCtrl::PreTranslateMessage(pMsg); 
} 
 
CEdit* CReportCtrl::GetEditControl() 
{ 
	return m_pWndEdit; 
} 
 
BOOL CReportCtrl::Create(CWnd* pParentWnd, UINT nID, LPCRECT lpRect, DWORD dwStyle)  
{ 
	// TODO: Add your specialized code here and/or call the base class 
	ASSERT(pParentWnd != NULL); 
 
	dwStyle &= ~MUST_NOT_STYLE; 
	dwStyle |= MUST_STYLE; 
 
	CRect rect; 
	if (lpRect == NULL) 
		pParentWnd->GetClientRect(&rect); 
	else 
		rect = *lpRect; 
 
	return CListCtrl::Create(dwStyle, rect, pParentWnd, nID); 
} 
 
void CReportCtrl::PreSubclassWindow()  
{ 
	// TODO: Add your specialized code here and/or call the base class 
	LONG lStyle = ::GetWindowLong(GetSafeHwnd(), GWL_STYLE); 
	lStyle &= ~MUST_NOT_STYLE; 
	lStyle |= MUST_STYLE; 
	::SetWindowLong(GetSafeHwnd(), GWL_STYLE, lStyle); 
	SetExtendedStyle(0); 
	ASSERT(GetHeaderCtrl() != NULL); 
	CListCtrl::PreSubclassWindow(); 
} 
 
BOOL CReportCtrl::IsSortable() const 
{ 
	if (!HasColumnHeader()) 
		return FALSE; 
 
	LONG lStyle = ::GetWindowLong(GetHeaderCtrl()->GetSafeHwnd(), GWL_STYLE); 
	return (lStyle & HDS_BUTTONS) != 0; 
} 
 
BOOL CReportCtrl::HasColumnHeader() const 
{ 
	return (GetStyle() & LVS_NOCOLUMNHEADER) == 0; 
} 
 
void CReportCtrl::ResizeToFitParent() 
{ 
	// resize the list ctrl to fit parent client area 
	if (GetSafeHwnd() != NULL) 
	{ 
		CRect rect; 
		GetParent()->GetClientRect(&rect); 
		MoveWindow(&rect); 
	} 
} 
 
BOOL CReportCtrl::ModifyStyleEx(DWORD dwRemove, DWORD dwAdd, UINT nFlags) 
{ 
	dwRemove &= ~MUST_EX_STYLE; 
	dwRemove |= MUST_NOT_EX_STYLE; 
	dwAdd &= ~MUST_NOT_EX_STYLE; 
	dwAdd |= MUST_EX_STYLE; 
	return CListCtrl::ModifyStyleEx(dwRemove, dwAdd, nFlags); 
} 
 
BOOL CReportCtrl::ModifyStyle(DWORD dwRemove, DWORD dwAdd, UINT nFlags) 
{ 
	dwRemove &= ~MUST_STYLE; 
	dwRemove |= MUST_NOT_STYLE; 
	dwAdd &= ~MUST_NOT_STYLE; 
	dwAdd |= MUST_STYLE; 
	return CListCtrl::ModifyStyle(dwRemove, dwAdd, nFlags); 
} 
 
void CReportCtrl::_MouseClkMonitor(UINT nMsg, UINT nFlags, CPoint point, BOOL bTriggerEdit) 
{ 
	LVHITTESTINFO hti; 
	hti.pt = point; 
	const int IDX = SubItemHitTest(&hti); 
	const BOOL BEFORE = CListCtrl::GetCheck(IDX) > 0; 
	const BOOL WAS_EDIT = _IsEditVisible(); 
	EndEdit(TRUE); 
	const BOOL WASACTIVE = bTriggerEdit ? ExamItemStates(IDX, RC_ITEM_FOCUSED | RC_ITEM_SELECTED) : FALSE; 
 
	switch (nMsg) 
	{ 
	case WM_LBUTTONDOWN: 
		CListCtrl::OnLButtonDown(nFlags, point); 
		break; 
 
	case WM_LBUTTONDBLCLK: 
		CListCtrl::OnLButtonDblClk(nFlags, point); 
		break; 
 
	case WM_MBUTTONDOWN: 
		CListCtrl::OnMButtonDown(nFlags, point); 
		break; 
 
	case WM_MBUTTONDBLCLK: 
		CListCtrl::OnMButtonDblClk(nFlags, point); 
		break; 
 
	case WM_RBUTTONDOWN: 
		CListCtrl::OnRButtonDown(nFlags, point); 
		break; 
 
	case WM_RBUTTONDBLCLK: 
		CListCtrl::OnRButtonDblClk(nFlags, point); 
		break; 
 
	default: 
		break;		 
	} 
	 
	const BOOL STATSCHANGED = _ItemCheckMonitor(IDX, BEFORE, CListCtrl::GetCheck(IDX) > 0, nMsg); 
 
	if (bTriggerEdit && m_bAllowEdit && !STATSCHANGED && !WAS_EDIT && WASACTIVE) 
		StartEdit(IDX, hti.iSubItem); 
} 
 
BOOL CReportCtrl::SetItemImage(int nItem, int nSubItem, int nImageIndex) 
{ 
	return CListCtrl::SetItem(nItem, nSubItem, LVIF_IMAGE, NULL, nImageIndex, 0, 0, 0); 
} 
 
int CReportCtrl::GetItemImage(int nItem, int nSubItem) const 
{ 
	LVITEM lvi; 
	lvi.iItem = nItem; 
	lvi.iSubItem = nSubItem; 
	lvi.mask = LVIF_IMAGE; 
	return CListCtrl::GetItem(&lvi) ? lvi.iImage : -1; 
} 
 
CImageList* CReportCtrl::SetImageList(CImageList *pImageList) 
{ 
	return CListCtrl::SetImageList(pImageList, LVSIL_SMALL); 
} 
 
CImageList* CReportCtrl::GetImageList() const 
{ 
	return CListCtrl::GetImageList(LVSIL_SMALL); 
} 
 
CImageList* CReportCtrl::SetImageList(UINT nBitmapID, COLORREF crMask) 
{ 
	m_imgList.DeleteImageList(); 
	m_imgList.Create(nBitmapID, 16, 4, crMask);	 
	return CListCtrl::SetImageList(&m_imgList, LVSIL_SMALL); 
} 
 
BOOL CReportCtrl::StartEdit(int nItem, int nSubItem) 
{ 
	// Get the grid width and height 
	if (!m_bAllowEdit || !_IsValidIndex(nItem) || nSubItem < 0 || nSubItem >= GetColumnCount()) 
		return FALSE; 
	 
	if (m_ptEditting.x == nItem && m_ptEditting.y == nSubItem) 
		return TRUE; 
 
	EndEdit(TRUE); 
	m_ptEditting.x = nItem; 
	m_ptEditting.y = nSubItem; 
	SetAllItemStates(RC_ITEM_SELECTED, RC_ITEM_UNSELECTED); // unselect all 
	SetItemStates(m_ptEditting.x, RC_ITEM_SELECTED | RC_ITEM_FOCUSED); 
 
	// determine editbox font and alignment 
	const DWORD FMT = _GetHeaderTextFormat(nSubItem); 
	if (FMT != m_dwPrevEditFmt) 
	{ 
		m_dwPrevEditFmt = FMT; 
 
		// Funny thing: 
		// Changing CEdit style among ES_LEFT, ES_CENTER, ES_RIGHT at runtime works 
		// sometimes and fails other times. It just cannot guarantee to be succeed. 
		// So I decided to destroy and recreate the CEdit every time when the text 
		// format changes. 
		if (m_pWndEdit->GetSafeHwnd() != NULL) 
			m_pWndEdit->DestroyWindow(); 
 
		if (!m_pWndEdit->Create(ES_AUTOHSCROLL | ES_NOHIDESEL | WS_CHILD | WS_BORDER | FMT, CRect(0, 0, 1, 1), this, 0)) 
			return FALSE; 
	} 
	else 
	{ 
		if (m_pWndEdit->GetSafeHwnd() == NULL 
			&& !m_pWndEdit->Create(ES_AUTOHSCROLL | ES_NOHIDESEL | WS_CHILD | WS_BORDER | FMT, CRect(0, 0, 1, 1), this, 0)) 
		{ 
			return FALSE; 
		} 
	} 
 
	m_pWndEdit->SetFont(GetFont()); 
 
	CRect rcEdit; 
	ListView_GetSubItemRect(GetSafeHwnd(), m_ptEditting.x, m_ptEditting.y, LVIR_LABEL, &rcEdit); 
	if (m_ptEditting.y > 0 && GetImageList() != NULL && GetItemImage(m_ptEditting.x, m_ptEditting.y) >= 0) 
		rcEdit.DeflateRect(16, 0, 0, 0); 
 
	// Move the editbox to that grid, obtain text from the grid, display the 
	// editbox, and, finally, highlights all text in the editbox and set the 
	// windows focus to the editbox. 
	m_pWndEdit->MoveWindow(&rcEdit); 
	m_pWndEdit->SetWindowText(GetItemText(m_ptEditting.x, m_ptEditting.y)); 
	m_pWndEdit->ShowWindow(SW_SHOW); 
	m_pWndEdit->SetSel(0, -1); 
	m_pWndEdit->SetFocus(); 
	return TRUE; 
} 
 
BOOL CReportCtrl::EndEdit(BOOL bCommit) 
{ 
	if (!_IsEditVisible()) 
		return FALSE; 
 
	CString str; 
	m_pWndEdit->GetWindowText(str); 
	BOOL bChanged = bCommit && str.Compare(GetItemText(m_ptEditting.x, m_ptEditting.y)) != 0; 
	if (bChanged) 
	{ 
		// update the list item 
		CListCtrl::SetItemText(m_ptEditting.x, m_ptEditting.y, str); 
		_UnsetSortedColumn(); 
		GetParent()->SendMessage(WM_EDIT_COMMITTED, (WPARAM)m_ptEditting.x, (LPARAM)m_ptEditting.y); 
	} 
	m_pWndEdit->ShowWindow(SW_HIDE); 
	m_ptEditting.x = -1; 
	m_ptEditting.y = -1; 
	return bChanged; 
} 
 
BOOL CReportCtrl::SetHeaderImage(int nColumn, int nImageIndex, BOOL bLeftSide) 
{ 
	if (GetHeaderCtrl()->GetImageList() == NULL) 
		CListCtrl::GetHeaderCtrl()->SetImageList(GetImageList()); 
	 
	HDITEM hi; 
	::memset(&hi, 0, sizeof(HDITEM)); 
	hi.mask = HDI_FORMAT; 
	if (!GetHeaderCtrl()->GetItem(nColumn, &hi)) 
		return FALSE; 
	 
	hi.mask |= HDI_IMAGE;		 
	hi.fmt |= HDF_IMAGE; 
 
	if (!bLeftSide) 
		hi.fmt |= HDF_BITMAP_ON_RIGHT; // draw the image on right side of text 
 
	hi.iImage = nImageIndex; 
	return CListCtrl::GetHeaderCtrl()->SetItem(nColumn, &hi); 
} 
 
int CReportCtrl::GetSortedColumn() const 
{ 
	return m_nSortCol; 
} 
 
BOOL CReportCtrl::IsSortAscending() const 
{ 
	return m_bSortAscending; 
} 
 
void CReportCtrl::_UnsetSortedColumn() 
{ 
	m_nSortCol = -1; 
	m_bSortAscending = TRUE; 
} 
 
DWORD CReportCtrl::_GetHeaderTextFormat(int nColumn) const 
{ 
	if (!HasColumnHeader()) 
		return ES_LEFT; 
 
	HDITEM hd; 
	hd.mask = HDI_FORMAT; 
	if (!GetHeaderCtrl()->GetItem(nColumn, &hd)) 
		return ES_LEFT; 
 
	if (hd.fmt & HDF_CENTER) 
	{	 
		return ES_CENTER; 
	} 
	else if (hd.fmt & HDF_RIGHT) 
	{ 
		return ES_RIGHT; 
	} 
	else 
	{ 
		return ES_LEFT; 
	} 
} 
 
int CReportCtrl::GetHeaderImage(int nColumn) const 
{ 
	HDITEM hi; 
	::memset(&hi, 0, sizeof(HDITEM)); 
	hi.mask = HDI_IMAGE; 
	return !GetHeaderCtrl()->GetItem(nColumn, &hi) ? hi.iImage : -1; 
} 
 
CImageList* CReportCtrl::SetHeaderImageList(CImageList *pImageList) 
{ 
	return CListCtrl::GetHeaderCtrl()->SetImageList(pImageList); 
} 
 
CImageList* CReportCtrl::SetHeaderImageList(UINT nBitmapID, COLORREF crMask) 
{ 
	m_headerImgList.Create(nBitmapID, 16, 4, crMask); 
	return SetHeaderImageList(&m_headerImgList); 
} 
 
BOOL CReportCtrl::ExamItemStates(int nItem, DWORD dwStates) const 
{ 
	if (dwStates == RC_ITEM_NONE) 
		return FALSE; 
 
	if (dwStates & RC_ITEM_ALL) 
		return TRUE; 
 
	return (GetItemStates(nItem) & dwStates) == dwStates; 
} 
 
int CReportCtrl::GetItemCount(DWORD dwStates) const 
{ 
	const int ITEMS = CListCtrl::GetItemCount(); 
	if (dwStates == RC_ITEM_ALL) 
		return ITEMS; 
 
	int nCount = 0; 
	for (int i = 0; i = 0; i--) 
	{ 
		if (ExamItemStates(i, dwStates)) 
			return i; 
	} 
	return -1; 
} 
 
void CReportCtrl::InvertItems(int nType) 
{ 
	const int ITEMS = CListCtrl::GetItemCount(); 
	if (nType == RC_INVERT_SELECTION) 
	{ 
		for (int i = 0; i < ITEMS; i++) 
			SetItemStates(i, ExamItemStates(i, RC_ITEM_SELECTED) ? RC_ITEM_UNSELECTED : RC_ITEM_SELECTED); 
	} 
	else if (nType == RC_INVERT_CHECKMARK) 
	{ 
		for (int i = 0; i < ITEMS; i++) 
			SetItemStates(i, ExamItemStates(i, RC_ITEM_CHECKED) ? RC_ITEM_UNCHECKED : RC_ITEM_CHECKED); 
	} 
} 
 
DWORD CReportCtrl::GetItemStates(int nItem) const 
{ 
	DWORD dwStates = 0; 
 
	if (CListCtrl::GetItemState(nItem, LVIS_SELECTED)) 
		dwStates |= RC_ITEM_SELECTED; 
	else 
		dwStates |= RC_ITEM_UNSELECTED; 
 
	if (CListCtrl::GetCheck(nItem) > 0) 
		dwStates |= RC_ITEM_CHECKED; 
	else 
		dwStates |= RC_ITEM_UNCHECKED; 
 
	if (CListCtrl::GetItemState(nItem, LVIS_FOCUSED)) 
		dwStates |= RC_ITEM_FOCUSED; 
	else 
		dwStates |= RC_ITEM_UNFOCUSED; 
 
	return dwStates; 
} 
 
BOOL CReportCtrl::SetItemStates(int nItem, DWORD dwNewStates) 
{ 
	BOOL bResult = _SetItemStatesNoVerify(nItem, dwNewStates); 
	if (dwNewStates & RC_ITEM_CHECKED) 
		_EnsureSingleCheck(nItem); 
	return bResult; 
} 
 
int CReportCtrl::SetAllItemStates(DWORD dwOldStates, DWORD dwNewStates) 
{ 
	if (dwNewStates == RC_ITEM_NONE || dwOldStates == RC_ITEM_NONE) 
		return 0; 
 
	int nCount = 0; 
	const int ITEMS = CListCtrl::GetItemCount(); 
	for (int i = 0; i < ITEMS; i++) 
	{ 
		if (dwOldStates == RC_ITEM_ALL || ExamItemStates(i, dwOldStates)) 
		{ 
			SetItemStates(i, dwNewStates); 
			nCount++; 
		} 
	} 
 
	if (dwNewStates & RC_ITEM_CHECKED) 
		_EnsureSingleCheck(-1); 
 
	return nCount; 
} 
 
void CReportCtrl::_EnsureSingleCheck(int nItem) 
{ 
	if (m_nChkStyle == RC_CHKBOX_SINGLE) 
	{ 
		const int FIRST = nItem < 0 ? GetFirstItem(RC_ITEM_CHECKED) : nItem; 
		if (FIRST >= 0) 
		{ 
			const int ITEMS = CListCtrl::GetItemCount(); 
			for (int i = FIRST + 1; i < ITEMS; i++) 
			{ 
				if (CListCtrl::GetCheck(i)) 
					CListCtrl::SetCheck(i, FALSE); 
			} 
		}		 
	} 
} 
 
BOOL CReportCtrl::_SetItemStatesNoVerify(int nItem, DWORD dwNewStates) 
{ 
	if (dwNewStates == RC_ITEM_NONE) 
		return FALSE; 
	 
	BOOL bResult = TRUE; 
	 
	if (dwNewStates & RC_ITEM_SELECTED) 
		bResult &= CListCtrl::SetItemState(nItem, LVIS_SELECTED, LVIS_SELECTED); 
 
	if (dwNewStates & RC_ITEM_UNSELECTED) 
		bResult &= CListCtrl::SetItemState(nItem, 0, LVIS_SELECTED); 
 
	if (dwNewStates & RC_ITEM_CHECKED) 
		bResult &= CListCtrl::SetCheck(nItem, TRUE); 
 
	if (dwNewStates & RC_ITEM_UNCHECKED) 
		bResult &= CListCtrl::SetCheck(nItem, FALSE); 
 
	if (dwNewStates & RC_ITEM_FOCUSED) 
		bResult &= CListCtrl::SetItemState(nItem, LVIS_FOCUSED, LVIS_FOCUSED); 
 
	if (dwNewStates & RC_ITEM_UNFOCUSED) 
		bResult &= CListCtrl::SetItemState(nItem, 0, LVIS_FOCUSED); 
 
	return bResult; 
} 
 
BOOL CReportCtrl::SetCheck(int nItem, BOOL bCheck) 
{ 
	const BOOL RES = CListCtrl::SetCheck(nItem, bCheck); 
	if (RES && bCheck) 
		_EnsureSingleCheck(nItem); 
	return RES; 
} 
 
CString CReportCtrl::GetHeaderText(int nColumn) const 
{ 
	if (!HasColumnHeader()) 
		return _T(""); 
 
	HDITEM hd; 
	TCHAR  szBuffer[256] = _T(""); 
	hd.mask = HDI_TEXT; 
	hd.pszText = szBuffer; 
	hd.cchTextMax = 255; 
	return GetHeaderCtrl()->GetItem(nColumn, &hd) ? hd.pszText : _T(""); 
} 
 
BOOL CReportCtrl::SetHeaderText(int nColumn, LPCTSTR lpText) 
{ 
	if (!HasColumnHeader()) 
		return FALSE; 
 
	LPTSTR psz = NULL; 
	if (lpText == NULL) 
	{ 
		psz = new TCHAR[1]; 
		*psz = _T('\0'); 
	} 
	else 
	{ 
		psz = _tcsdup(lpText); 
	} 
	HDITEM hd; 
	hd.mask = HDI_TEXT; 
	hd.pszText = psz; 
	hd.cchTextMax = _tcslen(psz); 
	const BOOL RES = CListCtrl::GetHeaderCtrl()->SetItem(nColumn, &hd); 
	delete [] psz; 
	return RES; 
} 
 
const CHeaderCtrl* CReportCtrl::GetHeaderCtrl() const 
{ 
	// Yes, CListCtrl already provides "GetHeaderCtrl", but not const.	 
	// I desperately need a "const" version of "GetHeaderCtrl" because that's the 
	// only way to make "GetColumnCount" const, which, in turn, is being called by 
	// A LOT of const member functions. So if "GetHeaderCtrl" is not const, there will 
	// be almost no const member function at all in this class. Terrible. 
	return (const CHeaderCtrl*)(CWnd::FromHandle(ListView_GetHeader(GetSafeHwnd()))); 
} 
 
void CReportCtrl::_FreeItemMemory(int nItem) 
{ 
	CItemData* p = (CItemData*)(CListCtrl::GetItemData(nItem)); 
	if (p != NULL) 
	{ 
		CListCtrl::SetItemData(nItem, 0); 
		delete p; 
	} 
} 
 
void CReportCtrl::_AllocItemMemory(int nItem) 
{ 
	ASSERT(_IsValidIndex(nItem)); 
	const int COLS = GetColumnCount(); 
	ASSERT(COLS > 0); 
	CItemData* pData = new CItemData;	 
	pData->dwData = CListCtrl::GetItemData(nItem); 
	pData->aTextColors.SetSize(COLS); 
	pData->aBkColors.SetSize(COLS); 
	for (int i = 0; i < COLS; i++) 
	{ 
		pData->aTextColors[i] = ::GetSysColor(COLOR_WINDOWTEXT); 
		pData->aBkColors[i] = ::GetSysColor(COLOR_WINDOW); 
	} 
	CListCtrl::SetItemData(nItem, (DWORD)pData); 
} 
 
void CReportCtrl::_UpdateColumn(int nColumn, BOOL bInsert) 
{ 
	const int ITEMS = GetItemCount(); 
	for (int i = 0; i < ITEMS; i++) 
	{ 
		CItemData* p = (CItemData*)(CListCtrl::GetItemData(i)); 
		ASSERT(p != NULL); 
		if (bInsert) 
			p->InsertColumn(nColumn); 
		else 
			p->DeleteColumn(nColumn); 
	} 
} 
 
BOOL CReportCtrl::SetItemData(int nItem, DWORD dwData) 
{ 
	CItemData* p = (CItemData*)(CListCtrl::GetItemData(nItem)); 
	if (p == NULL) 
		return FALSE; 
	p->dwData = dwData; 
	return CListCtrl::SetItemData(nItem, (DWORD)p); 
} 
 
DWORD CReportCtrl::GetItemData(int nItem) const 
{ 
	CItemData* p = (CItemData*)(CListCtrl::GetItemData(nItem)); 
	return p == NULL ? 0 : p->dwData; 
} 
 
BOOL CReportCtrl::GetItem(LVITEM *pItem) const 
{ 
	const BOOL RES = CListCtrl::GetItem(pItem); 
	if (RES && (pItem->mask & LVIF_PARAM) != 0) 
	{ 
		CItemData* p = (CItemData*)(pItem->lParam); 
		if (p != NULL) 
			pItem->lParam = p->dwData; 
	} 
	return RES; 
} 
 
BOOL CReportCtrl::SetItem(const LVITEM *pItem) 
{ 
	if (pItem == NULL) 
		return FALSE; 
	 
	LVITEM li; 
	memcpy(&li, pItem, sizeof(LVITEM)); 
	if ((li.mask & LVIF_PARAM) != 0) 
	{ 
		CItemData* p = (CItemData*)(CListCtrl::GetItemData(li.iItem)); 
		if (p != NULL) 
		{ 
			p->dwData = li.lParam; 
			li.lParam = (LPARAM)p; 
		}		 
	} 
	return CListCtrl::SetItem(&li); 
} 
 
BOOL CReportCtrl::SetItem(int nItem, int nSubItem, UINT nMask, LPCTSTR lpszItem, int nImage, UINT nState, UINT nStateMask, LPARAM lParam) 
{ 
	if ((nMask & LVIF_PARAM) != 0) 
	{ 
		CItemData* p = (CItemData*)(CListCtrl::GetItemData(nItem)); 
		if (p != NULL) 
		{ 
			p->dwData = lParam; 
			lParam = (LPARAM)p; 
		}		 
	} 
	return CListCtrl::SetItem(nItem, nSubItem, nMask, lpszItem, nImage, nState, nStateMask, lParam); 
} 
 
void CReportCtrl::SetItemTextColor(int nItem, int nSubItem, COLORREF color, BOOL bRedraw) 
{ 
	if (color == COLOR_INVALID) 
		color = ::GetSysColor(COLOR_WINDOWTEXT); 
	const int ROWS = GetItemCount(); 
	const int COLS = GetColumnCount(); 
	BOOL bRowValid = nItem >= 0 && nItem < ROWS; 
	BOOL bColValid = nSubItem >= 0 && nSubItem < COLS; 
 
	if (bRowValid && bColValid) 
	{ 
		// apply to individual grid 
		CItemData* p = (CItemData*)(CListCtrl::GetItemData(nItem)); 
		ASSERT(p != NULL); 
		p->aTextColors[nSubItem] = color; 
	} 
	else if (bRowValid && !bColValid) 
	{ 
		// apply to whole row for the existing item 
		CItemData* p = (CItemData*)(CListCtrl::GetItemData(nItem)); 
		ASSERT(p != NULL); 
		for (int i = 0; i < COLS; i++) 
			p->aTextColors[i] = color; 
	} 
	else if (!bRowValid && bColValid) 
	{ 
		// apply to whole column for all existing items 
		for (int i = 0; i < ROWS; i++) 
		{ 
			CItemData* p = (CItemData*)(CListCtrl::GetItemData(i)); 
			ASSERT(p != NULL); 
			p->aTextColors[nSubItem] = color; 
		} 
	} 
	else 
	{ 
		// apply to whole table for all existing items 
		for (int i = 0; i < ROWS; i++) 
		{ 
			CItemData* p = (CItemData*)(CListCtrl::GetItemData(i)); 
			ASSERT(p != NULL); 
			for (int j = 0; j < COLS; j++) 
				p->aTextColors[j] = color; 
		} 
	} 
 
	if (bRedraw) 
		RedrawWindow(); 
} 
 
void CReportCtrl::SetItemBkColor(int nItem, int nSubItem, COLORREF color, BOOL bRedraw) 
{ 
	if (color == COLOR_INVALID) 
		color = ::GetSysColor(COLOR_WINDOW); 
	const int ROWS = GetItemCount(); 
	const int COLS = GetColumnCount(); 
	BOOL bRowValid = nItem >= 0 && nItem < ROWS; 
	BOOL bColValid = nSubItem >= 0 && nSubItem < COLS; 
 
	if (bRowValid && bColValid) 
	{ 
		// apply to individual grid 
		CItemData* p = (CItemData*)(CListCtrl::GetItemData(nItem)); 
		ASSERT(p != NULL); 
		p->aBkColors[nSubItem] = color; 
	} 
	else if (bRowValid && !bColValid) 
	{ 
		// apply to whole row for the existing item 
		CItemData* p = (CItemData*)(CListCtrl::GetItemData(nItem)); 
		ASSERT(p != NULL); 
		for (int i = 0; i < COLS; i++) 
			p->aBkColors[i] = color; 
	} 
	else if (!bRowValid && bColValid) 
	{ 
		// apply to whole column for all existing items 
		for (int i = 0; i < ROWS; i++) 
		{ 
			CItemData* p = (CItemData*)(CListCtrl::GetItemData(i)); 
			ASSERT(p != NULL); 
			p->aBkColors[nSubItem] = color; 
		} 
	} 
	else 
	{ 
		// apply to whole table for all existing items 
		for (int i = 0; i < ROWS; i++) 
		{ 
			CItemData* p = (CItemData*)(CListCtrl::GetItemData(i)); 
			ASSERT(p != NULL); 
			for (int j = 0; j < COLS; j++) 
				p->aBkColors[j] = color; 
		} 
	} 
 
	if (bRedraw) 
		RedrawWindow(); 
} 
 
COLORREF CReportCtrl::GetItemTextColor(int nItem, int nSubItem) const 
{ 
	if (!_IsValidIndex(nItem) || nSubItem < 0 || nSubItem >= GetColumnCount()) 
		return COLOR_INVALID; 
	CItemData* p = (CItemData*)(CListCtrl::GetItemData(nItem)); 
	ASSERT(p != NULL); 
	return p->aTextColors[nSubItem]; 
} 
 
COLORREF CReportCtrl::GetItemBkColor(int nItem, int nSubItem) const 
{ 
	if (!_IsValidIndex(nItem) || nSubItem < 0 || nSubItem >= GetColumnCount()) 
		return COLOR_INVALID; 
	CItemData* p = (CItemData*)(CListCtrl::GetItemData(nItem)); 
	ASSERT(p != NULL); 
	return p->aBkColors[nSubItem]; 
} 
 
BOOL CReportCtrl::_PartialSort(int nStart, int nEnd) 
{ 
	if (nStart >= nEnd || !_IsValidIndex(nStart) || !_IsValidIndex(nEnd)) 
		return FALSE; 
 
	const int COUNT = nEnd - nStart + 1; 
	int i = 0; 
	int* aIndices = new int[COUNT]; 
	for (i = 0; i < COUNT; i++) 
		aIndices[i] = nStart + i; 
	 
	_QuickSortRecursive(aIndices, 0, COUNT - 1);  
 
	// rearrange items 
	const int COLS = GetColumnCount(); 
	ROWINFO* aRows = new ROWINFO[COUNT]; 
	for (i = 0; i < COUNT; i++) 
	{ 
		int n = aIndices[i]; 
		aRows[i].dwStates = GetItemStates(aIndices[i]); 
		aRows[i].dwData = CListCtrl::GetItemData(aIndices[i]); 
		aRows[i].aImages.SetSize(COLS); 
		aRows[i].aTexts.SetSize(COLS); 
		for (int j = 0; j < COLS; j++) 
		{ 
			aRows[i].aImages[j] = GetItemImage(aIndices[i], j); 
			aRows[i].aTexts[j] = GetItemText(aIndices[i], j); 
		} 
	} 
 
	for (i = 0; i < COUNT; i++) 
	{ 
		SetItemStates(nStart + i, aRows[i].dwStates); 
		CListCtrl::SetItemData(nStart + i, aRows[i].dwData); 
		for (int j = 0; j < COLS; j++) 
		{ 
			SetItemImage(nStart + i, j, aRows[i].aImages[j]);			 
			CListCtrl::SetItemText(nStart + i, j, aRows[i].aTexts[j]); 
		} 
	} 
	delete [] aRows; 
	delete [] aIndices; 
	return TRUE; 
} 
 
int CReportCtrl::_CompareItems(int nItem1, int nItem2) 
{ 
	const CString s1 = GetItemText(nItem1, m_nSortCol); 
	const CString s2 = GetItemText(nItem2, m_nSortCol); 
 
	DWORD dw1, dw2; 
	if(_ITEM_COMPARE_FUNCS::_IsHexNumber(s1, dw1) && _ITEM_COMPARE_FUNCS::_IsHexNumber(s2, dw2)) 
		return _ITEM_COMPARE_FUNCS::_HexNumberCompare(dw1, dw2); 
 
	double f1, f2; 
	if(_ITEM_COMPARE_FUNCS::_IsDecNumber(s1, f1) && _ITEM_COMPARE_FUNCS::_IsDecNumber(s2, f2)) 
		return _ITEM_COMPARE_FUNCS::_DecNumberCompare(f1, f2); 
 
	COleDateTime date1, date2; 
	if(_ITEM_COMPARE_FUNCS::_IsDate(s1, date1) && _ITEM_COMPARE_FUNCS::_IsDate(s2, date2)) 
		return _ITEM_COMPARE_FUNCS::_DateCompare(date1, date2); 
	 
	// plain text. 
	return s1.Compare(s2); 
} 
 
int CReportCtrl::_FindSeparator(int nStartAfter, int nColumn) const 
{ 
	if (m_pszSeparator == NULL) 
		return -1; 
 
	const int ITEMS = GetItemCount(); 
	for (int i = nStartAfter + 1; i < ITEMS; i++) 
	{ 
		if (GetItemText(i, nColumn) == m_pszSeparator) 
			return i; 
	} 
	return -1; 
} 
 
void CReportCtrl::_QuickSortRecursive(int* pArr, int nLow, int nHigh) 
{ 
	int i = nHigh, j = nLow; 
	int n = pArr[(nLow + nHigh) / 2]; 
	do 
	{ 
		if (m_bSortAscending) 
		{ 
			while (_CompareItems(pArr[j], n) < 0) 
				j++; 
 
			while (_CompareItems(pArr[i], n) > 0) 
				i--; 
		} 
		else 
		{ 
			while (_CompareItems(pArr[j], n) > 0) 
				j++; 
 
			while (_CompareItems(pArr[i], n) < 0) 
				i--; 
		} 
 
		if ( i >= j ) 
		{ 
			if ( i != j ) 
			{ 
				int nTemp = pArr[i]; 
				pArr[i] = pArr[j]; 
				pArr[j] = nTemp; 
			} 
 
			i--; 
			j++; 
		} 
		 
	} while (j <= i); 
 
	if (nLow < i) 
		_QuickSortRecursive(pArr,nLow,i); 
 
	if (j < nHigh) 
		_QuickSortRecursive(pArr,j,nHigh); 
}