天天看點

C#繪圖雙緩沖技術總結(轉)

GDI+的雙緩沖問題

一直以來的誤區:.net1.1 和 .net 2.0 在處理控件雙緩沖上是有差別的。

.net 1.1 中,使用:this.SetStyle(ControlStyles.DoubleBuffer, true); 

.net 2.0中,使用:this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);

怪不說老是提示參數無效,一直也不知道是這個問題,呵呵

要知道,圖元無閃爍的實作和圖元的繪制方法沒有多少關系,隻是繪制方法可以控制圖元的重新整理區域,使雙緩沖性能更優!

導緻畫面閃爍的關鍵原因分析:

      一、繪制視窗由于大小位置狀态改變進行重繪操作時

     繪圖視窗内容或大小每改變一次,都要調用Paint事件進行重繪操作,該操作會使畫面重新重新整理一次以維持視窗正常顯示。重新整理過程中會導緻所有圖元重新繪制,而各個圖元的重繪操作并不會導緻Paint事件發生,是以視窗的每一次重新整理隻會調用Paint事件一次。視窗重新整理一次的過程中,每一個圖元的重繪都會立即顯示到視窗,是以整個視窗中,隻要是圖元所在的位置,都在重新整理,而重新整理的時間是有差别的,閃爍現象自然會出現。

     是以說,此時導緻視窗閃爍現象的關鍵因素并不在于Paint事件調用的次數多少,而在于各個圖元的重繪。

     根據以上分析可知,當圖元數目不多時,視窗重新整理的位置也不多,視窗閃爍效果并不嚴重;當圖元數目較多時,繪圖視窗進行重繪的圖元數量增加,繪圖視窗每一次重新整理都會導緻較多的圖元重新繪制,視窗的較多位置都在重新整理,閃爍現象自然就會越來越嚴重。特别是圖元比較大繪制時間比較長時,閃爍問題會更加嚴重,因為時間延遲會更長。

     解決上述問題的關鍵在于:視窗重新整理一次的過程中,讓所有圖元同時顯示到視窗。

      二、進行滑鼠跟蹤繪制操作或者對圖元進行變形操作時

     當進行滑鼠跟蹤繪制操作或者對圖元進行變形操作時,Paint事件會頻繁發生,這會使視窗的重新整理次數大大增加。雖然視窗重新整理一次的過程中所有圖元同時顯示到視窗,但也會有時間延遲,因為此時視窗重新整理的時間間隔遠小于圖元每一次顯示到視窗所用的時間。是以閃爍現象并不能完全消除!

     是以說,此時導緻視窗閃爍現象的關鍵因素在于Paint事件發生的次數多少。

     解決此問題的關鍵在于:設定窗體或控件的幾個關鍵屬性。

下面來介紹解決辦法的具體細節:

解決雙緩沖的關鍵技術:

1、設定顯示圖元控件的幾個屬性:  必須要設定,否則效果不是很明顯!

this.SetStyle(ControlStyles.OptimizedDoubleBuffer |   

                    ControlStyles.ResizeRedraw |

                    ControlStyles.AllPaintingInWmPaint, true);

2、視窗重新整理一次的過程中,讓所有圖元同時顯示到視窗。

    可以通過以下幾種方式實作,這幾種方式都涉及到Graphics對象的建立方式。

Graphics對象的建立方式:

a、在記憶體上建立一塊和顯示控件相同大小的畫布,在這塊畫布上建立Graphics對象。

     接着所有的圖元都在這塊畫布上繪制,繪制完成以後再使用該畫布覆寫顯示控件的背景,進而達到“顯示一次僅重新整理一次”的效果!

  實作代碼(在OnPaint方法中):

  Rectangle rect = e.ClipRectangle;

  Bitmap bufferimage = new Bitmap(this.Width, this.Height);

     Graphics g = Graphics.FromImage(bufferimage);

  g.Clear(this.BackColor);

     g.SmoothingMode = SmoothingMode.HighQuality; //高品質

     g.PixelOffsetMode = PixelOffsetMode.HighQuality; //高像素偏移品質

  foreach (IShape drawobject in doc.drawObjectList)

      {

                 if (rect.IntersectsWith(drawobject.Rect))

                {

                    drawobject.Draw(g);

                    if (drawobject.TrackerState == config.Module.Core.TrackerState.Selected

                        && this.CurrentOperator == Enum.Operator.Transfrom)//僅當編輯節點操作時顯示圖元熱點

                    {

                        drawobject.DrawTracker(g);

                    }

                }

       }

    using (Graphics tg = e.Graphics)

            {

                tg.DrawImage(bufferimage, 0, 0);  //把畫布貼到畫面上

            }

b、直接在記憶體上建立Graphics對象:

     Rectangle rect = e.ClipRectangle;

     BufferedGraphicsContext currentContext = BufferedGraphicsManager.Current;

            BufferedGraphics myBuffer = currentContext.Allocate(e.Graphics, e.ClipRectangle);

            Graphics g = myBuffer.Graphics;

            g.SmoothingMode = SmoothingMode.HighQuality;

            g.PixelOffsetMode = PixelOffsetMode.HighSpeed;

            g.Clear(this.BackColor);

            foreach (IShape drawobject in doc.drawObjectList)

                if (rect.IntersectsWith(drawobject.Rect))

    myBuffer.Render(e.Graphics);