C#双缓冲技术实例详解

本文实例分析了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。

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);
g.Dispose();
myBuffer.Dispose();//释放资源

至此,双缓冲问题解决,两种方式的实现效果都一样,但最后一种方式的占有的内存很少,不会出现内存泄露!

接下来是对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时,重新画图。

更多关于C#相关内容感兴趣的读者可查看本站专题:《C#面向对象程序设计入门教程》、《C#常见控件用法教程》及《C#数据结构与算法教程》

希望本文所述对大家C#程序设计有所帮助。

(0)

相关推荐

  • C# Memcached缓存用法实例详解

    本文实例讲述了C#中Memcached缓存的用法,分享给大家供大家参考.具体方法如下: ICacheStrategy.cs文件如下: 复制代码 代码如下: public interface ICacheStrategy {         /// <summary>         /// 添加数据到缓存         /// </summary>         /// <param name="objId">缓存名称</param>

  • C#读取中文字符及清空缓冲区的实现代码

    开时始,得到的中文文件中的字符是乱码的,鸟符号看的头晕.于是就细究streamreader读取的编码格式,默认的编码是ascii,单字节的,就尝试utf8,乱码:尝试gb2312,OK! 可另一个问题又出现了,得到的两个文件的行数都不到1500行,尝试N次还是不行,很郁闷.google了下,看到try catch,就想到释放缓冲区,结果很HAPPY! 复制代码 代码如下: private static void FnFileProcess() { StreamReader reader = ne

  • c#自带缓存使用方法 c#移除清理缓存

    复制代码 代码如下: /// <summary>/// 获取数据缓存/// </summary>/// <param name="CacheKey">键</param>public static object GetCache(string CacheKey){    System.Web.Caching.Cache objCache = HttpRuntime.Cache;    return objCache[CacheKey];}/

  • C#中缓存的基本用法总结

    本文初步探讨了C#缓存的原理及应用,并以实例加以分析总结,这些对C#初学者来说是很有必要熟练掌握的内容.具体如下: 一.概述: 缓存应用目的:缓存主要是为了提高数据的读取速度.因为服务器和应用客户端之间存在着流量的瓶颈,所以读取大容量数据时,使用缓存来直接为客户端服务,可以减少客户端与服务器端的数据交互,从而大大提高程序的性能. 1.缓存的引用空间:System.Web.Caching; 缓存命名空间主要提供三种操作:缓存数据对象.对象的缓存依赖和数据库的缓存依赖.其中缓存任何对象都使用一个类C

  • C#自定义缓存封装类实例

    本文实例讲述了C#自定义缓存封装类.分享给大家供大家参考.具体如下: 这个自定义的C#类封装了部分常用的缓存操作,包括写入缓存,读取缓存,设置缓存过期时间等等,简化了C#的缓存操作,代码非常简单,易于阅读. using System; using System.Web; namespace DotNet.Utilities { /// <summary> /// 缓存相关的操作类 /// </summary> public class DataCache { /// <sum

  • C#手工双缓冲技术用法实例分析

    本文实例讲述了C#手工双缓冲技术.分享给大家供大家参考.具体如下: using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; namespace advanced_drawing { public partial class F

  • asp.net(C#)遍历memcached缓存对象

    STATS命令 遍历memcached缓存对象(C#)转载之青草堂 出于性能考虑,memcached没有提供遍历功能,不过我们可以通过以下两个stats命令得到所有的缓存对象. 1.stats items 显示各个slab中item的数目. 2.stats cachedump slab_id limit_num 显示某个slab中的前limit_num个key列表,显示格式:ITEM key_name [ value_length b; expire_time|access_time s] 除了

  • C#默认双缓冲技术实例分析

    本文实例讲述了C#默认双缓冲技术.分享给大家供大家参考.具体如下: using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; namespace advanced_drawing { public partial class F

  • C#词法分析器之输入缓冲和代码定位的应用分析

    一.输入缓冲 在介绍如何进行词法分析之前,先来说说一个不怎么被提及的问题--怎么从源文件中读取字符流.为什么这个问题这么重要呢?是因为在词法分析中,对字符流是有要求的,它必须能够支持回退操作(就是将多个字符放回到流中,以后会再次被读取). 先来解释下为什么需要支持回退操作,举个简单的例子来说,现在要对两个模式进行匹配: 图 1 流的回退过程 上面是一个简单的匹配过程,仅为了展示回退过程,在后面实现 DFA 模拟器时会详细解释是如何匹配词素的. 现在来看看 C# 中与输入相关的类,有 Stream

  • C#双缓冲实现方法(可防止闪屏)

    本文实例讲述了C#双缓冲实现方法.分享给大家供大家参考,具体如下: // 该调用是 Windows.Forms 窗体设计器所必需的. InitializeComponent(); // TODO: 在 InitComponent 调用后添加任何初始化 this.SetStyle(ControlStyles.AllPaintingInWmPaint,true); //开启双缓冲 this.SetStyle(ControlStyles.DoubleBuffer,true); this.SetStyl

随机推荐