GDI+ 原生支援 JPEG/PNG 等格式,但在 VC6 專案中直接於 OnDraw 讀取檔案繪圖,拖曳捲軸時會頻繁觸發重繪導致嚴重閃爍與效能低落。本文分享雙重緩衝實作技巧:「資料讀取與繪圖分離,離屏繪圖一次完成,再貼回螢幕」,並展示關鍵程式碼片段。
// 錯誤寫法:每次 OnDraw 都讀檔、解碼、繪圖
void CMyView::OnDraw(CDC* pDC)
{
CString path = m_pDoc->GetImagePath();
Image img(path); // 重複解碼 JPEG
Graphics g(pDC->m_hDC);
g.DrawImage(&img, 0, 0); // 直接畫到螢幕 DC
}
後果:捲軸拖動產生 WM_PAINT → 呼叫 OnDraw → 重複讀檔/解碼 → CPU 飆高、畫面閃爍。
class CMyDoc : public CDocument
{
public:
CString m_strMapPath, m_strMapName; // 影像路徑與檔名
int m_nMapWidth, m_nMapHeight; // 影像尺寸
// ...
};
class CMyView : public CScrollView
{
CDC m_memDC;
CBitmap m_memBitmap;
CBitmap* m_pOldBitmap;
// ...
};
void CMyView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint)
{
CMyDoc* pDoc = GetDocument();
if (!pDoc) return;
// 1. 選入記憶體位圖
m_pOldBitmap = m_memDC.SelectObject(&m_memBitmap);
// 2. 清背景
m_memDC.FillSolidRect(0, 0, 2000, 2000, RGB(100,100,100));
// 3. GDI+ 讀檔並畫到記憶體 DC
CString fullPath = pDoc->m_strMapPath + pDoc->m_strMapName;
USES_CONVERSION;
LPWSTR wPath = A2W(fullPath);
Image img(wPath); // 只在影像改變時解碼一次
pDoc->m_nMapWidth = img.GetWidth();
pDoc->m_nMapHeight = img.GetHeight();
Graphics g(m_memDC.GetSafeHdc());
g.DrawImage(&img, 0, 0, img.GetWidth(), img.GetHeight());
// 4. 還原 DC
m_memDC.SelectObject(m_pOldBitmap);
// 5. 更新捲軸範圍並要求重繪
SetScrollSizes(MM_TEXT, CSize(pDoc->m_nMapWidth, pDoc->m_nMapHeight));
Invalidate(); // 觸發 OnDraw
}
void CMyView::OnDraw(CDC* pDC)
{
CMyDoc* pDoc = GetDocument();
if (!pDoc) return;
// 僅將記憶體位圖貼到螢幕,極快且無閃爍
pDC->BitBlt(0, 0, pDoc->m_nMapWidth, pDoc->m_nMapHeight, &m_memDC, 0, 0, SRCCOPY);
}

| 指標 | 傳統 OnDraw 讀檔 | 雙重緩衝架構 |
|---|---|---|
| 重繪觸發時機 | 每次 WM_PAINT 讀檔解碼 | 僅影像切換時解碼一次 |
| 捲軸拖曳流暢度 | 嚴重卡頓、閃爍 | 極順滑,純 BitBlt | CPU 佔用 | 高(重複解碼) | 低(單次解碼) |
| 記憶體管理 | 臨時物件頻繁建構/解構 | 固定記憶體 DC/位圖複用 |
核心原則:「耗時操作(讀檔、解碼、複雜繪圖)放在 OnUpdate/資料變更時單次執行,OnDraw 只做極輕量的 BitBlt/Blit」。此模式不限於 GDI+,適用於所有大型位圖、向量圖、複雜場景渲染的 VC/MFC 專案。