菜鳥學堂:
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();//釋放資源
至此,雙緩沖問題解決,兩種方式的實現效果都一樣,但最后一種方式的占有的內存很少,不會出現內存泄露!
參考資料:
1、http://dev.yesky.com/msdn/148/2596148.shtml
2、gdi+圖形程序設計 [美]mahesh chand 著 電子工業出版社
3、c#高級編程(第三版)