C#-gdi繪圖,雙緩衝繪圖,Paint事件的觸發

1、 畫面閃爍問題與雙緩衝技術

1.1 致使畫面閃爍的關鍵緣由分析:

1  繪製窗口因爲大小位置狀態改變進行重繪操做時
  繪圖窗口內容或大小每改變一次,都要調用Paint事件進行重繪操做,該操做會使畫面從新刷新一次以維持窗口正常顯示。刷新過程當中會致使全部圖元從新繪製,html

而各個圖元的重繪操做並不會致使Paint事件發生,所以窗口的每一次刷新只會調用Paint事件一次。窗口刷新一次的過程當中,每個圖元的重繪都會當即顯示到窗口,c#

所以整個窗口中,只要是圖元所在的位置,都在刷新,而刷新的時間是有差異的,閃爍現象天然會出現
dom

  因此說,此時致使窗口閃爍現象的關鍵因素並不在於Paint事件調用的次數多少,而在於各個圖元的重繪。
編輯器

  根據以上分析可知,當圖數目很少時,窗口刷新的位置也很少,窗口閃爍效果並不嚴重;當圖元數目較多時,繪圖窗口進行重繪的圖元數量增長,繪圖窗口每一次刷新ide

都會致使較多的圖元從新繪製,窗口的較多位置都在刷新,閃爍現象天然就會愈來愈嚴重。特別是圖元比較大繪製時間比較長時,閃爍問題會更加嚴重,由於時間延遲會更長。
post

解決上述問題的關鍵在於窗口刷新一次的過程當中,讓全部圖元同時顯示到窗口學習

 

二、進行鼠標跟蹤繪製操做或者對圖元進行變形操做時
  當進行鼠標跟蹤繪製操做或者對圖元進行變形操做時,Paint事件會頻繁發生,這會使窗口的刷新次數大大增長。雖然窗口刷新一次的過程當中全部圖元同時顯示到窗口,但
動畫

也會有時間延遲,由於此時窗口刷新的時間間隔遠小於圖元每一次顯示到窗口所用的時間。所以閃爍現象並不能徹底消除!
  因此說,此時致使窗口閃爍現象的關鍵因素在於Paint事件發生的次數多少
  解決此問題的關鍵在於:設置窗體或控件的幾個關鍵屬性
ui

1.2 雙緩衝的關鍵技術

一、設置顯示圖元控件的幾個屬性,這樣可使效果更加明顯。this

this.SetStyle(ControlStyles.OptimizedDoubleBuffer|ControlStyles.ResizeRedraw|ControlStyles.AllPaintingInWmPaint,true); 

二、窗口刷新一次的過程當中,讓全部圖元同時顯示到窗口。

Bitmap bmp=null;
Graphics g_bmp=null;
bmp=new Bitmap(this.Width,this.Height);
g_bmp=Graphics.FromImage(bmp);
g_bmp.Clear(this.BackColor);
g_bmp.DrawString("重繪",this.Font,new SolidBrush(this.ForeColor),this.Location.X+1,this.Location.Y+1);
this.Refresh();

//在OnPaint方法中實現下面代碼
private void this_Paint(object sender,PaintEventArgs e)
{
    Graphics g=e.Graphics;
    if(g==null) return;
    if(g_bmp!=null)
    {
        g.DrawImage((Image)bmp,0,0);
    }
}

1.3  窗口刷新一次的過程當中,讓全部圖元同時顯示到窗口 

能夠經過如下幾種方式實現,這幾種方式都涉及到Graphics對象的建立方式。具體實現:

一、  利用默認雙緩衝

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

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

   SetStyle(ControlStyles.OptimizedDoubleBuffer, true);


二、  手工設置雙緩衝
.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;
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)

三、   本身開闢一個緩衝區

如一個不顯示的Bitmap對象,在其中繪製完成後,再一次性顯示。

完整代碼以下:

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


另一個例子,差很少
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);
myBuffer.Dispose();//釋放資源

至此,雙緩衝問題解決,兩種方式的實現效果都同樣,但最後一種方式的佔有的內存不多,不會出現內存泄露!

1.4 對acdsee拖動圖片效果的實現

開始不懂雙緩衝,覺得雙緩衝能夠解決這個問題,結果發現使用了雙緩衝沒啥效果,請教了高人,而後修改了些代碼,完成這個效果。
圖片是在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);//圖片移動後顯示的底色
            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時,從新畫圖。

 

2、示例1

在使用gdi技術繪圖時,有時會發現圖形線條不夠流暢,或者在改變窗體大小時會閃爍不斷的現象.(Use DoubleBuffer to solve it!)

1.線條不流暢:窗體在重繪時自身重繪與圖形重繪之間存在時間差,致使兩者圖像顯示不協調

2.改變窗體大小不流暢:重繪時自身背景顏色與圖形顏色頻繁交替,形成視覺上的閃爍

 

下面,用四個圖形例子解決這個問題 :貝塞爾曲線,圓形,矩形,不規則圖形

 思路:首先用 width 定義位圖的寬度; height 定義位圖的高度

//建立一個與窗體工做區大小相同的位圖實例
// image:Image類的子類的實例引用
Bitmap localBitmap=new Bitmap(CilentRectangle.Width,CilentRectangle.Height) //建立位圖實例

// image:要繪製的圖像  x:繪製的圖像的左上角 x座標 y:左上角y座標
Graphics g=e.Graphics;//獲取窗體畫布
g.DrawImage(localBitmap,0,0);  //在窗體中繪製出內存中的圖像

實現:因爲Paint被 .net隱藏,咱們須要在窗體代碼中加上本身的Paint事件中繪製窗口

 
this.Paint += new System.Windows.Forms.PaintEventHandler(this.Form1_Paint);
private void InitializeComponent()
{
    this.SuspendLayout();
    // 
    // Form1
    // 
    this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
    this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
    this.ClientSize = new System.Drawing.Size(388, 325);
    this.MaximizeBox = false;
    this.MinimizeBox = false;
    this.Name = "Form1";
    this.Text = "雙緩衝技術繪圖";
    this.Paint += new System.Windows.Forms.PaintEventHandler(this.Form1_Paint);

    this.ResumeLayout(false);
}

源代碼:

using System;  
using System.Collections.Generic;  
using System.ComponentModel;  
using System.Data;  
using System.Drawing;  
using System.Linq;  
using System.Text;  
using System.Windows.Forms;  
using System.Drawing.Drawing2D;  
  
namespace DoubleBuffer  
{  
    public partial class Form1 : Form  
    {  
        public Form1()  
        {  
            InitializeComponent();  
        }  
  
        private void Form1_Paint(object sender, PaintEventArgs e)  
        {  
            Bitmap localBitmap = new Bitmap(ClientRectangle.Width, ClientRectangle.Height);  
            //建立位圖實例  
            Graphics g_bmp= Graphics.FromImage(localBitmap);  
            g_bmp.Clear(BackColor);  
            g_bmp.SmoothingMode = SmoothingMode.AntiAlias;  
            PaintImage(g_bmp);  
Graphics g
= e.Graphics;//獲取窗體畫布 g.DrawImage(localBitmap, 0, 0); //在窗體的畫布中繪畫出內存中的圖像 g_bmp.Dispose(); localBitmap.Dispose(); g.Dispose(); } private void PaintImage(Graphics g) { //繪圖 GraphicsPath path = new GraphicsPath(new Point[]{ new Point(100,60),new Point(350,200),new Point(105,225),new Point(190,ClientRectangle.Bottom), new Point(50,ClientRectangle.Bottom),new Point(50,180)}, new byte[]{ (byte)PathPointType.Start, (byte)PathPointType.Bezier, (byte)PathPointType.Bezier, (byte)PathPointType.Bezier, (byte)PathPointType.Line, (byte)PathPointType.Line}); PathGradientBrush pgb = new PathGradientBrush(path); pgb.SurroundColors = new Color[] { Color.Green, Color.Yellow, Color.Red, Color.Blue, Color.Orange, Color.LightBlue }; g.FillPath(pgb, path); g.DrawString("雙緩衝繪圖", new Font("宋體", 18, FontStyle.Bold), new SolidBrush(Color.Red), new PointF(110, 20)); g.DrawBeziers(new Pen(new SolidBrush(Color.Green),2),new Point[] {new Point(120,100),new Point(120,120),new Point(120,100),new Point(120,150)}); g.DrawArc(new Pen(new SolidBrush(Color.Blue), 5), new Rectangle(new Point(120, 170), new Size(60, 60)), 0, 360); g.DrawRectangle(new Pen(new SolidBrush(Color.Orange), 3), new Rectangle(new Point(240, 260), new Size(90, 50))); } } }

 

// Form 設計

namespace DoubleBuffer  
{  
    partial class Form1  
    {  
        /// <summary>  
        /// 必需的設計器變量。  
        /// </summary>  
        private System.ComponentModel.IContainer components = null;  
  
        /// <summary>  
        /// 清理全部正在使用的資源。  
        /// </summary>  
        /// <param name="disposing">若是應釋放託管資源,爲 true;不然爲 false。</param>  
        protected override void Dispose(bool disposing)  
        {  
            if (disposing && (components != null))  
            {  
                components.Dispose();  
            }  
            base.Dispose(disposing);  
        }  
 
        #region Windows 窗體設計器生成的代碼  
  
        /// <summary>  
        /// 設計器支持所需的方法 - 不要  
        /// 使用代碼編輯器修改此方法的內容。  
        /// </summary>  
        private void InitializeComponent()  
        {  
            this.SuspendLayout();  
            //   
            // Form1  
            //   
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);  
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;  
            this.ClientSize = new System.Drawing.Size(388, 325);  
            this.MaximizeBox = false;  
            this.MinimizeBox = false;  
            this.Name = "Form1";  
            this.Text = "雙緩衝技術繪圖";  
            this.Paint += new System.Windows.Forms.PaintEventHandler(this.Form1_Paint);  
            this.ResumeLayout(false);  
  
        }  
 
        #endregion  
    }  
}  

 

當變化窗體時,會致使圖像出現變形,可把窗體屬性中的ResizeRedraw 設置爲 true

增長繪製隨機圖形功能的動畫效果以下:

如今將源碼貢獻自此,讓不太懂雙緩衝繪圖的有一個大體的瞭解,以便少走筆者學習的彎路。若有問題,歡迎詢問評論。

Source Code Download Here !

 

 

參考文章

ShinePans C#-gdi繪圖,雙緩衝繪圖,Paint事件的觸發

飛雲若雪C#雙緩衝繪圖

一個年輕人, c# 雙緩衝 技術與例子(解決應用程序閃爍問題)

相關文章
相關標籤/搜索