一、 画面闪烁问题与双缓冲技术

1.1 导致画面闪烁的关键原因分析:

1  绘制窗口由于大小位置状态改变进行重绘操作时
  绘图窗口内容或大小每改变一次,都要调用Paint事件进行重绘操作,该操作会使画面重新刷新一次以维持窗口正常显示。刷新过程中会导致所有图元重新绘制,

而各个图元的重绘操作并不会导致Paint事件发生,因此窗口的每一次刷新只会调用Paint事件一次。窗口刷新一次的过程中,每一个图元的重绘都会立即显示到窗口,

因此整个窗口中,只要是图元所在的位置,都在刷新,而刷新的时间是有差别的,闪烁现象自然会出现。

  所以说,此时导致窗口闪烁现象的关键因素并不在于Paint事件调用的次数多少,而在于各个图元的重绘。

  根据以上分析可知,当图数目不多时,窗口刷新的位置也不多,窗口闪烁效果并不严重;当图元数目较多时,绘图窗口进行重绘的图元数量增加,绘图窗口每一次刷新

都会导致较多的图元重新绘制,窗口的较多位置都在刷新,闪烁现象自然就会越来越严重。特别是图元比较大绘制时间比较长时,闪烁问题会更加严重,因为时间延迟会更长。

解决上述问题的关键在于窗口刷新一次的过程中,让所有图元同时显示到窗口

2、进行鼠标跟踪绘制操作或者对图元进行变形操作时
  当进行鼠标跟踪绘制操作或者对图元进行变形操作时,Paint事件会频繁发生,这会使窗口的刷新次数大大增加。虽然窗口刷新一次的过程中所有图元同时显示到窗口,但

也会有时间延迟,因为此时窗口刷新的时间间隔远小于图元每一次显示到窗口所用的时间。因此闪烁现象并不能完全消除!
  所以说,此时导致窗口闪烁现象的关键因素在于Paint事件发生的次数多少
  解决此问题的关键在于:设置窗体或控件的几个关键属性

1.2 双缓冲的关键技术

1、设置显示图元控件的几个属性,这样可以使效果更加明显。

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

2、窗口刷新一次的过程中,让所有图元同时显示到窗口。

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、  利用默认双缓冲

(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;
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对象,在其中绘制完成后,再一次性显示。

完整代码如下:

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) //draggingmousedrag = 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时,重新画图。

二、示例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  }
}  

当变化窗体时,会导致图像出现变形,可把窗体属性中的ResizeReDrae 设置为 true

增加绘制随机图形功能的动画效果如下:

现在将源码贡献自此,让不太懂双缓冲绘图的有一个大致的了解,以便少走笔者学习的弯路。如有问题,欢迎询问评论。

Source Code Download Here !

参考文章

ShinePans , C#-gdi绘图,双缓冲绘图,Paint事件的触发

飛雲若雪, C#双缓冲绘图

一个年轻人, c# 双缓冲 技术与例子(解决应用程序闪烁问题)

C#-gdi绘图,双缓冲绘图,Paint事件的触发相关推荐

  1. c语言双缓冲怎么用,C语言游戏编程:GDI怎么实现双缓冲绘图去掉闪烁

    在上篇文章中将我要用 C语言重新写一个俄罗斯方块 ,使用的是GDI的绘图模式(目前正在移植到DX上去,想添加一些更好友好的动画).数据与动画分离,动画的帧率保持在30左右.但是绘图的时候画面出现了强烈 ...

  2. c#双缓冲绘图(不闪烁的几种方法)

    C#绘图双缓冲 C#双缓冲解释: 简单说就是当我们在进行画图操作时,系统并不是直接把内容呈现到屏幕上,而是先在内存中保存,然后一次性把结果输出来,如果没用双缓冲的话,你会发现在画图过程中屏幕会闪的很厉 ...

  3. C#绘图双缓冲技术总结(转)

    GDI+的双缓冲问题 一直以来的误区:.net1.1 和 .net 2.0 在处理控件双缓冲上是有区别的. .net 1.1 中,使用:this.SetStyle(ControlStyles.Doub ...

  4. 【QT】QT从零入门教程(十三):QT画笔工具QPainter (双缓冲绘图)

      QPainter 用于执行绘图操作,其提供的 API 在 GUI 或 QImage.QOpenGLPaintDevice.QWidget 和QPaintDevice 显示图形(线.形状.渐变等). ...

  5. 【Qt】2D绘图之双缓冲绘图

    00. 目录 文章目录 00. 目录 01. 概述 02. 开发环境 03. 绘制矩形 04. 双缓冲绘图 05. 附录 01. 概述 所谓的双缓冲绘图的概念.双缓冲(double-buffers)绘 ...

  6. android双缓冲绘图技术分析

    转载请说明出处:http://www.jianshu.com/p/efc0bebfd22e 双缓冲.多缓冲技术在计算机科学中其实是一个广义的概念,不过其本质上的意思都是差不多的.今天我们就来讲一讲双缓 ...

  7. [Qt教程] 第18篇 2D绘图(八)双缓冲绘图

    [Qt教程] 第18篇 2D绘图(八)双缓冲绘图 楼主  发表于 2013-5-2 22:07:23 | 查看: 789| 回复: 1 双缓冲绘图 版权声明 该文章原创于Qter开源社区(www.qt ...

  8. MFC绘制动态曲线,用双缓冲绘图技术防闪烁

    转载自:ZHY_ongu的博客:MFC绘制动态曲线,用双缓冲绘图技术防闪烁 先上效果图    随着时间的推移,曲线向右平移,同时X轴的时间坐标跟着更新. 一.如何绘制动态曲线. 所谓动画,都是一帧一帧 ...

  9. VC中的双缓冲绘图技术

    之前在做图形绘制的时候,发现在拖动图形时候,会出现闪烁的情况,后来就上网找了一下双缓冲绘图,本文非原创,只是想保存下来,以后要用的时候不用再到处去搜,也希望能帮助有这方面困惑的朋友 原文来自http: ...

  10. Qt之旅---10 双缓冲绘图

    1 基本知识 但我们使用painter进行快速绘图时会看到类似图下 的情况 双缓冲绘图技术 的原理 用两个画布进行绘图,一个用于显示,一个用于绘制,也就是将图画之后再显示 就可以避免上面的情况. 2 ...

最新文章

  1. springboot添加多数据源连接池并配置Mybatis
  2. linux双网卡绑定+单网卡多IP绑定
  3. python使用正则表达式识别大写字母并在大写字母前插入空格
  4. 社交产品后端架构设计--转载
  5. 神策数据受邀参加全国 APP 个人信息保护监管会
  6. python 中国社区_python
  7. python中isinstance(3、object)_python中isinstance函数判断各种类型的小细节
  8. C# 代码重启windows服务
  9. 策略人员平常的工作都有哪一些?
  10. 邻居家小孩来问WiFi密码,告诉后邻居家竟然几部手机电视全用上,该怎么办?
  11. Waymo无人车卷入严重事故!车身损毁严重,安全员受轻伤
  12. 关于面向对象和面向过程本质的区别(个人感悟)
  13. 云上的消费你真的算清楚了?
  14. 谈谈工业App (1)
  15. 联想g500网卡linux驱动,联想g500无线网卡驱动下载-lenovog500无线网卡驱动v10.0.0.225 官方版 - 极光下载站...
  16. 2017年第十七届迪培思广州国际广告展会刊(参展商名录)
  17. 计算机网络第三章——数据链路层
  18. 关于python赋值语句下列选项中描述正确的是_关于 Python 语句 P = –P,以下选项中描述正确的是________...
  19. XAMPP之二-Apache无法启动解决办法
  20. 算法总结-UT哈希算法

热门文章

  1. 152.乘积最大子序列
  2. 凸优化第六章逼近与拟合 6.1范数逼近
  3. Springboot项目优化和Jvm调优和启停脚本
  4. 机器学习系列(5)_从白富美相亲看特征预处理与选择(上)
  5. 01串匹配问题 —— Binary Strings【Gym - 101845B】
  6. 【完全背包】自然数拆分Lunatic版
  7. windows anaconda 虚拟环境 与 系统变量冲突 pyinstaller 打包软件过大
  8. 一道很有趣的多元函数求极值问题
  9. php 浮点型能位运算,重读PHP手册笔记系列(二)
  10. 645.错误的集合(力扣leetcode) 博主可答疑该问题