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

VC++ 防止視窗與控制項閃爍四大絕招:InvalidateRect、OnEraseBkgnd、CRgn 區域擦除、雙緩衝

     編輯:VC 教學

前言

頻繁重繪、大面積擦除背景、子控制項重疊是導致閃爍的主因。本文提供四層次解決方案,從簡單到徹底,並附贈巨集套件一鍵套用。

1. 精確無效區 — InvalidateRect 取代 Invalidate


// 差:整個視窗重繪
Invalidate();

// 好:僅標記需更新的矩形
CRect rc(10,10,100,100);
InvalidateRect(rc, FALSE); // FALSE=不擦除背景,由 WM_PAINT 統一處理

2. 禁止系統自動擦除背景 — OnEraseBkgnd 返回 TRUE


BOOL CMyWnd::OnEraseBkgnd(CDC* pDC)
{
    return TRUE; // 告訴系統「我自己會處理擦除」,避免白閃
}

3. 區域化擦除 — 僅擦除非控制項區域 (CRgn + FillRgn)


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;
}

4. 一鍵套用巨集套件 (ERASE_BKGND_BEGIN/END)


#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));
}

5. 雙緩衝 — 記憶體 DC 離屏繪圖再 BitBlt


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);
}

CListCtrl 拖曳閃爍專用解法


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 更新
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved