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();//釋放資源
至此,雙緩衝問題解決,兩種方式的實現效果都一樣,但最後一種方式的佔有的記憶體很少,不會出現記憶體泄露!