快速业务通道

Windows 窗体的.Net 框架绘图技术

作者 佚名技术 来源 NET编程 浏览 发布时间 2012-03-13
节作更详细的阐述。

  双重缓冲区绘图技术

  双重缓冲区技术能够使程序的绘图更加快速和平滑,有效减少绘制时的图像闪烁。该技术的基本原理是先将图像绘制到内存中的一块画布上,一旦所有的绘制操作都完成了,再将内存中的画布推到窗体的或者控件的表面将其显示出来。通过这种操作后的程序能使用户感觉其更加快速和美观。

  下面提供的示例程序能够阐明双重缓冲区的概念和实现方法,这个示例所包含的功能已相当完整,且完全可以在实际应用中使用。在该章节后面还会提及该技术应该配合控件的一些属性设置才能达到更好的效果。

  要想领略双重缓冲区绘图技术所带来的好处就请运行SpiderWeb示例程序吧。程序启动并运行后对窗口大小进行调整,你会发现使用这种绘图算法的效率不高,并且在调整大小的过程中有大量的闪烁出现。

  不具备双重缓冲区技术的SpiderWeb示例程序

  纵观程序的源码你会发现在程序Paint事件激活后是通过调用LineDrawRoutine方法来实现线的绘制的。LineDrawRoutine方法有两个参数,第一个是Graphics对象是用于绘制线条的地方,第二个是绘图工具Pen对象用来画线条。代码相当简单,一个循环语句,LINEFREQ常量等,程序从窗体表面的左下一直划线到其右上。请注意,程序使用浮点数来计算在窗体上的绘制位置,这样做的好处就是当窗体的大小发生变化时位置数据会更加精确。

  private void LineDrawRoutine(Graphics g, Pen p)
  {
  float width = ClientRectangle.Width;
  float height = ClientRectangle.Height;
  float xDelta = width / LINEFREQ;
  float yDelta = height / LINEFREQ;
  for (int i = 0; i < LINEFREQ; i++)
  {
  g.DrawLine(p, 0, height - (yDelta * i), xDelta * i, 0);
  }
  }


  撰写很简单的用于响应Paint事件SpiderWeb_Paint的代码,正如前面所提到的,Graphics对象就是从Paint事件参数PaintEventArgs对象中提取出来的表示窗体的绘制表面。这个Graphics对象连同新创建Pen对象一起传递给LineDrawRoutine方法来画出蜘蛛网似的线条,使用完Graphics对象和Pen对象后释放其占用的资源,那么整个绘制操作就完成了。

  private void SpiderWeb_Paint(object sender, PaintEventArgs e)
  {
  Graphics g = e.Graphics;
  Pen redPen = new Pen(Color.Red);
  LineDrawRoutine(g, redPen);
  redPen.Dispose();
  g.Dispose();
  }

  那么到底作怎么样的改动才能使上面的SpiderWeb程序实现简单的双重缓冲区技术呢?原理其实相当简单,就是将应该画到窗体表面的绘制操作改成先画到内存中的位图上,LineDrawRoutine向这个在内存中隐藏的画布执行同样的蜘蛛网绘制操作,等到绘制完毕再通过调用Graphics.DrawImage方法将隐藏的画布上内容推到窗体表面来显示出来,最后,再加上一些小的改动一个高性能的绘图窗体程序就完成了。

  请比较下面双重缓冲区绘图事件与前面介绍的简单绘图事件间的区别:

  private void SpiderWeb_DblBuff_Paint(object sender, PaintEventArgs e)
  {
  Graphics g = e.Graphics;
  Pen bluePen = new Pen(Color.Blue);
  Bitmap localBitmap = new Bitmap(ClientRectangle.Width,ClientRectangle.Height);
  Graphics bitmapGraphics = Graphics.FromImage(localBitmap);
  LineDrawRoutine(bitmapGraphics, bluePen);
  //把在内存里处理的bitmap推向前台并显示
  g.DrawImage(localBitmap, 0, 0);
  bitmapGraphics.Dispose();
  bluePen.Dispose();
  localBitmap.Dispose();
  g.Dispose();
  }

  上面的示例代码创建了内存位图对象,它的大小等于窗体的客户区域(就是绘图表面)的大小,通过调用Graphics.FromImage将内存中位图的引用传递给Graphics对象,也就是说后面所有对该Graphics对象的操作实际上都是对内存中的位图进行操作的,该操作在C++中等同于将位图对象的指针复制给Graphics对象,两个对象使用的是同一块内存地址。现在Graphics对象表示的是屏幕后方的一块画布,而它在双重缓冲区技术中起到至关重要的作用。所有的线条绘制操作都已经针对于内存中的位图对象,下一步就通过调用DrawImage方法将该位图复制到窗体,蜘蛛网的线条就会立刻显示在窗体的绘制表面而且丝毫没有闪烁出现。

  这一系列的操作完成后还不是特别有效,因为我们先前提到了,控件的样式也是定义Windows 窗体程序行为的一条途径,为了更好的实现双重缓冲区必须设置控件的Opaque属性,这个属性指明窗体是不负责在后台绘制自己的,换句话说,如果这个属性设置了,那么必须为清除和重绘操作添加相关的代码。具备双重缓冲区版本的SpiderWeb程序通过以上的设置在每一次需要重绘时都表现良好,窗体表面用其自己的背景色进行清除,这样就更加减少了闪烁的出现。

  public SpiderWeb_DblBuff()
  {
  SetStyle(ControlStyles.ResizeRedraw | ControlStyles.Opaque, true);
  }
  private void SpiderWeb_DblBuff_Paint(object sender, PaintEventArgs e)
  {
  Bitmap localBitmap = new Bitmap(ClientRectangle.Width,
  ClientRectangle.Height);
  Graphics bitmapGraphics = Graphics.FromImage(localBitmap);
  bitmapGraphics.Clear(BackColor);
  LineDrawRoutine(bitmapGraphics, bluePen);
  }

  结果怎么样?图像的绘制平滑多了。从内存中将蜘蛛网的线条推到前台以显示出来是完全没有闪烁的,但是我们还是稍微停顿一下,先将内存中的位图修整一下再显示出来,可以添加一行代码以便使线条看上去更加平坦。

以下是引用片段:
  bitmapGraphics.SmoothingMode = SmoothingMode.AntiAlias;


  在将内存中的位图对象赋给Graphics后通过放置这行代码,我们在画布上所画的每一个线条都使用了反锯齿,使凹凸不平的线条显得更加平坦。

  具备双重缓冲区技术的且使用AntiAliasing(反锯齿)属性的SpiderWeb_DblBuff示例程序

  完成了简单的双重缓冲区应用后有两个问题需要向读者阐明,.Net中的某些控件例如:Button、PictureBox、Label还有PropertyGrid都已经很好的利用了该技术!这些控件在默认状态下会自动启用双重缓冲区技术,用户可以通过对“DoubleBuffer”属性的设置来就可以实现双重缓冲区技术。所以,用户若使用PictureBox来绘制蜘蛛网将会更有效率一些,而且也使程序变得更加简单了。

  我们在这里讨论的双重缓冲区技术既不是完全被优化但也没有什么太大的负面影响。双重缓冲区技术是减少Windows 窗体绘制时闪烁的一条重要途径,但是它也确实消耗不少内存,因为它将会使用双倍的内存空间:应用程序所显示的图像和屏幕后方内存中的图像。每次Paint事件被激活时都会动态的创建位图对象,这种机制会相当耗费内存。而自带双重缓冲区技术的控件在使用DoubleBuffer属性后执行起来的优化程度则会更好一些。

  使用GDI+的DIB(与设备无关的位图)对象来实现这种画面以外的内存缓冲,自带双重缓冲区机制的控件则能好的利用该位图对象。DIB是底层Win32的对象用于高效的屏幕绘制。同样,值得注意的是GDI+的第一个版本GDI中仅与硬件加速有关以及一些简单功能可以直接使用,由于这样的限制,像反锯齿和半透明等屏幕绘制方法执行起来的速度则相当慢。尽管双重缓冲区机制消耗了一些内存但是它的使用不容置疑的增强了程序的执行性能。

  智能重绘,在绘制前需要斟酌一下

  “智能无效”(智能重绘)就是在暗示程序员应该明白仅应对程序中无效的区域进行重绘,对Regions对象所对应的无效区域进行重绘可以提高绘制性能,使用Regions对象你可以仅排除或绘制控件和窗体的部分区域已获得更好的性能。我们现在就开始来看一下BasicClip示例程序,这个程序使用保存在PaintEventArgs对象的ClipRectangle对象,之前我们已经提及,无论何时当程序的大小发生变化时Paint事件都会被激活。BasicClip示例程序用红和蓝两种颜色填充剪切的矩形区域,利用不同的速度调整窗体的大小几次以后,你会发现绘制的矩形区域其实就是窗体的无效区域(包括大于原始窗体大小的区域部分和缩少了的区域部分),示例程序的Paint事件代码如下:

  private void BasicClip_Paint(object sender, PaintEventArgs e)
  {
  Graphics g = e.Graphics;
  if (currentBrush.Color == Color.Red)
  currentBrush.Color = Color.Blue;
  else
  currentBrush.Color = Color.Red;
  g.FillRectangle(currentBrush, e.ClipRectangle);
  g.Dispose();
  }

  该示例程序的唯一目的就是演示怎样仅针对部分区域进行图形绘制。

  BasicClip示例程序中的彩色矩形区域就是表示窗体的下方和右侧的无效区域。

  Regions是一种被用来定义Windows 窗体或者控件区域的对象,调整窗体大小后所获得的Regions就是窗体重绘的最小区域。当程序需要进行绘制的时候仅绘制感兴趣的特殊区域,这样绘制更小的区域就会使程序的运行速度更快。

  为了更好的演示Regions的用法,请查看TextCliping示例程序。该程序重载了OnPaintBackground和OnPaint方法,直接重载这些方法比侦听事件更能保证代码在其它的绘制操作之前被调用,而且对于自定义控件的绘制也更加有效。为了清楚起见,示例程序提供了一个Setup方法,该方法定义了全局的Graphics对象。

  private void Setup()
  {
  GraphicsPath textPath = new GraphicsPath();
  textPath.AddString(displayString, FontFamily.GenericSerif,
  0, 75, new Point(10, 50), new StringFormat());
  textRegion = new Region(textPath);
  backgroundBrush = new TextureBrush(new Bitmap("CoffeeBeanSmall.jpg"),
  WrapMode.Tile);
  foregroundBrush = new SolidBrush(Color.Red);
  }

  上面的Setup方法首先定义一个空的GraphicsPath对象变量textPath,下一步字符串“Windows Forms”的边界被添加到该路径中,围绕这个轮廓创建Region。这样,一个被绘制在窗体表面的以字符串轮廓为区域的Region就被创建了。最后,Setup方法创建以材质刷子为背景和以实色刷子为前景来绘制窗体。

  protected override void OnPaintBackground(PaintEventArgs e)
  {
  base.OnPaintBackground(e);
  Graphics bgGraphics = e.Graphics;
  bgGraphics.SetClip(textRegion, CombineMode.Exclude);
  bgGraphics.FillRectangle(backgroundBrush, e.ClipRectangle);
  bgGraphics.Dispose();
  }

  上面定义的OnPaintBackground方法先立刻调用基类方法,这能够保证所有底层绘制的代码都能够被执行。下一步,从PaintEventArgs中获得Graphics对象,再将Graphics对象的剪切区域定义为textRegion对象。通过指定CombineMode.Exclude参数,明确无论在哪里绘制或怎样绘制Graphics对象都不绘制textRegion区域内部。

  protected override void OnPaint(PaintEventArgs e)
  {
  base.OnPaint(e);
  Graphics fgGraphics = e.Graphics;
  fgGraphics.FillRegion(foregroundBrush, textRegion);
  fgGraphics.Dispose();
  }

  最后,OnPaint事件负责精确的绘制出字符串。可以很容易的通过调用Graphics的FillRegion方法来实现。通过指定的前景刷子foregroundBrush和textRegion且仅是该区域被绘制。结果,Windows 窗体程序在运行之前确实“思考”该怎样进行绘制。

  TextClipping示例程序,通过Region定义的Windows Forms字符串。能够使程序在绘制时避开一个区域。

  适当的组合使用区域和智能重绘你可以编写出运行速度快且不会引起闪烁的绘制代码,并且比单独使用双重缓冲区绘制还要节省内存的消耗。

  结论

  如果你的程序确定要进行绘制操作,使用几种技术可以增强绘制性能。确保争取设置控件属性以及适当的Paint事件处理是编写健壮程序的开始。在权衡好利弊后可以使用双重缓冲区技术产生非常“保护视力”的结果。最后,在实际绘制前进行思考到底哪些客户区域或Region需要被绘制将非常有益。

  希望通过这篇文章能够使读者更好的理解关于.net框架的绘制技术及其应用。

凌众科技专业提供服务器租用、服务器托管、企业邮局、虚拟主机等服务,公司网站:http://www.lingzhong.cn 为了给广大客户了解更多的技术信息,本技术文章收集来源于网络,凌众科技尊重文章作者的版权,如果有涉及你的版权有必要删除你的文章,请和我们联系。以上信息与文章正文是不可分割的一部分,如果您要转载本文章,请保留以上信息,谢谢!

分享到: 更多

Copyright ©1999-2011 厦门凌众科技有限公司 厦门优通互联科技开发有限公司 All rights reserved

地址(ADD):厦门软件园二期望海路63号701E(东南融通旁) 邮编(ZIP):361008

电话:0592-5908028 传真:0592-5908039 咨询信箱:web@lingzhong.cn 咨询OICQ:173723134

《中华人民共和国增值电信业务经营许可证》闽B2-20100024  ICP备案:闽ICP备05037997号