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

VC++ 雙緩衝繪圖消除螢幕閃爍:記憶體 DC 完整實作指南

     編輯:關於VC++

前言

在 VC++ 進行繪圖時,直接在 OnPaint 或 OnDraw 中繪製複雜圖形到螢幕 DC(CPaintDC 或 CClientDC),常會因繪製順序、覆蓋區域重疊導致明顯閃爍。解決方案是採用雙緩衝(Double Buffering):先在記憶體裝置內容(Memory DC)完成所有繪圖,再一次 BitBlt 到螢幕,因記憶體複製極快,使用者感知不到中間過程,即可消除閃爍。

核心原理

  1. 建立一個與螢幕 DC 相容的記憶體 DC(CDC::CreateCompatibleDC)。
  2. 建立一個與螢幕 DC 相容的位圖(CBitmap::CreateCompatibleBitmap),尺寸等於客戶區大小。
  3. 將位圖選入記憶體 DC(SelectObject),此後所有繪圖操作導向該位圖。
  4. 在記憶體 DC 上完成所有繪圖(文字、圖形、圖片等)。
  5. 呼叫 BitBlt 或 StretchBlt 將記憶體位圖一次複製到螢幕 DC。
  6. 清理:將原位圖選回記憶體 DC,刪除記憶體 DC 與位圖物件。

完整程式碼範例 (MFC)

void CMyView::OnDraw(CDC* pDC)
{
    CRect rect;
    GetClientRect(&rect);
    if (rect.IsRectEmpty()) return;

    // 1. 建立記憶體 DC
    CDC memDC;
    if (!memDC.CreateCompatibleDC(pDC)) return;

    // 2. 建立相容位圖
    CBitmap bitmap;
    if (!bitmap.CreateCompatibleBitmap(pDC, rect.Width(), rect.Height())) return;

    // 3. 選入位圖並保存舊位圖
    CBitmap* pOldBitmap = memDC.SelectObject(&bitmap);

    // 4. 填滿背景(避免殘留)
    memDC.FillSolidRect(&rect, pDC->GetBkColor()); // 或 GetSysColor(COLOR_WINDOW)

    // ===== 在此進行所有繪圖操作,針對 memDC =====
    // 範例:畫矩形、文字、載入圖片等
    memDC.Rectangle(10, 10, 100, 100);
    memDC.TextOut(20, 20, _T("Hello Double Buffering!"));
    // ...

    // 5. 一次複製到螢幕
    pDC->BitBlt(0, 0, rect.Width(), rect.Height(), &memDC, 0, 0, SRCCOPY);

    // 6. 還原與清理(選回舊位圖後,bitmap 與 memDC 解構時會自動釋放)
    memDC.SelectObject(pOldBitmap);
    // bitmap 與 memDC 離開作用域自動 DeleteObject/DeleteDC
}

進階優化:成員變數快取 DC 與位圖

若繪圖頻率高(如遊戲、即時繪圖),可在視窗建立時一次配置,避免重複建立/銷毀開銷:

class CMyView : public CView
{
    CDC m_memDC;
    CBitmap m_bitmap;
    CBitmap* m_pOldBitmap;
    CRect m_clientRect;

    void InitMemDC(CDC* pDC)
    {
        GetClientRect(&m_clientRect);
        m_memDC.CreateCompatibleDC(pDC);
        m_bitmap.CreateCompatibleBitmap(pDC, m_clientRect.Width(), m_clientRect.Height());
        m_pOldBitmap = m_memDC.SelectObject(&m_bitmap);
    }

    void OnSize(UINT nType, int cx, int cy)
    {
        CView::OnSize(nType, cx, cy);
        // 視窗大小改變時重建位圖
        m_memDC.SelectObject(m_pOldBitmap);
        m_bitmap.DeleteObject();
        m_memDC.DeleteDC();
        CDC* pDC = GetDC();
        InitMemDC(pDC);
        ReleaseDC(pDC);
    }

    void OnDraw(CDC* pDC)
    {
        // 直接使用 m_memDC 繪圖
        m_memDC.FillSolidRect(m_clientRect, RGB(255,255,255));
        // ... 繪圖 ...
        pDC->BitBlt(0, 0, m_clientRect.Width(), m_clientRect.Height(), &m_memDC, 0, 0, SRCCOPY);
    }

    ~CMyView()
    {
        m_memDC.SelectObject(m_pOldBitmap);
    }
};

常見錯誤與注意事項

  • 忘記選回舊位圖:導致記憶體 DC 持有位圖,解構時無法正確釋放 GDI 物件,造成資源洩漏。
  • 位圖尺寸與客戶區不符:OnSize 時必須重建位圖。
  • 背景未清除:記憶體位圖預設未初始化,每幀必須 FillSolidRect 或 PatBlt 清背景。
  • 列印/預覽 DC 不支援 CreateCompatibleBitmap:列印時應直接在傳入的 pDC 繪圖,或使用 CreateCompatibleDC(NULL) 建立螢幕相容 DC 再 StretchBlt。
  • GDI+ 混用:若使用 GDI+,需建立 Graphics 物件指向記憶體 DC 的 GetSafeHdc(),注意鎖定/解鎖。

替代方案:WM_ERASEBKGND 與 WS_EX_COMPOSITED

  • 回傳 TRUE 於 OnEraseBkgnd 禁止背景擦除,配合雙緩衝可進一步減少閃爍。
  • Windows XP+ 可在視窗類別加入 WS_EX_COMPOSITED 擴充樣式,系統自動提供視窗級雙緩衝(子視窗也需支援)。
  • 現代專案建議直接使用 GDI+ 或 Direct2D,原生支援雙緩衝與硬體加速。

總結

雙緩衝是消除 GDI 繪圖閃爍的標準技術。掌握 CreateCompatibleDC、CreateCompatibleBitmap、SelectObject、BitBlt 流程,並妥善管理 GDI 物件生命週期,即可實現流暢無閃爍的繪圖體驗。

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