由于需要ListCtrl可以内嵌CheckBox, Radio, Combo, Edit, Progress, 并且支持按字符串,日期,数字格式排序,在网上搜罗一阵也没找到合适的,只好自己动手改造,效果还不错
// ListCtrl should be set with LVS_REPORT style
// ListCtrlCellEx.h - ListCtrl单元格以及HeadCtrl
Code
#pragma once
#include <list>
using namespace std;
namespace ListCtrlEx
{
typedef list<CString> CStrList;
//////////////////////////////////////////////////////////////////////////
class AFX_EXT_CLASS CInPlaceCombo : public CComboBox
{
public:
static CInPlaceCombo* GetInstance();
static void DeleteInstance();
BOOL ShowComboCtrl(DWORD dwStyle, const CRect& rCellRect, CWnd* pParentWnd, UINT uiResourceID,
int iRowIndex, int iColumnIndex, CStrList &lstDropDown, CString strCurSel = _T(""), int iCurSel = -1);
virtual BOOL PreTranslateMessage(MSG* pMsg);
protected:
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
afx_msg void OnKillFocus(CWnd* pNewWnd);
afx_msg void OnChar(UINT nChar, UINT nRepCnt, UINT nFlags);
afx_msg void OnCloseup();
DECLARE_MESSAGE_MAP()
private:
CInPlaceCombo();
CInPlaceCombo (CInPlaceCombo&) {}
const CInPlaceCombo& operator = (CInPlaceCombo) { return *this; }
virtual ~CInPlaceCombo();
static CInPlaceCombo* m_pInPlaceCombo;
int m_iRowIndex;
int m_iColumnIndex;
CString m_strWindowText;
CStrList m_DropDownList;
// To indicate whether ESC key was pressed
BOOL m_bESC;
};
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
class AFX_EXT_CLASS CInPlaceEdit : public CEdit
{
#define CTRL_C 0x3
#define CTRL_V 0x16
#define CTRL_X 0x18
public:
static CInPlaceEdit* GetInstance();
static void DeleteInstance();
BOOL ShowEditCtrl(DWORD dwStyle, const RECT& rCellRect, CWnd* pParentWnd,
UINT uiResourceID, int iRowIndex, int iColumnIndex,
CString& strValidChars, CString& rstrCurSelection);
virtual BOOL PreTranslateMessage(MSG* pMsg);
protected:
// Generated message map functions
//{{AFX_MSG(CInPlaceEdit)
afx_msg void OnKillFocus(CWnd* pNewWnd);
afx_msg void OnChar(UINT nChar, UINT nRepCnt, UINT nFlags);
afx_msg LRESULT OnPaste(WPARAM wParam, LPARAM lParam);
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
private:
CInPlaceEdit();
CInPlaceEdit (CInPlaceEdit&) {}
const CInPlaceEdit& operator = (CInPlaceEdit) { return *this; }
virtual ~CInPlaceEdit();
static CInPlaceEdit* m_pInPlaceEdit;
int m_iRowIndex;
int m_iColumnIndex;
CString m_strValidChars;
CString m_strWindowText;
// To indicate whether ESC key was pressed
BOOL m_bESC;
};
//////////////////////////////////////////////////////////////////////////
// CSortHeadCtrl
class AFX_EXT_CLASS CListCtrlExSortHead : public CHeaderCtrl
{
DECLARE_DYNAMIC(CListCtrlExSortHead)
public:
CListCtrlExSortHead();
virtual ~CListCtrlExSortHead();
void SetSortArrow( const int nSortColumn, const BOOL bDesc );
inline int GetCrntSortCol() const;
inline BOOL GetCrntSortDirection() const;
protected:
int m_iSortColumn;
BOOL m_bSortDesc;
DECLARE_MESSAGE_MAP()
afx_msg void OnNMCustomdraw(NMHDR *pNMHDR, LRESULT *pResult);
virtual void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct);
};
}
//////////////////////////////////////////////////////////////////////////
#pragma once
#include <list>
using namespace std;
namespace ListCtrlEx
{
typedef list<CString> CStrList;
//////////////////////////////////////////////////////////////////////////
class AFX_EXT_CLASS CInPlaceCombo : public CComboBox
{
public:
static CInPlaceCombo* GetInstance();
static void DeleteInstance();
BOOL ShowComboCtrl(DWORD dwStyle, const CRect& rCellRect, CWnd* pParentWnd, UINT uiResourceID,
int iRowIndex, int iColumnIndex, CStrList &lstDropDown, CString strCurSel = _T(""), int iCurSel = -1);
virtual BOOL PreTranslateMessage(MSG* pMsg);
protected:
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
afx_msg void OnKillFocus(CWnd* pNewWnd);
afx_msg void OnChar(UINT nChar, UINT nRepCnt, UINT nFlags);
afx_msg void OnCloseup();
DECLARE_MESSAGE_MAP()
private:
CInPlaceCombo();
CInPlaceCombo (CInPlaceCombo&) {}
const CInPlaceCombo& operator = (CInPlaceCombo) { return *this; }
virtual ~CInPlaceCombo();
static CInPlaceCombo* m_pInPlaceCombo;
int m_iRowIndex;
int m_iColumnIndex;
CString m_strWindowText;
CStrList m_DropDownList;
// To indicate whether ESC key was pressed
BOOL m_bESC;
};
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
class AFX_EXT_CLASS CInPlaceEdit : public CEdit
{
#define CTRL_C 0x3
#define CTRL_V 0x16
#define CTRL_X 0x18
public:
static CInPlaceEdit* GetInstance();
static void DeleteInstance();
BOOL ShowEditCtrl(DWORD dwStyle, const RECT& rCellRect, CWnd* pParentWnd,
UINT uiResourceID, int iRowIndex, int iColumnIndex,
CString& strValidChars, CString& rstrCurSelection);
virtual BOOL PreTranslateMessage(MSG* pMsg);
protected:
// Generated message map functions
//{{AFX_MSG(CInPlaceEdit)
afx_msg void OnKillFocus(CWnd* pNewWnd);
afx_msg void OnChar(UINT nChar, UINT nRepCnt, UINT nFlags);
afx_msg LRESULT OnPaste(WPARAM wParam, LPARAM lParam);
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
private:
CInPlaceEdit();
CInPlaceEdit (CInPlaceEdit&) {}
const CInPlaceEdit& operator = (CInPlaceEdit) { return *this; }
virtual ~CInPlaceEdit();
static CInPlaceEdit* m_pInPlaceEdit;
int m_iRowIndex;
int m_iColumnIndex;
CString m_strValidChars;
CString m_strWindowText;
// To indicate whether ESC key was pressed
BOOL m_bESC;
};
//////////////////////////////////////////////////////////////////////////
// CSortHeadCtrl
class AFX_EXT_CLASS CListCtrlExSortHead : public CHeaderCtrl
{
DECLARE_DYNAMIC(CListCtrlExSortHead)
public:
CListCtrlExSortHead();
virtual ~CListCtrlExSortHead();
void SetSortArrow( const int nSortColumn, const BOOL bDesc );
inline int GetCrntSortCol() const;
inline BOOL GetCrntSortDirection() const;
protected:
int m_iSortColumn;
BOOL m_bSortDesc;
DECLARE_MESSAGE_MAP()
afx_msg void OnNMCustomdraw(NMHDR *pNMHDR, LRESULT *pResult);
virtual void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct);
};
}
//////////////////////////////////////////////////////////////////////////
// ListCtrlCellEx.cpp
Code
#include "stdafx.h"
#include "ListCtrlCellEx.h"
using namespace ListCtrlEx;
//////////////////////////////////////////////////////////////////////////
// ------------ for CInPlaceCombo --------------------//
CInPlaceCombo* CInPlaceCombo::m_pInPlaceCombo = NULL;
CInPlaceCombo::CInPlaceCombo()
{
m_iRowIndex = -1;
m_iColumnIndex = -1;
m_bESC = FALSE;
}
CInPlaceCombo::~CInPlaceCombo()
{
if (::IsWindow(m_pInPlaceCombo->m_hWnd))
{
SendMessage(WM_CLOSE);
}
}
BEGIN_MESSAGE_MAP(CInPlaceCombo, CComboBox)
//{{AFX_MSG_MAP(CInPlaceCombo)
ON_WM_CREATE()
ON_WM_KILLFOCUS()
ON_WM_CHAR()
ON_CONTROL_REFLECT(CBN_CLOSEUP, OnCloseup)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
int CInPlaceCombo::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CComboBox::OnCreate(lpCreateStruct) == -1)
{
return -1;
}
CFont* pFont = GetParent()->GetFont();
SetFont(pFont);
SetFocus();
ResetContent();
CStrList::iterator iter=m_DropDownList.begin();
for (; iter!=m_DropDownList.end(); ++iter)
{
AddString(*iter);
}
return 0;
}
BOOL CInPlaceCombo::PreTranslateMessage(MSG* pMsg)
{
if (pMsg->message == WM_KEYDOWN)
{
if(pMsg->wParam == VK_RETURN || pMsg->wParam == VK_ESCAPE)
{
::TranslateMessage(pMsg);
::DispatchMessage(pMsg);
return TRUE;
}
}
return CComboBox::PreTranslateMessage(pMsg);
}
void CInPlaceCombo::OnKillFocus(CWnd* pNewWnd)
{
CComboBox::OnKillFocus(pNewWnd);
CString str;
GetWindowText(str);
// Send Notification to parent of ListView ctrl
LV_DISPINFO dispinfo;
dispinfo.hdr.hwndFrom = GetParent()->m_hWnd;
dispinfo.hdr.idFrom = GetDlgCtrlID();
dispinfo.hdr.code = LVN_ENDLABELEDIT;
dispinfo.item.mask = LVIF_TEXT;
dispinfo.item.iItem = m_iRowIndex;
dispinfo.item.iSubItem = m_iColumnIndex;
dispinfo.item.pszText = m_bESC ? LPTSTR((LPCTSTR)m_strWindowText) : LPTSTR((LPCTSTR)str);
dispinfo.item.cchTextMax = m_bESC ? m_strWindowText.GetLength() : str.GetLength();
GetParent()->SendMessage(WM_NOTIFY, GetParent()->GetDlgCtrlID(), (LPARAM)&dispinfo);
PostMessage(WM_CLOSE);
}
void CInPlaceCombo::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
{
// If the key is "Esc" set focus back to the list control
if (nChar == VK_ESCAPE || nChar == VK_RETURN)
{
if (nChar == VK_ESCAPE)
{
m_bESC = TRUE;
}
GetParent()->SetFocus();
return;
}
CComboBox::OnChar(nChar, nRepCnt, nFlags);
}
void CInPlaceCombo::OnCloseup()
{
GetParent()->SetFocus();
}
CInPlaceCombo* CInPlaceCombo::GetInstance()
{
if(m_pInPlaceCombo == NULL)
{
m_pInPlaceCombo = new CInPlaceCombo;
}
return m_pInPlaceCombo;
}
void CInPlaceCombo::DeleteInstance()
{
delete m_pInPlaceCombo;
m_pInPlaceCombo = NULL;
}
BOOL CInPlaceCombo::ShowComboCtrl(DWORD dwStyle, const CRect &rCellRect, CWnd* pParentWnd, UINT uiResourceID,
int iRowIndex, int iColumnIndex, CStrList &lstDropDown, CString strCurSel, int iCurSel )
{
m_iRowIndex = iRowIndex;
m_iColumnIndex = iColumnIndex;
m_bESC = FALSE;
m_DropDownList.clear();
m_DropDownList.insert(m_DropDownList.begin(), lstDropDown.begin(), lstDropDown.end());
BOOL bRetVal = TRUE;
if (-1 != iCurSel)
{
GetLBText(iCurSel, m_strWindowText);
}
else if (!strCurSel.IsEmpty())
{
m_strWindowText = strCurSel;
}
if (NULL == m_pInPlaceCombo->m_hWnd)
{
bRetVal = m_pInPlaceCombo->Create(dwStyle, rCellRect, pParentWnd, uiResourceID);
}
SetCurSel(iCurSel);
return bRetVal;
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// --------------- for CInPlaceEdit -------------------//
CInPlaceEdit* CInPlaceEdit::m_pInPlaceEdit=NULL;
CInPlaceEdit::CInPlaceEdit()
{
m_iRowIndex= -1;
m_iColumnIndex = -1;
m_bESC = FALSE;
m_strValidChars.Empty();
}
CInPlaceEdit::~CInPlaceEdit()
{
if (::IsWindow(m_pInPlaceEdit->m_hWnd))
{
SendMessage(WM_CLOSE);
}
}
BEGIN_MESSAGE_MAP(CInPlaceEdit, CEdit)
//{{AFX_MSG_MAP(CInPlaceEdit)
ON_WM_KILLFOCUS()
ON_WM_CHAR()
ON_MESSAGE(WM_PASTE, CInPlaceEdit::OnPaste)
ON_WM_CREATE()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
/////////////////////////////////////////////////////////////////////////////
// CInPlaceEdit message handlers
LRESULT CInPlaceEdit::OnPaste(WPARAM /*wParam*/, LPARAM /*lParam*/)
{
if (!m_strValidChars.IsEmpty())
{
CString strFromClipboard;
// get the text from clipboard
if(OpenClipboard()) {
HANDLE l_hData = GetClipboardData(CF_TEXT);
if(NULL == l_hData) {
return 1;
}
char *l_pBuffer = (char*)GlobalLock(l_hData);
if(NULL != l_pBuffer) {
strFromClipboard = l_pBuffer;
}
GlobalUnlock(l_hData);
CloseClipboard();
}
// Validate the characters before pasting
for(int iCounter_ = 0; iCounter_ < strFromClipboard.GetLength(); iCounter_++)
{
if (-1 == m_strValidChars.Find(strFromClipboard.GetAt(iCounter_)))
{
return 1;
}
}
}
//let the individual control handle other processing
CEdit::Default();
return 1;
}
void CInPlaceEdit::OnKillFocus(CWnd* pNewWnd)
{
CEdit::OnKillFocus(pNewWnd);
CString strEdit;
GetWindowText(strEdit);
// Send Notification to parent of edit ctrl
LV_DISPINFO dispinfo;
dispinfo.hdr.hwndFrom = GetParent()->m_hWnd;
dispinfo.hdr.idFrom = GetDlgCtrlID();
dispinfo.hdr.code = LVN_ENDLABELEDIT;
dispinfo.item.mask = LVIF_TEXT;
dispinfo.item.iItem = m_iRowIndex;
dispinfo.item.iSubItem = m_iColumnIndex;
// escape key is down so use old string
dispinfo.item.pszText = m_bESC ? LPTSTR((LPCTSTR)m_strWindowText) : LPTSTR((LPCTSTR)strEdit);
dispinfo.item.cchTextMax = m_bESC ? m_strWindowText.GetLength() : strEdit.GetLength();
GetParent()->SendMessage(WM_NOTIFY, GetParent()->GetDlgCtrlID(), (LPARAM)&dispinfo);
PostMessage(WM_CLOSE);
}
void CInPlaceEdit::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
{
// TODO: Add your message handler code here and/or call default
if ( (m_strValidChars.IsEmpty()) // no valid chars
|| ((-1 != m_strValidChars.Find(static_cast<TCHAR> (nChar)))
|| (nChar == VK_BACK) || (nChar == CTRL_C) || (nChar == CTRL_V) || (nChar == CTRL_X)))
{
CEdit::OnChar(nChar, nRepCnt, nFlags);
}
else
{
MessageBeep(MB_ICONEXCLAMATION);
return;
}
}
BOOL CInPlaceEdit::PreTranslateMessage(MSG* pMsg)
{
if (WM_KEYDOWN == pMsg->message && (VK_ESCAPE == pMsg->wParam || VK_RETURN == pMsg->wParam))
{
if (VK_ESCAPE == pMsg->wParam)
{
m_bESC = TRUE;
}
GetParent()->SetFocus();
return TRUE;
}
return CEdit::PreTranslateMessage(pMsg);
}
int CInPlaceEdit::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CEdit::OnCreate(lpCreateStruct) == -1)
return -1;
// Set the proper font
CFont* pFont = GetParent()->GetFont();
SetFont(pFont);
ShowWindow(SW_SHOW);
SetWindowText(m_strWindowText);
SetSel(0, -1);
SetFocus();
return 0;
}
CInPlaceEdit* CInPlaceEdit::GetInstance()
{
if(m_pInPlaceEdit == NULL)
{
m_pInPlaceEdit = new CInPlaceEdit;
}
return m_pInPlaceEdit;
}
void CInPlaceEdit::DeleteInstance()
{
delete m_pInPlaceEdit;
m_pInPlaceEdit = NULL;
}
BOOL CInPlaceEdit::ShowEditCtrl(DWORD dwStyle, const RECT &rCellRect, CWnd* pParentWnd,
UINT uiResourceID, int iRowIndex, int iColumnIndex,
CString& strValidChars, CString& rstrCurSelection)
{
m_iRowIndex = iRowIndex;
m_iColumnIndex = iColumnIndex;
m_strValidChars = strValidChars;
m_strWindowText = rstrCurSelection;
m_bESC = FALSE;
if (NULL == m_pInPlaceEdit->m_hWnd)
{
return m_pInPlaceEdit->Create(dwStyle, rCellRect, pParentWnd, uiResourceID);
}
return TRUE;
}
//////////////////////////////////////////////////////////////////////////
// CListCtrlExSortHead
IMPLEMENT_DYNAMIC(CListCtrlExSortHead, CHeaderCtrl)
CListCtrlExSortHead::CListCtrlExSortHead()
: m_iSortColumn(-1),m_bSortDesc(FALSE)
{
}
CListCtrlExSortHead::~CListCtrlExSortHead()
{
}
BEGIN_MESSAGE_MAP(CListCtrlExSortHead, CHeaderCtrl)
ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, &CListCtrlExSortHead::OnNMCustomdraw)
END_MESSAGE_MAP()
// CSortHeadCtrl message handlers
void CListCtrlExSortHead::SetSortArrow( const int nSortColumn, const BOOL bDesc )
{
if (nSortColumn<0 || nSortColumn>=this->GetItemCount())
{
ASSERT(FALSE);
}
m_iSortColumn=nSortColumn;
m_bSortDesc=bDesc;
// change the item to owner drawn.
HD_ITEM hditem;
hditem.mask = HDI_FORMAT;
VERIFY( GetItem( nSortColumn, &hditem ) );
hditem.fmt |= HDF_OWNERDRAW;
VERIFY( SetItem( nSortColumn, &hditem ) );
// invalidate the header control so it gets redrawn
Invalidate();
//UpdateWindow();
}
inline int CListCtrlExSortHead::GetCrntSortCol() const
{
return m_iSortColumn;
}
inline BOOL CListCtrlExSortHead::GetCrntSortDirection() const
{
return m_bSortDesc;
}
void CListCtrlExSortHead::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
CDC* pDC = CDC::FromHandle(lpDrawItemStruct->hDC);
// Get the column rect
CRect rcLabel(lpDrawItemStruct->rcItem);
// Draw the background
CBrush br(::GetSysColor(COLOR_3DFACE));
pDC->FillRect(rcLabel, &br);
// Labels are offset by a certain amount
// This offset is related to the width of a space character
int offset = pDC->GetTextExtent(_T(" "), 1).cx*2;
// Get the column text and format
TCHAR buf[256];
HD_ITEM hditem;
hditem.mask = HDI_TEXT | HDI_FORMAT;
hditem.pszText = buf;
hditem.cchTextMax = 255;
GetItem(lpDrawItemStruct->itemID, &hditem);
// Determine format for drawing column label
UINT uFormat = DT_SINGLELINE | DT_NOPREFIX | DT_VCENTER | DT_END_ELLIPSIS ;
if (hditem.fmt & HDF_CENTER)
uFormat |= DT_CENTER;
else if (hditem.fmt & HDF_RIGHT)
uFormat |= DT_RIGHT;
else
uFormat |= DT_LEFT;
// Adjust the rect if the mouse button is pressed on it
if (lpDrawItemStruct->itemState == ODS_SELECTED)
{
rcLabel.left++;
rcLabel.top += 2;
rcLabel.right++;
}
// Adjust the rect further if Sort arrow is to be displayed
if (lpDrawItemStruct->itemID == static_cast<UINT>(m_iSortColumn))
{
rcLabel.right -= 3 * offset;
}
rcLabel.left += offset;
rcLabel.right -= offset;
// Draw column label
pDC->DrawText(buf, -1, rcLabel, uFormat);
// Draw the Sort arrow
if (lpDrawItemStruct->itemID == static_cast<UINT>(m_iSortColumn))
{
CRect rcIcon(lpDrawItemStruct->rcItem);
// Set up pens to use for drawing the triangle
CPen penLight(PS_SOLID, 1, GetSysColor(COLOR_3DHILIGHT));
CPen penShadow(PS_SOLID, 1, GetSysColor(COLOR_3DSHADOW));
CPen *pOldPen = pDC->SelectObject(&penLight);
if (!m_bSortDesc)
{
// Draw triangle pointing upwards
pDC->MoveTo(rcIcon.right - 2*offset, offset - 1);
pDC->LineTo(rcIcon.right - 3*offset/2, rcIcon.bottom - offset);
pDC->LineTo(rcIcon.right - 5*offset/2-2, rcIcon.bottom - offset);
pDC->MoveTo(rcIcon.right - 5*offset/2-1, rcIcon.bottom - offset - 1);
pDC->SelectObject(&penShadow);
pDC->LineTo(rcIcon.right - 2*offset, offset-2);
}
else
{
// Draw triangle pointing downwards
pDC->MoveTo(rcIcon.right - 3*offset/2, offset-1);
pDC->LineTo(rcIcon.right - 2*offset-1, rcIcon.bottom - offset + 1);
pDC->MoveTo(rcIcon.right - 2*offset-1, rcIcon.bottom - offset);
pDC->SelectObject(&penShadow);
pDC->LineTo(rcIcon.right - 5*offset/2-1, offset - 1);
pDC->LineTo(rcIcon.right - 3*offset/2, offset - 1);
}
// Restore the pen
pDC->SelectObject(pOldPen);
}
}
void ListCtrlEx::CListCtrlExSortHead::OnNMCustomdraw(NMHDR *pNMHDR, LRESULT *pResult)
{
LPNMCUSTOMDRAW pNMCD = reinterpret_cast<LPNMCUSTOMDRAW>(pNMHDR);
switch(pNMCD->dwDrawStage)
{
case CDDS_PREPAINT:
*pResult = CDRF_NOTIFYSUBITEMDRAW; // ask for subitem notifications.
*pResult = CDRF_DODEFAULT;
break;
case CDDS_ITEMPREPAINT:
*pResult = CDRF_NOTIFYSUBITEMDRAW; // ask for subitem notifications.
*pResult = CDRF_DODEFAULT;
break;
case CDDS_ITEMPREPAINT|CDDS_SUBITEM: // recd when CDRF_NOTIFYSUBITEMDRAW is returned in
*pResult = CDRF_DODEFAULT;
break;
default: // it wasn't a notification that was interesting to us.
*pResult = CDRF_DODEFAULT;
break;
}
*pResult = 0;
}
#include "stdafx.h"
#include "ListCtrlCellEx.h"
using namespace ListCtrlEx;
//////////////////////////////////////////////////////////////////////////
// ------------ for CInPlaceCombo --------------------//
CInPlaceCombo* CInPlaceCombo::m_pInPlaceCombo = NULL;
CInPlaceCombo::CInPlaceCombo()
{
m_iRowIndex = -1;
m_iColumnIndex = -1;
m_bESC = FALSE;
}
CInPlaceCombo::~CInPlaceCombo()
{
if (::IsWindow(m_pInPlaceCombo->m_hWnd))
{
SendMessage(WM_CLOSE);
}
}
BEGIN_MESSAGE_MAP(CInPlaceCombo, CComboBox)
//{{AFX_MSG_MAP(CInPlaceCombo)
ON_WM_CREATE()
ON_WM_KILLFOCUS()
ON_WM_CHAR()
ON_CONTROL_REFLECT(CBN_CLOSEUP, OnCloseup)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
int CInPlaceCombo::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CComboBox::OnCreate(lpCreateStruct) == -1)
{
return -1;
}
CFont* pFont = GetParent()->GetFont();
SetFont(pFont);
SetFocus();
ResetContent();
CStrList::iterator iter=m_DropDownList.begin();
for (; iter!=m_DropDownList.end(); ++iter)
{
AddString(*iter);
}
return 0;
}
BOOL CInPlaceCombo::PreTranslateMessage(MSG* pMsg)
{
if (pMsg->message == WM_KEYDOWN)
{
if(pMsg->wParam == VK_RETURN || pMsg->wParam == VK_ESCAPE)
{
::TranslateMessage(pMsg);
::DispatchMessage(pMsg);
return TRUE;
}
}
return CComboBox::PreTranslateMessage(pMsg);
}
void CInPlaceCombo::OnKillFocus(CWnd* pNewWnd)
{
CComboBox::OnKillFocus(pNewWnd);
CString str;
GetWindowText(str);
// Send Notification to parent of ListView ctrl
LV_DISPINFO dispinfo;
dispinfo.hdr.hwndFrom = GetParent()->m_hWnd;
dispinfo.hdr.idFrom = GetDlgCtrlID();
dispinfo.hdr.code = LVN_ENDLABELEDIT;
dispinfo.item.mask = LVIF_TEXT;
dispinfo.item.iItem = m_iRowIndex;
dispinfo.item.iSubItem = m_iColumnIndex;
dispinfo.item.pszText = m_bESC ? LPTSTR((LPCTSTR)m_strWindowText) : LPTSTR((LPCTSTR)str);
dispinfo.item.cchTextMax = m_bESC ? m_strWindowText.GetLength() : str.GetLength();
GetParent()->SendMessage(WM_NOTIFY, GetParent()->GetDlgCtrlID(), (LPARAM)&dispinfo);
PostMessage(WM_CLOSE);
}
void CInPlaceCombo::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
{
// If the key is "Esc" set focus back to the list control
if (nChar == VK_ESCAPE || nChar == VK_RETURN)
{
if (nChar == VK_ESCAPE)
{
m_bESC = TRUE;
}
GetParent()->SetFocus();
return;
}
CComboBox::OnChar(nChar, nRepCnt, nFlags);
}
void CInPlaceCombo::OnCloseup()
{
GetParent()->SetFocus();
}
CInPlaceCombo* CInPlaceCombo::GetInstance()
{
if(m_pInPlaceCombo == NULL)
{
m_pInPlaceCombo = new CInPlaceCombo;
}
return m_pInPlaceCombo;
}
void CInPlaceCombo::DeleteInstance()
{
delete m_pInPlaceCombo;
m_pInPlaceCombo = NULL;
}
BOOL CInPlaceCombo::ShowComboCtrl(DWORD dwStyle, const CRect &rCellRect, CWnd* pParentWnd, UINT uiResourceID,
int iRowIndex, int iColumnIndex, CStrList &lstDropDown, CString strCurSel, int iCurSel )
{
m_iRowIndex = iRowIndex;
m_iColumnIndex = iColumnIndex;
m_bESC = FALSE;
m_DropDownList.clear();
m_DropDownList.insert(m_DropDownList.begin(), lstDropDown.begin(), lstDropDown.end());
BOOL bRetVal = TRUE;
if (-1 != iCurSel)
{
GetLBText(iCurSel, m_strWindowText);
}
else if (!strCurSel.IsEmpty())
{
m_strWindowText = strCurSel;
}
if (NULL == m_pInPlaceCombo->m_hWnd)
{
bRetVal = m_pInPlaceCombo->Create(dwStyle, rCellRect, pParentWnd, uiResourceID);
}
SetCurSel(iCurSel);
return bRetVal;
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// --------------- for CInPlaceEdit -------------------//
CInPlaceEdit* CInPlaceEdit::m_pInPlaceEdit=NULL;
CInPlaceEdit::CInPlaceEdit()
{
m_iRowIndex= -1;
m_iColumnIndex = -1;
m_bESC = FALSE;
m_strValidChars.Empty();
}
CInPlaceEdit::~CInPlaceEdit()
{
if (::IsWindow(m_pInPlaceEdit->m_hWnd))
{
SendMessage(WM_CLOSE);
}
}
BEGIN_MESSAGE_MAP(CInPlaceEdit, CEdit)
//{{AFX_MSG_MAP(CInPlaceEdit)
ON_WM_KILLFOCUS()
ON_WM_CHAR()
ON_MESSAGE(WM_PASTE, CInPlaceEdit::OnPaste)
ON_WM_CREATE()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
/////////////////////////////////////////////////////////////////////////////
// CInPlaceEdit message handlers
LRESULT CInPlaceEdit::OnPaste(WPARAM /*wParam*/, LPARAM /*lParam*/)
{
if (!m_strValidChars.IsEmpty())
{
CString strFromClipboard;
// get the text from clipboard
if(OpenClipboard()) {
HANDLE l_hData = GetClipboardData(CF_TEXT);
if(NULL == l_hData) {
return 1;
}
char *l_pBuffer = (char*)GlobalLock(l_hData);
if(NULL != l_pBuffer) {
strFromClipboard = l_pBuffer;
}
GlobalUnlock(l_hData);
CloseClipboard();
}
// Validate the characters before pasting
for(int iCounter_ = 0; iCounter_ < strFromClipboard.GetLength(); iCounter_++)
{
if (-1 == m_strValidChars.Find(strFromClipboard.GetAt(iCounter_)))
{
return 1;
}
}
}
//let the individual control handle other processing
CEdit::Default();
return 1;
}
void CInPlaceEdit::OnKillFocus(CWnd* pNewWnd)
{
CEdit::OnKillFocus(pNewWnd);
CString strEdit;
GetWindowText(strEdit);
// Send Notification to parent of edit ctrl
LV_DISPINFO dispinfo;
dispinfo.hdr.hwndFrom = GetParent()->m_hWnd;
dispinfo.hdr.idFrom = GetDlgCtrlID();
dispinfo.hdr.code = LVN_ENDLABELEDIT;
dispinfo.item.mask = LVIF_TEXT;
dispinfo.item.iItem = m_iRowIndex;
dispinfo.item.iSubItem = m_iColumnIndex;
// escape key is down so use old string
dispinfo.item.pszText = m_bESC ? LPTSTR((LPCTSTR)m_strWindowText) : LPTSTR((LPCTSTR)strEdit);
dispinfo.item.cchTextMax = m_bESC ? m_strWindowText.GetLength() : strEdit.GetLength();
GetParent()->SendMessage(WM_NOTIFY, GetParent()->GetDlgCtrlID(), (LPARAM)&dispinfo);
PostMessage(WM_CLOSE);
}
void CInPlaceEdit::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
{
// TODO: Add your message handler code here and/or call default
if ( (m_strValidChars.IsEmpty()) // no valid chars
|| ((-1 != m_strValidChars.Find(static_cast<TCHAR> (nChar)))
|| (nChar == VK_BACK) || (nChar == CTRL_C) || (nChar == CTRL_V) || (nChar == CTRL_X)))
{
CEdit::OnChar(nChar, nRepCnt, nFlags);
}
else
{
MessageBeep(MB_ICONEXCLAMATION);
return;
}
}
BOOL CInPlaceEdit::PreTranslateMessage(MSG* pMsg)
{
if (WM_KEYDOWN == pMsg->message && (VK_ESCAPE == pMsg->wParam || VK_RETURN == pMsg->wParam))
{
if (VK_ESCAPE == pMsg->wParam)
{
m_bESC = TRUE;
}
GetParent()->SetFocus();
return TRUE;
}
return CEdit::PreTranslateMessage(pMsg);
}
int CInPlaceEdit::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CEdit::OnCreate(lpCreateStruct) == -1)
return -1;
// Set the proper font
CFont* pFont = GetParent()->GetFont();
SetFont(pFont);
ShowWindow(SW_SHOW);
SetWindowText(m_strWindowText);
SetSel(0, -1);
SetFocus();
return 0;
}
CInPlaceEdit* CInPlaceEdit::GetInstance()
{
if(m_pInPlaceEdit == NULL)
{
m_pInPlaceEdit = new CInPlaceEdit;
}
return m_pInPlaceEdit;
}
void CInPlaceEdit::DeleteInstance()
{
delete m_pInPlaceEdit;
m_pInPlaceEdit = NULL;
}
BOOL CInPlaceEdit::ShowEditCtrl(DWORD dwStyle, const RECT &rCellRect, CWnd* pParentWnd,
UINT uiResourceID, int iRowIndex, int iColumnIndex,
CString& strValidChars, CString& rstrCurSelection)
{
m_iRowIndex = iRowIndex;
m_iColumnIndex = iColumnIndex;
m_strValidChars = strValidChars;
m_strWindowText = rstrCurSelection;
m_bESC = FALSE;
if (NULL == m_pInPlaceEdit->m_hWnd)
{
return m_pInPlaceEdit->Create(dwStyle, rCellRect, pParentWnd, uiResourceID);
}
return TRUE;
}
//////////////////////////////////////////////////////////////////////////
// CListCtrlExSortHead
IMPLEMENT_DYNAMIC(CListCtrlExSortHead, CHeaderCtrl)
CListCtrlExSortHead::CListCtrlExSortHead()
: m_iSortColumn(-1),m_bSortDesc(FALSE)
{
}
CListCtrlExSortHead::~CListCtrlExSortHead()
{
}
BEGIN_MESSAGE_MAP(CListCtrlExSortHead, CHeaderCtrl)
ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, &CListCtrlExSortHead::OnNMCustomdraw)
END_MESSAGE_MAP()
// CSortHeadCtrl message handlers
void CListCtrlExSortHead::SetSortArrow( const int nSortColumn, const BOOL bDesc )
{
if (nSortColumn<0 || nSortColumn>=this->GetItemCount())
{
ASSERT(FALSE);
}
m_iSortColumn=nSortColumn;
m_bSortDesc=bDesc;
// change the item to owner drawn.
HD_ITEM hditem;
hditem.mask = HDI_FORMAT;
VERIFY( GetItem( nSortColumn, &hditem ) );
hditem.fmt |= HDF_OWNERDRAW;
VERIFY( SetItem( nSortColumn, &hditem ) );
// invalidate the header control so it gets redrawn
Invalidate();
//UpdateWindow();
}
inline int CListCtrlExSortHead::GetCrntSortCol() const
{
return m_iSortColumn;
}
inline BOOL CListCtrlExSortHead::GetCrntSortDirection() const
{
return m_bSortDesc;
}
void CListCtrlExSortHead::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
CDC* pDC = CDC::FromHandle(lpDrawItemStruct->hDC);
// Get the column rect
CRect rcLabel(lpDrawItemStruct->rcItem);
// Draw the background
CBrush br(::GetSysColor(COLOR_3DFACE));
pDC->FillRect(rcLabel, &br);
// Labels are offset by a certain amount
// This offset is related to the width of a space character
int offset = pDC->GetTextExtent(_T(" "), 1).cx*2;
// Get the column text and format
TCHAR buf[256];
HD_ITEM hditem;
hditem.mask = HDI_TEXT | HDI_FORMAT;
hditem.pszText = buf;
hditem.cchTextMax = 255;
GetItem(lpDrawItemStruct->itemID, &hditem);
// Determine format for drawing column label
UINT uFormat = DT_SINGLELINE | DT_NOPREFIX | DT_VCENTER | DT_END_ELLIPSIS ;
if (hditem.fmt & HDF_CENTER)
uFormat |= DT_CENTER;
else if (hditem.fmt & HDF_RIGHT)
uFormat |= DT_RIGHT;
else
uFormat |= DT_LEFT;
// Adjust the rect if the mouse button is pressed on it
if (lpDrawItemStruct->itemState == ODS_SELECTED)
{
rcLabel.left++;
rcLabel.top += 2;
rcLabel.right++;
}
// Adjust the rect further if Sort arrow is to be displayed
if (lpDrawItemStruct->itemID == static_cast<UINT>(m_iSortColumn))
{
rcLabel.right -= 3 * offset;
}
rcLabel.left += offset;
rcLabel.right -= offset;
// Draw column label
pDC->DrawText(buf, -1, rcLabel, uFormat);
// Draw the Sort arrow
if (lpDrawItemStruct->itemID == static_cast<UINT>(m_iSortColumn))
{
CRect rcIcon(lpDrawItemStruct->rcItem);
// Set up pens to use for drawing the triangle
CPen penLight(PS_SOLID, 1, GetSysColor(COLOR_3DHILIGHT));
CPen penShadow(PS_SOLID, 1, GetSysColor(COLOR_3DSHADOW));
CPen *pOldPen = pDC->SelectObject(&penLight);
if (!m_bSortDesc)
{
// Draw triangle pointing upwards
pDC->MoveTo(rcIcon.right - 2*offset, offset - 1);
pDC->LineTo(rcIcon.right - 3*offset/2, rcIcon.bottom - offset);
pDC->LineTo(rcIcon.right - 5*offset/2-2, rcIcon.bottom - offset);
pDC->MoveTo(rcIcon.right - 5*offset/2-1, rcIcon.bottom - offset - 1);
pDC->SelectObject(&penShadow);
pDC->LineTo(rcIcon.right - 2*offset, offset-2);
}
else
{
// Draw triangle pointing downwards
pDC->MoveTo(rcIcon.right - 3*offset/2, offset-1);
pDC->LineTo(rcIcon.right - 2*offset-1, rcIcon.bottom - offset + 1);
pDC->MoveTo(rcIcon.right - 2*offset-1, rcIcon.bottom - offset);
pDC->SelectObject(&penShadow);
pDC->LineTo(rcIcon.right - 5*offset/2-1, offset - 1);
pDC->LineTo(rcIcon.right - 3*offset/2, offset - 1);
}
// Restore the pen
pDC->SelectObject(pOldPen);
}
}
void ListCtrlEx::CListCtrlExSortHead::OnNMCustomdraw(NMHDR *pNMHDR, LRESULT *pResult)
{
LPNMCUSTOMDRAW pNMCD = reinterpret_cast<LPNMCUSTOMDRAW>(pNMHDR);
switch(pNMCD->dwDrawStage)
{
case CDDS_PREPAINT:
*pResult = CDRF_NOTIFYSUBITEMDRAW; // ask for subitem notifications.
*pResult = CDRF_DODEFAULT;
break;
case CDDS_ITEMPREPAINT:
*pResult = CDRF_NOTIFYSUBITEMDRAW; // ask for subitem notifications.
*pResult = CDRF_DODEFAULT;
break;
case CDDS_ITEMPREPAINT|CDDS_SUBITEM: // recd when CDRF_NOTIFYSUBITEMDRAW is returned in
*pResult = CDRF_DODEFAULT;
break;
default: // it wasn't a notification that was interesting to us.
*pResult = CDRF_DODEFAULT;
break;
}
*pResult = 0;
}
// ListCtrlEx.h - 去除 //#define USING_CUSTOM_DRAW前面的注释,即启用自绘效果
Code
#ifndef __LISTCTRLEX__
#define __LISTCTRLEX__
#include <map>
#include <vector>
#include <list>
#include "ListCtrlCellEx.h"
//////////////////////////////////////////////////////////////////////////
//#define USING_CUSTOM_DRAW // define this to control the cell control theme (whether or not to draw them hand on ))
//////////////////////////////////////////////////////////////////////////
namespace ListCtrlEx
{
using namespace std;
enum CellType
{
Normal, // can show icon
CheckBox, // can be checked
RadioBox, // can be checked
ComboBox, // can be shown in place
EditBox, // can be shown in place
Progress // can show progress here
};
enum SortType
{
SortByString,
SortByDigit,
SortByDate
};
struct CheckCellMsg
{
CheckCellMsg(int nRow=-1, int nCol=-1, BOOL bChecked=TRUE)
:m_nRow(nRow), m_nColumn(nCol), m_bChecked(bChecked)
{}
int m_nRow;
int m_nColumn;
BOOL m_bChecked;
} ;
struct EnableCellMsg
{
EnableCellMsg(int nRow=-1, int nCol=-1, BOOL bEnabled=TRUE)
:m_nRow(nRow), m_nColumn(nCol), m_bEnabled(bEnabled)
{}
int m_nRow;
int m_nColumn;
BOOL m_bEnabled;
} ;
// user messages
#define WM_ListCtrlEx_LBUTTONDOWN WM_USER+8001
#define WM_ListCtrlEx_LBUTTONDBLCLK WM_USER+8002
#define WM_ListCtrlEx_RBUTTONDOWN WM_USER+8003
#define WM_ListCtrlEx_CHECKCELL WM_USER+8004
#define WM_ListCtrlEx_ENABLECELL WM_USER+8005
#define WM_ListCtrlEx_CHECK_CHANGED WM_USER+8006
#define WM_ListCtrlEx_ENABLE_CHANGED WM_USER+8007
// User define message
// This message is posted to the parent
// The message can be handled to make the necessary validations, if any
#define WM_VALIDATE WM_USER + 8008
// User define message
// This message is posted to the parent
// The message should be handled to spcify the items to the added to the combo
#define WM_SET_ITEMS WM_USER + 8009
// CListCtrlEx
class AFX_EXT_CLASS CListCtrlEx : public CListCtrl
{
DECLARE_DYNAMIC(CListCtrlEx)
public:
struct CellData
{
private:
DWORD m_dwData; // enabled at the first bit and checked at the second bit (count from the back)
/*BOOL m_bEnabled;
BOOL m_bChecked;*/
public:
#define Enable_Mask 0x00000001
#define Check_Mask 0x00000002
CellData(BOOL bEnabled=TRUE, BOOL bChecked=FALSE, int nRadioGroup=0, int nImage=-1, UINT uProgMaxVal=100)
: m_nRadioGroup(nRadioGroup), m_strValidChars(_T("")),
m_nImage(nImage), m_uProgresVal(0), m_uProgressMax(uProgMaxVal)
{
SetEnable(bEnabled);
SetCheck(bChecked);
};
int m_nImage;
int m_nRadioGroup;
CStrList m_lstString;
CString m_strValidChars;
UINT m_uProgresVal;
UINT m_uProgressMax;
inline void SetEnable(BOOL bEnbaled=TRUE) {
if (bEnbaled)
m_dwData |= Enable_Mask;
else
m_dwData &=(~Enable_Mask);
}
inline BOOL GetEnable() const {
return (m_dwData&Enable_Mask);
}
inline void SetCheck(BOOL bChecked=TRUE){
if(bChecked)
m_dwData|=Check_Mask;
else
m_dwData &=(~Check_Mask);
}
inline BOOL GetCheck() const {
return (m_dwData&Check_Mask);
}
};
typedef CellType ColumnType;
typedef map<int, ColumnType> ColMap; // column to column type
typedef pair<int, int> CellIndex; // row, column
typedef map<CellIndex, CellData> CellDataMap; // cell index(row, column) to cell data
typedef map<CellIndex, CProgressCtrl*> ProgressMap;
public:
CListCtrlEx();
virtual ~CListCtrlEx();
// overrides
public:
CImageList* SetImageList( CImageList* pImageList, int nImageListType );
int InsertColumn(int nCol, CString strColHead, int nWidth=-1,ColumnType eColType=Normal,
int nFormat= LVCFMT_CENTER , SortType eSortBy=SortByString, int nSubItem= -1 );
BOOL DeleteColumn( int nCol );
BOOL DeleteAllItems( );
BOOL DeleteItem( int nItem );
// operations
public:
static BOOL DrawGradientRect(CDC *pDC, CRect &rect, COLORREF crTopLeft, COLORREF crBottomRight);
BOOL GetCheckRect(int iRow, int iCol, CRect &rect);
BOOL GetCellRect(CellIndex ix, CRect &rect);
BOOL GetCellRect(int iRow, int iCol, CRect &rect, int nArea=LVIR_BOUNDS);
BOOL GetRowRect(int nRow, CRect &rcRow);
BOOL GetColRect(int nCol, CRect &rcCol);
int GetColumnCount() { return GetHeaderCtrl()->GetItemCount(); }
int GetRowCount() { return GetItemCount(); }
ColumnType GetColumnType(int nColIndex);
void SetColumnType(int nColIndex, ColumnType eColType);
BOOL IsSupportSort();
void SetSupportSort(BOOL bSuptSort=TRUE);
SortType GetColumnSortBy(int nColIndex);
void SetColumnSortBy(int nColIndex, SortType eSortBy=SortByString);
BOOL IsColumnSortAsc(int nColIndex);
// for all cell types
BOOL GetCellEnabled(int nRow, int nCol);
void SetCellEnabled(int nRow, int nCol, BOOL bEnabled=TRUE);
// for normal & edit box
void SetCellImage(int nRow, int nCol, int nImage=-1);
// for radio & check box
BOOL GetCellChecked(int nRow, int nCol);
void SetCellChecked(int nRow, int nCol, BOOL bChecked=TRUE);
// for radio only
int GetCellRadioGrp(int nRow, int nCol);
void SetCellRadioGrp(int nRow, int nCol, int nGroupNO=0);
// for combo box only
void SetCellStringList(int nRow, int nCol, CStrList &lstStrings);
void AddCellString(int nRow, int nCol, CString &str);
// for edit box only
void SetCellValidChars(int nRow, int nCol, CString &strValidChars);
// for progress bar only
BOOL IsShowPogressPercent();
void SetShowProgressPercent(BOOL bShowProgPercent=TRUE);
void SetCellProgressMaxValue(int nRow, int nCol, UINT uMaxValue=100);
void SetCellProgressValue(int nRow, int nCol, UINT uValue);
//
CellIndex Point2Cell(const CPoint &point);
protected:
virtual void PreSubclassWindow();
DECLARE_MESSAGE_MAP()
// message handlers
afx_msg void OnNMCustomdraw(NMHDR *pNMHDR, LRESULT *pResult);
afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
afx_msg void OnLButtonDblClk(UINT nFlags, CPoint point);
afx_msg void OnRButtonDown(UINT nFlags, CPoint point);
afx_msg void OnLvnBeginlabeledit(NMHDR *pNMHDR, LRESULT *pResult);
afx_msg void OnLvnEndlabeledit(NMHDR *pNMHDR, LRESULT *pResult);
afx_msg void OnLvnColumnclick(NMHDR *pNMHDR, LRESULT *pResult);
afx_msg void OnDestroy();
// implementation
private:
inline void DrawCell(CDC *pDC, CString &strText, CRect &rcCell, BOOL bSelected,
int nRow, int nCol, LRESULT *pResult);
void DrawNormal(CDC *pDC, CString &strText, CRect &rcCell, BOOL bSelected, const CellData &cellData, int uLvcFmt=LVCFMT_CENTER);
void DrawCheckBox(CDC *pDC, CString &strText, CRect &rcCell, BOOL bSelected, const CellData &cellData);
void DrawRadioBox(CDC *pDC, CString &strText, CRect &rcCell, BOOL bSelected, const CellData &cellData);
void DrawComboBox(CDC *pDC, CString &strText, CRect &rcCell, BOOL bSelected, const CellData &cellData);
void DrawEditBox(CDC *pDC, CString &strText, CRect &rcCell, BOOL bSelected, const CellData &cellData, int uLvcFmt=LVCFMT_CENTER);
void DrawProgress(CDC *pDC, CString &strText, CRect &rcCell, BOOL bSelected, const CellData &cellData);
int GetColumnFmt(int nCol);
inline void ShowCellInPlace(CellIndex ix, CellType eCellType);
void ShowInPlaceCombo(CellIndex ix);
void ShowInPlaceEdit(CellIndex ix);
private:
void SetColumnSortDirection(int nColIndex, BOOL bIsAsc=TRUE);
int GetProgBarSize(const CellData &cellData, CRect &rcProg, float *pPersent=NULL);
CImageList* TheImageList();
CellData& FindCellData(CellIndex ix);
CellData& FindCellData(int nRow, int nCol);
CellDataMap m_mapCell2Data;
ColMap m_mapCol2ColType;
DWORD m_dwEditStyle;
DWORD m_dwComboStyle;
int m_nImageListType;
DWORD m_dwListCtrlExStyle; //
protected:
struct _ColumnSort_t
{
_ColumnSort_t(SortType eSortType=SortByString, BOOL bIsAsc=TRUE)
:m_eSortType(eSortType), m_bIsAsc(bIsAsc)
{};
SortType m_eSortType;
BOOL m_bIsAsc;
};
struct _SortParam_t
{
_ColumnSort_t m_ColParam;
int m_nSortCol;
CListCtrlEx *m_pTheList;
};
typedef map<int, _ColumnSort_t> ColSortMap;
ColSortMap m_mapCol2Sort;
CListCtrlExSortHead m_ctlSortHead;
static int CALLBACK CompareFunc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort);
static bool IsDigitStr(LPCTSTR str);
static int IntCompare(LPCTSTR strInt1st, LPCTSTR strInt2nd);
static int StrCompare(LPCTSTR str1st, LPCTSTR str2nd);
void PreSortItems( vector<DWORD_PTR> &vec2StoreData );
void PostSortItems( vector<DWORD_PTR> &vec2StoreData );
};
}
#endif
#ifndef __LISTCTRLEX__
#define __LISTCTRLEX__
#include <map>
#include <vector>
#include <list>
#include "ListCtrlCellEx.h"
//////////////////////////////////////////////////////////////////////////
//#define USING_CUSTOM_DRAW // define this to control the cell control theme (whether or not to draw them hand on ))
//////////////////////////////////////////////////////////////////////////
namespace ListCtrlEx
{
using namespace std;
enum CellType
{
Normal, // can show icon
CheckBox, // can be checked
RadioBox, // can be checked
ComboBox, // can be shown in place
EditBox, // can be shown in place
Progress // can show progress here
};
enum SortType
{
SortByString,
SortByDigit,
SortByDate
};
struct CheckCellMsg
{
CheckCellMsg(int nRow=-1, int nCol=-1, BOOL bChecked=TRUE)
:m_nRow(nRow), m_nColumn(nCol), m_bChecked(bChecked)
{}
int m_nRow;
int m_nColumn;
BOOL m_bChecked;
} ;
struct EnableCellMsg
{
EnableCellMsg(int nRow=-1, int nCol=-1, BOOL bEnabled=TRUE)
:m_nRow(nRow), m_nColumn(nCol), m_bEnabled(bEnabled)
{}
int m_nRow;
int m_nColumn;
BOOL m_bEnabled;
} ;
// user messages
#define WM_ListCtrlEx_LBUTTONDOWN WM_USER+8001
#define WM_ListCtrlEx_LBUTTONDBLCLK WM_USER+8002
#define WM_ListCtrlEx_RBUTTONDOWN WM_USER+8003
#define WM_ListCtrlEx_CHECKCELL WM_USER+8004
#define WM_ListCtrlEx_ENABLECELL WM_USER+8005
#define WM_ListCtrlEx_CHECK_CHANGED WM_USER+8006
#define WM_ListCtrlEx_ENABLE_CHANGED WM_USER+8007
// User define message
// This message is posted to the parent
// The message can be handled to make the necessary validations, if any
#define WM_VALIDATE WM_USER + 8008
// User define message
// This message is posted to the parent
// The message should be handled to spcify the items to the added to the combo
#define WM_SET_ITEMS WM_USER + 8009
// CListCtrlEx
class AFX_EXT_CLASS CListCtrlEx : public CListCtrl
{
DECLARE_DYNAMIC(CListCtrlEx)
public:
struct CellData
{
private:
DWORD m_dwData; // enabled at the first bit and checked at the second bit (count from the back)
/*BOOL m_bEnabled;
BOOL m_bChecked;*/
public:
#define Enable_Mask 0x00000001
#define Check_Mask 0x00000002
CellData(BOOL bEnabled=TRUE, BOOL bChecked=FALSE, int nRadioGroup=0, int nImage=-1, UINT uProgMaxVal=100)
: m_nRadioGroup(nRadioGroup), m_strValidChars(_T("")),
m_nImage(nImage), m_uProgresVal(0), m_uProgressMax(uProgMaxVal)
{
SetEnable(bEnabled);
SetCheck(bChecked);
};
int m_nImage;
int m_nRadioGroup;
CStrList m_lstString;
CString m_strValidChars;
UINT m_uProgresVal;
UINT m_uProgressMax;
inline void SetEnable(BOOL bEnbaled=TRUE) {
if (bEnbaled)
m_dwData |= Enable_Mask;
else
m_dwData &=(~Enable_Mask);
}
inline BOOL GetEnable() const {
return (m_dwData&Enable_Mask);
}
inline void SetCheck(BOOL bChecked=TRUE){
if(bChecked)
m_dwData|=Check_Mask;
else
m_dwData &=(~Check_Mask);
}
inline BOOL GetCheck() const {
return (m_dwData&Check_Mask);
}
};
typedef CellType ColumnType;
typedef map<int, ColumnType> ColMap; // column to column type
typedef pair<int, int> CellIndex; // row, column
typedef map<CellIndex, CellData> CellDataMap; // cell index(row, column) to cell data
typedef map<CellIndex, CProgressCtrl*> ProgressMap;
public:
CListCtrlEx();
virtual ~CListCtrlEx();
// overrides
public:
CImageList* SetImageList( CImageList* pImageList, int nImageListType );
int InsertColumn(int nCol, CString strColHead, int nWidth=-1,ColumnType eColType=Normal,
int nFormat= LVCFMT_CENTER , SortType eSortBy=SortByString, int nSubItem= -1 );
BOOL DeleteColumn( int nCol );
BOOL DeleteAllItems( );
BOOL DeleteItem( int nItem );
// operations
public:
static BOOL DrawGradientRect(CDC *pDC, CRect &rect, COLORREF crTopLeft, COLORREF crBottomRight);
BOOL GetCheckRect(int iRow, int iCol, CRect &rect);
BOOL GetCellRect(CellIndex ix, CRect &rect);
BOOL GetCellRect(int iRow, int iCol, CRect &rect, int nArea=LVIR_BOUNDS);
BOOL GetRowRect(int nRow, CRect &rcRow);
BOOL GetColRect(int nCol, CRect &rcCol);
int GetColumnCount() { return GetHeaderCtrl()->GetItemCount(); }
int GetRowCount() { return GetItemCount(); }
ColumnType GetColumnType(int nColIndex);
void SetColumnType(int nColIndex, ColumnType eColType);
BOOL IsSupportSort();
void SetSupportSort(BOOL bSuptSort=TRUE);
SortType GetColumnSortBy(int nColIndex);
void SetColumnSortBy(int nColIndex, SortType eSortBy=SortByString);
BOOL IsColumnSortAsc(int nColIndex);
// for all cell types
BOOL GetCellEnabled(int nRow, int nCol);
void SetCellEnabled(int nRow, int nCol, BOOL bEnabled=TRUE);
// for normal & edit box
void SetCellImage(int nRow, int nCol, int nImage=-1);
// for radio & check box
BOOL GetCellChecked(int nRow, int nCol);
void SetCellChecked(int nRow, int nCol, BOOL bChecked=TRUE);
// for radio only
int GetCellRadioGrp(int nRow, int nCol);
void SetCellRadioGrp(int nRow, int nCol, int nGroupNO=0);
// for combo box only
void SetCellStringList(int nRow, int nCol, CStrList &lstStrings);
void AddCellString(int nRow, int nCol, CString &str);
// for edit box only
void SetCellValidChars(int nRow, int nCol, CString &strValidChars);
// for progress bar only
BOOL IsShowPogressPercent();
void SetShowProgressPercent(BOOL bShowProgPercent=TRUE);
void SetCellProgressMaxValue(int nRow, int nCol, UINT uMaxValue=100);
void SetCellProgressValue(int nRow, int nCol, UINT uValue);
//
CellIndex Point2Cell(const CPoint &point);
protected:
virtual void PreSubclassWindow();
DECLARE_MESSAGE_MAP()
// message handlers
afx_msg void OnNMCustomdraw(NMHDR *pNMHDR, LRESULT *pResult);
afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
afx_msg void OnLButtonDblClk(UINT nFlags, CPoint point);
afx_msg void OnRButtonDown(UINT nFlags, CPoint point);
afx_msg void OnLvnBeginlabeledit(NMHDR *pNMHDR, LRESULT *pResult);
afx_msg void OnLvnEndlabeledit(NMHDR *pNMHDR, LRESULT *pResult);
afx_msg void OnLvnColumnclick(NMHDR *pNMHDR, LRESULT *pResult);
afx_msg void OnDestroy();
// implementation
private:
inline void DrawCell(CDC *pDC, CString &strText, CRect &rcCell, BOOL bSelected,
int nRow, int nCol, LRESULT *pResult);
void DrawNormal(CDC *pDC, CString &strText, CRect &rcCell, BOOL bSelected, const CellData &cellData, int uLvcFmt=LVCFMT_CENTER);
void DrawCheckBox(CDC *pDC, CString &strText, CRect &rcCell, BOOL bSelected, const CellData &cellData);
void DrawRadioBox(CDC *pDC, CString &strText, CRect &rcCell, BOOL bSelected, const CellData &cellData);
void DrawComboBox(CDC *pDC, CString &strText, CRect &rcCell, BOOL bSelected, const CellData &cellData);
void DrawEditBox(CDC *pDC, CString &strText, CRect &rcCell, BOOL bSelected, const CellData &cellData, int uLvcFmt=LVCFMT_CENTER);
void DrawProgress(CDC *pDC, CString &strText, CRect &rcCell, BOOL bSelected, const CellData &cellData);
int GetColumnFmt(int nCol);
inline void ShowCellInPlace(CellIndex ix, CellType eCellType);
void ShowInPlaceCombo(CellIndex ix);
void ShowInPlaceEdit(CellIndex ix);
private:
void SetColumnSortDirection(int nColIndex, BOOL bIsAsc=TRUE);
int GetProgBarSize(const CellData &cellData, CRect &rcProg, float *pPersent=NULL);
CImageList* TheImageList();
CellData& FindCellData(CellIndex ix);
CellData& FindCellData(int nRow, int nCol);
CellDataMap m_mapCell2Data;
ColMap m_mapCol2ColType;
DWORD m_dwEditStyle;
DWORD m_dwComboStyle;
int m_nImageListType;
DWORD m_dwListCtrlExStyle; //
protected:
struct _ColumnSort_t
{
_ColumnSort_t(SortType eSortType=SortByString, BOOL bIsAsc=TRUE)
:m_eSortType(eSortType), m_bIsAsc(bIsAsc)
{};
SortType m_eSortType;
BOOL m_bIsAsc;
};
struct _SortParam_t
{
_ColumnSort_t m_ColParam;
int m_nSortCol;
CListCtrlEx *m_pTheList;
};
typedef map<int, _ColumnSort_t> ColSortMap;
ColSortMap m_mapCol2Sort;
CListCtrlExSortHead m_ctlSortHead;
static int CALLBACK CompareFunc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort);
static bool IsDigitStr(LPCTSTR str);
static int IntCompare(LPCTSTR strInt1st, LPCTSTR strInt2nd);
static int StrCompare(LPCTSTR str1st, LPCTSTR str2nd);
void PreSortItems( vector<DWORD_PTR> &vec2StoreData );
void PostSortItems( vector<DWORD_PTR> &vec2StoreData );
};
}
#endif
// ListCtrlEx.cpp
Code
#include "stdafx.h"
#include "ListCtrlEx.h"
#include "PerfTimer.h"
using namespace ListCtrlEx;
#define IDC_CELL_EDIT 1000001
#define IDC_CELL_COMBO 1000002
#define IDC_PROGRESS_START IDC_CELL_COMBO+1
#define COLOR_FRAME #336699
#define COLOR_MARK #33ff99
#define SHOW_PROGRESS_PERCENT 0x00000001
#define SUPPORT_SORT 0x00000002
//////////////////////////////////////////////////////////////////////////
// CListCtrlEx
IMPLEMENT_DYNAMIC(CListCtrlEx, CListCtrl)
CListCtrlEx::CListCtrlEx()
{
m_nImageListType=LVSIL_NORMAL;
m_dwListCtrlExStyle=0;
m_dwEditStyle = ES_AUTOHSCROLL | ES_AUTOVSCROLL | ES_LEFT | ES_NOHIDESEL;
m_dwComboStyle = WS_BORDER | WS_CHILD | WS_VISIBLE | ES_AUTOHSCROLL | ES_AUTOVSCROLL |
CBS_DROPDOWNLIST | CBS_DISABLENOSCROLL;
}
CListCtrlEx::~CListCtrlEx()
{
CInPlaceCombo::DeleteInstance();
CInPlaceEdit::DeleteInstance();
}
BEGIN_MESSAGE_MAP(CListCtrlEx, CListCtrl)
ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, &CListCtrlEx::OnNMCustomdraw)
ON_WM_LBUTTONDOWN()
ON_WM_LBUTTONDBLCLK()
ON_WM_RBUTTONDOWN()
ON_NOTIFY_REFLECT(LVN_BEGINLABELEDIT, &CListCtrlEx::OnLvnBeginlabeledit)
ON_NOTIFY_REFLECT(LVN_ENDLABELEDIT, &CListCtrlEx::OnLvnEndlabeledit)
ON_NOTIFY_REFLECT(LVN_COLUMNCLICK, &CListCtrlEx::OnLvnColumnclick)
ON_WM_DESTROY()
END_MESSAGE_MAP()
//////////////////////////////////////////////////////////////////////////
// override CListCtrl interfaces
CImageList* CListCtrlEx::SetImageList( CImageList* pImageList, int nImageListType )
{
m_nImageListType=nImageListType;
return CListCtrl::SetImageList(pImageList, nImageListType);
}
int CListCtrlEx::InsertColumn(int nCol, CString strColHead, int nWidth, ColumnType eColType, int nFormat,
SortType eSortBy, int nSubItem )
{
//
m_mapCol2ColType[nCol]=eColType;
m_mapCol2Sort[nCol].m_eSortType=eSortBy;
return CListCtrl::InsertColumn(nCol, strColHead, nFormat, nWidth, nSubItem);
}
BOOL CListCtrlEx::DeleteColumn( int nCol )
{
if (!CListCtrl::DeleteColumn(nCol))
{
return FALSE;
}
m_mapCol2ColType.erase(nCol);
return TRUE;
}
BOOL CListCtrlEx::DeleteAllItems( )
{
if (!CListCtrl::DeleteAllItems())
{
return FALSE;
}
m_mapCell2Data.clear();
return TRUE;
}
BOOL CListCtrlEx::DeleteItem( int nItem )
{
if (!CListCtrl::DeleteItem(nItem))
{
return FALSE;
}
// erase all of the row
int nColCnt=GetColumnCount();
for (int nCol=0; nCol<nColCnt; ++nCol)
{
m_mapCell2Data.erase(make_pair(nItem, nCol));
}
return TRUE;
}
// public interfaces
CListCtrlEx::CellIndex CListCtrlEx::Point2Cell(const CPoint &point)
{
LVHITTESTINFO lvHitTestInfo;
CRect rcItem;
lvHitTestInfo.pt = point;
if (HitTest(&lvHitTestInfo) >= 0 && lvHitTestInfo.iItem >= 0)
{
int nRow = lvHitTestInfo.iItem;
int nColCnt=GetColumnCount();
for (int nCol=0; nCol<nColCnt; ++nCol)
{
if (GetCellRect(nRow, nCol, rcItem))
{
if (rcItem.PtInRect(point))
{
return make_pair(nRow, nCol);
}
}
}
}
return make_pair(-1, -1);
}
//////////////////////////////////////////////////////////////////////////
// need to deal with delete column
void CListCtrlEx::SetColumnType(int nColIndex, ColumnType eColType)
{
ASSERT(0<=nColIndex && nColIndex<GetColumnCount());
// init if empty
if (m_mapCol2ColType.empty())
{
for (int i=0; i<GetColumnCount(); ++i)
{
m_mapCol2ColType.insert(ColMap::value_type(i, Normal));
}
}
// assign
m_mapCol2ColType[nColIndex]=eColType;
// maybe should update window here
}
CListCtrlEx::ColumnType CListCtrlEx::GetColumnType(int nColIndex)
{
// insert new if not found
ColMap::iterator iter=m_mapCol2ColType.find(nColIndex);
if (iter==m_mapCol2ColType.end())
{
m_mapCol2ColType[nColIndex]=Normal;
}
return m_mapCol2ColType[nColIndex];
}
SortType CListCtrlEx::GetColumnSortBy(int nColIndex)
{
// insert new if not found
ColSortMap::iterator iter=m_mapCol2Sort.find(nColIndex);
if (m_mapCol2Sort.end()==iter)
{
m_mapCol2Sort[nColIndex]=_ColumnSort_t();
}
return m_mapCol2Sort[nColIndex].m_eSortType;
}
void CListCtrlEx::SetColumnSortBy(int nColIndex, SortType eSortBy)
{
// init sort map
if (m_mapCol2Sort.empty())
{
for (int i=0; i<GetColumnCount(); ++i)
{
m_mapCol2Sort[i]=_ColumnSort_t();
}
}
// assign
m_mapCol2Sort[nColIndex].m_eSortType=eSortBy;
}
//////////////////////////////////////////////////////////////////////////
// need to deal with delete items
void CListCtrlEx::SetCellEnabled(int nRow, int nCol, BOOL bEnabled)
{
ASSERT(0<=nRow && nRow<GetItemCount() && nCol>=0 && nCol< GetColumnCount());
//FindCellData(nRow, nCol).m_bEnabled=bEnabled;
FindCellData(nRow, nCol).SetEnable(bEnabled);
CRect rcCell;
if(GetCellRect(nRow, nCol, rcCell))
{
InvalidateRect(&rcCell);
UpdateWindow();
// send message to parent
CWnd *pWnd=NULL;
if ((pWnd = GetParent())!=NULL )
{
EnableCellMsg msg(nRow, nCol, bEnabled);
pWnd->SendMessage(WM_ListCtrlEx_ENABLE_CHANGED, (WPARAM)this, (LPARAM)&msg);
}
}
}
BOOL CListCtrlEx::GetCellEnabled(int nRow, int nCol)
{
ASSERT(0<=nRow && nRow<GetItemCount() && nCol>=0 && nCol< GetColumnCount());
return FindCellData(nRow, nCol).GetEnable();
}
void CListCtrlEx::SetCellImage(int nRow, int nCol, int nImage)
{
ASSERT(0<=nRow && nRow<GetItemCount() && nCol>=0 && nCol< GetColumnCount());
FindCellData(nRow, nCol).m_nImage=nImage;
}
void CListCtrlEx::SetCellChecked(int nRow, int nCol, BOOL bChecked)
{
int nColCnt=GetColumnCount();
ASSERT(0<=nRow && nRow<GetItemCount() && nCol>=0 && nCol<nColCnt );
ColumnType eColType=m_mapCol2ColType[nCol];
// only check box and radio box can be checked
if (CheckBox==eColType)
{
FindCellData(nRow, nCol).SetCheck(bChecked);
CRect rcCell;
if(GetCellRect(nRow, nCol, rcCell))
{
InvalidateRect(&rcCell); // update check box here immediately
UpdateWindow();
// send message to parent
CWnd *pWnd=NULL;
if ((pWnd = GetParent())!=NULL )
{
CheckCellMsg msg(nRow, nCol, bChecked);
pWnd->SendMessage(WM_ListCtrlEx_CHECKCELL, (WPARAM)this, (LPARAM)&msg);
}
}
}
else if(RadioBox==eColType)
{
int nGrpNo=FindCellData(nRow, nCol).m_nRadioGroup;
int nRowCnt=GetItemCount();
CRect rcCell;
for (int y=0; y<nColCnt; ++y)
{
if (RadioBox==m_mapCol2ColType[y])
{
for (int x=0; x<nRowCnt; ++x)
{
if (nGrpNo==FindCellData(x,y).m_nRadioGroup && FindCellData(x,y).GetCheck()) // only need to update checked one
{
FindCellData(x,y).SetCheck(FALSE);
if(GetCellRect(x, y, rcCell))
{
InvalidateRect(&rcCell); // update radio box here immediately
UpdateWindow();
}
}
}
}
}
bChecked=TRUE; // check on a radio will always cause to be true
FindCellData(nRow, nCol).SetCheck(bChecked);
if(GetCellRect(nRow, nCol, rcCell))
{
InvalidateRect(&rcCell); // update radio box here immediately
UpdateWindow();
}
// send message to parent
CWnd *pWnd=NULL;
if ((pWnd = GetParent())!=NULL )
{
CheckCellMsg msg(nRow, nCol, bChecked);
pWnd->SendMessage(WM_ListCtrlEx_CHECKCELL, (WPARAM)this, (LPARAM)&msg);
}
}
}
BOOL CListCtrlEx::GetCellChecked(int nRow, int nCol)
{
ASSERT(0<=nRow && nRow<GetItemCount() && nCol>=0 && nCol< GetColumnCount());
return FindCellData(nRow, nCol).GetCheck();
}
int CListCtrlEx::GetCellRadioGrp(int nRow, int nCol)
{
ASSERT(0<=nRow && nRow<GetItemCount() && nCol>=0 && nCol< GetColumnCount());
return FindCellData(nRow, nCol).m_nRadioGroup;
}
void CListCtrlEx::SetCellRadioGrp(int nRow, int nCol, int nGroupNO)
{
ASSERT(0<=nRow && nRow<GetItemCount() && nCol>=0 && nCol< GetColumnCount());
FindCellData(nRow, nCol).m_nRadioGroup=nGroupNO;
}
void CListCtrlEx::SetCellStringList(int nRow, int nCol, CStrList &lstStrings)
{
ASSERT(0<=nRow && nRow<GetItemCount() && nCol>=0 && nCol< GetColumnCount());
CStrList &aCellStrList=FindCellData(nRow, nCol).m_lstString;
aCellStrList.clear();
aCellStrList.insert(aCellStrList.begin(), lstStrings.begin(), lstStrings.end());
}
void CListCtrlEx::AddCellString(int nRow, int nCol, CString &str)
{
ASSERT(0<=nRow && nRow<GetItemCount() && nCol>=0 && nCol< GetColumnCount());
CStrList &aCellStrList=FindCellData(nRow, nCol).m_lstString;
aCellStrList.push_back(str);
}
void CListCtrlEx::SetCellValidChars(int nRow, int nCol, CString &strValidChars)
{
ASSERT(0<=nRow && nRow<GetItemCount() && nCol>=0 && nCol< GetColumnCount());
FindCellData(nRow, nCol).m_strValidChars=strValidChars;
}
void CListCtrlEx::SetCellProgressMaxValue(int nRow, int nCol, UINT uMaxValue)
{
ASSERT(0<=nRow && nRow<GetItemCount() && nCol>=0 && nCol< GetColumnCount());
FindCellData(nRow, nCol).m_uProgressMax=uMaxValue;
CRect rcCell;
if(GetCellRect(nRow, nCol, rcCell))
{
InvalidateRect(&rcCell); // update progress bar here immediately
UpdateWindow();
}
}
void CListCtrlEx::SetCellProgressValue(int nRow, int nCol, UINT uValue)
{
ASSERT(0<=nRow && nRow<GetItemCount() && nCol>=0 && nCol< GetColumnCount());
FindCellData(nRow, nCol).m_uProgresVal=uValue;
CRect rcCell;
if(GetCellRect(nRow, nCol, rcCell))
{
InvalidateRect(&rcCell); // update progress bar here immediately
UpdateWindow();
}
}
BOOL CListCtrlEx::IsShowPogressPercent()
{
return m_dwListCtrlExStyle & SHOW_PROGRESS_PERCENT;
}
void CListCtrlEx::SetShowProgressPercent(BOOL bShowProgPercent)
{
if (bShowProgPercent)
m_dwListCtrlExStyle|=SHOW_PROGRESS_PERCENT;
else
m_dwListCtrlExStyle&=(~SHOW_PROGRESS_PERCENT);
}
BOOL CListCtrlEx::IsSupportSort()
{
return m_dwListCtrlExStyle & SUPPORT_SORT;
}
void CListCtrlEx::SetSupportSort(BOOL bSuptSort)
{
if (bSuptSort)
m_dwListCtrlExStyle|=SUPPORT_SORT;
else
m_dwListCtrlExStyle&=(~SUPPORT_SORT);
}
//////////////////////////////////////////////////////////////////////////
BOOL CListCtrlEx::GetCellRect(CellIndex ix, CRect &rect)
{
return GetCellRect(ix.first, ix.second, rect);
}
BOOL CListCtrlEx::GetCellRect(int iRow, int iCol, CRect &rect, int nArea)
{
ASSERT(0<=iRow && iRow<GetItemCount() && iCol>=0 && iCol< GetColumnCount());
if(iCol)
return GetSubItemRect(iRow, iCol, nArea, rect);
if(GetColumnCount()== 1)
return GetItemRect(iRow, rect, nArea);
iCol = 1;
CRect rCol1;
if(!GetSubItemRect(iRow, iCol, nArea, rCol1))
return FALSE;
if(!GetItemRect(iRow, rect, nArea))
return FALSE;
rect.right = rCol1.left;
return TRUE;
}
BOOL CListCtrlEx::GetCheckRect(int iRow, int iCol, CRect &rect)
{
// this rect is to do action rect, so it's larger than the draw box
if (GetCellRect(iRow, iCol, rect))
{
//rect.bottom -= 1;
//rect.top+=1;
rect.left += 7;
rect.right = rect.left + rect.Height(); // width = height
return TRUE;
}
return FALSE;
}
BOOL CListCtrlEx::GetRowRect(int nRow, CRect &rcRow)
{
ASSERT(0<=nRow && nRow<GetItemCount());
return GetItemRect(nRow, rcRow, LVIR_BOUNDS);
}
BOOL CListCtrlEx::GetColRect(int nCol, CRect &rcCol)
{
ASSERT(nCol>=0 && nCol< GetColumnCount());
rcCol =CRect(0,0,0,0);
CRect rcCellInCol(0,0,0,0);
int nRowCnt=GetItemCount();
for (int i=0; i<nRowCnt; ++i)
{
if (GetCellRect(i, nCol, rcCellInCol))
{
if (0==i)
{
rcCol=rcCellInCol;
}
else
{
rcCol.UnionRect(&rcCol, &rcCellInCol);
}
}
else
{
return FALSE;
}
}
return TRUE;
}
int CListCtrlEx::GetColumnFmt(int nCol)
{
ASSERT(0<=nCol && nCol<GetColumnCount());
LVCOLUMN lvColumn;
ZeroMemory(&lvColumn, sizeof(lvColumn));
if (GetColumn(nCol, &lvColumn))
{
return lvColumn.fmt;
}
return LVCFMT_CENTER;
}
//////////////////////////////////////////////////////////////////////////
// customer draw
void CListCtrlEx::OnNMCustomdraw(NMHDR *pNMHDR, LRESULT *pResult)
{
//FuncTime(_T("OnNMCustomdraw"));
//LPNMCUSTOMDRAW pNMCD = reinterpret_cast(pNMHDR);
LPNMLVCUSTOMDRAW lplvcd = reinterpret_cast<LPNMLVCUSTOMDRAW>(pNMHDR);
int iCol = lplvcd->iSubItem;
int iRow = (int)lplvcd->nmcd.dwItemSpec;
switch(lplvcd->nmcd.dwDrawStage)
{
case CDDS_PREPAINT:
*pResult = CDRF_NOTIFYSUBITEMDRAW; // ask for subitem notifications.
break;
case CDDS_ITEMPREPAINT:
*pResult = CDRF_NOTIFYSUBITEMDRAW; // ask for subitem notifications.
break;
case CDDS_ITEMPREPAINT|CDDS_SUBITEM: // recd when CDRF_NOTIFYSUBITEMDRAW is returned in
{
// response to CDDS_ITEMPREPAINT.
*pResult = CDRF_DODEFAULT;
CString strText = GetItemText(iRow, iCol);
CRect rcCell;
if (GetCellRect(iRow, iCol, rcCell))
{
bool bSelected=GetItemState(iRow, LVIS_SELECTED) == LVIS_SELECTED;
// get the device context.
CDC *pDC= CDC::FromHandle(lplvcd->nmcd.hdc);
// draw the cell.
DrawCell(pDC, strText, rcCell, bSelected, iRow, iCol, pResult ); // *pResult will be changed here
}
break;
}
default: // it wasn't a notification that was interesting to us.
*pResult = CDRF_DODEFAULT;
break;
}
return;
}
void CListCtrlEx::DrawCell(CDC *pDC, CString &strText, CRect &rcCell, BOOL bSelected,
int nRow, int nCol, LRESULT *pResult)
{
switch(GetColumnType(nCol))
{
default:
case Normal:
*pResult |= CDRF_SKIPDEFAULT;
DrawNormal(pDC, strText, rcCell, bSelected, FindCellData(nRow, nCol), GetColumnFmt(nCol));
return;
case CheckBox:
*pResult |= CDRF_SKIPDEFAULT;
DrawCheckBox(pDC, strText, rcCell, bSelected, FindCellData(nRow, nCol));
return;
case RadioBox:
*pResult |= CDRF_SKIPDEFAULT;
DrawRadioBox(pDC, strText, rcCell, bSelected, FindCellData(nRow, nCol));
return;
case ComboBox:
*pResult |= CDRF_SKIPDEFAULT;
DrawComboBox(pDC, strText, rcCell, bSelected, FindCellData(nRow, nCol));
return;
case EditBox:
*pResult|=CDRF_SKIPDEFAULT;
DrawEditBox(pDC, strText, rcCell, bSelected, FindCellData(nRow, nCol), GetColumnFmt(nCol));
return;
case Progress:
*pResult|=CDRF_SKIPDEFAULT;
DrawProgress(pDC, strText, rcCell, bSelected, FindCellData(nRow,nCol));
return;
}
}
// draw normal text
void CListCtrlEx::DrawNormal(CDC *pDC, CString &strText, CRect &rcCell, BOOL bSelected, const CellData &cellData, int uLvcFmt)
{
UNREFERENCED_PARAMETER(cellData);
COLORREF crText=bSelected?GetSysColor(COLOR_HIGHLIGHTTEXT):GetSysColor(COLOR_WINDOWTEXT);
COLORREF crTextBkgrnd=bSelected?GetSysColor(COLOR_HIGHLIGHT):GetSysColor(COLOR_WINDOW);
// fill background
CRect rcText(rcCell), rcIcon(rcCell);
pDC->FillSolidRect(&rcCell, crTextBkgrnd);
//-- draw icon--//
CImageList *pImageList=TheImageList();
if (cellData.m_nImage>=0 && pImageList)
{
rcIcon.right=rcIcon.left+rcIcon.Height();
//UINT fStyle=bSelected?ILD_NORMAL:(ILD_NORMAL|ILD_SELECTED);
pImageList->DrawIndirect( pDC, cellData.m_nImage, rcIcon.TopLeft(), rcIcon.Size(), CPoint(0, 0), ILD_NORMAL,
SRCCOPY,crTextBkgrnd, crTextBkgrnd );
//pImageList->DrawIndirect( pDC, cellData.m_nImage, rcIcon.TopLeft(), rcIcon.Size(), CPoint(0, 0));
}
else
{
rcIcon.right=rcIcon.left;
}
//------------draw text---------------------/
UINT nFormat=DT_CENTER;
switch(uLvcFmt)
{
default:
case LVCFMT_CENTER: break;
case LVCFMT_LEFT: nFormat=DT_LEFT; break;
case LVCFMT_RIGHT: nFormat=DT_RIGHT; break;
}
COLORREF crOldText=pDC->SetTextColor(crText);
rcText.left=rcIcon.right+3;
pDC->DrawText(strText, &rcText, nFormat);
pDC->SetTextColor(crOldText);
}
// draw a check box cell
void CListCtrlEx::DrawCheckBox(CDC *pDC, CString &strText, CRect &rcCell, BOOL bSelected, const CellData &cellData)
{
COLORREF crText=bSelected?GetSysColor(COLOR_HIGHLIGHTTEXT):GetSysColor(COLOR_WINDOWTEXT);
COLORREF crTextBkgrnd=bSelected?GetSysColor(COLOR_HIGHLIGHT):GetSysColor(COLOR_WINDOW);
COLORREF crCheckFrame=cellData.GetEnable()?RGB(51,102,153):RGB(192,192,192);
COLORREF crCheckMark=cellData.GetEnable()?RGB(51,255,153):RGB(192,192,192);
//------------calculate the check box & Text rect ---------------------/
CRect rcText(rcCell);
CRect rcCheck(rcCell);
rcCheck.bottom -= 2;
rcCheck.top+=2;
rcCheck.left += 7;
rcCheck.right = rcCheck.left + rcCheck.Height(); // width = height
rcText.left=rcCheck.right+3;
//------------draw the inside check box region---------------------/
pDC->FillSolidRect(&rcText, crTextBkgrnd);
pDC->FillSolidRect(&rcCheck, GetSysColor(COLOR_WINDOW)); // refresh the check box
//------------draw the check box frame border ---------------------/
#ifdef USING_CUSTOM_DRAW
//CBrush brush(crCheckFrame);
//pDC->FrameRect(&rcCheck, &brush); //fill frame of the check box
CPen penFrame(PS_SOLID, 2,crCheckFrame);
CPen *pOldPen=pDC->SelectObject(&penFrame);
pDC->Rectangle(&rcCheck);
//------------draw the check mark---------------------/
if (cellData.GetCheck() ) // draw the checked Mark
{
int deta=rcCheck.Width()/4;
CPen penMark(PS_SOLID, 1,crCheckMark);
pDC->SelectObject(&penMark);
int x = rcCheck.left + deta*3;
int y = rcCheck.top + deta;
int i;
for (i = 0; i < 4; i++)
{
pDC->MoveTo(x, y);
pDC->LineTo(x, y+3);
x--;
y++;
}
for (i = 0; i < 3; i++)
{
pDC->MoveTo(x, y);
pDC->LineTo(x, y+3);
x--;
y--;
}
}
pDC->SelectObject(pOldPen);
#else
crCheckMark;crCheckFrame; // only for kill the warnings
UINT nState=DFCS_BUTTONCHECK;
if (cellData.GetCheck() ) // draw the checked Mark
{
nState|=DFCS_CHECKED;
}
if (bSelected)
{
nState|=DFCS_HOT;
}
if (!cellData.GetEnable())
{
nState|=DFCS_INACTIVE;
}
pDC->DrawFrameControl(&rcCheck, DFC_BUTTON, nState);
#endif
//------------draw text---------------------/
COLORREF crOldText=pDC->SetTextColor(crText);
pDC->DrawText(strText, &rcText, DT_LEFT);
pDC->SetTextColor(crOldText);
}
// draw a Radio box cell
void CListCtrlEx::DrawRadioBox(CDC *pDC, CString &strText, CRect &rcCell, BOOL bSelected, const CellData &cellData)
{
COLORREF crText=bSelected?GetSysColor(COLOR_HIGHLIGHTTEXT):GetSysColor(COLOR_WINDOWTEXT);
COLORREF crTextBkgrnd=bSelected?GetSysColor(COLOR_HIGHLIGHT):GetSysColor(COLOR_WINDOW);
COLORREF crRadioFrame=cellData.GetEnable()?RGB(51,102,153):RGB(192,192,192);
COLORREF crRadioFrameBk=GetSysColor(COLOR_WINDOW);
COLORREF crRadioMark=cellData.GetEnable()?RGB(0,0,0):RGB(192,192,192);
//------------calculate the check box & Text rect ---------------------/
CRect rcText(rcCell);
CRect rcRadio(rcCell);
rcRadio.bottom -= 1;
rcRadio.top+=1;
rcRadio.left += 7;
rcRadio.right = rcRadio.left + rcRadio.Height(); // width = height
rcText.left=rcRadio.right+3;
//------------draw the inside radio box region---------------------/
pDC->FillSolidRect(&rcText, crTextBkgrnd);
pDC->FillSolidRect(&rcRadio, crRadioFrameBk); // refresh the check box
//------------draw the radio box frame border ---------------------/
#ifdef USING_CUSTOM_DRAW
// using three dot to draw the radio box
CPoint pos=rcRadio.TopLeft();
pos +=CSize(rcRadio.Width()/2, rcRadio.Height()/2);
int nPenWid=rcRadio.Height();
// ----draw frame----/
// outer frame
CPen pen(PS_DOT, nPenWid, crRadioFrame);
CPen *pOldPen=pDC->SelectObject(&pen);
pDC->MoveTo(pos);
pDC->LineTo(pos);
// inner frame
pDC->SelectObject(pOldPen);
pen.DeleteObject();
nPenWid=((nPenWid-4)>0)?(nPenWid-4):4;
pen.CreatePen(PS_DOT, nPenWid, crRadioFrameBk);
pDC->SelectObject(&pen);
pDC->MoveTo(pos);
pDC->LineTo(pos);
// draw mark
if (cellData.GetCheck())
{
pDC->SelectObject(pOldPen);
pen.DeleteObject();
nPenWid=((nPenWid-4)>0)?(nPenWid-4):4;
pen.CreatePen(PS_DOT, nPenWid, crRadioMark);
pDC->SelectObject(&pen);
pDC->MoveTo(pos);
pDC->LineTo(pos);
}
pDC->SelectObject(pOldPen);
#else
crRadioFrame;crRadioMark; // only for kill the warnings
UINT nState=DFCS_BUTTONRADIO;
if (cellData.GetCheck() ) // draw the checked Mark
{
nState|=DFCS_CHECKED;
}
if (!cellData.GetEnable())
{
nState|=DFCS_INACTIVE;
}
pDC->DrawFrameControl(&rcRadio, DFC_BUTTON, nState);
#endif
//------------draw text---------------------/
COLORREF crOldText=pDC->SetTextColor(crText);
pDC->DrawText(strText, &rcText, DT_LEFT);
pDC->SetTextColor(crOldText);
}
// draw a combo box cell
void CListCtrlEx::DrawComboBox(CDC *pDC, CString &strText, CRect &rcCell, BOOL bSelected, const CellData &cellData)
{
COLORREF crText=bSelected?GetSysColor(COLOR_HIGHLIGHTTEXT):GetSysColor(COLOR_WINDOWTEXT);
COLORREF crTextBkgrnd=bSelected?GetSysColor(COLOR_HIGHLIGHT):GetSysColor(COLOR_WINDOW);
COLORREF crComboTL=cellData.GetEnable()?RGB(255,255,255):RGB(192,192,192); //#d4e1fc:#c0c0c0;
COLORREF crComboBR=cellData.GetEnable()? RGB(177,203,250):RGB(192,192,192);
COLORREF crComboMark=cellData.GetEnable()?RGB(77,97,133):RGB(0,0,0);
//------------calculate the check box & Text rect ---------------------/
CRect rcText(rcCell);
CRect rcCombo(rcCell);
rcCombo.bottom -= 0;
rcCombo.top+=1;
rcCombo.right -= 2;
rcCombo.left = rcCombo.right - rcCombo.Height(); // width = height
rcText.right=rcCombo.left-3;
rcText.left+=2;
//---------refresh the cell------/
pDC->FillSolidRect(&rcCell, GetSysColor(COLOR_WINDOW));
pDC->FillSolidRect(&rcText, crTextBkgrnd);
//------------draw the combo box frame border ---------------------/
#ifdef USING_CUSTOM_DRAW
CBrush brBottomRight(crComboBR);
CBrush *pOldBrush=pDC->SelectObject(&brBottomRight);
/*CRect rcInner(rcCombo);
rcInner.DeflateRect(1,1,1,1);*/
//pDC->FillSolidRect(&rcInner, crComboBR);
DrawGradientRect(pDC, rcCombo, crComboTL, crComboBR);
//pDC->Draw3dRect(&rcCombo, crComboTL, crComboBR);
pDC->SelectObject(pOldBrush);
CPen penMark(PS_SOLID, 1, crComboMark);
CPen *pOldPen=pDC->SelectObject(&penMark);
// draw the arrow
{
int xOrg=rcCombo.Width()/2+rcCombo.left;
int yOrg=rcCombo.Height()/2+rcCombo.top+2;
int xOff=max(rcCombo.Width()/4,1);
int yOff=max(rcCombo.Height()/4,1);
int y=yOrg-yOff;
for (int x=(xOrg-xOff); x<xOrg; ++x, ++y)
{
pDC->MoveTo(x,y);
pDC->LineTo(x+2*(xOrg-x), y);
}
}
pDC->SelectObject(pOldPen);
#else
crComboTL;crComboBR;crComboMark; // only for kill the warnings
UINT nState=DFCS_SCROLLCOMBOBOX;
if (cellData.GetCheck() ) // draw the checked Mark
{
nState|=DFCS_CHECKED;
}
if (bSelected)
{
nState|=DFCS_HOT;
}
if (!cellData.GetEnable())
{
nState|=DFCS_INACTIVE;
}
pDC->DrawFrameControl(&rcCombo, DFC_SCROLL, nState);
#endif
//------------draw text---------------------/
COLORREF crOldText=pDC->SetTextColor(crText);
pDC->DrawText(strText, &rcText, DT_LEFT);
pDC->SetTextColor(crOldText);
}
// draw a edit box cell
void CListCtrlEx::DrawEditBox(CDC *pDC, CString &strText, CRect &rcCell, BOOL bSelected, const CellData &cellData, int uLvcFmt)
{
COLORREF crText=bSelected?GetSysColor(COLOR_HIGHLIGHTTEXT):GetSysColor(COLOR_WINDOWTEXT);
COLORREF crTextBkgrnd=bSelected?GetSysColor(COLOR_HIGHLIGHT):GetSysColor(COLOR_WINDOW);
COLORREF crTextUnabled=bSelected?GetSysColor(COLOR_HIGHLIGHTTEXT):RGB(192,192,192);
CRect rcText(rcCell), rcIcon(rcCell);
// fill background
pDC->FillSolidRect(&rcCell, crTextBkgrnd);
//-- draw icon--//
CImageList *pImageList=TheImageList();
if (cellData.m_nImage>=0 && pImageList)
{
rcIcon.right=rcIcon.left+rcIcon.Height();
pImageList->DrawIndirect( pDC, cellData.m_nImage, rcIcon.TopLeft(), rcIcon.Size(), CPoint(0, 0), ILD_NORMAL,
SRCCOPY,crTextBkgrnd, crTextBkgrnd );
}
else
{
rcIcon.right=rcIcon.left;
}
#ifdef USING_CUSTOM_DRAW
#else
#endif
//------------draw text---------------------/
UINT nFormat=DT_CENTER;
switch(uLvcFmt)
{
default:
case LVCFMT_CENTER: break;
case LVCFMT_LEFT: nFormat=DT_LEFT; break;
case LVCFMT_RIGHT: nFormat=DT_RIGHT; break;
}
COLORREF crOldText=pDC->SetTextColor(crText);
if (!cellData.GetEnable())
{
pDC->SetTextColor(crTextUnabled);
}
rcText.left=rcIcon.right+3;
pDC->DrawText(strText, &rcText, nFormat);
pDC->SetTextColor(crOldText);
}
// draw a progress bar
void CListCtrlEx::DrawProgress(CDC *pDC, CString &strText, CRect &rcCell, BOOL bSelected, const CellData &cellData)
{
COLORREF crText=bSelected?GetSysColor(COLOR_HIGHLIGHTTEXT):GetSysColor(COLOR_WINDOWTEXT);
COLORREF crTextBkgrnd=bSelected?GetSysColor(COLOR_HIGHLIGHT):GetSysColor(COLOR_WINDOW);
COLORREF crProgBack=bSelected?GetSysColor(COLOR_HOTLIGHT):GetSysColor(COLOR_BTNFACE);
COLORREF crProgFill=bSelected?COLOR_FRAME:COLOR_FRAME;
CRect rcProg(rcCell), rcIcon(rcCell);
// fill background
pDC->FillSolidRect(&rcCell, crTextBkgrnd);
//-- draw icon--//
CImageList *pImageList=TheImageList();
if (cellData.m_nImage>=0 && pImageList)
{
rcIcon.right=rcIcon.left+rcIcon.Height();
pImageList->DrawIndirect( pDC, cellData.m_nImage, rcIcon.TopLeft(), rcIcon.Size(), CPoint(0, 0), ILD_NORMAL,
SRCCOPY,crTextBkgrnd, crTextBkgrnd );
}
else
{
rcIcon.right=rcIcon.left;
}
rcProg.left=rcIcon.right+3;
pDC->FillSolidRect(&rcProg, crProgBack);
pDC->DrawEdge(&rcProg, EDGE_SUNKEN, BF_RECT);
CRect rcFill(rcProg);
rcFill.DeflateRect(1,1,1,1);
float fPersent=0.0f;
int nFill=GetProgBarSize(cellData, rcProg, &fPersent);
rcFill.right=min(rcFill.left+nFill, rcFill.right);
pDC->FillSolidRect(&rcFill, crProgFill);
COLORREF crOldText=pDC->SetTextColor(crText);
if (IsShowPogressPercent())
{
strText.Format(_T("%.2f%%"), fPersent*100);
}
pDC->DrawText(strText, &rcProg, DT_CENTER);
pDC->SetTextColor(crOldText);
#ifdef USING_CUSTOM_DRAW
#else
#endif
}
//////////////////////////////////////////////////////////////////////////
// kernel data
int CListCtrlEx::GetProgBarSize(const CListCtrlEx::CellData &cellData, CRect &rcProg, float *pPersent)
{
float fScale=0.0f;
if (cellData.m_uProgresVal>=cellData.m_uProgressMax)
fScale=1.0f;
else if(cellData.m_uProgresVal<=0 || cellData.m_uProgressMax<=0)
fScale=0.0f;
else
fScale=(float)cellData.m_uProgresVal/(float)cellData.m_uProgressMax;
if (pPersent)
{
*pPersent=fScale;
}
return (int)(rcProg.Width()*fScale);
}
CImageList* CListCtrlEx::TheImageList()
{
return GetImageList(m_nImageListType);
}
CListCtrlEx::CellData& CListCtrlEx::FindCellData(CellIndex ix)
{
return FindCellData(ix.first, ix.second);
}
CListCtrlEx::CellData& CListCtrlEx::FindCellData(int nRow, int nCol)
{
// init if empty
if (m_mapCell2Data.empty())
{
for (int x=0; x<GetItemCount(); ++x)
{
for (int y=0; y< GetColumnCount(); ++y)
{
m_mapCell2Data[make_pair(x,y)]=CellData(TRUE, FALSE, y);
}
}
}
CellDataMap::iterator iter=m_mapCell2Data.find(make_pair(nRow, nCol));
if (iter==m_mapCell2Data.end())
{
m_mapCell2Data[make_pair(nRow, nCol)]=CellData(TRUE, FALSE, nCol);
}
return m_mapCell2Data[make_pair(nRow, nCol)];
}
//////////////////////////////////////////////////////////////////////////
// show control
void CListCtrlEx::ShowCellInPlace(CListCtrlEx::CellIndex ix, CellType eCellType)
{
// roll up make the cell be visible
if (!EnsureVisible(ix.first, TRUE))
{
return;
}
switch(eCellType)
{
default:
case Normal:
case CheckBox:
case RadioBox:
return;
case ComboBox:
ShowInPlaceCombo(ix);
break;
case EditBox:
ShowInPlaceEdit(ix);
break;
}
}
// show combo box in place
void CListCtrlEx::ShowInPlaceCombo(CellIndex ix)
{
if (ix.first>=0 && ix.first<GetItemCount() && ix.second>=0 && ix.second< GetColumnCount())
{
CString strCurSel= GetItemText(ix.first, ix.second);
CRect rcCell;
if (GetCellRect(ix, rcCell))
{
GetParent()->SendMessage(WM_SET_ITEMS, (WPARAM)this, (LPARAM)&ix);
CInPlaceCombo* pInPlaceComboBox = CInPlaceCombo::GetInstance();
ASSERT(pInPlaceComboBox);
pInPlaceComboBox->ShowComboCtrl(m_dwComboStyle, rcCell, this, IDC_CELL_COMBO,
ix.first, ix.second, FindCellData(ix).m_lstString, strCurSel );
pInPlaceComboBox->SelectString(-1, strCurSel);
}
}
}
// show edit box in place
void CListCtrlEx::ShowInPlaceEdit(CellIndex ix)
{
if (ix.first>=0 && ix.first<GetItemCount() && ix.second>=0 && ix.second< GetColumnCount())
{
CString strCurSel= GetItemText(ix.first, ix.second);
CRect rcCell;
if (GetCellRect(ix, rcCell))
{
GetParent()->SendMessage(WM_SET_ITEMS, (WPARAM)this, (LPARAM)&ix);
CInPlaceEdit* pInPlaceEdit = CInPlaceEdit::GetInstance();
ASSERT(pInPlaceEdit);
pInPlaceEdit->ShowEditCtrl(m_dwEditStyle, rcCell, this, IDC_CELL_EDIT, ix.first, ix.second,
FindCellData(ix).m_strValidChars, strCurSel);
}
}
}
//////////////////////////////////////////////////////////////////////////
// message handlers
void ListCtrlEx::CListCtrlEx::OnLButtonDown(UINT nFlags, CPoint point)
{
//FuncTime(_T("OnLButtonDown"));
CWnd* pWnd=NULL;
bool bShouldAction=false;
CellIndex ix=Point2Cell(point);
bShouldAction=(ix.first>=0 && ix.second>=0 && ix.first<GetItemCount() && ix.second<GetColumnCount());
CListCtrl::OnLButtonDown(nFlags, point);
// If the SHIFT or CTRL key is down call the base class
// Check the high bit of GetKeyState to determine whether SHIFT or CTRL key is down
if ((GetKeyState(VK_SHIFT) & 0x80) || (GetKeyState(VK_CONTROL) & 0x80))
{
return;
}
if (bShouldAction && GetCellEnabled(ix.first, ix.second))
{
/*CRect rcItem;
GetRowRect(ix.first, rcItem);*/
switch(m_mapCol2ColType[ix.second])
{
case CheckBox:
case RadioBox:
{ // can check cell
CRect rcCheck;
bool bShouldCheck=(GetCheckRect(ix.first, ix.second, rcCheck) && rcCheck.PtInRect(point));
if (bShouldCheck)
{
SetCellChecked(ix.first, ix.second, !GetCellChecked(ix.first, ix.second));
if ((pWnd = GetParent())!=NULL)
{
CheckCellMsg msg(ix.first, ix.second, GetCellChecked(ix.first, ix.second));
pWnd->SendMessage(WM_ListCtrlEx_CHECK_CHANGED, (WPARAM)this, (LPARAM)&msg);
}
}
}
break;
case ComboBox:
case EditBox:
{ // can show or edit in place cell
ShowCellInPlace(ix, m_mapCol2ColType[ix.second]);
}
break;
case Normal:
default:
break;
}
//InvalidateRect(&rcItem); // normal or default should be update immediately for the focus state
//UpdateWindow();
}
// notify parent with user messages
if ((pWnd = GetParent())!=NULL )
{
pWnd->SendMessage(WM_ListCtrlEx_LBUTTONDOWN, (WPARAM)this, (LPARAM)&ix);
}
}
void ListCtrlEx::CListCtrlEx::OnLButtonDblClk(UINT nFlags, CPoint point)
{
CWnd* pWnd=NULL;
bool bShouldAction=false;
CellIndex ix=Point2Cell(point);
bShouldAction=(ix.first>=0 && ix.second>=0 && ix.first<GetItemCount() && ix.second<GetColumnCount());
// actions
if (bShouldAction && GetCellEnabled(ix.first, ix.second))
{
CRect rcItem;
GetRowRect(ix.first, rcItem);
CRect rcCheck;
bool bShouldCheck=(GetCheckRect(ix.first, ix.second, rcCheck) && rcCheck.PtInRect(point));
if (bShouldCheck)
{
//SetCellChecked(ix.first, ix.second, !GetCellChecked(ix.first, ix.second));
//InvalidateRect(&rcCheck); // should update window here immediately
//UpdateWindow();
}
InvalidateRect(&rcItem); // normal or default should be update immediately for the focus state
UpdateWindow();
}
// notify parent with user messages
if ((pWnd = GetParent())!=NULL )
{
pWnd->PostMessage(WM_ListCtrlEx_LBUTTONDBLCLK, (WPARAM)this, (LPARAM)&ix);
}
// do default action
CListCtrl::OnLButtonDblClk(nFlags, point);
}
void ListCtrlEx::CListCtrlEx::OnRButtonDown(UINT nFlags, CPoint point)
{
CWnd* pWnd=NULL;
bool bShouldAction=false;
CellIndex ix=Point2Cell(point);
bShouldAction=(ix.first>=0 && ix.second>=0 && ix.first<GetItemCount() && ix.second<GetColumnCount());
// actions
// notify parent with user messages
if ((pWnd = GetParent())!=NULL )
{
pWnd->PostMessage(WM_ListCtrlEx_RBUTTONDOWN, (WPARAM)this, (LPARAM)&ix);
}
// do default action
CListCtrl::OnRButtonDown(nFlags, point);
//////////////////////////////////////////////////////////////////////////
// for test
#ifdef _DEBUG
/*int nHot=GetHotItem();
vector ivec;
POSITION pos = GetFirstSelectedItemPosition();
while (pos)
{
ivec.push_back(GetNextSelectedItem(pos));
}
CString str, strTmp;
str.Format(_T("Hot item: %d \r\n"), nHot);
vector::iterator iter=ivec.begin();
for (; iter!=ivec.end(); ++iter)
{
strTmp.Format(_T("Selected item: %d\r\n"), *iter);
str+=strTmp;
}
strTmp.Format(_T("Click point cell: row=%d, col=%d\r\n"), ix.first, ix.second);
str=_T("Testing: \r\n")+str+strTmp;
AfxMessageBox(str); */
#endif
}
void ListCtrlEx::CListCtrlEx::OnLvnBeginlabeledit(NMHDR *pNMHDR, LRESULT *pResult)
{
NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);
// TODO: Add your control notification handler code here
if (!GetCellEnabled(pDispInfo->item.iItem, pDispInfo->item.iSubItem))
{
*pResult = 1;
return;
}
*pResult = 0;
}
void ListCtrlEx::CListCtrlEx::OnLvnEndlabeledit(NMHDR *pNMHDR, LRESULT *pResult)
{
NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);
// TODO: Add your control notification handler code here
// Update the item text with the new text
SetItemText(pDispInfo->item.iItem, pDispInfo->item.iSubItem, pDispInfo->item.pszText);
GetParent()->SendMessage(WM_VALIDATE, GetDlgCtrlID(), (LPARAM)pDispInfo);
*pResult = 0;
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// static members
int CALLBACK CListCtrlEx::CompareFunc(LPARAM lParam1, LPARAM lParam2,
LPARAM lParamSort)
{
_SortParam_t *pSortParam_t=reinterpret_cast<_SortParam_t*>(lParamSort);
if (NULL==pSortParam_t)
return 0;
CListCtrlEx *pList=pSortParam_t->m_pTheList;
ASSERT(pList!=NULL && ::IsWindow(pList->m_hWnd));
CString strCell1 = pList->GetItemText((int)lParam1, pSortParam_t->m_nSortCol);
CString strCell2 = pList->GetItemText((int)lParam2, pSortParam_t->m_nSortCol);
int nResult=0;
try
{
switch(pSortParam_t->m_ColParam.m_eSortType)
{
default:
case SortByString:
nResult=StrCompare(strCell1,strCell2);
break;
case SortByDigit:
nResult=IntCompare(strCell1,strCell2);
break;
}
}
catch ()
{
nResult=StrCompare(strCell1,strCell2);
}
return (pSortParam_t->m_ColParam.m_bIsAsc?nResult:-nResult);
}
#ifdef _DEBUG
#define ASSERT_VALID_STRING( str ) ASSERT( !IsBadStringPtr( str, 0xfffff ) )
#else // _DEBUG
#define ASSERT_VALID_STRING( str ) ( (void)0 )
#endif // _DEBUG
bool CListCtrlEx::IsDigitStr(LPCTSTR str)
{
int nLen=(int)_tcslen(str);
int nPointCnt=0;
TCHAR ch=0;
for (int i=0; i<nLen; ++i)
{
ch=str[i];
// for negative
if (0==i && _T('-')==ch) {
continue;
}else if ( 0!=i && _T('-')==ch) {
return false;
}
if (_T('.')==ch) {
++nPointCnt;
}
if (nPointCnt>1) {
return false;
}
if ((ch<48 || ch>57) && _T('.')!=ch) {
return false;
}
}
return true;
}
int CListCtrlEx::IntCompare(LPCTSTR strInt1st,LPCTSTR strInt2nd)
{
ASSERT_VALID_STRING( strInt1st );
ASSERT_VALID_STRING( strInt2nd);
if (!IsDigitStr(strInt1st) || !IsDigitStr(strInt2nd))
{
//AfxThrowInvalidArgException();
return StrCompare(strInt1st, strInt2nd);
}
const int num1st=_ttoi(strInt1st);
const int num2nd=_ttoi(strInt2nd);
return (num1st-num2nd);
}
int CListCtrlEx::StrCompare(LPCTSTR str1st, LPCTSTR str2nd)
{
ASSERT_VALID_STRING( str1st );
ASSERT_VALID_STRING( str2nd);
return _tcscmp(str1st, str2nd);
}
BOOL CListCtrlEx::DrawGradientRect(CDC *pDC, CRect &rect, COLORREF crTopLeft, COLORREF crBottomRight)
{
ASSERT(pDC);
if (0>=rect.Width() || 0>=rect.Height())
{
return FALSE;
}
// draw a rect with 45 degree slope gradient color from top left to bottom right
CBrush brColor(crBottomRight);
pDC->FillRect(&rect, &brColor);
int nSteps=max(rect.Width(), rect.Height());
int rBR=GetRValue(crBottomRight), gBR=GetGValue(crBottomRight), bBR=GetBValue(crBottomRight);
int rTL=GetRValue(crTopLeft), gTL=GetGValue(crTopLeft), bTL=GetBValue(crTopLeft);
int rDiff=(rBR-rTL)/nSteps;
int gDiff=(gBR-gTL)/nSteps;
int bDiff=(bBR-bTL)/nSteps;
CRgn rgRect, rgRound, rg2Fill;
rgRect.CreateRectRgnIndirect(&rect);
rg2Fill.CreateRectRgn(0,0,0,0);
for (int i=nSteps; i>=0; --i)
{
CRect rcRound(rect.left-i, rect.top-i, rect.left+i, rect.top+i);
rgRound.CreateEllipticRgnIndirect(&rcRound);
int nCombineResult =rg2Fill.CombineRgn(&rgRect, &rgRound, RGN_AND);
if( nCombineResult != ERROR && nCombineResult != NULLREGION ){
pDC->FillRgn(&rg2Fill, &brColor);
}
rgRound.DeleteObject();
// calculate new color
brColor.DeleteObject();
brColor.CreateSolidBrush(RGB(rTL+i*rDiff, gTL+i*gDiff, bTL+i*bDiff));
}
return TRUE;
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
void ListCtrlEx::CListCtrlEx::OnLvnColumnclick(NMHDR *pNMHDR, LRESULT *pResult)
{
LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
if(IsSupportSort())
{
vector<DWORD_PTR> vecItemDatas(GetItemCount());
//map mapOldRow2NewRow(GetItemCount());
PreSortItems(vecItemDatas);
//SortType eSortBy=GetColumnSortBy(pNMLV->iSubItem);
BOOL bIsAsc=!IsColumnSortAsc(pNMLV->iSubItem);
SetColumnSortDirection(pNMLV->iSubItem, bIsAsc);
_SortParam_t aSortParam;
aSortParam.m_ColParam=m_mapCol2Sort[pNMLV->iSubItem];
aSortParam.m_nSortCol=pNMLV->iSubItem;
aSortParam.m_pTheList=this;
SortItems(CompareFunc, (DWORD_PTR)&aSortParam);
//m_ctlSortHead.SetSortArrow(pNMLV->iSubItem, !bIsAsc);
PostSortItems(vecItemDatas);
}
*pResult = 0;
}
// before sort items, backup old item data here, then set the item data with old row NO.
// vec2StoreData must be initialized as size of "GetItemCount()"
void CListCtrlEx::PreSortItems( vector<DWORD_PTR> &vec2StoreData )
{
int nRowCnt=GetItemCount();
for (int i=0; i<nRowCnt; ++i)
{
vec2StoreData[i]=GetItemData(i);
SetItemData(i, (DWORD_PTR)i);
}
}
// after sort items, re-mapping the cell data here and restore the old item data here
// vec2StoreData must be initialized as size of "GetItemCount()" and assigned with old item data
void CListCtrlEx::PostSortItems( vector<DWORD_PTR> &vec2StoreData )
{
// adjust the cell data map
CellDataMap newMap;
int nRowCnt=GetItemCount();
int nColCnt=GetColumnCount();
for (int i=0; i<nRowCnt; ++i)
{
int nOldRow=(int)GetItemData(i);
for (int j=0; j<nColCnt; ++j)
{
newMap[make_pair(i,j)]=m_mapCell2Data[make_pair(nOldRow, j)];
}
// restore item data
SetItemData(i, (DWORD_PTR)vec2StoreData[nOldRow]);
}
m_mapCell2Data.swap(newMap);
}
void ListCtrlEx::CListCtrlEx::PreSubclassWindow()
{
// TODO: Add your specialized code here and/or call the base class
ASSERT( GetStyle() & LVS_REPORT );
CListCtrl::PreSubclassWindow();
VERIFY(m_ctlSortHead.SubclassWindow(this->GetHeaderCtrl()->GetSafeHwnd()) );
}
BOOL CListCtrlEx::IsColumnSortAsc(int nColIndex)
{
// insert new if not found
ColSortMap::iterator iter=m_mapCol2Sort.find(nColIndex);
if (m_mapCol2Sort.end()==iter)
{
m_mapCol2Sort[nColIndex]=_ColumnSort_t();
}
return m_mapCol2Sort[nColIndex].m_bIsAsc;
}
void CListCtrlEx::SetColumnSortDirection(int nColIndex, BOOL bIsAsc )
{
// init sort map
if (m_mapCol2Sort.empty())
{
for (int i=0; i<GetColumnCount(); ++i)
{
m_mapCol2Sort[i]=_ColumnSort_t();
}
}
// assign
m_mapCol2Sort[nColIndex].m_bIsAsc=bIsAsc;
}
void ListCtrlEx::CListCtrlEx::OnDestroy()
{
CListCtrl::OnDestroy();
if (::IsWindow(CInPlaceCombo::GetInstance()->m_hWnd))
{
CInPlaceCombo::GetInstance()->PostMessage(WM_CLOSE);
}
if (::IsWindow(CInPlaceEdit::GetInstance()->m_hWnd))
{
CInPlaceEdit::GetInstance()->PostMessage(WM_CLOSE);
}
}
#include "stdafx.h"
#include "ListCtrlEx.h"
#include "PerfTimer.h"
using namespace ListCtrlEx;
#define IDC_CELL_EDIT 1000001
#define IDC_CELL_COMBO 1000002
#define IDC_PROGRESS_START IDC_CELL_COMBO+1
#define COLOR_FRAME #336699
#define COLOR_MARK #33ff99
#define SHOW_PROGRESS_PERCENT 0x00000001
#define SUPPORT_SORT 0x00000002
//////////////////////////////////////////////////////////////////////////
// CListCtrlEx
IMPLEMENT_DYNAMIC(CListCtrlEx, CListCtrl)
CListCtrlEx::CListCtrlEx()
{
m_nImageListType=LVSIL_NORMAL;
m_dwListCtrlExStyle=0;
m_dwEditStyle = ES_AUTOHSCROLL | ES_AUTOVSCROLL | ES_LEFT | ES_NOHIDESEL;
m_dwComboStyle = WS_BORDER | WS_CHILD | WS_VISIBLE | ES_AUTOHSCROLL | ES_AUTOVSCROLL |
CBS_DROPDOWNLIST | CBS_DISABLENOSCROLL;
}
CListCtrlEx::~CListCtrlEx()
{
CInPlaceCombo::DeleteInstance();
CInPlaceEdit::DeleteInstance();
}
BEGIN_MESSAGE_MAP(CListCtrlEx, CListCtrl)
ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, &CListCtrlEx::OnNMCustomdraw)
ON_WM_LBUTTONDOWN()
ON_WM_LBUTTONDBLCLK()
ON_WM_RBUTTONDOWN()
ON_NOTIFY_REFLECT(LVN_BEGINLABELEDIT, &CListCtrlEx::OnLvnBeginlabeledit)
ON_NOTIFY_REFLECT(LVN_ENDLABELEDIT, &CListCtrlEx::OnLvnEndlabeledit)
ON_NOTIFY_REFLECT(LVN_COLUMNCLICK, &CListCtrlEx::OnLvnColumnclick)
ON_WM_DESTROY()
END_MESSAGE_MAP()
//////////////////////////////////////////////////////////////////////////
// override CListCtrl interfaces
CImageList* CListCtrlEx::SetImageList( CImageList* pImageList, int nImageListType )
{
m_nImageListType=nImageListType;
return CListCtrl::SetImageList(pImageList, nImageListType);
}
int CListCtrlEx::InsertColumn(int nCol, CString strColHead, int nWidth, ColumnType eColType, int nFormat,
SortType eSortBy, int nSubItem )
{
//
m_mapCol2ColType[nCol]=eColType;
m_mapCol2Sort[nCol].m_eSortType=eSortBy;
return CListCtrl::InsertColumn(nCol, strColHead, nFormat, nWidth, nSubItem);
}
BOOL CListCtrlEx::DeleteColumn( int nCol )
{
if (!CListCtrl::DeleteColumn(nCol))
{
return FALSE;
}
m_mapCol2ColType.erase(nCol);
return TRUE;
}
BOOL CListCtrlEx::DeleteAllItems( )
{
if (!CListCtrl::DeleteAllItems())
{
return FALSE;
}
m_mapCell2Data.clear();
return TRUE;
}
BOOL CListCtrlEx::DeleteItem( int nItem )
{
if (!CListCtrl::DeleteItem(nItem))
{
return FALSE;
}
// erase all of the row
int nColCnt=GetColumnCount();
for (int nCol=0; nCol<nColCnt; ++nCol)
{
m_mapCell2Data.erase(make_pair(nItem, nCol));
}
return TRUE;
}
// public interfaces
CListCtrlEx::CellIndex CListCtrlEx::Point2Cell(const CPoint &point)
{
LVHITTESTINFO lvHitTestInfo;
CRect rcItem;
lvHitTestInfo.pt = point;
if (HitTest(&lvHitTestInfo) >= 0 && lvHitTestInfo.iItem >= 0)
{
int nRow = lvHitTestInfo.iItem;
int nColCnt=GetColumnCount();
for (int nCol=0; nCol<nColCnt; ++nCol)
{
if (GetCellRect(nRow, nCol, rcItem))
{
if (rcItem.PtInRect(point))
{
return make_pair(nRow, nCol);
}
}
}
}
return make_pair(-1, -1);
}
//////////////////////////////////////////////////////////////////////////
// need to deal with delete column
void CListCtrlEx::SetColumnType(int nColIndex, ColumnType eColType)
{
ASSERT(0<=nColIndex && nColIndex<GetColumnCount());
// init if empty
if (m_mapCol2ColType.empty())
{
for (int i=0; i<GetColumnCount(); ++i)
{
m_mapCol2ColType.insert(ColMap::value_type(i, Normal));
}
}
// assign
m_mapCol2ColType[nColIndex]=eColType;
// maybe should update window here
}
CListCtrlEx::ColumnType CListCtrlEx::GetColumnType(int nColIndex)
{
// insert new if not found
ColMap::iterator iter=m_mapCol2ColType.find(nColIndex);
if (iter==m_mapCol2ColType.end())
{
m_mapCol2ColType[nColIndex]=Normal;
}
return m_mapCol2ColType[nColIndex];
}
SortType CListCtrlEx::GetColumnSortBy(int nColIndex)
{
// insert new if not found
ColSortMap::iterator iter=m_mapCol2Sort.find(nColIndex);
if (m_mapCol2Sort.end()==iter)
{
m_mapCol2Sort[nColIndex]=_ColumnSort_t();
}
return m_mapCol2Sort[nColIndex].m_eSortType;
}
void CListCtrlEx::SetColumnSortBy(int nColIndex, SortType eSortBy)
{
// init sort map
if (m_mapCol2Sort.empty())
{
for (int i=0; i<GetColumnCount(); ++i)
{
m_mapCol2Sort[i]=_ColumnSort_t();
}
}
// assign
m_mapCol2Sort[nColIndex].m_eSortType=eSortBy;
}
//////////////////////////////////////////////////////////////////////////
// need to deal with delete items
void CListCtrlEx::SetCellEnabled(int nRow, int nCol, BOOL bEnabled)
{
ASSERT(0<=nRow && nRow<GetItemCount() && nCol>=0 && nCol< GetColumnCount());
//FindCellData(nRow, nCol).m_bEnabled=bEnabled;
FindCellData(nRow, nCol).SetEnable(bEnabled);
CRect rcCell;
if(GetCellRect(nRow, nCol, rcCell))
{
InvalidateRect(&rcCell);
UpdateWindow();
// send message to parent
CWnd *pWnd=NULL;
if ((pWnd = GetParent())!=NULL )
{
EnableCellMsg msg(nRow, nCol, bEnabled);
pWnd->SendMessage(WM_ListCtrlEx_ENABLE_CHANGED, (WPARAM)this, (LPARAM)&msg);
}
}
}
BOOL CListCtrlEx::GetCellEnabled(int nRow, int nCol)
{
ASSERT(0<=nRow && nRow<GetItemCount() && nCol>=0 && nCol< GetColumnCount());
return FindCellData(nRow, nCol).GetEnable();
}
void CListCtrlEx::SetCellImage(int nRow, int nCol, int nImage)
{
ASSERT(0<=nRow && nRow<GetItemCount() && nCol>=0 && nCol< GetColumnCount());
FindCellData(nRow, nCol).m_nImage=nImage;
}
void CListCtrlEx::SetCellChecked(int nRow, int nCol, BOOL bChecked)
{
int nColCnt=GetColumnCount();
ASSERT(0<=nRow && nRow<GetItemCount() && nCol>=0 && nCol<nColCnt );
ColumnType eColType=m_mapCol2ColType[nCol];
// only check box and radio box can be checked
if (CheckBox==eColType)
{
FindCellData(nRow, nCol).SetCheck(bChecked);
CRect rcCell;
if(GetCellRect(nRow, nCol, rcCell))
{
InvalidateRect(&rcCell); // update check box here immediately
UpdateWindow();
// send message to parent
CWnd *pWnd=NULL;
if ((pWnd = GetParent())!=NULL )
{
CheckCellMsg msg(nRow, nCol, bChecked);
pWnd->SendMessage(WM_ListCtrlEx_CHECKCELL, (WPARAM)this, (LPARAM)&msg);
}
}
}
else if(RadioBox==eColType)
{
int nGrpNo=FindCellData(nRow, nCol).m_nRadioGroup;
int nRowCnt=GetItemCount();
CRect rcCell;
for (int y=0; y<nColCnt; ++y)
{
if (RadioBox==m_mapCol2ColType[y])
{
for (int x=0; x<nRowCnt; ++x)
{
if (nGrpNo==FindCellData(x,y).m_nRadioGroup && FindCellData(x,y).GetCheck()) // only need to update checked one
{
FindCellData(x,y).SetCheck(FALSE);
if(GetCellRect(x, y, rcCell))
{
InvalidateRect(&rcCell); // update radio box here immediately
UpdateWindow();
}
}
}
}
}
bChecked=TRUE; // check on a radio will always cause to be true
FindCellData(nRow, nCol).SetCheck(bChecked);
if(GetCellRect(nRow, nCol, rcCell))
{
InvalidateRect(&rcCell); // update radio box here immediately
UpdateWindow();
}
// send message to parent
CWnd *pWnd=NULL;
if ((pWnd = GetParent())!=NULL )
{
CheckCellMsg msg(nRow, nCol, bChecked);
pWnd->SendMessage(WM_ListCtrlEx_CHECKCELL, (WPARAM)this, (LPARAM)&msg);
}
}
}
BOOL CListCtrlEx::GetCellChecked(int nRow, int nCol)
{
ASSERT(0<=nRow && nRow<GetItemCount() && nCol>=0 && nCol< GetColumnCount());
return FindCellData(nRow, nCol).GetCheck();
}
int CListCtrlEx::GetCellRadioGrp(int nRow, int nCol)
{
ASSERT(0<=nRow && nRow<GetItemCount() && nCol>=0 && nCol< GetColumnCount());
return FindCellData(nRow, nCol).m_nRadioGroup;
}
void CListCtrlEx::SetCellRadioGrp(int nRow, int nCol, int nGroupNO)
{
ASSERT(0<=nRow && nRow<GetItemCount() && nCol>=0 && nCol< GetColumnCount());
FindCellData(nRow, nCol).m_nRadioGroup=nGroupNO;
}
void CListCtrlEx::SetCellStringList(int nRow, int nCol, CStrList &lstStrings)
{
ASSERT(0<=nRow && nRow<GetItemCount() && nCol>=0 && nCol< GetColumnCount());
CStrList &aCellStrList=FindCellData(nRow, nCol).m_lstString;
aCellStrList.clear();
aCellStrList.insert(aCellStrList.begin(), lstStrings.begin(), lstStrings.end());
}
void CListCtrlEx::AddCellString(int nRow, int nCol, CString &str)
{
ASSERT(0<=nRow && nRow<GetItemCount() && nCol>=0 && nCol< GetColumnCount());
CStrList &aCellStrList=FindCellData(nRow, nCol).m_lstString;
aCellStrList.push_back(str);
}
void CListCtrlEx::SetCellValidChars(int nRow, int nCol, CString &strValidChars)
{
ASSERT(0<=nRow && nRow<GetItemCount() && nCol>=0 && nCol< GetColumnCount());
FindCellData(nRow, nCol).m_strValidChars=strValidChars;
}
void CListCtrlEx::SetCellProgressMaxValue(int nRow, int nCol, UINT uMaxValue)
{
ASSERT(0<=nRow && nRow<GetItemCount() && nCol>=0 && nCol< GetColumnCount());
FindCellData(nRow, nCol).m_uProgressMax=uMaxValue;
CRect rcCell;
if(GetCellRect(nRow, nCol, rcCell))
{
InvalidateRect(&rcCell); // update progress bar here immediately
UpdateWindow();
}
}
void CListCtrlEx::SetCellProgressValue(int nRow, int nCol, UINT uValue)
{
ASSERT(0<=nRow && nRow<GetItemCount() && nCol>=0 && nCol< GetColumnCount());
FindCellData(nRow, nCol).m_uProgresVal=uValue;
CRect rcCell;
if(GetCellRect(nRow, nCol, rcCell))
{
InvalidateRect(&rcCell); // update progress bar here immediately
UpdateWindow();
}
}
BOOL CListCtrlEx::IsShowPogressPercent()
{
return m_dwListCtrlExStyle & SHOW_PROGRESS_PERCENT;
}
void CListCtrlEx::SetShowProgressPercent(BOOL bShowProgPercent)
{
if (bShowProgPercent)
m_dwListCtrlExStyle|=SHOW_PROGRESS_PERCENT;
else
m_dwListCtrlExStyle&=(~SHOW_PROGRESS_PERCENT);
}
BOOL CListCtrlEx::IsSupportSort()
{
return m_dwListCtrlExStyle & SUPPORT_SORT;
}
void CListCtrlEx::SetSupportSort(BOOL bSuptSort)
{
if (bSuptSort)
m_dwListCtrlExStyle|=SUPPORT_SORT;
else
m_dwListCtrlExStyle&=(~SUPPORT_SORT);
}
//////////////////////////////////////////////////////////////////////////
BOOL CListCtrlEx::GetCellRect(CellIndex ix, CRect &rect)
{
return GetCellRect(ix.first, ix.second, rect);
}
BOOL CListCtrlEx::GetCellRect(int iRow, int iCol, CRect &rect, int nArea)
{
ASSERT(0<=iRow && iRow<GetItemCount() && iCol>=0 && iCol< GetColumnCount());
if(iCol)
return GetSubItemRect(iRow, iCol, nArea, rect);
if(GetColumnCount()== 1)
return GetItemRect(iRow, rect, nArea);
iCol = 1;
CRect rCol1;
if(!GetSubItemRect(iRow, iCol, nArea, rCol1))
return FALSE;
if(!GetItemRect(iRow, rect, nArea))
return FALSE;
rect.right = rCol1.left;
return TRUE;
}
BOOL CListCtrlEx::GetCheckRect(int iRow, int iCol, CRect &rect)
{
// this rect is to do action rect, so it's larger than the draw box
if (GetCellRect(iRow, iCol, rect))
{
//rect.bottom -= 1;
//rect.top+=1;
rect.left += 7;
rect.right = rect.left + rect.Height(); // width = height
return TRUE;
}
return FALSE;
}
BOOL CListCtrlEx::GetRowRect(int nRow, CRect &rcRow)
{
ASSERT(0<=nRow && nRow<GetItemCount());
return GetItemRect(nRow, rcRow, LVIR_BOUNDS);
}
BOOL CListCtrlEx::GetColRect(int nCol, CRect &rcCol)
{
ASSERT(nCol>=0 && nCol< GetColumnCount());
rcCol =CRect(0,0,0,0);
CRect rcCellInCol(0,0,0,0);
int nRowCnt=GetItemCount();
for (int i=0; i<nRowCnt; ++i)
{
if (GetCellRect(i, nCol, rcCellInCol))
{
if (0==i)
{
rcCol=rcCellInCol;
}
else
{
rcCol.UnionRect(&rcCol, &rcCellInCol);
}
}
else
{
return FALSE;
}
}
return TRUE;
}
int CListCtrlEx::GetColumnFmt(int nCol)
{
ASSERT(0<=nCol && nCol<GetColumnCount());
LVCOLUMN lvColumn;
ZeroMemory(&lvColumn, sizeof(lvColumn));
if (GetColumn(nCol, &lvColumn))
{
return lvColumn.fmt;
}
return LVCFMT_CENTER;
}
//////////////////////////////////////////////////////////////////////////
// customer draw
void CListCtrlEx::OnNMCustomdraw(NMHDR *pNMHDR, LRESULT *pResult)
{
//FuncTime(_T("OnNMCustomdraw"));
//LPNMCUSTOMDRAW pNMCD = reinterpret_cast
LPNMLVCUSTOMDRAW lplvcd = reinterpret_cast<LPNMLVCUSTOMDRAW>(pNMHDR);
int iCol = lplvcd->iSubItem;
int iRow = (int)lplvcd->nmcd.dwItemSpec;
switch(lplvcd->nmcd.dwDrawStage)
{
case CDDS_PREPAINT:
*pResult = CDRF_NOTIFYSUBITEMDRAW; // ask for subitem notifications.
break;
case CDDS_ITEMPREPAINT:
*pResult = CDRF_NOTIFYSUBITEMDRAW; // ask for subitem notifications.
break;
case CDDS_ITEMPREPAINT|CDDS_SUBITEM: // recd when CDRF_NOTIFYSUBITEMDRAW is returned in
{
// response to CDDS_ITEMPREPAINT.
*pResult = CDRF_DODEFAULT;
CString strText = GetItemText(iRow, iCol);
CRect rcCell;
if (GetCellRect(iRow, iCol, rcCell))
{
bool bSelected=GetItemState(iRow, LVIS_SELECTED) == LVIS_SELECTED;
// get the device context.
CDC *pDC= CDC::FromHandle(lplvcd->nmcd.hdc);
// draw the cell.
DrawCell(pDC, strText, rcCell, bSelected, iRow, iCol, pResult ); // *pResult will be changed here
}
break;
}
default: // it wasn't a notification that was interesting to us.
*pResult = CDRF_DODEFAULT;
break;
}
return;
}
void CListCtrlEx::DrawCell(CDC *pDC, CString &strText, CRect &rcCell, BOOL bSelected,
int nRow, int nCol, LRESULT *pResult)
{
switch(GetColumnType(nCol))
{
default:
case Normal:
*pResult |= CDRF_SKIPDEFAULT;
DrawNormal(pDC, strText, rcCell, bSelected, FindCellData(nRow, nCol), GetColumnFmt(nCol));
return;
case CheckBox:
*pResult |= CDRF_SKIPDEFAULT;
DrawCheckBox(pDC, strText, rcCell, bSelected, FindCellData(nRow, nCol));
return;
case RadioBox:
*pResult |= CDRF_SKIPDEFAULT;
DrawRadioBox(pDC, strText, rcCell, bSelected, FindCellData(nRow, nCol));
return;
case ComboBox:
*pResult |= CDRF_SKIPDEFAULT;
DrawComboBox(pDC, strText, rcCell, bSelected, FindCellData(nRow, nCol));
return;
case EditBox:
*pResult|=CDRF_SKIPDEFAULT;
DrawEditBox(pDC, strText, rcCell, bSelected, FindCellData(nRow, nCol), GetColumnFmt(nCol));
return;
case Progress:
*pResult|=CDRF_SKIPDEFAULT;
DrawProgress(pDC, strText, rcCell, bSelected, FindCellData(nRow,nCol));
return;
}
}
// draw normal text
void CListCtrlEx::DrawNormal(CDC *pDC, CString &strText, CRect &rcCell, BOOL bSelected, const CellData &cellData, int uLvcFmt)
{
UNREFERENCED_PARAMETER(cellData);
COLORREF crText=bSelected?GetSysColor(COLOR_HIGHLIGHTTEXT):GetSysColor(COLOR_WINDOWTEXT);
COLORREF crTextBkgrnd=bSelected?GetSysColor(COLOR_HIGHLIGHT):GetSysColor(COLOR_WINDOW);
// fill background
CRect rcText(rcCell), rcIcon(rcCell);
pDC->FillSolidRect(&rcCell, crTextBkgrnd);
//-- draw icon--//
CImageList *pImageList=TheImageList();
if (cellData.m_nImage>=0 && pImageList)
{
rcIcon.right=rcIcon.left+rcIcon.Height();
//UINT fStyle=bSelected?ILD_NORMAL:(ILD_NORMAL|ILD_SELECTED);
pImageList->DrawIndirect( pDC, cellData.m_nImage, rcIcon.TopLeft(), rcIcon.Size(), CPoint(0, 0), ILD_NORMAL,
SRCCOPY,crTextBkgrnd, crTextBkgrnd );
//pImageList->DrawIndirect( pDC, cellData.m_nImage, rcIcon.TopLeft(), rcIcon.Size(), CPoint(0, 0));
}
else
{
rcIcon.right=rcIcon.left;
}
//------------draw text---------------------/
UINT nFormat=DT_CENTER;
switch(uLvcFmt)
{
default:
case LVCFMT_CENTER: break;
case LVCFMT_LEFT: nFormat=DT_LEFT; break;
case LVCFMT_RIGHT: nFormat=DT_RIGHT; break;
}
COLORREF crOldText=pDC->SetTextColor(crText);
rcText.left=rcIcon.right+3;
pDC->DrawText(strText, &rcText, nFormat);
pDC->SetTextColor(crOldText);
}
// draw a check box cell
void CListCtrlEx::DrawCheckBox(CDC *pDC, CString &strText, CRect &rcCell, BOOL bSelected, const CellData &cellData)
{
COLORREF crText=bSelected?GetSysColor(COLOR_HIGHLIGHTTEXT):GetSysColor(COLOR_WINDOWTEXT);
COLORREF crTextBkgrnd=bSelected?GetSysColor(COLOR_HIGHLIGHT):GetSysColor(COLOR_WINDOW);
COLORREF crCheckFrame=cellData.GetEnable()?RGB(51,102,153):RGB(192,192,192);
COLORREF crCheckMark=cellData.GetEnable()?RGB(51,255,153):RGB(192,192,192);
//------------calculate the check box & Text rect ---------------------/
CRect rcText(rcCell);
CRect rcCheck(rcCell);
rcCheck.bottom -= 2;
rcCheck.top+=2;
rcCheck.left += 7;
rcCheck.right = rcCheck.left + rcCheck.Height(); // width = height
rcText.left=rcCheck.right+3;
//------------draw the inside check box region---------------------/
pDC->FillSolidRect(&rcText, crTextBkgrnd);
pDC->FillSolidRect(&rcCheck, GetSysColor(COLOR_WINDOW)); // refresh the check box
//------------draw the check box frame border ---------------------/
#ifdef USING_CUSTOM_DRAW
//CBrush brush(crCheckFrame);
//pDC->FrameRect(&rcCheck, &brush); //fill frame of the check box
CPen penFrame(PS_SOLID, 2,crCheckFrame);
CPen *pOldPen=pDC->SelectObject(&penFrame);
pDC->Rectangle(&rcCheck);
//------------draw the check mark---------------------/
if (cellData.GetCheck() ) // draw the checked Mark
{
int deta=rcCheck.Width()/4;
CPen penMark(PS_SOLID, 1,crCheckMark);
pDC->SelectObject(&penMark);
int x = rcCheck.left + deta*3;
int y = rcCheck.top + deta;
int i;
for (i = 0; i < 4; i++)
{
pDC->MoveTo(x, y);
pDC->LineTo(x, y+3);
x--;
y++;
}
for (i = 0; i < 3; i++)
{
pDC->MoveTo(x, y);
pDC->LineTo(x, y+3);
x--;
y--;
}
}
pDC->SelectObject(pOldPen);
#else
crCheckMark;crCheckFrame; // only for kill the warnings
UINT nState=DFCS_BUTTONCHECK;
if (cellData.GetCheck() ) // draw the checked Mark
{
nState|=DFCS_CHECKED;
}
if (bSelected)
{
nState|=DFCS_HOT;
}
if (!cellData.GetEnable())
{
nState|=DFCS_INACTIVE;
}
pDC->DrawFrameControl(&rcCheck, DFC_BUTTON, nState);
#endif
//------------draw text---------------------/
COLORREF crOldText=pDC->SetTextColor(crText);
pDC->DrawText(strText, &rcText, DT_LEFT);
pDC->SetTextColor(crOldText);
}
// draw a Radio box cell
void CListCtrlEx::DrawRadioBox(CDC *pDC, CString &strText, CRect &rcCell, BOOL bSelected, const CellData &cellData)
{
COLORREF crText=bSelected?GetSysColor(COLOR_HIGHLIGHTTEXT):GetSysColor(COLOR_WINDOWTEXT);
COLORREF crTextBkgrnd=bSelected?GetSysColor(COLOR_HIGHLIGHT):GetSysColor(COLOR_WINDOW);
COLORREF crRadioFrame=cellData.GetEnable()?RGB(51,102,153):RGB(192,192,192);
COLORREF crRadioFrameBk=GetSysColor(COLOR_WINDOW);
COLORREF crRadioMark=cellData.GetEnable()?RGB(0,0,0):RGB(192,192,192);
//------------calculate the check box & Text rect ---------------------/
CRect rcText(rcCell);
CRect rcRadio(rcCell);
rcRadio.bottom -= 1;
rcRadio.top+=1;
rcRadio.left += 7;
rcRadio.right = rcRadio.left + rcRadio.Height(); // width = height
rcText.left=rcRadio.right+3;
//------------draw the inside radio box region---------------------/
pDC->FillSolidRect(&rcText, crTextBkgrnd);
pDC->FillSolidRect(&rcRadio, crRadioFrameBk); // refresh the check box
//------------draw the radio box frame border ---------------------/
#ifdef USING_CUSTOM_DRAW
// using three dot to draw the radio box
CPoint pos=rcRadio.TopLeft();
pos +=CSize(rcRadio.Width()/2, rcRadio.Height()/2);
int nPenWid=rcRadio.Height();
// ----draw frame----/
// outer frame
CPen pen(PS_DOT, nPenWid, crRadioFrame);
CPen *pOldPen=pDC->SelectObject(&pen);
pDC->MoveTo(pos);
pDC->LineTo(pos);
// inner frame
pDC->SelectObject(pOldPen);
pen.DeleteObject();
nPenWid=((nPenWid-4)>0)?(nPenWid-4):4;
pen.CreatePen(PS_DOT, nPenWid, crRadioFrameBk);
pDC->SelectObject(&pen);
pDC->MoveTo(pos);
pDC->LineTo(pos);
// draw mark
if (cellData.GetCheck())
{
pDC->SelectObject(pOldPen);
pen.DeleteObject();
nPenWid=((nPenWid-4)>0)?(nPenWid-4):4;
pen.CreatePen(PS_DOT, nPenWid, crRadioMark);
pDC->SelectObject(&pen);
pDC->MoveTo(pos);
pDC->LineTo(pos);
}
pDC->SelectObject(pOldPen);
#else
crRadioFrame;crRadioMark; // only for kill the warnings
UINT nState=DFCS_BUTTONRADIO;
if (cellData.GetCheck() ) // draw the checked Mark
{
nState|=DFCS_CHECKED;
}
if (!cellData.GetEnable())
{
nState|=DFCS_INACTIVE;
}
pDC->DrawFrameControl(&rcRadio, DFC_BUTTON, nState);
#endif
//------------draw text---------------------/
COLORREF crOldText=pDC->SetTextColor(crText);
pDC->DrawText(strText, &rcText, DT_LEFT);
pDC->SetTextColor(crOldText);
}
// draw a combo box cell
void CListCtrlEx::DrawComboBox(CDC *pDC, CString &strText, CRect &rcCell, BOOL bSelected, const CellData &cellData)
{
COLORREF crText=bSelected?GetSysColor(COLOR_HIGHLIGHTTEXT):GetSysColor(COLOR_WINDOWTEXT);
COLORREF crTextBkgrnd=bSelected?GetSysColor(COLOR_HIGHLIGHT):GetSysColor(COLOR_WINDOW);
COLORREF crComboTL=cellData.GetEnable()?RGB(255,255,255):RGB(192,192,192); //#d4e1fc:#c0c0c0;
COLORREF crComboBR=cellData.GetEnable()? RGB(177,203,250):RGB(192,192,192);
COLORREF crComboMark=cellData.GetEnable()?RGB(77,97,133):RGB(0,0,0);
//------------calculate the check box & Text rect ---------------------/
CRect rcText(rcCell);
CRect rcCombo(rcCell);
rcCombo.bottom -= 0;
rcCombo.top+=1;
rcCombo.right -= 2;
rcCombo.left = rcCombo.right - rcCombo.Height(); // width = height
rcText.right=rcCombo.left-3;
rcText.left+=2;
//---------refresh the cell------/
pDC->FillSolidRect(&rcCell, GetSysColor(COLOR_WINDOW));
pDC->FillSolidRect(&rcText, crTextBkgrnd);
//------------draw the combo box frame border ---------------------/
#ifdef USING_CUSTOM_DRAW
CBrush brBottomRight(crComboBR);
CBrush *pOldBrush=pDC->SelectObject(&brBottomRight);
/*CRect rcInner(rcCombo);
rcInner.DeflateRect(1,1,1,1);*/
//pDC->FillSolidRect(&rcInner, crComboBR);
DrawGradientRect(pDC, rcCombo, crComboTL, crComboBR);
//pDC->Draw3dRect(&rcCombo, crComboTL, crComboBR);
pDC->SelectObject(pOldBrush);
CPen penMark(PS_SOLID, 1, crComboMark);
CPen *pOldPen=pDC->SelectObject(&penMark);
// draw the arrow
{
int xOrg=rcCombo.Width()/2+rcCombo.left;
int yOrg=rcCombo.Height()/2+rcCombo.top+2;
int xOff=max(rcCombo.Width()/4,1);
int yOff=max(rcCombo.Height()/4,1);
int y=yOrg-yOff;
for (int x=(xOrg-xOff); x<xOrg; ++x, ++y)
{
pDC->MoveTo(x,y);
pDC->LineTo(x+2*(xOrg-x), y);
}
}
pDC->SelectObject(pOldPen);
#else
crComboTL;crComboBR;crComboMark; // only for kill the warnings
UINT nState=DFCS_SCROLLCOMBOBOX;
if (cellData.GetCheck() ) // draw the checked Mark
{
nState|=DFCS_CHECKED;
}
if (bSelected)
{
nState|=DFCS_HOT;
}
if (!cellData.GetEnable())
{
nState|=DFCS_INACTIVE;
}
pDC->DrawFrameControl(&rcCombo, DFC_SCROLL, nState);
#endif
//------------draw text---------------------/
COLORREF crOldText=pDC->SetTextColor(crText);
pDC->DrawText(strText, &rcText, DT_LEFT);
pDC->SetTextColor(crOldText);
}
// draw a edit box cell
void CListCtrlEx::DrawEditBox(CDC *pDC, CString &strText, CRect &rcCell, BOOL bSelected, const CellData &cellData, int uLvcFmt)
{
COLORREF crText=bSelected?GetSysColor(COLOR_HIGHLIGHTTEXT):GetSysColor(COLOR_WINDOWTEXT);
COLORREF crTextBkgrnd=bSelected?GetSysColor(COLOR_HIGHLIGHT):GetSysColor(COLOR_WINDOW);
COLORREF crTextUnabled=bSelected?GetSysColor(COLOR_HIGHLIGHTTEXT):RGB(192,192,192);
CRect rcText(rcCell), rcIcon(rcCell);
// fill background
pDC->FillSolidRect(&rcCell, crTextBkgrnd);
//-- draw icon--//
CImageList *pImageList=TheImageList();
if (cellData.m_nImage>=0 && pImageList)
{
rcIcon.right=rcIcon.left+rcIcon.Height();
pImageList->DrawIndirect( pDC, cellData.m_nImage, rcIcon.TopLeft(), rcIcon.Size(), CPoint(0, 0), ILD_NORMAL,
SRCCOPY,crTextBkgrnd, crTextBkgrnd );
}
else
{
rcIcon.right=rcIcon.left;
}
#ifdef USING_CUSTOM_DRAW
#else
#endif
//------------draw text---------------------/
UINT nFormat=DT_CENTER;
switch(uLvcFmt)
{
default:
case LVCFMT_CENTER: break;
case LVCFMT_LEFT: nFormat=DT_LEFT; break;
case LVCFMT_RIGHT: nFormat=DT_RIGHT; break;
}
COLORREF crOldText=pDC->SetTextColor(crText);
if (!cellData.GetEnable())
{
pDC->SetTextColor(crTextUnabled);
}
rcText.left=rcIcon.right+3;
pDC->DrawText(strText, &rcText, nFormat);
pDC->SetTextColor(crOldText);
}
// draw a progress bar
void CListCtrlEx::DrawProgress(CDC *pDC, CString &strText, CRect &rcCell, BOOL bSelected, const CellData &cellData)
{
COLORREF crText=bSelected?GetSysColor(COLOR_HIGHLIGHTTEXT):GetSysColor(COLOR_WINDOWTEXT);
COLORREF crTextBkgrnd=bSelected?GetSysColor(COLOR_HIGHLIGHT):GetSysColor(COLOR_WINDOW);
COLORREF crProgBack=bSelected?GetSysColor(COLOR_HOTLIGHT):GetSysColor(COLOR_BTNFACE);
COLORREF crProgFill=bSelected?COLOR_FRAME:COLOR_FRAME;
CRect rcProg(rcCell), rcIcon(rcCell);
// fill background
pDC->FillSolidRect(&rcCell, crTextBkgrnd);
//-- draw icon--//
CImageList *pImageList=TheImageList();
if (cellData.m_nImage>=0 && pImageList)
{
rcIcon.right=rcIcon.left+rcIcon.Height();
pImageList->DrawIndirect( pDC, cellData.m_nImage, rcIcon.TopLeft(), rcIcon.Size(), CPoint(0, 0), ILD_NORMAL,
SRCCOPY,crTextBkgrnd, crTextBkgrnd );
}
else
{
rcIcon.right=rcIcon.left;
}
rcProg.left=rcIcon.right+3;
pDC->FillSolidRect(&rcProg, crProgBack);
pDC->DrawEdge(&rcProg, EDGE_SUNKEN, BF_RECT);
CRect rcFill(rcProg);
rcFill.DeflateRect(1,1,1,1);
float fPersent=0.0f;
int nFill=GetProgBarSize(cellData, rcProg, &fPersent);
rcFill.right=min(rcFill.left+nFill, rcFill.right);
pDC->FillSolidRect(&rcFill, crProgFill);
COLORREF crOldText=pDC->SetTextColor(crText);
if (IsShowPogressPercent())
{
strText.Format(_T("%.2f%%"), fPersent*100);
}
pDC->DrawText(strText, &rcProg, DT_CENTER);
pDC->SetTextColor(crOldText);
#ifdef USING_CUSTOM_DRAW
#else
#endif
}
//////////////////////////////////////////////////////////////////////////
// kernel data
int CListCtrlEx::GetProgBarSize(const CListCtrlEx::CellData &cellData, CRect &rcProg, float *pPersent)
{
float fScale=0.0f;
if (cellData.m_uProgresVal>=cellData.m_uProgressMax)
fScale=1.0f;
else if(cellData.m_uProgresVal<=0 || cellData.m_uProgressMax<=0)
fScale=0.0f;
else
fScale=(float)cellData.m_uProgresVal/(float)cellData.m_uProgressMax;
if (pPersent)
{
*pPersent=fScale;
}
return (int)(rcProg.Width()*fScale);
}
CImageList* CListCtrlEx::TheImageList()
{
return GetImageList(m_nImageListType);
}
CListCtrlEx::CellData& CListCtrlEx::FindCellData(CellIndex ix)
{
return FindCellData(ix.first, ix.second);
}
CListCtrlEx::CellData& CListCtrlEx::FindCellData(int nRow, int nCol)
{
// init if empty
if (m_mapCell2Data.empty())
{
for (int x=0; x<GetItemCount(); ++x)
{
for (int y=0; y< GetColumnCount(); ++y)
{
m_mapCell2Data[make_pair(x,y)]=CellData(TRUE, FALSE, y);
}
}
}
CellDataMap::iterator iter=m_mapCell2Data.find(make_pair(nRow, nCol));
if (iter==m_mapCell2Data.end())
{
m_mapCell2Data[make_pair(nRow, nCol)]=CellData(TRUE, FALSE, nCol);
}
return m_mapCell2Data[make_pair(nRow, nCol)];
}
//////////////////////////////////////////////////////////////////////////
// show control
void CListCtrlEx::ShowCellInPlace(CListCtrlEx::CellIndex ix, CellType eCellType)
{
// roll up make the cell be visible
if (!EnsureVisible(ix.first, TRUE))
{
return;
}
switch(eCellType)
{
default:
case Normal:
case CheckBox:
case RadioBox:
return;
case ComboBox:
ShowInPlaceCombo(ix);
break;
case EditBox:
ShowInPlaceEdit(ix);
break;
}
}
// show combo box in place
void CListCtrlEx::ShowInPlaceCombo(CellIndex ix)
{
if (ix.first>=0 && ix.first<GetItemCount() && ix.second>=0 && ix.second< GetColumnCount())
{
CString strCurSel= GetItemText(ix.first, ix.second);
CRect rcCell;
if (GetCellRect(ix, rcCell))
{
GetParent()->SendMessage(WM_SET_ITEMS, (WPARAM)this, (LPARAM)&ix);
CInPlaceCombo* pInPlaceComboBox = CInPlaceCombo::GetInstance();
ASSERT(pInPlaceComboBox);
pInPlaceComboBox->ShowComboCtrl(m_dwComboStyle, rcCell, this, IDC_CELL_COMBO,
ix.first, ix.second, FindCellData(ix).m_lstString, strCurSel );
pInPlaceComboBox->SelectString(-1, strCurSel);
}
}
}
// show edit box in place
void CListCtrlEx::ShowInPlaceEdit(CellIndex ix)
{
if (ix.first>=0 && ix.first<GetItemCount() && ix.second>=0 && ix.second< GetColumnCount())
{
CString strCurSel= GetItemText(ix.first, ix.second);
CRect rcCell;
if (GetCellRect(ix, rcCell))
{
GetParent()->SendMessage(WM_SET_ITEMS, (WPARAM)this, (LPARAM)&ix);
CInPlaceEdit* pInPlaceEdit = CInPlaceEdit::GetInstance();
ASSERT(pInPlaceEdit);
pInPlaceEdit->ShowEditCtrl(m_dwEditStyle, rcCell, this, IDC_CELL_EDIT, ix.first, ix.second,
FindCellData(ix).m_strValidChars, strCurSel);
}
}
}
//////////////////////////////////////////////////////////////////////////
// message handlers
void ListCtrlEx::CListCtrlEx::OnLButtonDown(UINT nFlags, CPoint point)
{
//FuncTime(_T("OnLButtonDown"));
CWnd* pWnd=NULL;
bool bShouldAction=false;
CellIndex ix=Point2Cell(point);
bShouldAction=(ix.first>=0 && ix.second>=0 && ix.first<GetItemCount() && ix.second<GetColumnCount());
CListCtrl::OnLButtonDown(nFlags, point);
// If the SHIFT or CTRL key is down call the base class
// Check the high bit of GetKeyState to determine whether SHIFT or CTRL key is down
if ((GetKeyState(VK_SHIFT) & 0x80) || (GetKeyState(VK_CONTROL) & 0x80))
{
return;
}
if (bShouldAction && GetCellEnabled(ix.first, ix.second))
{
/*CRect rcItem;
GetRowRect(ix.first, rcItem);*/
switch(m_mapCol2ColType[ix.second])
{
case CheckBox:
case RadioBox:
{ // can check cell
CRect rcCheck;
bool bShouldCheck=(GetCheckRect(ix.first, ix.second, rcCheck) && rcCheck.PtInRect(point));
if (bShouldCheck)
{
SetCellChecked(ix.first, ix.second, !GetCellChecked(ix.first, ix.second));
if ((pWnd = GetParent())!=NULL)
{
CheckCellMsg msg(ix.first, ix.second, GetCellChecked(ix.first, ix.second));
pWnd->SendMessage(WM_ListCtrlEx_CHECK_CHANGED, (WPARAM)this, (LPARAM)&msg);
}
}
}
break;
case ComboBox:
case EditBox:
{ // can show or edit in place cell
ShowCellInPlace(ix, m_mapCol2ColType[ix.second]);
}
break;
case Normal:
default:
break;
}
//InvalidateRect(&rcItem); // normal or default should be update immediately for the focus state
//UpdateWindow();
}
// notify parent with user messages
if ((pWnd = GetParent())!=NULL )
{
pWnd->SendMessage(WM_ListCtrlEx_LBUTTONDOWN, (WPARAM)this, (LPARAM)&ix);
}
}
void ListCtrlEx::CListCtrlEx::OnLButtonDblClk(UINT nFlags, CPoint point)
{
CWnd* pWnd=NULL;
bool bShouldAction=false;
CellIndex ix=Point2Cell(point);
bShouldAction=(ix.first>=0 && ix.second>=0 && ix.first<GetItemCount() && ix.second<GetColumnCount());
// actions
if (bShouldAction && GetCellEnabled(ix.first, ix.second))
{
CRect rcItem;
GetRowRect(ix.first, rcItem);
CRect rcCheck;
bool bShouldCheck=(GetCheckRect(ix.first, ix.second, rcCheck) && rcCheck.PtInRect(point));
if (bShouldCheck)
{
//SetCellChecked(ix.first, ix.second, !GetCellChecked(ix.first, ix.second));
//InvalidateRect(&rcCheck); // should update window here immediately
//UpdateWindow();
}
InvalidateRect(&rcItem); // normal or default should be update immediately for the focus state
UpdateWindow();
}
// notify parent with user messages
if ((pWnd = GetParent())!=NULL )
{
pWnd->PostMessage(WM_ListCtrlEx_LBUTTONDBLCLK, (WPARAM)this, (LPARAM)&ix);
}
// do default action
CListCtrl::OnLButtonDblClk(nFlags, point);
}
void ListCtrlEx::CListCtrlEx::OnRButtonDown(UINT nFlags, CPoint point)
{
CWnd* pWnd=NULL;
bool bShouldAction=false;
CellIndex ix=Point2Cell(point);
bShouldAction=(ix.first>=0 && ix.second>=0 && ix.first<GetItemCount() && ix.second<GetColumnCount());
// actions
// notify parent with user messages
if ((pWnd = GetParent())!=NULL )
{
pWnd->PostMessage(WM_ListCtrlEx_RBUTTONDOWN, (WPARAM)this, (LPARAM)&ix);
}
// do default action
CListCtrl::OnRButtonDown(nFlags, point);
//////////////////////////////////////////////////////////////////////////
// for test
#ifdef _DEBUG
/*int nHot=GetHotItem();
vector
POSITION pos = GetFirstSelectedItemPosition();
while (pos)
{
ivec.push_back(GetNextSelectedItem(pos));
}
CString str, strTmp;
str.Format(_T("Hot item: %d \r\n"), nHot);
vector
for (; iter!=ivec.end(); ++iter)
{
strTmp.Format(_T("Selected item: %d\r\n"), *iter);
str+=strTmp;
}
strTmp.Format(_T("Click point cell: row=%d, col=%d\r\n"), ix.first, ix.second);
str=_T("Testing: \r\n")+str+strTmp;
AfxMessageBox(str);
#endif
}
void ListCtrlEx::CListCtrlEx::OnLvnBeginlabeledit(NMHDR *pNMHDR, LRESULT *pResult)
{
NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);
// TODO: Add your control notification handler code here
if (!GetCellEnabled(pDispInfo->item.iItem, pDispInfo->item.iSubItem))
{
*pResult = 1;
return;
}
*pResult = 0;
}
void ListCtrlEx::CListCtrlEx::OnLvnEndlabeledit(NMHDR *pNMHDR, LRESULT *pResult)
{
NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);
// TODO: Add your control notification handler code here
// Update the item text with the new text
SetItemText(pDispInfo->item.iItem, pDispInfo->item.iSubItem, pDispInfo->item.pszText);
GetParent()->SendMessage(WM_VALIDATE, GetDlgCtrlID(), (LPARAM)pDispInfo);
*pResult = 0;
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// static members
int CALLBACK CListCtrlEx::CompareFunc(LPARAM lParam1, LPARAM lParam2,
LPARAM lParamSort)
{
_SortParam_t *pSortParam_t=reinterpret_cast<_SortParam_t*>(lParamSort);
if (NULL==pSortParam_t)
return 0;
CListCtrlEx *pList=pSortParam_t->m_pTheList;
ASSERT(pList!=NULL && ::IsWindow(pList->m_hWnd));
CString strCell1 = pList->GetItemText((int)lParam1, pSortParam_t->m_nSortCol);
CString strCell2 = pList->GetItemText((int)lParam2, pSortParam_t->m_nSortCol);
int nResult=0;
try
{
switch(pSortParam_t->m_ColParam.m_eSortType)
{
default:
case SortByString:
nResult=StrCompare(strCell1,strCell2);
break;
case SortByDigit:
nResult=IntCompare(strCell1,strCell2);
break;
}
}
catch ()
{
nResult=StrCompare(strCell1,strCell2);
}
return (pSortParam_t->m_ColParam.m_bIsAsc?nResult:-nResult);
}
#ifdef _DEBUG
#define ASSERT_VALID_STRING( str ) ASSERT( !IsBadStringPtr( str, 0xfffff ) )
#else // _DEBUG
#define ASSERT_VALID_STRING( str ) ( (void)0 )
#endif // _DEBUG
bool CListCtrlEx::IsDigitStr(LPCTSTR str)
{
int nLen=(int)_tcslen(str);
int nPointCnt=0;
TCHAR ch=0;
for (int i=0; i<nLen; ++i)
{
ch=str[i];
// for negative
if (0==i && _T('-')==ch) {
continue;
}else if ( 0!=i && _T('-')==ch) {
return false;
}
if (_T('.')==ch) {
++nPointCnt;
}
if (nPointCnt>1) {
return false;
}
if ((ch<48 || ch>57) && _T('.')!=ch) {
return false;
}
}
return true;
}
int CListCtrlEx::IntCompare(LPCTSTR strInt1st,LPCTSTR strInt2nd)
{
ASSERT_VALID_STRING( strInt1st );
ASSERT_VALID_STRING( strInt2nd);
if (!IsDigitStr(strInt1st) || !IsDigitStr(strInt2nd))
{
//AfxThrowInvalidArgException();
return StrCompare(strInt1st, strInt2nd);
}
const int num1st=_ttoi(strInt1st);
const int num2nd=_ttoi(strInt2nd);
return (num1st-num2nd);
}
int CListCtrlEx::StrCompare(LPCTSTR str1st, LPCTSTR str2nd)
{
ASSERT_VALID_STRING( str1st );
ASSERT_VALID_STRING( str2nd);
return _tcscmp(str1st, str2nd);
}
BOOL CListCtrlEx::DrawGradientRect(CDC *pDC, CRect &rect, COLORREF crTopLeft, COLORREF crBottomRight)
{
ASSERT(pDC);
if (0>=rect.Width() || 0>=rect.Height())
{
return FALSE;
}
// draw a rect with 45 degree slope gradient color from top left to bottom right
CBrush brColor(crBottomRight);
pDC->FillRect(&rect, &brColor);
int nSteps=max(rect.Width(), rect.Height());
int rBR=GetRValue(crBottomRight), gBR=GetGValue(crBottomRight), bBR=GetBValue(crBottomRight);
int rTL=GetRValue(crTopLeft), gTL=GetGValue(crTopLeft), bTL=GetBValue(crTopLeft);
int rDiff=(rBR-rTL)/nSteps;
int gDiff=(gBR-gTL)/nSteps;
int bDiff=(bBR-bTL)/nSteps;
CRgn rgRect, rgRound, rg2Fill;
rgRect.CreateRectRgnIndirect(&rect);
rg2Fill.CreateRectRgn(0,0,0,0);
for (int i=nSteps; i>=0; --i)
{
CRect rcRound(rect.left-i, rect.top-i, rect.left+i, rect.top+i);
rgRound.CreateEllipticRgnIndirect(&rcRound);
int nCombineResult =rg2Fill.CombineRgn(&rgRect, &rgRound, RGN_AND);
if( nCombineResult != ERROR && nCombineResult != NULLREGION ){
pDC->FillRgn(&rg2Fill, &brColor);
}
rgRound.DeleteObject();
// calculate new color
brColor.DeleteObject();
brColor.CreateSolidBrush(RGB(rTL+i*rDiff, gTL+i*gDiff, bTL+i*bDiff));
}
return TRUE;
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
void ListCtrlEx::CListCtrlEx::OnLvnColumnclick(NMHDR *pNMHDR, LRESULT *pResult)
{
LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
if(IsSupportSort())
{
vector<DWORD_PTR> vecItemDatas(GetItemCount());
//map
PreSortItems(vecItemDatas);
//SortType eSortBy=GetColumnSortBy(pNMLV->iSubItem);
BOOL bIsAsc=!IsColumnSortAsc(pNMLV->iSubItem);
SetColumnSortDirection(pNMLV->iSubItem, bIsAsc);
_SortParam_t aSortParam;
aSortParam.m_ColParam=m_mapCol2Sort[pNMLV->iSubItem];
aSortParam.m_nSortCol=pNMLV->iSubItem;
aSortParam.m_pTheList=this;
SortItems(CompareFunc, (DWORD_PTR)&aSortParam);
//m_ctlSortHead.SetSortArrow(pNMLV->iSubItem, !bIsAsc);
PostSortItems(vecItemDatas);
}
*pResult = 0;
}
// before sort items, backup old item data here, then set the item data with old row NO.
// vec2StoreData must be initialized as size of "GetItemCount()"
void CListCtrlEx::PreSortItems( vector<DWORD_PTR> &vec2StoreData )
{
int nRowCnt=GetItemCount();
for (int i=0; i<nRowCnt; ++i)
{
vec2StoreData[i]=GetItemData(i);
SetItemData(i, (DWORD_PTR)i);
}
}
// after sort items, re-mapping the cell data here and restore the old item data here
// vec2StoreData must be initialized as size of "GetItemCount()" and assigned with old item data
void CListCtrlEx::PostSortItems( vector<DWORD_PTR> &vec2StoreData )
{
// adjust the cell data map
CellDataMap newMap;
int nRowCnt=GetItemCount();
int nColCnt=GetColumnCount();
for (int i=0; i<nRowCnt; ++i)
{
int nOldRow=(int)GetItemData(i);
for (int j=0; j<nColCnt; ++j)
{
newMap[make_pair(i,j)]=m_mapCell2Data[make_pair(nOldRow, j)];
}
// restore item data
SetItemData(i, (DWORD_PTR)vec2StoreData[nOldRow]);
}
m_mapCell2Data.swap(newMap);
}
void ListCtrlEx::CListCtrlEx::PreSubclassWindow()
{
// TODO: Add your specialized code here and/or call the base class
ASSERT( GetStyle() & LVS_REPORT );
CListCtrl::PreSubclassWindow();
VERIFY(m_ctlSortHead.SubclassWindow(this->GetHeaderCtrl()->GetSafeHwnd()) );
}
BOOL CListCtrlEx::IsColumnSortAsc(int nColIndex)
{
// insert new if not found
ColSortMap::iterator iter=m_mapCol2Sort.find(nColIndex);
if (m_mapCol2Sort.end()==iter)
{
m_mapCol2Sort[nColIndex]=_ColumnSort_t();
}
return m_mapCol2Sort[nColIndex].m_bIsAsc;
}
void CListCtrlEx::SetColumnSortDirection(int nColIndex, BOOL bIsAsc )
{
// init sort map
if (m_mapCol2Sort.empty())
{
for (int i=0; i<GetColumnCount(); ++i)
{
m_mapCol2Sort[i]=_ColumnSort_t();
}
}
// assign
m_mapCol2Sort[nColIndex].m_bIsAsc=bIsAsc;
}
void ListCtrlEx::CListCtrlEx::OnDestroy()
{
CListCtrl::OnDestroy();
if (::IsWindow(CInPlaceCombo::GetInstance()->m_hWnd))
{
CInPlaceCombo::GetInstance()->PostMessage(WM_CLOSE);
}
if (::IsWindow(CInPlaceEdit::GetInstance()->m_hWnd))
{
CInPlaceEdit::GetInstance()->PostMessage(WM_CLOSE);
}
}
// demo
ListCtrlExDemo