转自: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