標籤:
導致畫面閃爍的關鍵原因分析:
一、繪製視窗由於大小位置狀態改變進行重繪操作時,繪圖視窗內容或大小每改變一次,都要調用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、在記憶體中建立一塊“虛擬畫布”:
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);
還有的方式
在建構函式中加如下代碼
代碼一:
SetStyle(ControlStyles.UserPaint, true);
SetStyle(ControlStyles.AllPaintingInWmPaint, true); // 禁止擦除背景.
SetStyle(ControlStyles.DoubleBuffer, true); // 雙緩衝
代碼二:
this.SetStyle(ControlStyles.DoubleBuffer | ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint, true);
this.UpdateStyles();
=======================================================================
使用 GDI+ 雙緩衝 解決繪圖閃爍問題
現在的問題是很多人不知道怎麼怎麼使用GDI+ 雙緩衝
public partial class Form1 : Form
{
//記錄矩形位置的變數
Point p = Point .Empty ;
Point location = new Point(0, 0);
int x = 0;
int y = 0;
public Form1()
{
InitializeComponent();
//採用雙緩衝技術的控制項必需的設定
this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
this.SetStyle(ControlStyles.UserPaint, true);
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
Graphics g = e.Graphics;
g.FillRectangle(Brushes.Black, x, y, 200, 200);
}
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Right) return;
p = e.Location;
}
private void Form1_MouseUp(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Right) return;
location.X += e.X - p.X;
location.Y += e.Y - p.Y;
p = Point.Empty;
}
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
if (p == Point.Empty) return;
x = e.X - p.X + location.X;
y = e.Y - p.Y + location.Y;
this.Invalidate(true);//觸發Paint事件
}
}
這個簡單的例子實現了用滑鼠拖動視窗中矩形,利用雙緩衝技術使動畫過程不會產生閃爍.
在這個例子上我犯的錯誤:
在 OnPaint(PaintEventArgs e)中,我使用下面兩種方法擷取graphics 對象
Graphics g = this.CreateGraphics();
Graphics g = Graphics.FromHwnd(this.Handle);
這都使雙緩衝失效.
獲得graphics 對象還有兩種方法是
Graphics g = Graphics.FromImage(image); //後面將用此方法實現雙緩衝
Graphics g = e.Graphics; //這是唯一好使的方法
上面是在Form視窗直接繪製圖形,那麼如何在控制項上(比如Panel)利用雙緩衝技術繪圖呢?
在視窗窗建立一個Panel , 並修改一下代碼
private void panel1_Paint(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
g.FillRectangle(Brushes.Black, x, y, 200, 200);
}
運行後發現拖動在panel1上繪製的圖形依然有閃爍,那麼是不是應該這樣設定
panel1.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);//這樣並不行,
因為SetStyle()在Panel類中不是public方法
使用從Panel類繼承的MyPanel類 的建構函式中設定雙緩衝
public class MyPanel:Panel
{
public MyPanel()
{
this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
this.SetStyle(ControlStyles.UserPaint, true);
}
}
不管怎麼說這個方法的確好用,不過注意以後你使用的是MyPanel類,而不是Panel.
把自己定義MyPanel從工具列裡拖到視窗上和Panle一樣使用.
注意:在控制項上繪製就必須設定該控制項的DoubleBuffer,而不是Form的DoubleBuffer.
在此之前我採用自己的方法實現雙緩衝而不是控制項自身的DoubleBuffer
先在記憶體裡繪製圖形,包括清除舊畫面和繪製新畫面,然後將記憶體的圖形繪製到螢幕上
public void Draw(System.Windows.Forms.Panel _panel, float _x, float _y)
{
Graphics g = Graphics.FromHwnd(_panel.Handle);
try{
//在記憶體建立一塊和panel一大小的地區
Bitmap bitmap = new Bitmap(_panel.ClientSize.Width, _panel.ClientSize.Height);
using (Graphics buffer = Graphics.FromImage(bitmap))
{
//buffer中繪圖
buffer.Clear(_panel.BackColor); //用背景色填充畫面
buffer.Transform = matrix;
buffer.DrawImage(source, _x/Scale , _y/Scale ); //繪製新畫面
//螢幕繪圖
g.DrawImage(bitmap, 0, 0); //將buffer繪製到螢幕上
}
}
finally
{
g.Dispose();
}
}
使用上面方法不需要任何設定.
總結一下
與繪圖有關的ControlStyles
enum ControlStyles{
AllPainingInWmPaint, //將繪製階段摺疊入Paint事件
DoubleBuffer, //直到Paint返回,再顯示繪製對象
UserPaint, //用於自身有著特別繪製的控制項
Opaque, //忽略OnPaintBackground,Paint事件繪製整個地區
ResizeRedraw,//當調整控制項大小時使整個工作區無效
SupportsTransparentBackColor,//類比透明控制項
...
}
1.在OnPaint(PaintEventArgs e)或Paint中 使用e擷取graphics,我之所以費了很大週摺就是因為在網上找到一篇實現雙緩衝文章介紹,不要使用e擷取graphics,而用this.CreateGraphics(),還有的文章介紹了奇怪的方法居然最終也好使.
2.在繼承了Form和control 的控制項上利用雙緩衝繪製的時候,可以在控制項的建構函式裡設定雙緩衝屬性,而在視窗Form 裡設定doublebuffer,只針對視窗的繪製起作用.
3.使用自己的方法,雙緩衝的原理都是一樣的.
樣本:
private void DrawRectBackImage()
{
Graphics g = Graphics.FromHwnd(_currentChart.Handle);
try
{
//在記憶體建立一塊和panel一大小的地區
Bitmap bitmap = new Bitmap(_currentChart.ClientSize.Width, _currentChart.ClientSize.Height);
using (Graphics buffer = Graphics.FromImage(bitmap))
{
buffer.Transform =new Matrix();
//要畫的背景圖片
buffer.DrawImage(curChartImage, _currentChart.ClientRectangle);
//背景圖片上的內容
DrawFitAdjustRect(buffer);
//螢幕繪圖
g.DrawImage(bitmap, 0, 0); //將buffer繪製到螢幕上
}
}
finally
{
g.Dispose();
}
}
C#畫圖解決閃爍問題