C#高效繪圖(轉)

原文鏈接:http://blog.csdn.net/suncherrydream/article/details/17585201

雙緩衝技術

雙緩衝是將圖片在顯示到DC前,現在要內存建一個DC,也就是用於存儲這張圖片的內存區,然後在將這部分update到你要顯示的地方

這樣,可以防止畫面抖動很大

這樣和你說吧,如果要實現你要的效果,你必須用指針訪問內存

比如,把程序聲明成unsafe的,然後按照上面的操作進行

 

this.clear(this.BackColor)不行的 invalidate(),閃的厲害 所以不行

 

我再來詳細解釋一下剛纔實現雙緩衝的具體步驟:

 

1、   在內存中建立一塊“虛擬畫布”:

Bitmap   bmp   =   new   Bitmap(600,   600);

2、   獲取這塊內存畫布的Graphics引用:

Graphics   g   =   Graphics.FromImage(bmp);

3、   在這塊內存畫布上繪圖:

g.FillEllipse(brush,   i   *   10,   j   *   10,   10,   10);

4、將內存畫布畫到窗口中

this.CreateGraphics().DrawImage(bmp,   0,   0);

重點:

 現在的cpu飛快,其實數學計算一般很快,cpu大部分時間是在處理繪圖,而繪圖有三種境界:1>每次重繪整體Invalidate()

 2>每次局部繪製Invalidate(Rect);

      3>有選擇的局部繪製。

  不能說,一定是第三種方式好,得視情況,境界高程序肯定就複雜,如果對效率要求不高或者繪圖量小當然直接用第一種方式。然而,稍微專業點的繪圖程序,第一第二種方式肯定滿足不了要求,必須選用第三種方式。而第三種方式的手段多樣,也得根據實際情況拿相應的解決之道。這裏講解一般的三種手段,他們可以聯合使用。

1. 緩存——Bitmap或者DoubleBuffer。緩存就是先把繪製的圖形繪製到一張內存位圖上,然後在一次性的貼位圖,他可以提高繪圖速度,也能避免閃爍。DoubleBuffer=true是C#窗體的屬性,設置了此屬性估計系統本身會起用無效區的內存位圖緩存,而不需要程序員Bitmap處理。

2. 合理利用無效區域。無效區域就是系統保存當前變化需要重繪的區域,可以在OnPaint()中,e.ClipRectangle(e.ClipRectangle.X)直接獲得,也可以通過其他方式獲得。Windows系統只會重繪無效區域內的繪圖信息,然而我們用戶的繪製代碼一般是繪製整個區域的,很多時候無效區域只是一小部分區域,雖然執行了所有的繪圖代碼,但是Windows系統只會重新更新無效區域內的繪圖。這裏有兩個利用點:

1>用戶請求重繪時,只請求重繪指定區域的,而不是整個區域,如Invalidate(Rect);

2>在用戶繪圖代碼

Graphics g; g.DrawLine\g.DrawString\g.FillRectangle...前,先判斷繪圖的內容是否在無效區域,如果不是就不直接g.Draw...繪圖代碼。

3. 直接貼圖。一般繪圖或者重繪是Windows根據無效區域繪製的,如果在鼠標移動時需要重繪通過Windows系統處理Paint消息,有時滿足不了要求,

比如①鼠標移動繪製十字測量線就得用異或線而不是Paint消息,

又比如②鼠標移動繪製跟隨的信息提示框需要頻繁擦除上次覆蓋的背景,

又比如③檯球滾動時檯球與球桌背景的關係。

類似的這些問題如何解決?首先肯定不能利用Windows原來的繪圖機制。其中一種解決方式是,不斷的幀間變化區域貼內存位圖——②中的信息框每次鼠標位置變化時可以重新g.Draw...或者貼早生成的信息框內存位圖,②中被信息框覆蓋的背景應該把本來的大背景截取此需要擦除區域的位置大小位圖貼回來就是擦除背景了。由於每次大背景發生變化時,都應會重新生成大背景內存位圖,所以可以是變化的背景。

  這三種方式可以一起使用,應該可以解決中等的繪圖項目的效率問題。中大型的繪圖,必須記住兩點1>只繪製電腦屏幕能

顯示的部分;2>只繪製變化的部分。

 

 

C#GDI+雙緩衝高效繪圖

Rectangle rectangle =  e.ClipRectangle;//取出次窗體或者畫布的有效區的矩形區域

BufferedGraphicsContext GraphicsContext = BufferedGraphicsManager.Current;//獲取程序住緩衝區域的BufferedGraphicsContext(雙緩存類,此類用於提供雙緩衝的功能)對象

BufferedGraphics myBuffer = GraphicsContext.Allocate(e.Graphics, e.ClipRectangle);//獲取緩衝區

Graphics g = myBuffer.Graphics;

指定在呈現期間像素偏移的方式。

g.PixelOffsetMode = PixelOffsetMode.HighQuality;//高質量低速度呈現

指定是否將平滑處理(消除鋸齒)應用於直線、曲線和已填充區域的邊緣。

g.SmoothingMode = SmoothingMode.HighQuality;// 指定高質量、低速度呈現。

g.Clear(BackColor);//或者使用invalidate方法==有效區的擦除

Pen bluePen2 = new Pen(Color.Blue);

LineDrawRoutine(g, bluePen2);

 

myBuffer.Render(e.Graphics);  //將圖形緩衝區的內容寫入指定的 Graphics 對象。

g.Dispose();

myBuffer.Dispose();

 

 

其實在C#裏如果是在Form中繪圖的話直接把Form的DoubleBuffered = true就可以了(利用winfrom窗體的默認雙緩衝)

 

把所有的繪圖放在一個picturebox裏面繪製,

不要直接再在form裏面繪

SetStyle(ControlStyles.UserPaint, true);

  SetStyle(ControlStyles.ResizeRedraw, true);

  SetStyle(ControlStyles.AllPaintingInWmPaint, true);

  SetStyle(ControlStyles.OptimizedDoubleBuffer, true);

  SetStyle(ControlStyles.Selectable, true);

 

 

 

如果你在Form中繪圖的話,不論是不是採用的雙緩存,都會看到圖片在更新的時候都會不斷地閃爍,解決方法就是在這個窗體的構造函數中增加以下三行代碼:

請在構造函數裏面底下加上如下幾行:

SetStyle(ControlStyles.UserPaint, true);

SetStyle(ControlStyles.AllPaintingInWmPaint, true);   //   禁止擦除背景.

SetStyle(ControlStyles.DoubleBuffer, true);   //   雙緩衝

參數說明:

UserPaint  

如果爲true,控件將自行繪製,而不是通過操作系統來繪製。此樣式僅適用於派生自   Control的類。

AllPaintingInWmPaint  

如果爲true,控件將忽略 WM_ERASEBKGND窗口消息以減少閃爍。僅當UserPaint   位設置爲true時,才應當應用該樣式。    

DoubleBuffer  

如果爲true,則繪製在緩衝區中進行,完成後將結果輸出到屏幕上。雙重緩衝區可防止由控件重繪引起的閃爍。要完全啓用雙重緩衝,還必須將UserPaint和AllPaintingInWmPaint樣式位設置爲   true。  

 

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、在內存中建立一塊“虛擬畫布”:Bitmap bmp = new Bitmap(600, 600);

2、獲取這塊內存畫布的Graphics引用:Graphics g = Graphics.FromImage(bmp);

3、在這塊內存畫布上繪圖:如畫線g.DrawLine(添加參數);

4、將內存畫布畫到窗口中:this.CreateGraphics().DrawImage(bmp, 0, 0);

 

在構造函數中加如下代碼

 

代碼一:

SetStyle(ControlStyles.UserPaint, true);

SetStyle(ControlStyles.AllPaintingInWmPaint, true); // 禁止擦除背景.

SetStyle(ControlStyles.DoubleBuffer, true); // 雙緩衝

或代碼二:

this.SetStyle(ControlStyles.DoubleBuffer | ControlStyles.UserPaint |

ControlStyles.AllPaintingInWmPaint, true);

this.UpdateStyles();

 

上述方式適合直接在窗體上繪製圖形,並且很容易做到。但有時我們需要在某個控件上繪製圖形,那該怎麼辦呢?原理跟直接在窗體上繪製圖形採用雙緩衝是一樣的,也要在控件的構造函數裏設置上述代碼一或代碼二。那麼又怎麼設置呢?

在Microsoft Visual Studio 2005環境下的,用的C#語言,並採用GDI+。目標是實現簡單的鼠標拖動畫線,並且要把之前畫過的線都重新畫出來。

整個程序使用了三個控件:一個SplitContainer控件、一個自定義的Panel控件和一個VS自帶的Panel控件。SplitContainer控件的大小設置成窗體寬、半窗體高並定位在窗體的下半部分。自定義的Panel控件和VS自帶的Panel控件都是通過設置它們的Dock屬性使它們綁定到SplitContainer控件的Panel1和Panel2上。附錄中會說到自定義的Panel控件是怎麼定義的。

窗體的上半部分採用雙緩衝。自定義的Panel控件採用了雙緩衝,是通過在自定義Panel控件時設置樣式來做到的(設置方法與窗體的雙緩衝設置方法一樣,如下面三條語句),這不能夠在面板的Paint方法裏直接設置,因爲SetStyle()在Panel類中不是public方法。VS自帶的Panel控件沒有采用雙緩衝。

 

SetStyle(ControlStyles.AllPaintingInWmPaint, true);

SetStyle(ControlStyles.UserPaint, true);

SetStyle(ControlStyles.OptimizedDoubleBuffer, true);

把三者結合

 

有兩種方式來創建Graphics對象:第一是在內存上創建一塊和顯示區域或控件相同大小的畫布,在這塊畫布上創建Graphics對象。接着所有的圖元都在這塊畫布上繪製,繪製完成以後再使用該畫布覆蓋顯示控件的背景,從而達到“顯示一次僅刷新一次”的效果!第二是直接在內存上創建Graphics對象。

 

 

使用雙緩衝的圖形可以減少或消除重繪顯示圖面時產生的閃爍。使用雙緩衝時,更新的圖形首先被繪製到內存的緩衝區中,然後,此緩衝區的內容被迅速寫入某些或所有顯示的圖面中。顯示圖形的重寫相對簡短,這通常可以減少或消除有時在更新圖形時出現的閃爍。

 

 

雙緩衝技術(C# GDI)

c#如何實現防窗體閃爍的功能。大家都會想到運用雙緩衝技術,那麼在c#中是如何做的?

1、 利用默認雙緩衝

(1)在應用程序中使用雙緩衝的最簡便的方法是使用 .NET Framework 爲窗體和控件提供的默認雙緩衝。通過將 DoubleBuffered 屬性設置爲 true。

this.DoubleBuffered=true;

(2)使用 SetStyle 方法可以爲 Windows 窗體和所創作的 Windows 控件啓用默認雙緩衝。

SetStyle(ControlStyles.OptimizedDoubleBuffer, true);

2、 手工設置雙緩衝

.netframework提供了一個類BufferedGraphicsContext負責單獨分配和管理圖形緩衝區。每個應用程序域都有自己的默認 BufferedGraphicsContext 實例來管理此應用程序的所有默認雙緩衝。大多數情況下,每個應用程序只有一個應用程序域,所以每個應用程序通常只有一個默認 BufferedGraphicsContext。默認 BufferedGraphicsContext 實例由 BufferedGraphicsManager 類管理。通過管理BufferedGraphicsContext實現雙緩衝的步驟如下:

(1)獲得對 BufferedGraphicsContext 類的實例的引用。

(2)通過調用 BufferedGraphicsContext.Allocate 方法創建 BufferedGraphics 類的實例。

(3)通過設置 BufferedGraphics.Graphics 屬性將圖形繪製到圖形緩衝區。

(4)當完成所有圖形緩衝區中的繪製操作時,可調用 BufferedGraphics.Render 方法將緩衝區的內容呈現到與該緩衝區關聯的繪圖圖面或者指定的繪圖圖面。

(5)完成呈現圖形之後,對 BufferedGraphics 實例調用釋放系統資源的 Dispose 方法。

完整的例子,在一個400*400的矩形框內繪製10000個隨機生成的小圓。

BufferedGraphicsContext current = BufferedGraphicsManager.Current; //(1)

BufferedGraphics bg = current.Allocate(this.CreateGraphics(),this.DisplayRectangle); //(2)

Graphics g = bg.Graphics;//(3)

//隨機 寬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)

BufferedGraphicsContext current = BufferedGraphicsManager.Current; //(1)

BufferedGraphics bg;

bg = current.Allocate(this.CreateGraphics(),this.DisplayRectangle); //(2)

Graphics g = bg.Graphics;//(3)

//隨機 寬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、 自己開闢一個緩衝區(如一個不顯示的Bitmap對象),在其中繪製完成後,再一次性顯示。

完整代碼如下:

view plaincopy to clipboardprint?

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));

BufferedGraphicsContext的構造函數

  • BufferedGraphicsContext

初始化BufferedGraphicsContext 類的新實例。

BufferedGraphicsContext的方法

  • Allocate(Graphics, Rectangle)

使用指定的Graphics 的像素格式,創建指定大小的圖形緩衝區。

  • Allocate(IntPtr, Rectangle)

使用指定的Graphics 的像素格式,創建指定大小的圖形緩衝區。

  • Dispose

Releases all resources used by theBufferedGraphicsContext.

  • Equals(Object)

確定指定的Object 是否等於當前的Object。(繼承自Object。)

  • Finalize

允許Object 在“垃圾回收”回收Object 之前嘗試釋放資源並執行其他清理操作。(繼承自Object。)

  • GetHashCode

用作特定類型的哈希函數。 (繼承自Object。)

  • GetType

獲取當前實例的Type。(繼承自Object。)

  • Invalidate

如果某個緩衝區已被分配但尚未釋放,則釋放當前的圖形緩衝區。

  • MemberwiseClone

創建當前Object 的淺表副本。(繼承自Object。)

  • ToString

返回表示當前Object 的String。(繼承自Object。)

BufferedGraphicsContext的屬性

  • MaximumBuffer

獲取或設置要使用的緩衝區的最大大小。

雙緩衝技術繪圖  

本文主要介紹 .Net 框架的基本繪圖技術。通過簡要的介紹和示例程序來探討繪圖技術的
優勢、劣勢以及其它相關注意事項。

  簡介

  幸運的是當編寫一個典型的Windows 窗體程序時,窗體和控件的繪製、效果等操作是
不需要特別加以考慮的。這是爲什麼呢?因爲通過使用 .Net 框架,開發人員可以拖動一
系列的控件到窗體上,並書寫一些簡單的與事件相關聯的代碼然後在IDE中按F5,一個完完
全全的窗體程序就誕生了!所有控件都將自己繪製自己,窗體或者控件的大小和縮放都調
整自如。在這裏經常會用到的,且需要引起一點注意的就是控件效果。遊戲,自定義圖表
控件以及屏幕保護程序的編寫會需要程序員額外撰寫用於響應 Paint 事件的代碼。

  本文針對那些Windows 窗體開發人員並有助於他們在應用程序編制過程中使用簡單的
繪圖技術。首先,我們會討論一些基本的繪圖概念。到底誰在負責進行繪製操作?Window
s 窗體程序是如何知道何時該進行繪製的?那些繪製代碼究竟被放置在哪裏?之後,還將
介紹圖像繪製的雙重緩衝區技術,你將會看到它是怎樣工作的,怎樣通過一個方法來實現
緩存和實際顯示的圖像間的交替。最後,我們將會探討”智能無效區域”,實際就是僅僅
重繪或者清除應用程序窗體上的無效部分,加快程序的顯示和響應速度。希望這些概念和
技術能夠引導讀者閱讀完本文,並且有助於更快和更有效的開發Windows 窗體程序。

  Windows 窗體使用GDI+圖像引擎,在本文中的所有繪圖代碼都會涉及使用託管的.Net
框架來操縱和使用Windows GDI+圖像引擎。

  儘管本文用於基本的窗體繪圖操作,但是它同樣提供了快速的、有效的且有助於提高
程序性能的技術和方法。所以,在通讀本文之前建議讀者對.Net框架有個基本的瞭解,包
括Windows 窗體事件處理、簡單的GDI+對象譬如Line,Pen和Brush等。熟悉Visual Basic
.Net或者C#編程語言。
概念

  Windows 應用程序是自己負責繪製的,當一個窗體”不乾淨”了,也就是說窗體改變
了大小,或者部分被其它程序窗體遮蓋,或者從最小化狀態恢復時,程序都會收到需要繪
制的信息。Windows把這種”不乾淨”狀態稱爲”無效的(Invalidated)”狀態,我們理解爲:需要重繪,當Windows 窗體程序需要重繪窗體時它會從Windows消息隊列中獲取繪製的信息。這個信息經過.Net框架封裝然後傳遞到窗體的 PaintBackground 和 Paint 事件中去,在上述事件中適當的書寫專門用於繪製的代碼即可。
簡單的繪圖示例如下:
using System;
using System.Drawing;
using System.Windows.Forms;
public class BasicX : Form {

 public BasicX() {
  InitializeComponent();
 }

 private void BasicX_Paint(object sender, PaintEventArgs e) {
  Graphics g = e.Graphics;
  Pen p = new Pen(Color.Red);
  int width = ClientRectangle.Width;
  int height= ClientRectangle.Height;
  g.DrawLine(p, 0,0, width, height);
  g.DrawLine(p, 0, height, width, 0);
  p.Dispose();
 }

 private void InitializeComponent() {
  this.SetStyle(ControlStyles.ResizeRedraw, true);
  this.ClientSize = new System.Drawing.Size(300, 300);
  this.Text = "BasicX";
  this.Paint += new PaintEventHandler(this.BasicX_Paint);
 }

 [System.STAThreadAttribute()]
 public static void Main() {
  Application.Run(new BasicX());
 }
}
上述代碼分成兩個基本的步驟來創建示例程序。首先 InitializeComponent 方法包含
一些屬性的設置和附加窗體 Paint 事件的處理過程。注意,在方法中控件的樣式也同時被
設置,設置控件的樣式也是自定義Windows 窗體及控件行爲的一種有效途徑,譬如:控件
的"ResizeRedraw"屬性指示當窗體的大小變化發生以後需要對其完全進行重繪,也就是說
重繪時總是需要對整個窗體的客戶區域進行重繪。窗體的“客戶區域”是指除了標題欄和
邊框的所有窗體區域。可以進行一個有趣的試驗,取消該控件的屬性然後再運行程序,我
們可以很明顯的看出爲什麼該屬性會被經常的設置,因爲窗體調整大小後的無效區域根本
不會被重繪。

  好了,我們需要注意一下BasicX_Paint方法,正如先前所提到的,Paint 事件在程序
需要重繪時被激活,程序窗體利用Paint事件來負責迴應需要重繪的系統消息,BasicX_Pa
int方法的調用需要一個對象 sender 和一個PaintEventArgs類型的變量,PaintEventArg
s類的實例或稱之爲變量 e 封裝了兩個重要的數據,第一個就是窗體的 Graphics 對象,
該對象表示窗體可繪製的表面也稱之爲畫布用於繪製諸如線、文本以及圖像等,第二個數
據就是ClipRectangle,該Rectangle對象表示窗體上無效的的矩形範圍,或者說就是窗體
需要重繪的區域。記住,當窗體的ResizeRedDraw設置後,調整大小後該ClipRectangle的
大小實際就等於窗體整個客戶區域的大小,或者是被其它程序窗體遮蓋的那部分剪切區域
。關於部分剪切區域的用處我們會在智能重繪章節作更詳細的闡述。
雙重緩衝區繪圖技術

  雙重緩衝區技術能夠使程序的繪圖更加快速和平滑,有效減少繪製時的圖像閃爍。該
技術的基本原理是先將圖像繪製到內存中的一塊畫布上,一旦所有的繪製操作都完成了,
再將內存中的畫布推到窗體的或者控件的表面將其顯示出來。通過這種操作後的程序能使
用戶感覺其更加快速和美觀。

  下面提供的示例程序能夠闡明雙重緩衝區的概念和實現方法,這個示例所包含的功能
已相當完整,且完全可以在實際應用中使用。在該章節後面還會提及該技術應該配合控件
的一些屬性設置才能達到更好的效果。

  要想領略雙重緩衝區繪圖技術所帶來的好處就請運行SpiderWeb示例程序吧。程序啓動
並運行後對窗口大小進行調整,你會發現使用這種繪圖算法的效率不高,並且在調整大小
的過程中有大量的閃爍出現。
縱觀程序的源碼你會發現在程序Paint事件激活後是通過調用LineDrawRoutine方法來實現
線的繪製的。LineDrawRoutine方法有兩個參數,第一個是Graphics對象是用於繪製線條的
地方,第二個是繪圖工具Pen對象用來畫線條。代碼相當簡單,一個循環語句,LINEFREQ常量等,程序從窗體表面的左下一直劃線到其右上。請注意,程序使用浮點數來計算在窗體
上的繪製位置,這樣做的好處就是當窗體的大小發生變化時位置數據會更加精確。

private void LineDrawRoutine(Graphics g, Pen p) {
 float width = ClientRectangle.Width;
 float height = ClientRectangle.Height;
 float xDelta = width / LINEFREQ;
 float yDelta = height / LINEFREQ;

 for (int i = 0; i < LINEFREQ; i++) {
  g.DrawLine(p, 0, height - (yDelta * i), xDelta * i, 0);
 }
}

  撰寫很簡單的用於響應Paint事件SpiderWeb_Paint的代碼,正如前面所提到的,Grap
hics對象就是從Paint事件參數PaintEventArgs對象中提取出來的表示窗體的繪製表面。這
個Graphics對象連同新創建Pen對象一起傳遞給LineDrawRoutine方法來畫出蜘蛛網似的線
條,使用完Graphics對象和Pen對象後釋放其佔用的資源,那麼整個繪製操作就完成了。

private void SpiderWeb_Paint(object sender, PaintEventArgs e) {
 Graphics g = e.Graphics;
 Pen redPen = new Pen(Color.Red);
 //call our isolated drawing routing
 LineDrawRoutine(g, redPen);
 redPen.Dispose();
 g.Dispose();
}

  那麼到底作怎麼樣的改動才能使上面的SpiderWeb程序實現簡單的雙重緩衝區技術呢?
原理其實相當簡單,就是將應該畫到窗體表面的繪製操作改成先畫到內存中的位圖上,Li
neDrawRoutine向這個在內存中隱藏的畫布執行同樣的蜘蛛網繪製操作,等到繪製完畢再通
過調用Graphics.DrawImage方法將隱藏的畫布上內容推到窗體表面來顯示出來,最後,再
加上一些小的改動一個高性能的繪圖窗體程序就完成了。

  比較下面雙重緩衝區繪圖事件與前面介紹的簡單繪圖事件間的區別:

private void SpiderWeb_DblBuff_Paint(object sender, PaintEventArgs e) {
 Graphics g = e.Graphics;
 Pen bluePen = new Pen(Color.Blue);
 //create our offscreen bitmap
 Bitmap localBitmap = new Bitmap(ClientRectangle.Width,ClientRectangle.Height
);
 Graphics bitmapGraphics = Graphics.FromImage(localBitmap);
 //call our isolated drawing routing
 LineDrawRoutine(bitmapGraphics, bluePen);
 //push our bitmap forward to the screen
 g.DrawImage(localBitmap, 0, 0);
 bitmapGraphics.Dispose();

 bluePen.Dispose();
 localBitmap.Dispose();
 g.Dispose();
}

上面的示例代碼創建了內存位圖對象,它的大小等於窗體的客戶區域(就是繪圖表面)
的大小,通過調用Graphics.FromImage將內存中位圖的引用傳遞給Graphics對象,也就是
說後面所有對該Graphics對象的操作實際上都是對內存中的位圖進行操作的,該操作在C+
+中等同於將位圖對象的指針複製給Graphics對象,兩個對象使用的是同一塊內存地址。現
在Graphics對象表示的是屏幕後方的一塊畫布,而它在雙重緩衝區技術中起到至關重要的
作用。所有的線條繪製操作都已經針對於內存中的位圖對象,下一步就通過調用DrawImag
e方法將該位圖複製到窗體,蜘蛛網的線條就會立刻顯示在窗體的繪製表面而且絲毫沒有閃
爍出現。
這一系列的操作完成後還不是特別有效,因爲我們先前提到了,控件的樣式也是定義Wind
ows 窗體程序行爲的一條途徑,爲了更好的實現雙重緩衝區必須設置控件的Opaque屬性,
這個屬性指明窗體是不負責在後臺繪製自己的,換句話說,如果這個屬性設置了,那麼必
須爲清除和重繪操作添加相關的代碼。具備雙重緩衝區版本的SpiderWeb程序通過以上的設
置在每一次需要重繪時都表現良好,窗體表面用其自己的背景色進行清除,這樣就更加減
少了閃爍的出現。

public SpiderWeb_DblBuff() {
 SetStyle(ControlStyles.ResizeRedraw | ControlStyles.Opaque, true);
}

private void SpiderWeb_DblBuff_Paint(object sender, PaintEventArgs e) {
 //create our offscreen bitmap
 Bitmap localBitmap = new Bitmap(ClientRectangle.Width, ClientRectangle.Heigh
t);
 Graphics bitmapGraphics = Graphics.FromImage(localBitmap);
 bitmapGraphics.Clear(BackColor);
 //call our isolated drawing routing
 LineDrawRoutine(bitmapGraphics, bluePen);
}

  結果怎麼樣?圖像的繪製平滑多了。從內存中將蜘蛛網的線條推到前臺以顯示出來是
完全沒有閃爍的,但是我們還是稍微停頓一下,先將內存中的位圖修整一下再顯示出來,
可以添加一行代碼以便使線條看上去更加平坦。

bitmapGraphics.SmoothingMode = SmoothingMode.AntiAlias;

  在將內存中的位圖對象賦給Graphics後通過放置這行代碼,我們在畫布上所畫的每一
個線條都使用了反鋸齒,使凹凸不平的線條顯得更加平坦。
完成了簡單的雙重緩衝區應用後有兩個問題需要向讀者闡明,.Net中的某些控件例如
:Button、PictureBox、Label還有PropertyGrid都已經很好的利用了該技術!這些控件在
默認狀態下會自動啓用雙重緩衝區技術,用戶可以通過對“DoubleBuffer”屬性的設置來
就可以實現雙重緩衝區技術。所以,用戶若使用PictureBox來繪製蜘蛛網將會更有效率一
些,而且也使程序變得更加簡單了。

  我們在這裏討論的雙重緩衝區技術既不是完全被優化但也沒有什麼太大的負面影響。
雙重緩衝區技術是減少Windows 窗體繪製時閃爍的一條重要途徑,但是它也確實消耗不少
內存,因爲它將會使用雙倍的內存空間:應用程序所顯示的圖像和屏幕後方內存中的圖像
。每次Paint事件被激活時都會動態的創建位圖對象,這種機制會相當耗費內存。而自帶雙
重緩衝區技術的控件在使用DoubleBuffer屬性後執行起來的優化程度則會更好一些。

  使用GDI+的DIB(與設備無關的位圖)對象來實現這種畫面以外的內存緩衝,自帶雙重緩
衝區機制的控件則能好的利用該位圖對象。DIB是底層Win32的對象用於高效的屏幕繪製。
同樣,值得注意的是GDI+的第一個版本GDI中僅與硬件加速有關以及一些簡單功能可以直接
使用,由於這樣的限制,像反鋸齒和半透明等屏幕繪製方法執行起來的速度則相當慢。盡
管雙重緩衝區機制消耗了一些內存但是它的使用不容置疑的增強了程序的執行性能。

智能重繪,在繪製前需要斟酌一下

“智能無效”(智能重繪)就是在暗示程序員應該明白僅應對程序中無效的區域進行重
繪,對Regions對象所對應的無效區域進行重繪可以提高繪製性能,使用Regions對象你可
以僅排除或繪製控件和窗體的部分區域已獲得更好的性能。我們現在就開始來看一下Basi
cClip示例程序,這個程序使用保存在PaintEventArgs對象的ClipRectangle對象,之前我
們已經提及,無論何時當程序的大小發生變化時Paint事件都會被激活。BasicClip示例程
序用紅和藍兩種顏色填充剪切的矩形區域,利用不同的速度調整窗體的大小几次以後,你
會發現繪製的矩形區域其實就是窗體的無效區域(包括大於原始窗體大小的區域部分和縮少
了的區域部分),示例程序的Paint事件代碼如下:

private void BasicClip_Paint(object sender, PaintEventArgs e) {
 Graphics g = e.Graphics;
 //swap colors
 if (currentBrush.Color == Color.Red)
  currentBrush.Color = Color.Blue;
 else
  currentBrush.Color = Color.Red;
  g.FillRectangle(currentBrush, e.ClipRectangle);
  g.Dispose();
}

  該示例程序的唯一目的就是演示怎樣僅針對部分區域進行圖形繪製。
 Regions是一種被用來定義Windows 窗體或者控件區域的對象,調整窗體大小後所獲得
的Regions就是窗體重繪的最小區域。當程序需要進行繪製的時候僅繪製感興趣的特殊區域
,這樣繪製更小的區域就會使程序的運行速度更快。

  爲了更好的演示Regions的用法,請查看TextCliping示例程序。該程序重載了OnPain
tBackground和OnPaint方法,直接重載這些方法比偵聽事件更能保證代碼在其它的繪製操
作之前被調用,而且對於自定義控件的繪製也更加有效。爲了清楚起見,示例程序提供了
一個Setup方法,該方法定義了全局的Graphics對象。

private void Setup() {
 GraphicsPath textPath = new GraphicsPath();
textPath.AddString(displayString, FontFamily.GenericSerif,
0, 75, new Point(10, 50), new StringFormat());
 textRegion = new Region(textPath);
 backgroundBrush = new TextureBrush(new Bitmap("CoffeeBeanSmall.jpg"),
WrapMode.Tile);
 foregroundBrush = new SolidBrush(Color.Red);
}

  上面的Setup方法首先定義一個空的GraphicsPath對象變量textPath,下一步字符串“
Windows Forms”的邊界被添加到該路徑中,圍繞這個輪廓創建Region。這樣,一個被繪製
在窗體表面的以字符串輪廓爲區域的Region就被創建了。最後,Setup方法創建以材質刷子
爲背景和以實色刷子爲前景來繪製窗體。

protected override void OnPaintBackground(PaintEventArgs e) {
 base.OnPaintBackground(e);
 Graphics bgGraphics = e.Graphics;
 bgGraphics.SetClip(textRegion, CombineMode.Exclude);
 bgGraphics.FillRectangle(backgroundBrush, e.ClipRectangle);
 bgGraphics.Dispose();
}

  上面定義的OnPaintBackground方法先立刻調用基類方法,這能夠保證所有底層繪製的
代碼都能夠被執行。下一步,從PaintEventArgs中獲得Graphics對象,再將Graphics對象
的剪切區域定義爲textRegion對象。通過指定CombineMode.Exclude參數,明確無論在哪裏
繪製或怎樣繪製Graphics對象都不繪製textRegion區域內部。

protected override void OnPaint(PaintEventArgs e) {
 base.OnPaint(e);
 Graphics fgGraphics = e.Graphics;
 fgGraphics.FillRegion(foregroundBrush, textRegion);
 fgGraphics.Dispose();
}

  最後,OnPaint事件負責精確的繪製出字符串。可以很容易的通過調用Graphics的Fil
lRegion方法來實現。通過指定的前景刷子foregroundBrush和textRegion且僅是該區域被
繪製。結果,Windows 窗體程序在運行之前確實“思考”該怎樣進行繪製。

TextClipping示例程序,通過Region定義的Windows Forms字符串。能夠使程序在繪製時避
開一個區域。

  適當的組合使用區域和智能重繪你可以編寫出運行速度快且不會引起閃爍的繪製代碼
,並且比單獨使用雙重緩衝區繪製還要節省內存的消耗。

  結論

  如果你的程序確定要進行繪製操作,使用幾種技術可以增強繪製性能。確保爭取設置
控件屬性以及適當的Paint事件處理是編寫健壯程序的開始。在權衡好利弊後可以使用雙重
緩衝區技術產生非常“保護視力”的結果。最後,在實際繪製前進行思考到底哪些客戶區
域或Region需要被繪製將非常有益。

發佈了29 篇原創文章 · 獲贊 59 · 訪問量 12萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章