C#雙緩沖技術(shù)實(shí)例詳解
本文實(shí)例分析了C#雙緩沖技術(shù)。分享給大家供大家參考,具體如下:
雙緩沖解決閃爍問(wèn)題。
整理:
GDI+的雙緩沖問(wèn)題
一直以來(lái)的誤區(qū):.net1.1 和 .net 2.0 在處理控件雙緩沖上是有區(qū)別的。
.net 1.1 中,使用:this.SetStyle(ControlStyles.DoubleBuffer, true);
.net 2.0中,使用:this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
導(dǎo)致畫(huà)面閃爍的關(guān)鍵原因分析:
一、繪制窗口由于大小位置狀態(tài)改變進(jìn)行重繪操作時(shí)
繪圖窗口內(nèi)容或大小每改變一次,都要調(diào)用Paint事件進(jìn)行重繪操作,該操作會(huì)使畫(huà)面重新刷新一次以維持窗口正常顯示。刷新過(guò)程中會(huì)導(dǎo)致所有圖元重新繪制,而各個(gè)圖元的重繪操作并不會(huì)導(dǎo)致Paint事件發(fā)生,因此窗口的每一次刷新只會(huì)調(diào)用Paint事件一次。窗口刷新一次的過(guò)程中,每一個(gè)圖元的重繪都會(huì)立即顯示到窗口,因此整個(gè)窗口中,只要是圖元所在的位置,都在刷新,而刷新的時(shí)間是有差別的,閃爍現(xiàn)象自然會(huì)出現(xiàn)。
所以說(shuō),此時(shí)導(dǎo)致窗口閃爍現(xiàn)象的關(guān)鍵因素并不在于Paint事件調(diào)用的次數(shù)多少,而在于各個(gè)圖元的重繪。
根據(jù)以上分析可知,當(dāng)圖元數(shù)目不多時(shí),窗口刷新的位置也不多,窗口閃爍效果并不嚴(yán)重;當(dāng)圖元數(shù)目較多時(shí),繪圖窗口進(jìn)行重繪的圖元數(shù)量增加,繪圖窗口每一次刷新都會(huì)導(dǎo)致較多的圖元重新繪制,窗口的較多位置都在刷新,閃爍現(xiàn)象自然就會(huì)越來(lái)越嚴(yán)重。特別是圖元比較大繪制時(shí)間比較長(zhǎng)時(shí),閃爍問(wèn)題會(huì)更加嚴(yán)重,因?yàn)闀r(shí)間延遲會(huì)更長(zhǎng)。
解決上述問(wèn)題的關(guān)鍵在于:窗口刷新一次的過(guò)程中,讓所有圖元同時(shí)顯示到窗口。
二、進(jìn)行鼠標(biāo)跟蹤繪制操作或者對(duì)圖元進(jìn)行變形操作時(shí)
當(dāng)進(jìn)行鼠標(biāo)跟蹤繪制操作或者對(duì)圖元進(jìn)行變形操作時(shí),Paint事件會(huì)頻繁發(fā)生,這會(huì)使窗口的刷新次數(shù)大大增加。雖然窗口刷新一次的過(guò)程中所有圖元同時(shí)顯示到窗口,但也會(huì)有時(shí)間延遲,因?yàn)榇藭r(shí)窗口刷新的時(shí)間間隔遠(yuǎn)小于圖元每一次顯示到窗口所用的時(shí)間。因此閃爍現(xiàn)象并不能完全消除!
所以說(shuō),此時(shí)導(dǎo)致窗口閃爍現(xiàn)象的關(guān)鍵因素在于Paint事件發(fā)生的次數(shù)多少。
解決此問(wèn)題的關(guān)鍵在于:設(shè)置窗體或控件的幾個(gè)關(guān)鍵屬性。
使用雙緩沖
解決雙緩沖的關(guān)鍵技術(shù):
1、設(shè)置顯示圖元控件的幾個(gè)屬性: 必須要設(shè)置,否則效果不是很明顯!
this.SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.ResizeRedraw | ControlStyles.AllPaintingInWmPaint, true);
2、窗口刷新一次的過(guò)程中,讓所有圖元同時(shí)顯示到窗口。
可以通過(guò)以下幾種方式實(shí)現(xiàn),這幾種方式都涉及到Graphics對(duì)象的創(chuàng)建方式。
具體實(shí)現(xiàn)
1、利用默認(rèn)雙緩沖
(1)在應(yīng)用程序中使用雙緩沖的最簡(jiǎn)便的方法是使用 .NET Framework 為窗體和控件提供的默認(rèn)雙緩沖。通過(guò)將 DoubleBuffered 屬性設(shè)置為 true。
this.DoubleBuffered=true;
(2)使用 SetStyle 方法可以為 Windows 窗體和所創(chuàng)作的 Windows 控件啟用默認(rèn)雙緩沖。
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
2、手工設(shè)置雙緩沖
.netframework提供了一個(gè)類BufferedGraphicsContext負(fù)責(zé)單獨(dú)分配和管理圖形緩沖區(qū)。每個(gè)應(yīng)用程序域都有自己的默認(rèn) BufferedGraphicsContext 實(shí)例來(lái)管理此應(yīng)用程序的所有默認(rèn)雙緩沖。大多數(shù)情況下,每個(gè)應(yīng)用程序只有一個(gè)應(yīng)用程序域,所以每個(gè)應(yīng)用程序通常只有一個(gè)默認(rèn) BufferedGraphicsContext。默認(rèn) BufferedGraphicsContext 實(shí)例由 BufferedGraphicsManager 類管理。通過(guò)管理BufferedGraphicsContext實(shí)現(xiàn)雙緩沖的步驟如下:
(1)獲得對(duì) BufferedGraphicsContext 類的實(shí)例的引用。
(2)通過(guò)調(diào)用 BufferedGraphicsContext.Allocate 方法創(chuàng)建 BufferedGraphics 類的實(shí)例。
(3)通過(guò)設(shè)置 BufferedGraphics.Graphics 屬性將圖形繪制到圖形緩沖區(qū)。
(4)當(dāng)完成所有圖形緩沖區(qū)中的繪制操作時(shí),可調(diào)用 BufferedGraphics.Render 方法將緩沖區(qū)的內(nèi)容呈現(xiàn)到與該緩沖區(qū)關(guān)聯(lián)的繪圖圖面或者指定的繪圖圖面。
(5)完成呈現(xiàn)圖形之后,對(duì) BufferedGraphics 實(shí)例調(diào)用釋放系統(tǒng)資源的 Dispose 方法。
完整的例子,在一個(gè)400*400的矩形框內(nèi)繪制10000個(gè)隨機(jī)生成的小圓。
BufferedGraphicsContext current = BufferedGraphicsManager.Current; //(1) BufferedGraphics bg; bg = current.Allocate(this.CreateGraphics(),this.DisplayRectangle); //(2) Graphics g = bg.Graphics;//(3) //隨機(jī) 寬400 高400 System.Random rnd = new Random(); int x,y,w,h,r,i; for (i = 0; i < 10000; i++) { x = rnd.Next(400); y = rnd.Next(400); r = rnd.Next(20); w = rnd.Next(10); h = rnd.Next(10); g.DrawEllipse(Pens.Blue, x, y, w, h); } bg.Render();//(4) //bg.Render(this.CreateGraphics()); bg.Dispose();//(5)
3、自己開(kāi)辟一個(gè)緩沖區(qū)(如一個(gè)不顯示的Bitmap對(duì)象),在其中繪制完成后,再一次性顯示。
完整代碼如下:
Bitmap bt = new Bitmap(400, 400); Graphics bg = Graphics.FromImage(bt); System.Random rnd = new Random(); int x, y, w, h, r, i; for (i = 0; i < 10000; i++) { x = rnd.Next(400); y = rnd.Next(400); r = rnd.Next(20); w = rnd.Next(10); h = rnd.Next(10); bg.DrawEllipse(Pens.Blue, x, y, w, h); } this.CreateGraphics().DrawImage(bt, new Point(0, 0));
另外一個(gè)例子,差不多
Graphics對(duì)象的創(chuàng)建方式:
a、在內(nèi)存上創(chuàng)建一塊和顯示控件相同大小的畫(huà)布,在這塊畫(huà)布上創(chuàng)建Graphics對(duì)象。
接著所有的圖元都在這塊畫(huà)布上繪制,繪制完成以后再使用該畫(huà)布覆蓋顯示控件的背景,從而達(dá)到“顯示一次僅刷新一次”的效果!
實(shí)現(xiàn)代碼(在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; //高質(zhì)量 g.PixelOffsetMode = PixelOffsetMode.HighQuality; //高像素偏移質(zhì)量 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)//僅當(dāng)編輯節(jié)點(diǎn)操作時(shí)顯示圖元熱點(diǎn) { drawobject.DrawTracker(g); } } } using (Graphics tg = e.Graphics) { tg.DrawImage(bufferimage, 0, 0);//把畫(huà)布貼到畫(huà)面上 }
b、直接在內(nèi)存上創(chuàng)建Graphics對(duì)象:
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)//僅當(dāng)編輯節(jié)點(diǎn)操作時(shí)顯示圖元熱點(diǎn) { drawobject.DrawTracker(g); } } } myBuffer.Render(e.Graphics); g.Dispose(); myBuffer.Dispose();//釋放資源
至此,雙緩沖問(wèn)題解決,兩種方式的實(shí)現(xiàn)效果都一樣,但最后一種方式的占有的內(nèi)存很少,不會(huì)出現(xiàn)內(nèi)存泄露!
接下來(lái)是對(duì)acdsee拖動(dòng)圖片效果的實(shí)現(xiàn)。開(kāi)始不懂雙緩沖,以為雙緩沖可以解決這個(gè)問(wèn)題,結(jié)果發(fā)現(xiàn)使用了雙緩沖沒(méi)啥效果,請(qǐng)教了高人,然后修改了些代碼,完成這個(gè)效果。
圖片是在pictureBox1里。
Bitmap currentMap; bool first = true; private void pictureBox1_MouseDown(object sender, MouseEventArgs e) { if (zoom == 0) { if (e.Button == MouseButtons.Left) //dragging mousedrag = e.Location; Image myImage = myMap.GetMap(); currentMap = new Bitmap(myImage); first = false; } } private void pictureBox1_MouseMove(object sender, MouseEventArgs e) { if (zoom == 0&&!first) { Image img = new Bitmap(Size.Width, Size.Height); Graphics g = Graphics.FromImage(img); g.Clear(Color.Transparent);//圖片移動(dòng)后顯示的底色 g.SmoothingMode = SmoothingMode.HighQuality; //高質(zhì)量 g.PixelOffsetMode = PixelOffsetMode.HighQuality; //高像素偏移質(zhì)量 g.DrawImageUnscaled(currentMap, new System.Drawing.Point(e.Location.X - mousedrag.X, e.Location.Y - mousedrag.Y));//在g中移動(dòng)圖片,原圖在(0,0)畫(huà)的,所以直接用new System.Drawing.Point(e.Location.X - mousedrag.X, e.Location.Y - mousedrag.Y)就好。 g.Dispose(); pictureBox1.Image = img;//img是在鼠標(biāo)這個(gè)位置時(shí)生成被移動(dòng)后的暫時(shí)的圖片 } } private void pictureBox1_MouseUp(object sender, MouseEventArgs e) { if (zoom == 0) { System.Drawing.Point pnt = new System.Drawing.Point(Width / 2 + (mousedrag.X - e.Location.X), Height / 2 + (mousedrag.Y - e.Location.Y)); myMap.Center = myMap.ImageToWorld(pnt); pictureBox1.Image = myMap.GetMap(); first = true; } }
說(shuō)說(shuō)思路,在鼠標(biāo)點(diǎn)下時(shí)創(chuàng)建一個(gè)bitmap,currentMap,用它來(lái)存放當(dāng)前圖像。鼠標(biāo)移動(dòng)時(shí),根據(jù)鼠標(biāo)位置畫(huà)圖,最后,鼠標(biāo)up時(shí),重新畫(huà)圖。
更多關(guān)于C#相關(guān)內(nèi)容感興趣的讀者可查看本站專題:《C#面向?qū)ο蟪绦蛟O(shè)計(jì)入門(mén)教程》、《C#常見(jiàn)控件用法教程》及《C#數(shù)據(jù)結(jié)構(gòu)與算法教程》
希望本文所述對(duì)大家C#程序設(shè)計(jì)有所幫助。
相關(guān)文章
C#將HashTable中鍵列表或值列表復(fù)制到一維數(shù)組的方法
這篇文章主要介紹了C#將HashTable中鍵列表或值列表復(fù)制到一維數(shù)組中方法,涉及C#操作HashTable的相關(guān)技巧,需要的朋友可以參考下2015-04-04C#獲取計(jì)算機(jī)硬件與操作系統(tǒng)的相關(guān)信息
這篇文章介紹了C#獲取計(jì)算機(jī)硬件與操作系統(tǒng)相關(guān)信息的方法,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-04-04C#中把字符串String轉(zhuǎn)換為整型Int的小例子
這篇文章主要介紹了C#中把字符串String轉(zhuǎn)換為整型Int的小例子,本文使用TryParse方法實(shí)現(xiàn)轉(zhuǎn)換,需要的朋友可以參考下2014-08-08C# 設(shè)計(jì)模式系列教程-狀態(tài)模式
狀態(tài)模式主要解決的是當(dāng)控制一個(gè)對(duì)象狀態(tài)轉(zhuǎn)換的條件表達(dá)式過(guò)于復(fù)雜時(shí)的情況。把狀態(tài)的判斷邏輯轉(zhuǎn)移到表示不同的一系列類當(dāng)中,可以把復(fù)雜的邏輯判斷簡(jiǎn)單化。2016-06-06