淘先锋技术网

首页 1 2 3 4 5 6 7


转自:http://www.vckbase.com/index.php/wv/1548


  • 文章概要:
  • 本文对CListCtrl控件进行了一个扩展,使它即可以编辑主项(Item),又可以编辑子项(SubItem),并尽量符合CListCtrl的操作习惯。
  • 一、说明

    大家都知道在MFC中通过给CListCtrl设置LVS_EDITLABELS属性,并且在程序中响应控件的LVN_ENDLABELEDIT消息可以修改列表控件每一行的第一项,也就是主项(Item)。代码如下:

    1. void CEditListCtrlSampleDlg::OnEndlabeleditList1(NMHDR* pNMHDR, LRESULT* pResult)
    2. {
    3. LV_DISPINFO* pDispInfo = (LV_DISPINFO*)pNMHDR;
    4. // TODO: Add your control notification handler code here
    5.  
    6. *pResult = TRUE;   //TRUE值表示可以修改主项,FALSE值表示不修改主项
    7. }

    但是让人郁闷的是,微软留了一手,CListCtrl不支持直接修改子项(SubItem)。无奈之下只好自力更生,对CListCtrl进行扩展。>_

    二、原理

    通过在浩如烟海的互联网上查找资料(当然包括了大名鼎鼎的VCKBASE),发现现有的实现大都是对子项鼠标单击一次就可以编辑。但本人对CListCtrl的单击一次高亮文本,再单击一次才开始编辑的操作模式感觉比较喜欢,所以就有了这篇文章的诞生。
    要想实现高亮文本也就是对文本进行着色处理,这可以通过对NM_CUSTOMDRAW消息进行处理实现,但是类向导中没有这个消息映射只能进行手工添加。

    要想编辑文本则可以通过EditLabel(int nItem)成员函数以及对LVN_BEGINLABELEDIT和LVN_ENDLABELEDIT的消息处理实现。

    三、实现

    本文最终实现的CEditListCtrl扩展类在尽量符合CListCtrl操作步骤的情况下实现对主项及子项的可编辑。

    成员变量说明:

    1. int m_iItem; //主项标识符
    2. int m_iSubItem; //子项标识符
    3. BOOL m_bFocus; //是否绘制项文本焦点框
    4. BOOL m_bHighLight; //是否高亮项文本
    5. CItemEdit m_edtItemEdit; //用于子类化EditLabel函数返回的CEdit*指针

    列表控件中所有项文本的绘制以及特效(焦点框、高亮)都在NM_CUSTOMDRAW消息处理中实现:

    01. void CEditListCtrl::OnCustomDraw(NMHDR* pNMHDR, LRESULT* pResult)
    02. {
    03. NMLVCUSTOMDRAW* pNMLVCustomDraw = (NMLVCUSTOMDRAW*)pNMHDR;
    04.  
    05. // Take the default processing unless we set this to something else below.
    06. *pResult = CDRF_DODEFAULT;
    07.  
    08. // First thing - check the draw stage. If it's the control's prepaint
    09. // stage, then tell Windows we want messages for every item.
    10.  
    11. if (pNMLVCustomDraw->nmcd.dwDrawStage == CDDS_PREPAINT)
    12. {
    13. *pResult = CDRF_NOTIFYITEMDRAW;
    14. }
    15. else if (pNMLVCustomDraw->nmcd.dwDrawStage == CDDS_ITEMPREPAINT)
    16. {
    17. // This is the notification message for an item.  We'll request
    18. // notifications before each subitem's prepaint stage.
    19. *pResult = CDRF_NOTIFYSUBITEMDRAW;
    20. }
    21. else if (pNMLVCustomDraw->nmcd.dwDrawStage == (CDDS_ITEMPREPAINT | CDDS_SUBITEM))
    22. {
    23. //当前要绘制的主项标识符和子项标识符
    24. int iItem = (int)pNMLVCustomDraw->nmcd.dwItemSpec;
    25. int iSubItem = pNMLVCustomDraw->iSubItem;
    26.  
    27. CDC* pDC = CDC::FromHandle(pNMLVCustomDraw->nmcd.hdc);
    28.  
    29. CString strItemText = GetItemText(iItem, iSubItem);
    30. CRect rcItem, rcText;
    31. GetSubItemRect(iItem, iSubItem, LVIR_LABEL, rcItem);
    32. rcText = rcItem;
    33.  
    34. CSize size = pDC->GetTextExtent(strItemText);
    35. if(strItemText == _T(""))
    36. {
    37. size.cx = 41;
    38. }
    39.  
    40. //设置文本高亮矩形
    41. rcText.left += 4;
    42. rcText.right = rcText.left + size.cx + 6;
    43. if(rcText.right > rcItem.right)
    44. {
    45. rcText.right = rcItem.right;
    46. }
    47.  
    48. COLORREF crOldTextColor = pDC->GetTextColor();
    49.  
    50. //绘制项焦点/高亮效果
    51. if(m_bFocus)
    52. {
    53. if((m_iItem == iItem) && (m_iSubItem == iSubItem))
    54. {  
    55. if(m_bHighLight)
    56. {                  
    57. pDC->SetTextColor(::GetSysColor(COLOR_HIGHLIGHTTEXT));
    58. pDC->FillSolidRect(&rcText, ::GetSysColor(COLOR_HIGHLIGHT));
    59. }
    60. pDC->DrawFocusRect(&rcText);
    61. }      
    62. }
    63.  
    64. //绘制项文本
    65. rcItem.left += 6;
    66. pDC->DrawText(strItemText, &rcItem, DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS | DT_NOCLIP);
    67.  
    68. pDC->SetTextColor(crOldTextColor);
    69. *pResult = CDRF_SKIPDEFAULT;// We've painted everything.
    70. }  
    71. }

    单击一次文本高亮,再单击一次文本开始编辑在WM_LBUTTONDOWN消息处理中实现:

    01. void CEditListCtrl::OnLButtonDown(UINT nFlags, CPoint point)
    02. {
    03. m_bFocus = TRUE;
    04. LVHITTESTINFO  lvhit;
    05. lvhit.pt = point;
    06. int item = SubItemHitTest(&lvhit);
    07.  
    08. //if (over a item/subitem)
    09. if (item != -1 && (lvhit.flags & LVHT_ONITEM))
    10. {
    11. CListCtrl::OnLButtonDown(nFlags, point);
    12.  
    13. if(m_bHighLight && m_iItem == lvhit.iItem && m_iSubItem == lvhit.iSubItem)
    14. {
    15. //第二次单击
    16. EditLabel(m_iItem);
    17. return;
    18. }
    19. else
    20. {
    21. //第一次单击
    22. m_iItem = lvhit.iItem;
    23. m_iSubItem = lvhit.iSubItem;
    24. m_bHighLight = TRUE;
    25. }
    26. }
    27. else
    28. {
    29. if(m_edtItemEdit.m_hWnd == NULL)
    30. {
    31. //未出现文本编辑框时
    32. m_bHighLight = FALSE;
    33. }
    34.  
    35. CListCtrl::OnLButtonDown(nFlags, point);
    36. }
    37.  
    38. Invalidate(); //强制重绘控件
    39. }

    关键的一步,对项文本进行编辑。在以上代码中当执行到EditLabel时将会产生一个编辑框,这时需要将它进行子类化处理,以控制它出现的位置。

    01. void CEditListCtrl::OnBeginlabeledit(NMHDR* pNMHDR, LRESULT* pResult)
    02. {
    03. LV_DISPINFO* pDispInfo = (LV_DISPINFO*)pNMHDR;
    04.  
    05. if (m_iSubItem >= 0)
    06. {
    07. ASSERT(m_iItem == pDispInfo->item.iItem);
    08. CRect  rcSubItem;
    09. GetSubItemRect( pDispInfo->item.iItem, m_iSubItem, LVIR_BOUNDS, rcSubItem);
    10.  
    11. //get edit control and subclass
    12. HWND hWnd= (HWND)SendMessage(LVM_GETEDITCONTROL);
    13. ASSERT(hWnd != NULL);
    14. VERIFY(m_edtItemEdit.SubclassWindow(hWnd));
    15.  
    16. //move edit control text 4 pixel to the right of org label,
    17. //as Windows does it...编辑框定位
    18. m_edtItemEdit.m_iXPos = rcSubItem.left + 4;
    19. m_edtItemEdit.SetWindowText(GetItemText(pDispInfo->item.iItem, m_iSubItem));
    20. }
    21.  
    22. *pResult = 0;
    23. }
    24.  
    25. void CEditListCtrl::OnEndlabeledit(NMHDR* pNMHDR, LRESULT* pResult)
    26. {
    27. LV_DISPINFO* pDispInfo = (LV_DISPINFO*)pNMHDR;
    28. LV_ITEM *plvItem = &pDispInfo->item;
    29.  
    30. if (m_iSubItem >= 0)
    31. {
    32. if (plvItem->pszText != NULL )
    33. {
    34. SetItemText(plvItem->iItem,m_iSubItem, plvItem->pszText);
    35. }
    36.  
    37. VERIFY(m_edtItemEdit.UnsubclassWindow()!=NULL);
    38. *pResult = 0;
    39. }
    40.  
    41. //编辑文本时对控件父窗口操作(如单击其它控件)引发"OnEndlabeledit"时刷新控件
    42. CRect rect;
    43. GetWindowRect(&rect);
    44. CPoint point;
    45. ::GetCursorPos(&point);
    46. if(!rect.PtInRect(point))
    47. {
    48. m_iItem = -1;
    49. m_iSubItem = -1;
    50. m_bFocus = FALSE;
    51. m_bHighLight = FALSE;
    52. }
    53. }

    通过以上三个步骤大体实现了本文要达到的目的,但是还不能放松。接下来还要进行一些显示细节方面的处理。这包括对WM_PAINT、WM_SETFOCUS和WM_KILLFOCUS消息的处理,限于篇幅,就不进行细讲了,有兴趣的朋友可以查看本文提供的源代码。最后实现的效果如下图所示:

    四、参考资料

    1. Simplified Subitem Editing

    http://www.codeguru.com/cpp/controls/listview/editingitemsandsubitem/article.php/c4175/

    2. 可设置单元格颜色的ClistCtrl类

    http://www.vckbase.com/document/viewdoc/?id=891