// ListCtrlEx.cpp : implementation of the CListCtrlEx class
#include "stdafx.h"
#include "ListCtrlEx.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
/////////////////////////////////////////////////////////////////////////////
// CListCtrlEx
IMPLEMENT_DYNCREATE(CListCtrlEx, CListCtrl)
BEGIN_MESSAGE_MAP(CListCtrlEx, CListCtrl)
//{{AFX_MSG_MAP(CListCtrlEx)
ON_WM_SIZE()
ON_WM_PAINT()
ON_WM_SETFOCUS()
ON_WM_KILLFOCUS()
//}}AFX_MSG_MAP
ON_MESSAGE(LVM_SETIMAGELIST, OnSetImageList)
ON_MESSAGE(LVM_SETTEXTCOLOR, OnSetTextColor)
ON_MESSAGE(LVM_SETTEXTBKCOLOR, OnSetTextBkColor)
ON_MESSAGE(LVM_SETBKCOLOR, OnSetBkColor)
ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTW, 0, 0xFFFF, OnToolTipText)
ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTA, 0, 0xFFFF, OnToolTipText)
END_MESSAGE_MAP()
/////////////////////////////////////////////////////////////////////////////
// CListCtrlEx construction/destruction
CListCtrlEx::CListCtrlEx()
{
m_bFullRowSel=FALSE;
m_bClientWidthSel=TRUE;
m_cxClient=0;
m_cxStateImageOffset=0;
m_clrText=::GetSysColor(COLOR_WINDOWTEXT);
m_clrTextBk=::GetSysColor(COLOR_WINDOW);
m_clrBkgnd=::GetSysColor(COLOR_WINDOW);
}
CListCtrlEx::~CListCtrlEx()
{
}
BOOL CListCtrlEx::PreCreateWindow(CREATESTRUCT& cs)
{
// default is report view and full row selection
cs.style &= ~LVS_TYPEMASK;
cs.style |= LVS_REPORT | LVS_OWNERDRAWFIXED;
m_bFullRowSel=TRUE;
return(CListCtrl::PreCreateWindow(cs));
}
BOOL CListCtrlEx::SetFullRowSel(BOOL bFullRowSel)
{
// no painting during change
LockWindowUpdate();
m_bFullRowSel=bFullRowSel;
BOOL bRet;
if(m_bFullRowSel)
bRet=ModifyStyle(0L,LVS_OWNERDRAWFIXED);
else
bRet=ModifyStyle(LVS_OWNERDRAWFIXED,0L);
// repaint window if we are not changing view type
if(bRet && (GetStyle() & LVS_TYPEMASK)==LVS_REPORT)
Invalidate();
// repaint changes
UnlockWindowUpdate();
return(bRet);
}
BOOL CListCtrlEx::GetFullRowSel()
{
return(m_bFullRowSel);
}
/////////////////////////////////////////////////////////////////////////////
// CListCtrlEx drawing
// offsets for first and other columns
#define OFFSET_FIRST 2
#define OFFSET_OTHER 6
void CListCtrlEx::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
CDC* pDC=CDC::FromHandle(lpDrawItemStruct->hDC);
CRect rcItem(lpDrawItemStruct->rcItem);
UINT uiFlags=ILD_TRANSPARENT;
CImageList* pImageList;
int nItem=lpDrawItemStruct->itemID;
BOOL bFocus=(GetFocus()==this);
COLORREF clrTextSave, clrBkSave;
COLORREF clrImage=m_clrBkgnd;
static _TCHAR szBuff[MAX_PATH];
LPCTSTR pszText;
// get item data
LV_ITEM lvi;
lvi.mask=LVIF_TEXT | LVIF_IMAGE | LVIF_STATE;
lvi.iItem=nItem;
lvi.iSubItem=0;
lvi.pszText=szBuff;
lvi.cchTextMax=sizeof(szBuff);
lvi.stateMask=0xFFFF; // get all state flags
GetItem(&lvi);
BOOL bSelected=(bFocus || (GetStyle() /*& LVS_SHOWSELALWAYS*/)) && lvi.state & LVIS_SELECTED;
bSelected=bSelected || (lvi.state & LVIS_DROPHILITED);
// set colors if item is selected
CRect rcAllLabels;
GetItemRect(nItem,rcAllLabels,LVIR_BOUNDS);
CRect rcLabel;
GetItemRect(nItem,rcLabel,LVIR_LABEL);
rcAllLabels.left=rcLabel.left;
if(m_bClientWidthSel && rcAllLabels.right<m_cxClient)
rcAllLabels.right=m_cxClient;
if(bSelected)
{
clrTextSave=pDC->SetTextColor(::GetSysColor(COLOR_HIGHLIGHTTEXT));
clrBkSave=pDC->SetBkColor(::GetSysColor(COLOR_HIGHLIGHT));
pDC->FillRect(rcAllLabels,&CBrush(::GetSysColor(COLOR_HIGHLIGHT)));
}
else
pDC->FillRect(rcAllLabels,&CBrush(m_clrTextBk));
// set color and mask for the icon
if(lvi.state & LVIS_CUT)
{
clrImage=m_clrBkgnd;
uiFlags|=ILD_BLEND50;
}
else if(bSelected)
{
clrImage=::GetSysColor(COLOR_HIGHLIGHT);
uiFlags|=ILD_BLEND50;
}
// draw state icon
UINT nStateImageMask=lvi.state & LVIS_STATEIMAGEMASK;
if(nStateImageMask)
{
int nImage=(nStateImageMask>>12)-1;
pImageList=GetImageList(LVSIL_STATE);
if(pImageList)
pImageList->Draw(pDC,nImage,CPoint(rcItem.left,rcItem.top),ILD_TRANSPARENT);
}
// draw normal and overlay icon
CRect rcIcon;
GetItemRect(nItem,rcIcon,LVIR_ICON);
pImageList=GetImageList(LVSIL_SMALL);
if(pImageList)
{
UINT nOvlImageMask=lvi.state & LVIS_OVERLAYMASK;
if(rcItem.left<rcItem.right-1)
ImageList_DrawEx(pImageList->m_hImageList,lvi.iImage,pDC->m_hDC,rcIcon.left,rcIcon.top,16,16,m_clrBkgnd,clrImage,uiFlags | nOvlImageMask);
}
// draw item label
GetItemRect(nItem,rcItem,LVIR_LABEL);
rcItem.right-=m_cxStateImageOffset;
pszText=MakeShortString(pDC,szBuff,rcItem.right-rcItem.left,2*OFFSET_FIRST);
rcLabel=rcItem;
rcLabel.left+=OFFSET_FIRST;
rcLabel.right-=OFFSET_FIRST;
pDC->DrawText(pszText,-1,rcLabel,DT_LEFT | DT_SINGLELINE | DT_NOPREFIX | DT_NOCLIP | DT_VCENTER);
// draw labels for extra columns
LV_COLUMN lvc;
lvc.mask=LVCF_FMT | LVCF_WIDTH;
for(int nColumn=1; GetColumn(nColumn,&lvc); nColumn++)
{
rcItem.left=rcItem.right;
rcItem.right+=lvc.cx;
int nRetLen=GetItemText(nItem,nColumn,szBuff,sizeof(szBuff));
if(nRetLen==0) continue;
pszText=MakeShortString(pDC,szBuff,rcItem.right-rcItem.left,2*OFFSET_OTHER);
UINT nJustify=DT_LEFT;
if(pszText==szBuff)
{
switch(lvc.fmt & LVCFMT_JUSTIFYMASK)
{
case LVCFMT_RIGHT:
nJustify=DT_RIGHT;
break;
case LVCFMT_CENTER:
nJustify=DT_CENTER;
break;
default:
break;
}
}
rcLabel=rcItem;
rcLabel.left+=OFFSET_OTHER;
rcLabel.right-=OFFSET_OTHER;
pDC->DrawText(pszText,-1,rcLabel,nJustify | DT_SINGLELINE | DT_NOPREFIX | DT_NOCLIP | DT_VCENTER);
}
// draw focus rectangle if item has focus
if(lvi.state & LVIS_FOCUSED && bFocus)
pDC->DrawFocusRect(rcAllLabels);
// set original colors if item was selected
if(bSelected)
{
pDC->SetTextColor(clrTextSave);
pDC->SetBkColor(clrBkSave);
}
}
LPCTSTR CListCtrlEx::MakeShortString(CDC* pDC, LPCTSTR lpszLong, int nColumnLen, int nOffset)
{
static const _TCHAR szThreeDots[]=_T("...");
int nStringLen=lstrlen(lpszLong);
if(nStringLen==0 || pDC->GetTextExtent(lpszLong,nStringLen).cx+nOffset<=nColumnLen)
return(lpszLong);
static _TCHAR szShort[MAX_PATH];
lstrcpy(szShort,lpszLong);
int nAddLen=pDC->GetTextExtent(szThreeDots,sizeof(szThreeDots)).cx;
for(int i=nStringLen-1; i>0; i--)
{
szShort[i]=0;
if(pDC->GetTextExtent(szShort,i).cx+nOffset+nAddLen<=nColumnLen)
break;
}
lstrcat(szShort,szThreeDots);
return(szShort);
}
void CListCtrlEx::RepaintSelectedItems()
{
CRect rcItem, rcLabel;
// invalidate focused item so it can repaint properly
int nItem=GetNextItem(-1,LVNI_FOCUSED);
if(nItem!=-1)
{
GetItemRect(nItem,rcItem,LVIR_BOUNDS);
GetItemRect(nItem,rcLabel,LVIR_LABEL);
rcItem.left=rcLabel.left;
InvalidateRect(rcItem,FALSE);
}
// if selected items should not be preserved, invalidate them
if(!(GetStyle() & LVS_SHOWSELALWAYS))
{
for(nItem=GetNextItem(-1,LVNI_SELECTED);
nItem!=-1; nItem=GetNextItem(nItem,LVNI_SELECTED))
{
GetItemRect(nItem,rcItem,LVIR_BOUNDS);
GetItemRect(nItem,rcLabel,LVIR_LABEL);
rcItem.left=rcLabel.left;
InvalidateRect(rcItem,FALSE);
}
}
// update changes
UpdateWindow();
}
/////////////////////////////////////////////////////////////////////////////
// CListCtrlEx diagnostics
#ifdef _DEBUG
void CListCtrlEx::Dump(CDumpContext& dc) const
{
CListCtrl::Dump(dc);
dc << "m_bFullRowSel = " << (UINT)m_bFullRowSel;
dc << "\n";
dc << "m_cxStateImageOffset = " << m_cxStateImageOffset;
dc << "\n";
}
#endif //_DEBUG
/////////////////////////////////////////////////////////////////////////////
// CListCtrlEx message handlers
LRESULT CListCtrlEx::OnSetImageList(WPARAM wParam, LPARAM lParam)
{
if((int)wParam==LVSIL_STATE)
{
int cx, cy;
if(::ImageList_GetIconSize((HIMAGELIST)lParam,&cx,&cy))
m_cxStateImageOffset=cx;
else
m_cxStateImageOffset=0;
}
return(Default());
}
LRESULT CListCtrlEx::OnSetTextColor(WPARAM wParam, LPARAM lParam)
{
m_clrText=(COLORREF)lParam;
return(Default());
}
LRESULT CListCtrlEx::OnSetTextBkColor(WPARAM wParam, LPARAM lParam)
{
m_clrTextBk=(COLORREF)lParam;
return(Default());
}
LRESULT CListCtrlEx::OnSetBkColor(WPARAM wParam, LPARAM lParam)
{
m_clrBkgnd=(COLORREF)lParam;
return(Default());
}
void CListCtrlEx::OnSize(UINT nType, int cx, int cy)
{
m_cxClient=cx;
CListCtrl::OnSize(nType, cx, cy);
}
void CListCtrlEx::OnPaint()
{
// in full row select mode, we need to extend the clipping region
// so we can paint a selection all the way to the right
if(m_bClientWidthSel && (GetStyle() & LVS_TYPEMASK)==LVS_REPORT && GetFullRowSel())
{
CRect rcAllLabels;
GetItemRect(0,rcAllLabels,LVIR_BOUNDS);
if(rcAllLabels.right<m_cxClient)
{
// need to call BeginPaint (in CPaintDC c-tor)
// to get correct clipping rect
CPaintDC dc(this);
CRect rcClip;
dc.GetClipBox(rcClip);
rcClip.left=min(rcAllLabels.right-1,rcClip.left);
rcClip.right=m_cxClient;
InvalidateRect(rcClip,FALSE);
// EndPaint will be called in CPaintDC d-tor
}
}
CListCtrl::OnPaint();
}
void CListCtrlEx::OnSetFocus(CWnd* pOldWnd)
{
CListCtrl::OnSetFocus(pOldWnd);
// check if we are getting focus from label edit box
if(pOldWnd!=NULL && pOldWnd->GetParent()==this)
return;
// repaint items that should change appearance
if(m_bFullRowSel && (GetStyle() & LVS_TYPEMASK)==LVS_REPORT)
RepaintSelectedItems();
}
void CListCtrlEx::OnKillFocus(CWnd* pNewWnd)
{
CListCtrl::OnKillFocus(pNewWnd);
// check if we are losing focus to label edit box
if(pNewWnd!=NULL && pNewWnd->GetParent()==this)
return;
// repaint items that should change appearance
if(m_bFullRowSel && (GetStyle() & LVS_TYPEMASK)==LVS_REPORT)
RepaintSelectedItems();
}
void CListCtrlEx::PreSubclassWindow()
{
CListCtrl::PreSubclassWindow();
EnableToolTips(TRUE);
}
int CListCtrlEx::CellRectFromPoint(CPoint& point, CRect* pRectCell, int* pCol) const
{
if((::GetWindowLong(m_hWnd, GWL_STYLE) & LVS_TYPEMASK) != LVS_REPORT )
return -1;
int nRow = GetTopIndex();
int nBottom = nRow + GetCountPerPage();
if(nBottom > GetItemCount())
nBottom = GetItemCount();
CHeaderCtrl* pHeader = (CHeaderCtrl*)GetDlgItem(0);
int nColumnCount = pHeader->GetItemCount();
for(; nRow <= nBottom; nRow++)
{
CRect rect;
GetItemRect(nRow, &rect, LVIR_BOUNDS);
if(rect.PtInRect(point))
{
for(int nColumn = 0; nColumn < nColumnCount; nColumn++)
{
int nColWidth = GetColumnWidth(nColumn);
if(point.x >= rect.left && point.x <= (rect.left + nColWidth))
{
CRect rectClient;
GetClientRect(&rectClient);
if(pCol)
*pCol = nColumn;
rect.right = rect.left + nColWidth;
if(rect.right > rectClient.right)
rect.right = rectClient.right;
*pRectCell = rect;
return nRow;
}
rect.left += nColWidth;
}
}
}
return -1;
}
BOOL CListCtrlEx::OnToolTipText(UINT /*uID*/, NMHDR* pNMHDR, LRESULT* pResult)
{
BOOL bRet = TRUE;
#ifdef _TOOL_TIP_ENABLE
TOOLTIPTEXTA* pTTTA = (TOOLTIPTEXTA*)pNMHDR;
TOOLTIPTEXTW* pTTTW = (TOOLTIPTEXTW*)pNMHDR;
UINT nID = pNMHDR->idFrom;
if(!nID)
bRet = FALSE;
if(bRet)
{
int nRow = ((nID-1) >> 10) & 0x3fffff ;
int nCol = (nID-1) & 0x3ff;
CString strTipText = GetItemText(nRow, nCol);
strTipText.TrimRight();
strTipText.TrimLeft();
#ifndef _UNICODE
if (pNMHDR->code == TTN_NEEDTEXTA)
lstrcpyn(pTTTA->szText, strTipText, 80);
else
_mbstowcsz(pTTTW->szText, strTipText, 80);
#else
if(pNMHDR->code == TTN_NEEDTEXTA)
_wcstombsz(pTTTA->szText, strTipText, 80);
else
lstrcpyn(pTTTW->szText, strTipText, 80);
#endif
}
*pResult = 0;
#endif
return bRet;
}
int CListCtrlEx::OnToolHitTest(CPoint point, TOOLINFO* pTI) const
{
int nID = -1;
#ifdef _TOOL_TIP_ENABLE
int nCol;
CRect rect;
int nRow = CellRectFromPoint(point, &rect, &nCol);
if(nRow == -1 )
return -1;
pTI->hwnd = m_hWnd;
pTI->uId = (UINT)((nRow << 10) + (nCol & 0x3ff) + 1);
nID = pTI->uId
pTI->lpszText = LPSTR_TEXTCALLBACK;
pTI->rect = rect;
#endif
return nID;
}