雙緩衝 是解決界面閃爍的經典技術:先在內存位圖上完成所有繪圖操作,最後一次性將完整圖像繪製到屏幕可見區域,避免用戶看到中間繪制過程。
protected override void OnPaint(PaintEventArgs e)
{
// 1. 創建與客戶區同大小的內存位圖
using (Bitmap buffer = new Bitmap(this.ClientSize.Width, this.ClientSize.Height))
{
// 2. 獲取位圖的 Graphics 對象
using (Graphics g = Graphics.FromImage(buffer))
{
g.Clear(this.BackColor); // 清空背景
// 3. 在這裡進行所有繪圖操作
// g.DrawString(...); g.DrawImage(...); g.FillRectangle(...);
// 4. 一次性繪制到屏幕
e.Graphics.DrawImage(buffer, 0, 0);
}
}
base.OnPaint(e);
}
WinForms 控件內置支持,只需在構造函數中設置:
public MyCustomControl()
{
// 核心三屬性組合
SetStyle(ControlStyles.AllPaintingInWmPaint, true); // 忽略 WM_ERASEBKGND,直接在 WM_PAINT 繪制背景
SetStyle(ControlStyles.UserPaint, true); // 自己繪制,不由系統默認繪制
SetStyle(ControlStyles.DoubleBuffer, true); // 啟用雙緩衝
// 或 .NET 2.0+ 簡寫:
// this.DoubleBuffered = true; // 內部等同於上述三項
// 可選:支持透明背景
SetStyle(ControlStyles.SupportsTransparentBackColor, true);
}
設置後,框架會自動在 OnPaint 提供的 e.Graphics 上繪制,無需手動創建 Bitmap。
public MyForm()
{
this.DoubleBuffered = true; // 表單及所有子控件繼承
}
當繪圖極其複雜、頻繁重繪(如遊戲循環、實時圖表)時,可避免每次 OnPaint 重新分配 Bitmap:
private Bitmap _offscreenBuffer;
private Graphics _offscreenGraphics;
protected override void OnSizeChanged(EventArgs e)
{
base.OnSizeChanged(e);
// 大小改變時重建緩衝區
_offscreenBuffer?.Dispose();
_offscreenBuffer = new Bitmap(this.ClientSize.Width, this.ClientSize.Height);
_offscreenGraphics?.Dispose();
_offscreenGraphics = Graphics.FromImage(_offscreenBuffer);
}
protected override void OnPaint(PaintEventArgs e)
{
if (_offscreenGraphics == null) return;
_offscreenGraphics.Clear(this.BackColor);
// 繪制邏輯...
DrawComplexScene(_offscreenGraphics);
e.Graphics.DrawImageUnscaled(_offscreenBuffer, 0, 0);
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
_offscreenBuffer?.Dispose();
_offscreenGraphics?.Dispose();
}
base.Dispose(disposing);
}
| 現象 | 原因 | 解決 |
|---|---|---|
| 閃爍依舊 | 未設置 AllPaintingInWmPaint 或 UserPaint | 三屬性同時設置,或直接 DoubleBuffered=true |
| 內存洩漏 | 手動創建 Bitmap/Graphics 未 Dispose | 使用 using 或在 Dispose 中釋放 |
| 縮放模糊 | DrawImage 預設插值 | 設置 g.InterpolationMode = InterpolationMode.NearestNeighbor |
| 透明背景不生效 | 缺少 SupportsTransparentBackColor | 添加該 Style,並設置 BackColor = Color.Transparent |
WM_ERASEBKGND 要求擦除背景 → 默認填充實心刷 → 產生閃爍AllPaintingInWmPaint=true 告訴框架:「不處理 WM_ERASEBKGND,背景我在 WM_PAINT 自己畫」UserPaint=true 表示完全接管繪制DoubleBuffer=true 在內存位圖上合成 WM_PAINT 內容,最後 BitBlt 到屏幕絕大多數情況下,只需在控件/表單構造函數添加 this.DoubleBuffered = true; 即可徹底解決閃爍。手動管理 Bitmap 僅在極高性能需求(>60 FPS 復雜繪圖)時必要。理解底層 WM 消息機制有助於排查疑難雜癥。