📄 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 <afxtempl.h>
#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<int, int> 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<COLORREF, COLORREF> aTextColors; // Sub item text colors
CArray<COLORREF, COLORREF> 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<int, int> 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);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -