自繪控制項(覆寫 `OnPaint`)頻繁重繪時,**畫面閃爍**是經典痛點。根本原因:
即便啟用 `DoubleBuffered = true`(雙緩衝),若**清背景與繪內容仍在同一幀分兩步**,閃爍依然可能發生(特別是大面積重繪)。
using System;
using System.Drawing;
using System.Windows.Forms;
public class FlickerFreeControl : Control
{
private Bitmap _backBuffer; // 後備緩衝區
private Graphics _backGraphics; // 後備繪圖面
private bool _disposed;
public FlickerFreeControl()
{
// 關鍵:抑制系統自動清背景,由我們在 OnPaint 全權接管
SetStyle(ControlStyles.AllPaintingInWmPaint |
ControlStyles.UserPaint |
ControlStyles.OptimizedDoubleBuffer, true);
// 固定大小或隨控制項縮放時重建緩衝區
this.Resize += (s, e) => RecreateBuffer();
}
private void RecreateBuffer()
{
if (ClientSize.Width == 0 || ClientSize.Height == 0) return;
_backBuffer?.Dispose();
_backGraphics?.Dispose();
_backBuffer = new Bitmap(ClientSize.Width, ClientSize.Height);
_backGraphics = Graphics.FromImage(_backBuffer);
// 可設定高品質插值
_backGraphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
_backGraphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
}
///
/// 遊戲迴圈/動畫計時器呼叫:更新邏輯 → 繪製到後備緩衝 → 觸發最小重繪
///
public void UpdateAndRender()
{
if (_backGraphics == null) RecreateBuffer();
if (_backGraphics == null) return;
// 1. 清除後備緩衝(自訂背景色/圖片)
_backGraphics.Clear(BackColor);
// 2. 在這裡繪製所有動態內容(圖形、文字、圖片...)
DrawFrame(_backGraphics);
// 3. 只讓 1x1 像素無效 → 觸發 OnPaint 但不清背景
Invalidate(new Rectangle(0, 0, 1, 1));
}
protected override void OnPaint(PaintEventArgs e)
{
// 直接把整張後備位圖畫上去,一步到位,無閃爍
if (_backBuffer != null)
{
e.Graphics.DrawImageUnscaled(_backBuffer, 0, 0);
}
// 不呼叫 base.OnPaint(e),避免預設清背景邏輯
}
protected override void OnPaintBackground(PaintEventArgs e)
{
// 關鍵:空實作,完全阻止系統清背景(WM_ERASEBKGND 不再處理)
}
private void DrawFrame(Graphics g)
{
// 範例:繪製移動的球
// 實際專案替換為你的繪圖邏輯
}
protected override void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
_backGraphics?.Dispose();
_backBuffer?.Dispose();
}
_disposed = true;
}
base.Dispose(disposing);
}
}
| ControlStyles 旗標 | 作用 | |
|---|---|---|
| `UserPaint` | 控制項自行繪製(覆寫 OnPaint),系統不再自動繪製。 | |
| `AllPaintingInWmPaint` | 抑制 `WM_ERASEBKGND`,所有繪製在 `WM_PAINT` 一次完成。 | |
| `OptimizedDoubleBuffer` | 啟用雙緩衝(內建),配合上兩者效果最佳。 |
| 誤區做法 | 結果 | 正確做法 |
|---|---|---|
| 只設 `DoubleBuffered = true` | 大面積重繪仍閃爍 | 加上 `UserPaint` + `AllPaintingInWmPaint` + 手動緩衝區 |
| `OnPaint` 內 `e.Graphics.Clear()` | 觸發清背景閃爍 | 清背景在**後備 Graphics** 做,`OnPaint` 只 `DrawImage` |
| `Invalidate()` 無參數 | 整個區域無效 → 觸發清背景 | `Invalidate(new Rectangle(0,0,1,1))` 最小區域 |
| 用 `PaintEventArgs.Graphics` 畫緩衝區 | 無法跨幀保留內容 | 用 `CreateGraphics()` 或 `Graphics.FromImage(bitmap)` 建立持久 Graphics |
WinForms 閃爍本質是 **「清背景」與「畫內容」分離導致的視覺間隙**。解法核心三步:**手動緩衝區 + 抑制系統清背景 + 最小化無效區域**。掌握 `ControlStyles` 三大旗標、`OnPaintBackground` 空實作、`Invalidate(1x1)` 技巧,即可徹底根治 GDI+ 自繪控制項閃爍問題。