首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> VC >> 關於VC++

VC++ 視窗與控件防閃爍終極指南:四大核心技巧與通用巨集

     編輯:關於VC++

MFC 開發中畫面閃爍是常見痛點,尤其在頻繁重繪、大量控件或複雜繪圖時。本文系統整理四大核心防閃技巧,並提供可直接套用的通用巨集 ERASE_BKGND_*,解決「計算空白區域」的繁瑣問題,另附 CListCtrl 拖曳防閃實戰範例。

技巧一:精確無效區域 — 用 InvalidateRect 取代 Invalidate

Invalidate() 會標記整個客戶區需重繪,耗時且易閃爍。改用 InvalidateRect(&rect, FALSE) 僅標記實際變更的矩形區域,FALSE 參數告知系統不擦除背景 (配合技巧二使用)。

技巧二:禁止系統自動擦除背景 — 覆寫 OnEraseBkgnd

系統預設在 WM_ERASEBKGND 中用背景刷子填滿客戶區,導致控件被覆蓋後再重繪產生閃爍。覆寫並直接回傳 TRUE (非 FALSE),表示「我已處理擦除,系統勿再處理」:

BOOL CMyWnd::OnEraseBkgnd(CDC* pDC) { return TRUE; // 禁止系統擦除 // return CWnd::OnEraseBkgnd(pDC); // 註解掉原始呼叫 }

隨後在 OnPaint 中自行以 dc.FillRect 或 FillRgn 只擦除必要區域。

技巧三:區域運算只擦除「空白區」— 保護控件免重繪

利用 CRgn 的 CombineRgn(RGN_XOR) 排除控件佔用區域,僅填充剩餘背景:

CRect rectClient; GetClientRect(&rectClient); CRgn rgnTotal, rgnEdit; rgnTotal.CreateRectRgnIndirect(&rectClient); rgnEdit.CreateRectRgnIndirect(&m_rectEdit); // 大型 Edit 控件區域 rgnTotal.CombineRgn(&rgnTotal, &rgnEdit, RGN_XOR); // 排除 Edit 區 CBrush brush(GetSysColor(COLOR_WINDOW)); pDC->FillRgn(&rgnTotal, &brush);

注意: 必須搭配技巧二 (禁止系統擦除) 使用,否則系統會先全域擦除一次。

技巧四:雙緩衝繪圖 — 記憶體 DC 畫好再 BitBlt 上螢幕

適用於繪圖邏輯複雜、耗時長的情況:

void CMyWnd::OnPaint() { CPaintDC dc(this); CMemDC memDC(&dc, &rectClient); // MFC 內建雙緩衝類別 (VC6 需自行實作) // 或手動建立: CDC dcMem; dcMem.CreateCompatibleDC(&dc); CBitmap bmp; bmp.CreateCompatibleBitmap(&dc, rectClient.Width(), rectClient.Height()); dcMem.SelectObject(&bmp); // ... 在 dcMem 上進行所有繪圖 ... dc.BitBlt(0, 0, rectClient.Width(), rectClient.Height(), &dcMem, 0, 0, SRCCOPY); }

通用巨集:自動計算並擦除空白區域 (貼於 OnEraseBkgnd 內)

解決「手動計算排除矩形」的麻煩,支援多控件、任意矩形:

#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 crRgn; crRgn.CreateRectRgnIndirect(&cr); \ if (bgRgn.CombineRgn(&bgRgn, &crRgn, RGN_XOR) == ERROR) return FALSE; } #define ADD_NOERASE_RECT(noEraseRect) \ { CRgn nrRgn; nrRgn.CreateRectRgnIndirect(noEraseRect); \ if (bgRgn.CombineRgn(&bgRgn, &nrRgn, RGN_XOR) == ERROR) return FALSE; } #define ERASE_BKGND_END(pDC, clrBrush) \ CBrush br; br.CreateSolidBrush(clrBrush); \ CPoint ptOrg = pDC->GetWindowOrg(); \ pDC->SetWindowOrg(bgRect.TopLeft()); \ pDC->FillRgn(&bgRgn, &br); \ pDC->SetWindowOrg(ptOrg); \ br.DeleteObject();

使用範例 1:對話框排除按鈕與列表框

BOOL CMyDlg::OnEraseBkgnd(CDC* pDC) { ERASE_BKGND_BEGIN; ADD_NOERASE_CONTROL(IDC_BUTTON1); ADD_NOERASE_CONTROL(IDC_BUTTON2); ADD_NOERASE_CONTROL(IDC_LIST_STAT); ERASE_BKGND_END(pDC, GetSysColor(COLOR_3DFACE)); return TRUE; // 關鍵:告知系統已處理 }

使用範例 2:CListCtrl 拖曳捲動防閃

BOOL CExListCtrl::OnEraseBkgnd(CDC* pDC) { // 計算有資料的項目區域 (不需擦除) CRect rect, dataRgnRect; CPoint ptTL, ptBR; GetItemPosition(0, &ptTL); GetItemPosition(GetItemCount()-1, &ptBR); if (!GetHeaderCtrl()->GetItemRect(GetHeaderCtrl()->GetItemCount()-1, &rect)) return CListCtrl::OnEraseBkgnd(pDC); ptBR.x = rect.right; dataRgnRect.SetRect(ptTL, ptBR - CPoint(2,2)); ClientToScreen(&dataRgnRect); ERASE_BKGND_BEGIN; ADD_NOERASE_RECT(dataRgnRect); // 排除資料區 ERASE_BKGND_END(pDC, GetBkColor()); return TRUE; }

原理:CListCtrl 捲動時系統會先擦背景再畫項目,空白區保留背景色。巨集確保只擦除空白區,資料區保持原樣,消除閃爍。

如何新增 WM_ERASEBKGND 處理函式

  1. ClassWizard → Message Maps → Object IDs 選擇視窗類別。
  2. Messages 選擇 WM_ERASEBKGND → Add Function → Edit Code。

綜合運用上述技巧,可徹底解決 90% 以上的 MFC 閃爍問題。針對極複雜場景,建議搭配 WS_EX_COMPOSITED (WinXP+) 或 WM_PRINTCLIENT 進階技術。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved