在 Windows GDI 繪圖中,直接在視窗的 Canvas 上繪製大量圖形(如數千個資料點的曲線圖)會造成:
WM_PAINT 處理過程中若被中斷,畫面殘缺。雙緩衝 將所有繪圖操作導向記憶體位圖,完成後一次 BitBlt 複製到螢幕,完美解決上述問題。
Graphics::TBitmap* BufferBmp = new Graphics::TBitmap();
try {
// 設定尺寸與畫布屬性
BufferBmp->Width = ClientWidth; // 或指定繪圖區寬高
BufferBmp->Height = ClientHeight;
BufferBmp->PixelFormat = pf24bit; // 依需求選擇 pf32bit/pf16bit
// 2. 在緩衝區繪圖 (完整繪製邏輯)
BufferBmp->Canvas->Brush->Color = clBtnFace;
BufferBmp->Canvas->FillRect(Rect(0, 0, ClientWidth, ClientHeight));
// 繪製座標軸、格線、曲線、標註...
BufferBmp->Canvas->MoveTo(x0, y0);
BufferBmp->Canvas->LineTo(x1, y1);
// ... 大量繪圖指令 ...
// 3. 一次性複製到螢幕
BitBlt(Canvas->Handle,
0, 0, ClientWidth, ClientHeight,
BufferBmp->Canvas->Handle,
0, 0, SRCCOPY);
}
__finally {
delete BufferBmp; // 4. 釋放記憶體
}
將 BufferBmp 宣告為表單成員變數,在 FormCreate 建立,FormDestroy 釋放,FormResize 時調整尺寸:
// .h
private:
Graphics::TBitmap* FBufferBmp;
// .cpp
void __fastcall TForm1::FormCreate(TObject* Sender)
{
FBufferBmp = new Graphics::TBitmap();
FBufferBmp->PixelFormat = pf32bit;
}
void __fastcall TForm1::FormDestroy(TObject* Sender)
{
delete FBufferBmp;
}
void __fastcall TForm1::FormResize(TObject* Sender)
{
if (FBufferBmp) {
FBufferBmp->Width = ClientWidth;
FBufferBmp->Height = ClientHeight;
}
Invalidate(); // 觸發重繪
}
void __fastcall TForm1::Paint()
{
// 直接使用 FBufferBmp 繪圖並 BitBlt
}
TPaintBox + OnPaint 替代表單直接繪圖TPaintBox 專為自訂繪圖設計,無視窗背景擦除開銷,配合雙緩衝效能最佳。
#include <gdiplus.h>
#pragma comment(lib, "gdiplus.lib")
// 初始化 (FormCreate)
ULONG_PTR gdiplusToken;
Gdiplus::GdiplusStartupInput gdiplusStartupInput;
Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
// 繪圖
Gdiplus::Graphics g(BufferBmp->Canvas->Handle);
g.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
Gdiplus::Pen pen(Gdiplus::Color(255, 0, 0, 255), 2.0f);
g.DrawLine(&pen, x0, y0, x1, y1);
// 關閉 (FormDestroy)
Gdiplus::GdiplusShutdown(gdiplusToken);
若只有部分資料更新,計算最小包含矩形 TRect DirtyRect,BitBlt 時僅複製該區域:
BitBlt(Canvas->Handle,
DirtyRect.Left, DirtyRect.Top,
DirtyRect.Width(), DirtyRect.Height(),
BufferBmp->Canvas->Handle,
DirtyRect.Left, DirtyRect.Top,
SRCCOPY);
| 現象 | 原因 | 解決 |
|---|---|---|
| 畫面全白/全黑 | BitBlt 來源/目的座標錯誤、緩衝區尺寸為 0 | 檢查 Width/Height > 0,座標對應正確 |
| 仍有閃爍 | 未攔截 WM_ERASEBKGND、或 Invalidate(true) 擦除背景 | 表單/控制項設定 DoubleBuffered = true (VCL 內建),或自行處理 WM_ERASEBKGND 回傳 TRUE |
| 記憶體洩漏 | 例外發生時未釋放 BufferBmp | 使用 try/__finally 或智慧指標 std::unique_ptr |
| 縮放模糊 | BitBlt 不支援縮放 | 改用 StretchBlt、SetStretchBltMode(HALFTONE) 或 GDI+ |
自 BCB 6 / Delphi 6 起,TWinControl 提供 DoubleBuffered 屬性:
// 表單或自訂控制項建構子
__fastcall TMyControl::TMyControl(TComponent* Owner)
: TCustomControl(Owner)
{
DoubleBuffered = true; // 自動處理 WM_ERASEBKGND + 記憶體位圖雙緩衝
}
若僅需一般業務繪圖,開啟此屬性即可,無需手寫 BitBlt。高效能/特殊需求 (遊戲、即時波形) 仍建議手動控制。