標準 CTreeCtrl 不支援背景圖片。網上常見做法在 OnPaint 繪製背景再貼上 Tree 內容,但展開/摺疊節點時背景會劇烈閃爍。本文結合雙緩衝、WM_ERASEBKGND 抑制、TVN_ITEMEXPANDING/EXPANDED 控制重繪時機,實現無閃爍背景圖片 TreeCtrl。
OnPaint 建立記憶體 DC,先讓 Tree 自繪到記憶體位圖,再混合背景圖,最後一次 BitBlt 到螢幕。SRCAND/SRCPAINT) 將 Tree 文字/圖示與背景圖無縫合成。OnEraseBkgnd 回傳 TRUE 防止系統擦除背景觸發閃爍。TVN_ITEMEXPANDING 呼叫 SetRedraw(FALSE) 暫停重繪,TVN_ITEMEXPANDED 呼叫 SetRedraw(TRUE) + Invalidate() 一次性更新。BOOL CMyTreeCtrl::SetBKImage(LPCTSTR lpszResource)
{
if (m_bitmap.m_hObject) m_bitmap.DeleteObject();
HBITMAP hBmp = (HBITMAP)::LoadImage(AfxGetInstanceHandle(),
lpszResource, IMAGE_BITMAP, 0, 0,
LR_CREATEDIBSECTION | LR_LOADFROMFILE);
if (!hBmp) return FALSE;
m_bitmap.Attach(hBmp);
return TRUE;
}
void CMyTreeCtrl::OnPaint()
{
CPaintDC dc(this);
CRect rcClient;
GetClientRect(&rcClient);
// 第一層記憶體 DC:讓 Tree 自繪內容
CDC memDC;
memDC.CreateCompatibleDC(&dc);
CBitmap bmpMem;
bmpMem.CreateCompatibleBitmap(&dc, rcClient.Width(), rcClient.Height());
CBitmap* pOldBmpMem = memDC.SelectObject(&bmpMem);
// 關鍵:傳入記憶體 DC 讓 DefWindowProc 繪製 Tree 內容
CWnd::DefWindowProc(WM_PAINT, (WPARAM)memDC.m_hDC, 0);
// 第二層記憶體 DC:建立遮罩 (單色位圖)
CDC maskDC;
maskDC.CreateCompatibleDC(&dc);
CBitmap bmpMask;
bmpMask.CreateBitmap(rcClient.Width(), rcClient.Height(), 1, 1, NULL);
CBitmap* pOldBmpMask = maskDC.SelectObject(&bmpMask);
// 將 Tree 內容複製到遮罩 (黑白)
maskDC.BitBlt(0, 0, rcClient.Width(), rcClient.Height(),
&memDC, rcClient.left, rcClient.top, SRCCOPY);
// 3. 在螢幕 DC 繪製背景圖 (平鋪)
CBrush brush;
brush.CreatePatternBrush(&m_bitmap);
dc.FillRect(rcClient, &brush);
// 4. 混合:Tree 文字/線條 (前景) + 背景圖
memDC.SetBkColor(RGB(0,0,0));
memDC.SetTextColor(RGB(255,255,255));
memDC.BitBlt(rcClient.left, rcClient.top, rcClient.Width(), rcClient.Height(),
&maskDC, rcClient.left, rcClient.top, SRCAND);
dc.SetBkColor(RGB(255,255,255));
dc.SetTextColor(RGB(0,0,0));
dc.BitBlt(rcClient.left, rcClient.top, rcClient.Width(), rcClient.Height(),
&maskDC, rcClient.left, rcClient.top, SRCAND);
dc.BitBlt(rcClient.left, rcClient.top, rcClient.Width(), rcClient.Height(),
&memDC, rcClient.left, rcClient.top, SRCPAINT);
// 清理
memDC.SelectObject(pOldBmpMem);
maskDC.SelectObject(pOldBmpMask);
brush.DeleteObject();
}
void CMyTreeCtrl::OnItemexpanding(NMHDR* pNMHDR, LRESULT* pResult)
{
SetRedraw(FALSE); // 暫停重繪,避免展開動畫中背景閃爍
*pResult = 0;
}
void CMyTreeCtrl::OnItemexpanded(NMHDR* pNMHDR, LRESULT* pResult)
{
Invalidate(); // 觸發一次完整重繪 (含背景混合)
SetRedraw(TRUE); // 恢復重繪
*pResult = 0;
}
BOOL CMyTreeCtrl::OnEraseBkgnd(CDC* pDC)
{
return TRUE; // 我們在 OnPaint 手繪背景,不需系統擦除
}
1. Tree 內容 → memDC (彩色)
2. memDC → maskDC (單色遮罩:文字/線條=白,背景=黑)
3. 螢幕 DC:
a) FillRect 背景圖
b) memDC SRCAND maskDC → 保留文字/線條區域 (黑變透明)
c) dc SRCAND maskDC → 背景圖保留非文字區域 (白變透明)
d) dc SRCPAINT memDC → 疊加文字/線條
結果:背景圖完整顯示,Tree 文字/線條清晰疊加,無閃爍。
OnPaint 混合開銷可觀,可考慮快取合成結果或改用 Owner Draw。完整原始碼 (VC6.0/VC.NET 測試通過) 可下載。