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

C# WinForms 解決 GDI+ 繪圖閃爍問題的終極方案

     編輯:關於C#

問題背景:為什麼 C# 控件會閃爍?

從 Java Swing 轉戰 C# WinForms 的開發者常會驚訝:Swing 只要設 setDoubleBuffered(true) 幾乎不會閃爍,但 WinForms 即使開啟 DoubleBuffered = true,在高頻重繪時依然會出現明顯閃爍。根本原因在於 Control.Refresh() 的運作機制:

  1. 先以背景色清空整個控件區域(產生一幀「空白幀」)。
  2. 再觸發 Paint 事件繪製新圖像。
  3. 若刷新頻率過高,人眼會捕捉到中間的空白幀,形成閃爍。

即便使用雙緩衝(先畫到記憶體位圖再一次性 DrawImage),清屏動作依然發生在螢幕可見區域,故無法徹底解決。

核心解法:Invalidate(Rectangle.Empty) 避開清屏

關鍵在於 不讓系統執行背景清除。Control.Invalidate() 若傳入面積為零的矩形(如 new Rectangle(0, 0, 1, 1)),會觸發 Paint 事件,但 不會擦除背景,從而消除空白幀。

完整實作步驟

public partial class GamePanel : UserControl
{
    private BufferedGraphics _buffer;
    private readonly Timer _renderTimer = new Timer { Interval = 16 }; // ~60 FPS

    public GamePanel()
    {
        // 1. 關鍵設定:禁止系統自動清背景
        SetStyle(ControlStyles.AllPaintingInWmPaint |
                 ControlStyles.UserPaint |
                 ControlStyles.OptimizedDoubleBuffer, true);

        // 2. 建立緩衝區(尺寸隨控件調整)
        Resize += (s, e) => RecreateBuffer();
        _renderTimer.Tick += (s, e) => Invalidate(new Rectangle(0, 0, 1, 1));
        _renderTimer.Start();
    }

    private void RecreateBuffer()
    {
        if (ClientSize.Width == 0 || ClientSize.Height == 0) return;
        _buffer?.Dispose();
        _buffer = BufferedGraphicsManager.Current.Allocate(
            CreateGraphics(), ClientRectangle);
    }

    protected override void OnPaint(PaintEventArgs e)
    {
        // 3. 所有繪圖操作在緩衝區進行
        var g = _buffer.Graphics;
        g.Clear(BackColor);               // 在記憶體中清背景,不閃爍
        DrawGameFrame(g);                 // 業務繪圖邏輯
        _buffer.Render(e.Graphics);       // 一次性輸出到螢幕
    }

    private void DrawGameFrame(Graphics g)
    {
        // 實際遊戲/動畫繪圖代碼
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing) _buffer?.Dispose();
        base.Dispose(disposing);
    }
}

關鍵點總結

技巧作用
SetStyle(..., UserPaint | AllPaintingInWmPaint)接管 WM_PAINT,阻止預設背景擦除
Invalidate(new Rectangle(0,0,1,1))觸發重繪但不清背景,消除空白幀
BufferedGraphics雙緩衝在記憶體完成合成,最後一次性 Blit 到螢幕
在 OnPaint 而非 CreateGraphics() 繪圖確保繪圖與視窗更新同步,避免撕裂

常見誤區

  • ❌ 在 Timer 裡直接呼叫 CreateGraphics() 繪圖 → 繪圖與視窗更新不同步,易撕裂。
  • ❌ 僅設 DoubleBuffered = true → 仍會先清背景再畫,高頻下仍閃爍。
  • ✅ 組合 UserPaint + AllPaintingInWmPaint + 微小 Invalidate + BufferedGraphics → 徹底解決。

延伸應用

此模式適用於:遊戲迴圈、即時圖表、視訊畫面、高頻動畫等所有「每幀全量重繪」場景。若為局部更新,可傳入實際髒矩形區域至 Invalidate(dirtyRect) 進一步降低 GPU 頻寬。

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