/******************************************************************************\ * file : ListCtrlEx.cpp * created: 1997.09.22 * * Zafir Anjum - Original author. * Mark Findlay - IE4 fixes. * Matthew Bells - Better TitleTips. * * description: * A super CListControl. * <P>features: * <UL> * <LI>Title Tip item expantion * <LI>full row selection<BR> * <I>Note:</I> this is also a feature in IE 4.0 with * LVS_EX_FULLROWSELECT. * <LI>notifies parent selection has changed * <LI>supports column dragging (with IE4) which changes the column order * </UL> * \******************************************************************************/ // ListCtrlEx.cpp : implementation of the CListCtrlEx class #include "stdafx.h" #include <assert.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_PAINT() ON_WM_SETFOCUS() ON_WM_KILLFOCUS() ON_WM_MOUSEMOVE() ON_WM_LBUTTONDOWN() ON_WM_KEYDOWN() ON_WM_DESTROY() ON_WM_MEASUREITEM() //}}AFX_MSG_MAP ON_MESSAGE(LVM_SETTEXTCOLOR, OnSetTextColor) ON_MESSAGE(LVM_SETTEXTBKCOLOR, OnSetTextBkColor) ON_MESSAGE(LVM_SETBKCOLOR, OnSetBkColor) ON_WM_MEASUREITEM_REFLECT( ) END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // CListCtrlEx construction/destruction CListCtrlEx::CListCtrlEx() { m_bFullRowSel = FALSE; m_bClientWidthSel = TRUE; m_clrText = ::GetSysColor(COLOR_WINDOWTEXT); m_clrTextBk = ::GetSysColor(COLOR_WINDOW); m_clrBkgnd = ::GetSysColor(COLOR_WINDOW); bShowLine = true; nFontHeight = -1; nTxtColor = 0; } CListCtrlEx::~CListCtrlEx() { } /* BOOL CListCtrlEx::OnEraseBkgnd(CDC* pDC) { CBitmap bmp; CBitmap *ptrBmpOld; CDC dcMemory; BITMAP bm; CRect rect; int i, j; int nHor, nVer; bmp.LoadBitmap("BackGround"); bmp.GetBitmap(&bm); GetClientRect(rect); nHor=rect.Width()/bm.bmWidth+1; nVer=rect.Height()/bm.bmHeight+1; dcMemory.CreateCompatibleDC(pDC); ptrBmpOld=dcMemory.SelectObject(&bmp); for(i=0; i<nHor; i++) { for(j=0; j<nVer; j++) { pDC->BitBlt ( i*bm.bmWidth, j*bm.bmHeight, bm.bmWidth, bm.bmHeight, &dcMemory, 0, 0, SRCCOPY ); } } dcMemory.SelectObject(ptrBmpOld); return true; } //*/ // Make sure the control is owner drawn 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 /* * DrawItem() is called by the framework whenever an item needs to be drawn * for owner drawn controls. * Note: * <UL> * <LI>LVS_SHOWSELALWAYS: non owner drawn controls show an item is * highlighted when the control does not have focus with a different * highlight color is (usually gray). This is not supported for * this control. * </UL> */ void CListCtrlEx::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct) { CDC* pDC = CDC::FromHandle(lpDrawItemStruct->hDC); int iSavedDC = pDC->SaveDC(); // Save DC state //* CFont *pOldFont = NULL; CFont font; if( nFontHeight != -1 ) { CFont* ptf = GetFont(); // �õ�ԭ�������� LOGFONT lf; ptf->GetLogFont(&lf); lf.lfHeight = nFontHeight; // �ı�����߶� //strcpy (lf.lfFaceName, "Arial Black"); // �ı��������� strcpy (lf.lfFaceName, "Arial"); // �ı��������� lf.lfWeight = 700; lf.lfCharSet = DEFAULT_CHARSET; //DEFAULT_CHARSET ANSI_CHARSET GB2312_CHARSET font.CreateFontIndirect(&lf); pOldFont = (CFont *)pDC->SelectObject(&font); // LOGFONT lf;//���� // lf.lfStrikeOut=0;//ɾ���� // lf.lfCharSet = DEFAULT_CHARSET ;//�ַ��� // lf.lfEscapement =0;//�Ƕ� // lf.lfItalic = 0 ;//��б // lf.lfUnderline = 0;//�»��� // lf.lfHeight = nFontHeight;//�ֺ� //strcpy( lf.lfFaceName, "����" ); // font.CreateFontIndirect(&lf); // CFont *pOldFont = (CFont *)pDC->SelectObject(&font); } //*/ int iItem = lpDrawItemStruct->itemID; // Get item image and state info LV_ITEM lvi; lvi.mask = LVIF_IMAGE | LVIF_STATE; lvi.iItem = iItem; lvi.iSubItem = 0; lvi.stateMask = 0xFFFF; // get all state flags GetItem(&lvi); bool bHighlight = ( (lvi.state & LVIS_DROPHILITED) || ((lvi.state & LVIS_SELECTED) && ((GetFocus() == this) || (GetStyle() & LVS_SHOWSELALWAYS))) ); // Get rectangles for drawing CRect rcBounds; CRect rcLabel; CRect rcIcon; GetItemRect(iItem, rcBounds, LVIR_BOUNDS); GetItemRect(iItem, rcLabel, LVIR_LABEL); GetItemRect(iItem, rcIcon, LVIR_ICON); CRect rcItem(rcBounds); CString sLabel = GetItemText(iItem, 0); // 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; rcBounds.left = rcLabel.left; CRect rcWnd; GetClientRect(&rcWnd); if(m_bClientWidthSel && rcBounds.right<rcWnd.right) rcBounds.right = rcWnd.right; // Draw the background if(bHighlight) { pDC->SetTextColor(::GetSysColor(COLOR_HIGHLIGHTTEXT)); pDC->SetBkColor(::GetSysColor(COLOR_HIGHLIGHT)); pDC->FillRect(rcBounds, &CBrush(::GetSysColor(COLOR_HIGHLIGHT))); } else { if( nTxtColor != 0 ) pDC->SetTextColor( nTxtColor ); if( bShowLine ) { if( iItem%2 ) pDC->FillRect(rcBounds, &CBrush(m_clrTextBk)); else pDC->FillRect(rcBounds, &CBrush(RGB(212,212,212))); } else { DWORD_PTR pRGB = GetItemData(iItem); COLORREF color = (COLORREF)pRGB; COLORREF white(RGB(255, 255, 255)); if(color != RGB(0, 0, 0)) pDC->FillRect(rcBounds, &CBrush(color)); else pDC->FillRect(rcBounds, &CBrush(white)); //pDC->FillRect(rcBounds, &CBrush(m_clrTextBk)); } } // Set clip region rcItem.right = rcItem.left + GetColumnWidth(0); // Draw state icon if(lvi.state & LVIS_STATEIMAGEMASK) { int nImage = ((lvi.state & LVIS_STATEIMAGEMASK)>>12) - 1; CImageList* pImageList = GetImageList(LVSIL_STATE); if(pImageList) { pImageList->Draw(pDC, nImage, CPoint(rcItem.left, rcItem.top), ILD_TRANSPARENT); } } // Draw normal and overlay icon CImageList* pImageList = GetImageList(LVSIL_SMALL); if(pImageList) { UINT nOvlImageMask = lvi.state & LVIS_OVERLAYMASK; pImageList->Draw(pDC, lvi.iImage, CPoint(rcIcon.left, rcIcon.top), (bHighlight?ILD_BLEND50:0) | ILD_TRANSPARENT | nOvlImageMask ); } // Draw item label - Column 0 rcLabel.left += offset/2-1; rcLabel.right -= offset; pDC->DrawText(sLabel,-1,rcLabel,DT_LEFT | DT_SINGLELINE | DT_NOPREFIX | DT_VCENTER | DT_END_ELLIPSIS); // Draw labels for remaining columns LV_COLUMN lvc; lvc.mask = LVCF_FMT | LVCF_WIDTH; /* // set clip region rcBounds.right = rcHighlight.right > rcBounds.right ? rcHighlight.right : rcBounds.right; rgn.CreateRectRgnIndirect(&rcBounds); pDC->SelectClipRgn(&rgn); */ for(int nColumn = 1; GetColumn(nColumn, &lvc); nColumn++) { rcItem.left = rcItem.right; rcItem.right += lvc.cx; sLabel = GetItemText(iItem, nColumn); // Get the text just ification UINT nJustify = DT_LEFT; 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; rcLabel.right -= offset; pDC->DrawText(sLabel, -1, rcLabel, nJustify | DT_SINGLELINE | DT_NOPREFIX | DT_VCENTER | DT_END_ELLIPSIS); } // draw focus rectangle if item has focus if ((lvi.state & LVIS_FOCUSED) && (GetFocus() == this)) pDC->DrawFocusRect(rcBounds); //* if( pOldFont != NULL ) pDC->SelectObject( pOldFont ); if( nFontHeight != -1 ) font.DeleteObject(); //*/ pDC->RestoreDC(iSavedDC); // Restore DC. } ///////////////////////////////////////////////////////////////////////////// // CListCtrlEx diagnostics #ifdef _DEBUG void CListCtrlEx::Dump(CDumpContext& dc) const { CListCtrl::Dump(dc); dc << "m_bFullRowSel = " << m_bFullRowSel; dc << "\n"; } #endif //_DEBUG /** * @param iRow [in] row of cell * @param iColunm [in] column of cell * @return Rectangle corresponding to the given cell. */ CRect CListCtrlEx::GetCellRect(int iRow, int iColumn)const { // Make sure that the ListView is in LVS_REPORT if((GetStyle() & LVS_TYPEMASK) != LVS_REPORT) return CRect(0,0,0,0); // Get the number of columns { CHeaderCtrl* pHeader = (CHeaderCtrl*)GetDlgItem(0); int iColumnCount = pHeader->GetItemCount(); assert(iColumn < iColumnCount); } CRect rect; GetItemRect(iRow, &rect, LVIR_BOUNDS); // Now find the column for(int colnum = 0; colnum < iColumn; colnum++) { rect.left += GetTrueColumnWidth(colnum); } // Found the column rect.right = rect.left + GetTrueColumnWidth(iColumn); RECT rectClient; GetClientRect(&rectClient); if(rect.right > rectClient.right) rect.right = rectClient.right; return rect; } /** * @author Mark Findlay */ CString CListCtrlEx::GetTrueItemText(int row, int col)const { // Get the header control CHeaderCtrl* pHeader = (CHeaderCtrl*)GetDlgItem(0); _ASSERTE(pHeader); // get the current number of columns int nCount = pHeader->GetItemCount(); // find the actual column requested. We will compare // against hi.iOrder for (int x=0; x< nCount; x++) { HD_ITEM hi = {0}; hi.mask = HDI_ORDER; BOOL bRet = pHeader->GetItem(x,&hi); _ASSERTE(bRet); if (hi.iOrder == col) { // Found it, get the associated text return GetItemText(row,x); } } _ASSERTE(FALSE); return "We better never fall through to here!"; } /** * @author Mark Findlay */ int CListCtrlEx::GetTrueColumnWidth(int nCurrentPosition)const { CHeaderCtrl* pHeader = (CHeaderCtrl*)GetDlgItem(0); _ASSERTE(pHeader); int nCount = pHeader->GetItemCount(); for (int x=0; x< nCount; x++) { HD_ITEM hi = {0}; hi.mask = HDI_WIDTH | HDI_ORDER; BOOL bRet = pHeader->GetItem(x,&hi); _ASSERTE(bRet); if (hi.iOrder == nCurrentPosition) return hi.cxy; } _ASSERTE(FALSE); return 0; // We would never fall through to here! } void CListCtrlEx::HideTitleTip() { m_titletip.ShowWindow(SW_HIDE); } /** * @param point [in] point in client coordinates * @param iRow [out] row containing the point * @param iColunm [out] column containing the point * * @author Matthew Bells */ bool CListCtrlEx::HitTestRowCol(CPoint& point, int& iRow, int& iColumn)const { // Make sure that the ListView is in LVS_REPORT if((GetStyle() & LVS_TYPEMASK) != LVS_REPORT) return false; int iPosX = point.x; iRow = HitTest(point); // Get the number of columns CHeaderCtrl* pHeader = (CHeaderCtrl*)GetDlgItem(0); int iColumnCount = pHeader->GetItemCount(); for(iColumn = 0; iColumn < iColumnCount; ++iColumn) { iPosX -= GetTrueColumnWidth(iColumn); if(iPosX < 0) break; } if(iColumn == iColumnCount) iColumn = -1; return (iRow != -1 && iColumn != -1); } void CListCtrlEx::RepaintSelectedItems() { CRect rcItem; CRect rcLabel; // Invalidate focused item so it can repaint int iItem = GetNextItem(-1, LVNI_FOCUSED); if(iItem != -1) { GetItemRect(iItem, rcItem, LVIR_BOUNDS); GetItemRect(iItem, rcLabel, LVIR_LABEL); rcItem.left = rcLabel.left; InvalidateRect(rcItem, FALSE); } // Invalidate selected items depending on LVS_SHOWSELALWAYS if(!(GetStyle() & LVS_SHOWSELALWAYS)) { for(iItem = GetNextItem(-1, LVNI_SELECTED); iItem != -1; iItem = GetNextItem(iItem, LVNI_SELECTED)) { GetItemRect(iItem, rcItem, LVIR_BOUNDS); GetItemRect(iItem, rcLabel, LVIR_LABEL); rcItem.left = rcLabel.left; InvalidateRect(rcItem, FALSE); } } UpdateWindow(); } ///////////////////////////////////////////////////////////////////////////// // CListCtrlEx message handlers void CListCtrlEx::OnDestroy() { m_titletip.DestroyWindow(); CListCtrl::OnDestroy(); } void CListCtrlEx::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) { inherited::OnChar(nChar, nRepCnt, nFlags); HideTitleTip(); SendSelChangedNotification(); } void CListCtrlEx::OnKillFocus(CWnd* pNewWnd) { CListCtrl::OnKillFocus(pNewWnd); // This should be hidden no matter if another control is getting focus // or the edit box. HideTitleTip(); // this really still has focus if one of its chilren (ie. the edit box) // has focus if(pNewWnd != NULL && pNewWnd->GetParent() == this) return; // repaint items that should change appearance if(m_bFullRowSel && (GetStyle() & LVS_TYPEMASK) == LVS_REPORT) RepaintSelectedItems(); } void CListCtrlEx::OnLButtonDown(UINT nFlags, CPoint point) { int iTest = GetKeyState(VK_LMENU); // Shortcut to editing. if((GetKeyState(VK_LMENU) & 0x8000) || (GetKeyState(VK_RMENU) & 0x8000)) { int iRow; int iColumn; if(HitTestRowCol(point, iRow, iColumn)) { SetFocus(); PostMessage(LVM_EDITLABEL, (WPARAM)iRow, 0); } } else { inherited::OnLButtonDown(nFlags, point); ShowTitleTip(point); // Make sure TitleTip changes if needed. SendSelChangedNotification(); } } void CListCtrlEx::OnMouseMove(UINT nFlags, CPoint point) { if( nFlags == 0 ) { ShowTitleTip(point); // Make sure TitleTip changes if needed. } CListCtrl::OnMouseMove(nFlags, point); } /* * When the regular list view control repaints an item, it repaints only the * area occupied by defined columns. If the last column does not extend to the * end of the client area, then the space to the right of the last column is * not repainted. If we are highlighting the full row then this area also needs * to be invalidated so that the code in DrawItem() can add or remove the * highlighting from this area. */ void CListCtrlEx::OnPaint() { /*for test ������װ��Header��ͼƬ CHeaderCtrl* pHeader = GetHeaderCtrl(); HD_ITEM Item; pHeader->GetItem( 0, &Item ); CRect ItemRect; pHeader->GetItemRect( 0, &ItemRect ); CDC *pTmpDC = pHeader->GetDC(); pTmpDC->FillRect( ItemRect, &CBrush(RGB(255,0,0))); ReleaseDC( pTmpDC ); //for test*/ // 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); CRect rcClient; GetClientRect(&rcClient); if(rcAllLabels.right < rcClient.right) { // 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 = rcClient.right; InvalidateRect(rcClip, FALSE); // EndPaint will be called in CPaintDC d-tor } } CListCtrl::OnPaint(); } LRESULT CListCtrlEx::OnSetBkColor(WPARAM wParam, LPARAM lParam) { m_clrBkgnd = (COLORREF)lParam; return(Default()); } /* * This is another step to mimic the default behaviour of the list view * control. When the control loses focus, the focus rectangle around the * selected (focus) item has to be removed. When the control gets back * focus, then the focus rectangle has to be redrawn. Both these handlers * call the RepaintSelectedItems() helper function. */ 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(); } LRESULT CListCtrlEx::OnSetTextBkColor(WPARAM wParam, LPARAM lParam) { m_clrTextBk = (COLORREF)lParam; return(Default()); } LRESULT CListCtrlEx::OnSetTextColor(WPARAM wParam, LPARAM lParam) { m_clrText = (COLORREF)lParam; return(Default()); } void CListCtrlEx::PreSubclassWindow() { CListCtrl::PreSubclassWindow(); m_titletip.Create(this); m_titletip.SetBackground(CBrush(GetBkColor())); } void CListCtrlEx::SendSelChangedNotification() { NMHDR nmh; nmh.hwndFrom = *this; nmh.idFrom = GetDlgCtrlID(); nmh.code = LVNU_SELCHANGED; GetParent()->SendMessage(WM_NOTIFY, GetDlgCtrlID(), (LPARAM)&nmh); } void CListCtrlEx::ShowTitleTip(CPoint point) { int iRow; int iCol; if(HitTestRowCol(point, iRow, iCol)) { CRect cellrect = GetCellRect(iRow, iCol); // offset is equal to TextExtent of 2 space characters. // Make sure you have the right font selected into the // device context before calling GetTextExtent. // You can save this value as a member variable. // offset = pDC->GetTextExtent(_T(" "), 1 ).cx*2; int offset = 6; /*if(iCol == 0) { CRect rcLabel; GetItemRect(iRow, &rcLabel, LVIR_LABEL ); offset = rcLabel.left - cellrect.left + offset / 2 - 1; }*/ if(iCol == 0) // TBD: test this with IE4 cellrect.left -= 2; // Does it also move the first column??? cellrect.top--; if(GetItemState(iRow, LVIS_SELECTED)) { m_titletip.SetBkColor(::GetSysColor(COLOR_HIGHLIGHT)); m_titletip.SetTextColor(::GetSysColor(COLOR_HIGHLIGHTTEXT)); } else { m_titletip.SetBkColor(m_clrTextBk); m_titletip.SetTextColor(m_clrText); } m_titletip.Show(cellrect, GetTrueItemText(iRow, iCol), offset-1); } } void CListCtrlEx::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct) { if( nFontHeight != -1 ) { lpMeasureItemStruct->itemHeight = nFontHeight + 4 ; } CListCtrl::OnMeasureItem(nIDCtl, lpMeasureItemStruct); } void CListCtrlEx::MeasureItem(LPMEASUREITEMSTRUCT lpMeasureItemStruct) { if( nFontHeight != -1 ) { lpMeasureItemStruct->itemHeight = nFontHeight + 4 ; } /* logfont lf; getfont()->getlogfont( &lf ); if( lf.lfheight <0 ) lpmeasureitemstruct->itemheight = -lf.lfheight; else lpmeasureitemstruct->itemheight = lf.lfheight; //*/ } //sendmessage( wm_windowposchanged, 0, (lparam)&wp );