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# GDI+的双缓冲问题相关推荐

  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. [WTL/ATL]_[Gdi/Gdiplus]_[实现双缓冲]

    场景 使用Gdi绘制文本,线条或图片(Gdiplus)时,如果HDC调用的次数过多,就会出现闪烁的现象, 怎么解决? 如果我用Gdiplus::Graphics进行绘制文本,线条或图片时, 不使用HD ...

  5. 如何使用C#自带的GDI+双缓冲类BufferedGraphics实现双缓冲功能

    小白摸索着编写一个上位机界面,将遇到的问题及解决方法记录下,以供后来的小白使用哈.这篇文章解决的问题是在绘图时候的闪烁问题.其中网上有些介绍的设置控制方式controlstyle的方法,这个方法我的理 ...

  6. 双缓冲技术(C# GDI)

    c#如何实现防窗体闪烁的功能.大家都会想到运用双缓冲技术,那么在c#中是如何做的? 1. 利用默认双缓冲 (1)在应用程序中使用双缓冲的最简便的方法是使用 .NET Framework 为窗体和控件提 ...

  7. C++零食:WTL中使用双缓冲避免闪烁

    双缓冲的原理可以这样形象的理解:把电脑屏幕看作一块黑板.首先我们在内存环境中建立一个"虚拟"的黑板,然后在这块黑板上绘制复杂的图形,等图形全部绘制完毕的时候,再一次性的把内存中绘制 ...

  8. MFC VC 双缓冲绘图基本原理与实现,详细解释

    MFC做了一些时间了,不得不面对 的是在界面上画图的. 当然你可以直接搜索到能用的代码,并且基本能满足要求.不过这样总不是学习的态度.本着学习分享的态度,现做一些基本的分析吧. 在MSDN上知道,我们 ...

  9. 双缓冲技术绘图原理及简单的VC实现

    为了增加自己对双缓冲绘图技术的理解,简要做个笔记(以Windows为例): 1.Windows 绘图原理  我们在 Windows 环境下看到各种元素,如菜单.按钮.窗口.图像,从根本上说,都是&qu ...

最新文章

  1. 解决VMware—MAC冲突问题
  2. MYSQL使用inner join 进行 查询/删除/修改示例
  3. 智迪科技携手海通安恒,启动SAP实施项目
  4. 测试的艺术:测试用例的设计
  5. 谁说菜鸟不会数据分析--数据分析那些事儿
  6. 用Spring组成自定义注释
  7. 吴玉禄他的机器人_中国人— 我们村的机器人
  8. VUE:兄弟组件间传参
  9. Java SSM篇3——Mybatis
  10. 一图感受各种机器学习算法
  11. java redis hession_spring: 整合 springmvc shiro redis hessian rocketMQ
  12. 自学编程变得越来越简单之后,那些老程序员是有多恐慌?
  13. 51nod-1337:翻转游戏
  14. Java-Android 之出滚动条和卷轴页面
  15. 形态学空间格局分析(MSPA)—ArcGIS结合Guidos软件
  16. MT6577/MT6589处理器参数对比分析
  17. java:编写一个程序完成:完成在银行的存款和取款操作。在定义银行类时,若取款数大于余额则作为异常处理。
  18. 心情不美丽,爬了一些美图,独自欣赏!
  19. 视频加水印,怎么给视频加水印?
  20. 数据库学习-三种异常

热门文章

  1. 由于断电,重启服务器后,LVM卷组出现错误:ata1.00 status drdy err unc
  2. 亚马逊平台如何选品?有哪些渠道可以快速选品?
  3. 实验十一、多级放大电路的参数设置
  4. wps控件属性怎么用_WPS表格怎么使用列表框控件?
  5. 案例推荐《淘工厂:中小微制造企业转型》
  6. 基于HTML开发外观漂亮大气的APP下载页源码分享
  7. seo提交工具_呼伦贝尔网站建设_5118站长数据分析平台非常适合SEO站长做数据统计...
  8. 紫金桥组态软件和厦门四信DTU通讯原理及配置
  9. 决策易线上营销CRM,微信粉丝运营重要利器!
  10. 新款戴尔笔记本win10系统改win7 安装教程