本文實例分析了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對象的創建方式。
具體實現
1、利用默認雙緩沖
(1)在應用程序中使用雙緩沖的最簡便的方法是使用 .Net Framework 為窗體和控件提供的默認雙緩沖。通過將 DoubleBuffered 屬性設置為 true。
? 1this
.DoubleBuffered=
true
;
(2)使用 SetStyle 方法可以為 Windows 窗體和所創作的 Windows 控件啟用默認雙緩沖。
? 1SetStyle(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個隨機生成的小圓。
? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19BufferedGraphicsContext 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對象),在其中繪制完成後,再一次性顯示。
完整代碼如下:
? 1 2 3 4 5 6 7 8 9 10 11 12 13 14Bitmap 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));
另外一個例子,差不多
Graphics對象的創建方式:
a、在內存上創建一塊和顯示控件相同大小的畫布,在這塊畫布上創建Graphics對象。
接著所有的圖元都在這塊畫布上繪制,繪制完成以後再使用該畫布覆蓋顯示控件的背景,從而達到“顯示一次僅刷新一次”的效果!
實現代碼(在OnPaint方法中):
? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22Rectangle 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對象:
? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22Rectangle 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();
//釋放資源
至此,雙緩沖問題解決,兩種方式的實現效果都一樣,但最後一種方式的占有的內存很少,不會出現內存洩露!
接下來是對acdsee拖動圖片效果的實現。開始不懂雙緩沖,以為雙緩沖可以解決這個問題,結果發現使用了雙緩沖沒啥效果,請教了高人,然後修改了些代碼,完成這個效果。
圖片是在pictureBox1裡。
? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38Bitmap 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);
//圖片移動後顯示的底色
g.SmoothingMode = SmoothingMode.HighQuality;
//高質量
g.PixelOffsetMode = PixelOffsetMode.HighQuality;
//高像素偏移質量
g.DrawImageUnscaled(currentMap,
new
System.Drawing.Point(e.Location.X - mousedrag.X, e.Location.Y - mousedrag.Y));
//在g中移動圖片,原圖在(0,0)畫的,所以直接用new System.Drawing.Point(e.Location.X - mousedrag.X, e.Location.Y - mousedrag.Y)就好。
g.Dispose();
pictureBox1.Image = img;
//img是在鼠標這個位置時生成被移動後的暫時的圖片
}
}
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
;
}
}
說說思路,在鼠標點下時創建一個bitmap,currentMap,用它來存放當前圖像。鼠標移動時,根據鼠標位置畫圖,最後,鼠標up時,重新畫圖。