服务器之家:专注于服务器技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - C# - C#双缓冲技术实例详解

C#双缓冲技术实例详解

2021-11-14 13:33Alexander C#

这篇文章主要介绍了C#双缓冲技术,结合实例形式较为详细的分析了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、设置显示图元控件的几个属性:  必须要设置,否则效果不是很明显!

?
1
2
3
this.SetStyle(ControlStyles.OptimizedDoubleBuffer |
          ControlStyles.ResizeRedraw |
          ControlStyles.AllPaintingInWmPaint, true);

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

可以通过以下几种方式实现,这几种方式都涉及到Graphics对象的创建方式。

具体实现

1、利用默认双缓冲

(1)在应用程序中使用双缓冲的最简便的方法是使用 .NET Framework 为窗体和控件提供的默认双缓冲。通过将 DoubleBuffered 属性设置为 true。

?
1
this.DoubleBuffered=true;

(2)使用 SetStyle 方法可以为 Windows 窗体和所创作的 Windows 控件启用默认双缓冲。

?
1
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个随机生成的小圆。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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对象),在其中绘制完成后,再一次性显示。

完整代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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方法中):

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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对象:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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里。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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# 6.0知识点

    三十分钟快速掌握C# 6.0知识点

    这篇文章主要介绍了C# 6.0的相关知识点,文中介绍的非常详细,通过这篇文字可以让大家在三十分钟内快速的掌握C# 6.0,需要的朋友可以参考借鉴,下面来...

    雨夜潇湘8272021-12-28
  • C#深入理解C#的数组

    深入理解C#的数组

    本篇文章主要介绍了C#的数组,数组是一种数据结构,详细的介绍了数组的声明和访问等,有兴趣的可以了解一下。...

    佳园9492021-12-10
  • C#C#设计模式之Strategy策略模式解决007大破密码危机问题示例

    C#设计模式之Strategy策略模式解决007大破密码危机问题示例

    这篇文章主要介绍了C#设计模式之Strategy策略模式解决007大破密码危机问题,简单描述了策略模式的定义并结合加密解密算法实例分析了C#策略模式的具体使用...

    GhostRider10972022-01-21
  • C#利用C#实现网络爬虫

    利用C#实现网络爬虫

    这篇文章主要介绍了利用C#实现网络爬虫,完整的介绍了C#实现网络爬虫详细过程,感兴趣的小伙伴们可以参考一下...

    C#教程网11852021-11-16
  • C#SQLite在C#中的安装与操作技巧

    SQLite在C#中的安装与操作技巧

    SQLite,是一款轻型的数据库,用于本地的数据储存。其优点有很多,下面通过本文给大家介绍SQLite在C#中的安装与操作技巧,感兴趣的的朋友参考下吧...

    蓝曈魅11162022-01-20
  • C#如何使用C#将Tensorflow训练的.pb文件用在生产环境详解

    如何使用C#将Tensorflow训练的.pb文件用在生产环境详解

    这篇文章主要给大家介绍了关于如何使用C#将Tensorflow训练的.pb文件用在生产环境的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考借鉴...

    bbird201811792022-03-05
  • C#VS2012 程序打包部署图文详解

    VS2012 程序打包部署图文详解

    VS2012虽然没有集成打包工具,但它为我们提供了下载的端口,需要我们手动安装一个插件InstallShield。网上有很多第三方的打包工具,但为什么偏要使用微软...

    张信秀7712021-12-15
  • C#C#微信公众号与订阅号接口开发示例代码

    C#微信公众号与订阅号接口开发示例代码

    这篇文章主要介绍了C#微信公众号与订阅号接口开发示例代码,结合实例形式简单分析了C#针对微信接口的调用与处理技巧,需要的朋友可以参考下...

    smartsmile20127762021-11-25