標準 `CListCtrl` 僅支援整行選取色、交替行色,無法針對單一儲存格設定前景/背景色。利用 `NM_CUSTOMDRAW` 通知訊息,在 `CDDS_ITEMPREPAINT | CDDS_SUBITEM` 階段攔截繪製流程,即可實現任意儲存格級別的顏色自訂。本文提供 `CColorListCtrl` 類別,封裝 `SetItemColor(nRow, nCol, clrText, clrBkgnd)` 介面,內部以 `map
class CColorListCtrl : public CListCtrl
{
DECLARE_DYNAMIC(CColorListCtrl)
public:
CColorListCtrl();
virtual ~CColorListCtrl();
// 設定單元格顏色:clrText=-1 表不變更前景,clrBkgnd=-1 表不變更背景
void SetItemColor(int nItem, int nSubItem, COLORREF clrText, COLORREF clrBkgnd);
void RemoveItemColor(int nItem, int nSubItem); // 恢復預設
void ClearAllColors(); // 清除所有自訂顏色
protected:
struct COLORPAIR { COLORREF clrText; COLORREF clrBkgnd; };
CMap m_mapColors; // 鍵 = MAKELONG(nItem, nSubItem)
afx_msg void OnCustomDraw(NMHDR* pNMHDR, LRESULT* pResult);
DECLARE_MESSAGE_MAP()
};
BEGIN_MESSAGE_MAP(CColorListCtrl, CListCtrl)
ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, OnCustomDraw)
END_MESSAGE_MAP()
void CColorListCtrl::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 nItem = (int)pLVCD->nmcd.dwItemSpec;
int nSubItem = pLVCD->iSubItem;
DWORD dwKey = MAKELONG(nItem, nSubItem);
COLORPAIR cp;
if (m_mapColors.Lookup(dwKey, cp))
{
// 設定文字色
if (cp.clrText != (COLORREF)-1)
pLVCD->clrText = cp.clrText;
// 設定背景色
if (cp.clrBkgnd != (COLORREF)-1)
pLVCD->clrTextBk = cp.clrBkgnd;
}
*pResult = CDRF_DODEFAULT; // 繼續預設繪製流程(已套用顏色)
break;
}
}
}
void CColorListCtrl::SetItemColor(int nItem, int nSubItem, COLORREF clrText, COLORREF clrBkgnd)
{
if (nItem < 0 || nSubItem < 0) return;
DWORD dwKey = MAKELONG(nItem, nSubItem);
COLORPAIR cp = {clrText, clrBkgnd};
m_mapColors[dwKey] = cp;
// 觸發重繪該項目
CRect rc; GetSubItemRect(nItem, nSubItem, LVIR_BOUNDS, rc);
InvalidateRect(&rc);
}
void CColorListCtrl::RemoveItemColor(int nItem, int nSubItem)
{
DWORD dwKey = MAKELONG(nItem, nSubItem);
m_mapColors.RemoveKey(dwKey);
CRect rc; GetSubItemRect(nItem, nSubItem, LVIR_BOUNDS, rc);
InvalidateRect(&rc);
}
void CColorListCtrl::ClearAllColors()
{
m_mapColors.RemoveAll();
Invalidate();
}
void CMyDialog::OnInitList()
{
m_list.SetExtendedStyle(LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES | LVS_EX_DOUBLEBUFFER);
m_list.InsertColumn(0, _T("ID"), LVCFMT_LEFT, 60);
m_list.InsertColumn(1, _T("姓名"), LVCFMT_LEFT, 120);
m_list.InsertColumn(2, _T("生日"), LVCFMT_LEFT, 100);
m_list.InsertItem(0, _T("1")); m_list.SetItemText(0, 1, _T("張三")); m_list.SetItemText(0, 2, _T("1990-01-15"));
m_list.InsertItem(1, _T("2")); m_list.SetItemText(1, 1, _T("李四")); m_list.SetItemText(1, 2, _T("1985-07-22"));
m_list.InsertItem(2, _T("3")); m_list.SetItemText(2, 1, _T("王五")); m_list.SetItemText(2, 2, _T("1992-11-03"));
// 設定單元格顏色:第 0 列第 1 欄(姓名)紅字綠底
m_list.SetItemColor(0, 1, RGB(255,0,0), RGB(0,255,0));
// 第 1 列第 2 欄(生日)藍字黃底
m_list.SetItemColor(1, 2, RGB(0,0,255), RGB(255,255,0));
// 第 2 列整行淺灰背景(遍歷所有欄)
for (int c = 0; c < 3; ++c) m_list.SetItemColor(2, c, (COLORREF)-1, RGB(230,230,230));
}

報表模式下,指定儲存格呈現自訂前景/背景色,整行選取時自訂色優先級高於選取高亮(可依需求在 `CDDS_ITEMPREPAINT` 階段處理選取狀態)。
若啟用 `LVS_EX_FULLROWSELECT`,選取高亮會覆蓋子項目背景色。解決方案:在 `CDDS_ITEMPREPAINT` 階段檢查 `pLVCD->nmcd.uItemState & CDIS_SELECTED`,若為選取狀態且該列有自訂色,手動繪製背景(`CDC::FillSolidRect`)並返回 `CDRF_SKIPDEFAULT`,但需自行繪製文字、圖示、狀態圖示,複雜度較高。替代方案:不使用 FULLROWSELECT,改用 `LVS_EX_ONECLICKACTIVATE` + `LVS_EX_UNDERLINEHOT` 模擬超連結風格,或接受子項目背景色在選取時被覆蓋(僅文字色保留)。
`NM_CUSTOMDRAW` 是 `CListCtrl` 視覺客製化的核心入口。`CColorListCtrl` 以 `CMap` 儲存稀疏顏色資料、在 `CDDS_SUBITEM` 階段套用 `clrText/clrTextBk`,實現零侵入、高效能的單元格著色。此模式同樣適用於 `CTreeCtrl`、`CHeaderCtrl` 等支援 CustomDraw 的通用控制項,是 MFC 開發者掌握進階介面定製的必修課。