頻繁重繪、大面積擦除背景、子控制項重疊是導致閃爍的主因。本文提供四層次解決方案,從簡單到徹底,並附贈巨集套件一鍵套用。
// 差:整個視窗重繪
Invalidate();
// 好:僅標記需更新的矩形
CRect rc(10,10,100,100);
InvalidateRect(rc, FALSE); // FALSE=不擦除背景,由 WM_PAINT 統一處理
BOOL CMyWnd::OnEraseBkgnd(CDC* pDC)
{
return TRUE; // 告訴系統「我自己會處理擦除」,避免白閃
}
BOOL CMyWnd::OnEraseBkgnd(CDC* pDC)
{
CRect rcClient; GetClientRect(rcClient);
CRgn rgnAll; rgnAll.CreateRectRgnIndirect(rcClient);
// 排除各子控制項佔據區域
CRect rcCtrl; CRgn rgnCtrl;
GetDlgItem(IDC_EDIT1)->GetWindowRect(rcCtrl); ScreenToClient(rcCtrl);
rgnCtrl.CreateRectRgnIndirect(rcCtrl);
rgnAll.CombineRgn(&rgnAll, &rgnCtrl, RGN_DIFF); // 差集
GetDlgItem(IDC_LIST1)->GetWindowRect(rcCtrl); ScreenToClient(rcCtrl);
rgnCtrl.CreateRectRgnIndirect(rcCtrl);
rgnAll.CombineRgn(&rgnAll, &rgnCtrl, RGN_DIFF);
// 填充背景色
CBrush brush(GetSysColor(COLOR_3DFACE));
pDC->FillRgn(&rgnAll, &brush);
return TRUE;
}
#define ERASE_BKGND_BEGIN \
CRect bgRect; GetWindowRect(&bgRect); \
CRgn bgRgn; bgRgn.CreateRectRgnIndirect(bgRect);
#define ADD_NOERASE_CONTROL(IDC) \
{ CRect cr; GetDlgItem(IDC)->GetWindowRect(&cr); \
CRgn rr; rr.CreateRectRgnIndirect(cr); \
if (bgRgn.CombineRgn(&bgRgn, &rr, RGN_DIFF) == ERROR) return FALSE; }
#define ADD_NOERASE_RECT(rect) \
{ CRgn rr; rr.CreateRectRgnIndirect(rect); \
if (bgRgn.CombineRgn(&bgRgn, &rr, RGN_DIFF) == ERROR) return FALSE; }
#define ERASE_BKGND_END(pDC, color) \
CBrush br; br.CreateSolidBrush(color); \
CPoint org = pDC->GetWindowOrg(); \
pDC->SetWindowOrg(bgRect.TopLeft()); \
pDC->FillRgn(&bgRgn, &br); \
pDC->SetWindowOrg(org); \
br.DeleteObject(); \
return TRUE;
// 使用範例
BOOL CMyDlg::OnEraseBkgnd(CDC* pDC)
{
ERASE_BKGND_BEGIN;
ADD_NOERASE_CONTROL(IDC_EDIT1);
ADD_NOERASE_CONTROL(IDC_LIST1);
ADD_NOERASE_CONTROL(IDC_BUTTON_OK);
ERASE_BKGND_END(pDC, GetSysColor(COLOR_3DFACE));
}
void CMyView::OnPaint()
{
CPaintDC dc(this);
CRect rc; GetClientRect(rc);
CDC memDC; memDC.CreateCompatibleDC(&dc);
CBitmap bmp; bmp.CreateCompatibleBitmap(&dc, rc.Width(), rc.Height());
CBitmap* pOld = memDC.SelectObject(&bmp);
// 所有繪圖操作導向 memDC
memDC.FillSolidRect(rc, RGB(255,255,255));
// ... 複雜繪圖 ...
dc.BitBlt(0,0,rc.Width(),rc.Height(), &memDC,0,0,SRCCOPY);
memDC.SelectObject(pOld);
}
BOOL CMyListCtrl::OnEraseBkgnd(CDC* pDC)
{
// 計算有資料的項目區域
CRect dataRect;
CPoint ptTopLeft, ptBottomRight;
GetItemPosition(0, &ptTopLeft);
GetItemPosition(GetItemCount()-1, &ptBottomRight);
CRect headerRect;
GetHeaderCtrl()->GetItemRect(GetHeaderCtrl()->GetItemCount()-1, headerRect);
ptBottomRight.x = headerRect.right;
dataRect.SetRect(ptTopLeft, ptBottomRight - CPoint(2,2));
ClientToScreen(dataRect);
ERASE_BKGND_BEGIN;
ADD_NOERASE_RECT(dataRect); // 保護資料區不被擦除
ERASE_BKGND_END(pDC, GetBkColor());
}
| 情境 | 推薦方案 |
|---|---|
| 簡單對話框、少量控制項 | OnEraseBkgnd 返回 TRUE + 區域化擦除巨集 |
| 大量自繪控制項、複雜佈局 | 雙緩衝 (OnPaint + MemDC) |
| CListCtrl/TreeCtrl 拖曳閃爍 | 區域化擦除保護資料區 |
| 即時動畫、遊戲迴圈 | 雙緩衝 + 高效率 Dirty Rect 更新 |