C#圖片閃爍


導致畫面閃爍的關鍵原因分析:
      一、繪制窗口由於大小位置狀態改變進行重繪操作時
     繪圖窗口內容或大小每改變一次,都要調用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();
            }
        }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM