MFC CListCtrl 僅支援標籤(第 0 欄)編輯(需設定 LVS_EDITLABELS),子項無法直接編輯。本文實作一個 CEditListCtrl 擴充類別,採用「單擊高亮、再單擊編輯」的直覺操作模式,完整支援主項與子項的即時編輯,並解決編輯框定位、焦點管理、重繪閃爍等細節問題。
int m_iItem; // 目前高亮/編輯的主項索引
int m_iSubItem; // 目前高亮/編輯的子項索引
BOOL m_bFocus; // 是否繪製焦點框
BOOL m_bHighLight; // 是否高亮顯示
CItemEdit m_edtItemEdit; // 子類化編輯框控制類別
void CEditListCtrl::OnCustomDraw(NMHDR* pNMHDR, LRESULT* pResult)
{
NMLVCUSTOMDRAW* pLVCD = (NMLVCUSTOMDRAW*)pNMHDR;
*pResult = CDRF_DODEFAULT;
switch (pLVCD->nmcd.dwDrawStage) {
case CDDS_PREPAINT:
*pResult = CDRF_NOTIFYITEMDRAW; break;
case CDDS_ITEMPREPAINT:
*pResult = CDRF_NOTIFYSUBITEMDRAW; break;
case CDDS_ITEMPREPAINT | CDDS_SUBITEM: {
int iItem = (int)pLVCD->nmcd.dwItemSpec;
int iSubItem = pLVCD->iSubItem;
CDC* pDC = CDC::FromHandle(pLVCD->nmcd.hdc);
CString strText = GetItemText(iItem, iSubItem);
CRect rcItem, rcText;
GetSubItemRect(iItem, iSubItem, LVIR_LABEL, rcItem);
rcText = rcItem;
rcText.left += 4;
rcText.right = min(rcText.left + pDC->GetTextExtent(strText).cx + 6, rcItem.right);
COLORREF crOld = pDC->GetTextColor();
if (m_bFocus && m_iItem == iItem && m_iSubItem == iSubItem) {
if (m_bHighLight) {
pDC->SetTextColor(::GetSysColor(COLOR_HIGHLIGHTTEXT));
pDC->FillSolidRect(rcText, ::GetSysColor(COLOR_HIGHLIGHT));
}
pDC->DrawFocusRect(rcText);
}
rcItem.left += 6;
pDC->DrawText(strText, rcItem, DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS | DT_NOCLIP);
pDC->SetTextColor(crOld);
*pResult = CDRF_SKIPDEFAULT;
} break;
}
}
void CEditListCtrl::OnLButtonDown(UINT nFlags, CPoint point)
{
m_bFocus = TRUE;
LVHITTESTINFO hit; hit.pt = point;
int item = SubItemHitTest(&hit);
if (item != -1 && (hit.flags & LVHT_ONITEM)) {
CListCtrl::OnLButtonDown(nFlags, point);
if (m_bHighLight && m_iItem == hit.iItem && m_iSubItem == hit.iSubItem) {
// 第二次點擊:進入編輯
EditLabel(m_iItem);
return;
} else {
// 第一次點擊:高亮
m_iItem = hit.iItem;
m_iSubItem = hit.iSubItem;
m_bHighLight = TRUE;
}
} else {
if (!m_edtItemEdit.m_hWnd) m_bHighLight = FALSE;
CListCtrl::OnLButtonDown(nFlags, point);
}
Invalidate(); // 強制重繪
}
void CEditListCtrl::OnBeginlabeledit(NMHDR* pNMHDR, LRESULT* pResult)
{
LV_DISPINFO* pInfo = (LV_DISPINFO*)pNMHDR;
if (m_iSubItem >= 0) {
ASSERT(m_iItem == pInfo->item.iItem);
CRect rcSub; GetSubItemRect(pInfo->item.iItem, m_iSubItem, LVIR_BOUNDS, rcSub);
HWND hEdit = (HWND)SendMessage(LVM_GETEDITCONTROL);
VERIFY(m_edtItemEdit.SubclassWindow(hEdit));
m_edtItemEdit.m_iXPos = rcSub.left + 4; // 文字右移 4 像素對齊
m_edtItemEdit.SetWindowText(GetItemText(pInfo->item.iItem, m_iSubItem));
}
*pResult = 0;
}
void CEditListCtrl::OnEndlabeledit(NMHDR* pNMHDR, LRESULT* pResult)
{
LV_DISPINFO* pInfo = (LV_DISPINFO*)pNMHDR;
LV_ITEM* pItem = &pInfo->item;
if (m_iSubItem >= 0 && pItem->pszText) {
SetItemText(pItem->iItem, m_iSubItem, pItem->pszText);
}
VERIFY(m_edtItemEdit.UnsubclassWindow());
// 點擊控制項外部時重置狀態
CRect rc; GetWindowRect(&rc);
CPoint pt; GetCursorPos(&pt);
if (!rc.PtInRect(pt)) {
m_iItem = m_iSubItem = -1;
m_bFocus = m_bHighLight = FALSE;
}
*pResult = 0;
}
為完善體驗,尚需處理:
WM_PAINT:確保編輯狀態下正確重繪WM_SETFOCUS / WM_KILLFOCUS:管理焦點狀態旗標WM_KEYDOWN (Esc/Enter):鍵盤取消/確認編輯完整原始碼請參考附件專案。
CEditListCtrl 以 自訂繪製 + 滑鼠狀態機 + 編輯框子類化 三大支柱,實現符合直覺的子項編輯體驗。核心技巧在於:
NM_CUSTOMDRAW 繪製高亮/焦點,避免系統預設選取樣式衝突LVN_BEGINLABELEDIT 重新定位編輯框至正確子項矩形CEdit 控制編輯框行為(如 m_iXPos 調整文字邊距)此類別可直接替換專案中現有 CListCtrl,無需修改資源與介面設計。