1. 程式人生 > >C# 繪圖雙緩衝技術總結

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))
                {
                    drawobject.Draw(g);
                    if (drawobject.TrackerState == config.Module.Core.TrackerState.Selected
                        && this.CurrentOperator == Enum.Operator.Transfrom)//僅當編輯節點操作時顯示圖元熱點
                    {
                        drawobject.DrawTracker(g);
                    }
                }
            }     myBuffer.Render(e.Graphics);
           g.Dispose();
           myBuffer.Dispose();//釋放資源 至此,雙緩衝問題解決,兩種方式的實現效果都一樣,但最後一種方式的佔有的記憶體很少,不會出現記憶體洩露! 參考資料: 2、GDI+圖形程式設計   [美]Mahesh Chand 著       電子工業出版社 3、C#高階程式設計(第三版) 原文連結:http://blog.csdn.net/ifooler/article/details/1598447