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

C# GDI+ 雙緩衝繪圖完全攻略:告別閃爍,打造流暢視覺體驗

     編輯:關於C語言

為什麼會閃爍?根因深度剖析

在 WinForms GDI+ 繪圖中,畫面閃爍主要源自兩類場景:

場景一:視窗重繪觸發大量圖元即時繪製

  • 視窗大小、位置、狀態改變會觸發 Paint 事件。
  • 每次重繪需遍歷所有圖元並立即繪製到螢幕,圖元越多、越複雜,繪製時間差越大,畫面逐個出現造成閃爍。
  • 關鍵在於:單次重繪過程中,圖元非同時呈現。

場景二:高頻觸發 Paint(滑鼠追蹤、圖元變形)

  • 滑鼠移動、拖曳縮放導致 Paint 極高頻率觸發。
  • 即便單次重繪已優化為同時呈現,但幀間隔遠小於繪製耗時,仍會產生殘影與閃爍。
  • 關鍵在於:Paint 觸發頻率過高。

雙緩衝核心技術:兩大支柱

支柱一:啟用控件層級雙緩衝樣式(必要條件)

// .NET 2.0+ 必須使用 OptimizedDoubleBuffer
this.SetStyle(
    ControlStyles.OptimizedDoubleBuffer |  // 雙緩衝
    ControlStyles.ResizeRedraw |           // 大小改變重繪
    ControlStyles.AllPaintingInWmPaint,    // 禁止擦除背景,減少閃爍
    true);
注意:.NET 1.1 使用 ControlStyles.DoubleBuffer,2.0 後改為 OptimizedDoubleBuffer,混用會報「參數無效」。

支柱二:離屏繪製——讓所有圖元「同時」上螢幕

核心邏輯:在記憶體建立離屏畫布 → 全部圖元繪製到畫布 → 一次性將畫布貼到螢幕。提供兩種實作方式:

方式 A:手動建立 Bitmap 畫布(傳統做法)

protected override void OnPaint(PaintEventArgs e) {
    Rectangle rect = e.ClipRectangle;
    // 1. 建立與控件同尺寸的 Bitmap
    using (Bitmap buffer = new Bitmap(this.Width, this.Height)) {
        using (Graphics g = Graphics.FromImage(buffer)) {
            g.Clear(this.BackColor);
            g.SmoothingMode = SmoothingMode.HighQuality;
            g.PixelOffsetMode = PixelOffsetMode.HighQuality;

            // 2. 遍歷圖元,繪製到離屏 Graphics
            foreach (IShape obj in doc.drawObjectList) {
                if (rect.IntersectsWith(obj.Rect)) {
                    obj.Draw(g);
                    // 選取態熱點繪製
                    if (obj.TrackerState == TrackerState.Selected && 
                        this.CurrentOperator == Operator.Transform) {
                        obj.DrawTracker(g);
                    }
                }
            }
        }
        // 3. 一次性貼圖到螢幕
        using (Graphics screen = e.Graphics) {
            screen.DrawImage(buffer, 0, 0);
        }
    }
}

方式 B:使用 BufferedGraphics(推薦,記憶體效率高、無洩漏)

protected override void OnPaint(PaintEventArgs e) {
    Rectangle rect = e.ClipRectangle;
    // 1. 從管理器配置緩衝區(自動處理尺寸、裝置相容)
    BufferedGraphicsContext ctx = BufferedGraphicsManager.Current;
    using (BufferedGraphics buffer = ctx.Allocate(e.Graphics, e.ClipRectangle)) {
        Graphics g = buffer.Graphics;
        g.SmoothingMode = SmoothingMode.HighQuality;
        g.PixelOffsetMode = PixelOffsetMode.HighSpeed;
        g.Clear(this.BackColor);

        // 2. 同樣遍歷繪製
        foreach (IShape obj in doc.drawObjectList) {
            if (rect.IntersectsWith(obj.Rect)) {
                obj.Draw(g);
                if (obj.TrackerState == TrackerState.Selected && 
                    this.CurrentOperator == Operator.Transform) {
                    obj.DrawTracker(g);
                }
            }
        }
        // 3. 渲染到螢幕
        buffer.Render(e.Graphics);
    } // using 確保 Dispose,釋放非托管資源
}

兩種方式對比與選型建議

維度手動 BitmapBufferedGraphics
實作複雜度略高(需自行管理 Bitmap 生命週期)低(框架封裝佳)
記憶體佔用較大(全尺寸 Bitmap)較小(可僅配置裁剪區域)
效能良好更優(內部優化)
資源洩漏風險較高(易忘 Dispose)低(using 自動釋放)
建議場景需精細控制畫布像素操作通用繪圖、高頻重繪、長時間運行程式

進階優化技巧

  1. 裁剪區域優化:僅重繪 e.ClipRectangle 範圍內的圖元(代碼中已示範 rect.IntersectsWith)。
  2. 圖元層級 Dirty Flag:標記變更的圖元,避免全量遍歷。
  3. 非同步繪製:極複雜場景可考慮背景執行緒預渲染 Bitmap,UI 執行緒僅負責 DrawImage。
  4. 抗鋸齒與像素偏移:SmoothingMode.HighQuality + PixelOffsetMode.HighQuality 顯著提升視覺品質。

總結

雙緩衝 = 控件樣式啟用 + 離屏繪製一次性呈現。掌握 BufferedGraphics 現代寫法,配合裁剪區域與資源釋放最佳實踐,即可徹底解決 GDI+ 繪圖閃爍問題,打造絲滑流暢的視覺體驗。

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