导致画面闪烁的关键原因分析:
      一、绘制窗口由于大小位置状态改变进行重绘操作时,绘图窗口内容或大小每改变一次,都要调用Paint事件进行重绘操作,该操作会使画面重新刷新一次以维持窗口正常显示。刷新过程中会导致所有图元重新绘制,而各个图元的重绘操作并不会导致Paint事件发生,因此窗口的每一次刷新只会调用Paint事件一次。窗口刷新一次的过程中,每一个图元的重绘都会立即显示到窗口,因此整个窗口中,只要是图元所在的位置,都在刷新,而刷新的时间是有差别的,闪烁现象自然会出现。所以说,此时导致窗口闪烁现象的关键因素并不在于Paint事件调用的次数多少,而在于各个图元的重绘。
     根据以上分析可知,当图元数目不多时,窗口刷新的位置也不多,窗口闪烁效果并不严重;当图元数目较多时,绘图窗口进行重绘的图元数量增加,绘图窗口每一次刷新都会导致较多的图元重新绘制,窗口的较多位置都在刷新,闪烁现象自然就会越来越严重。特别是图元比较大绘制时间比较长时,闪烁问题会更加严重,因为时间延迟会更长。
     解决上述问题的关键在于:窗口刷新一次的过程中,让所有图元同时显示到窗口。
      二、进行鼠标跟踪绘制操作或者对图元进行变形操作时,当进行鼠标跟踪绘制操作或者对图元进行变形操作时,Paint事件会频繁发生,这会使窗口的刷新次数大大增加。虽然窗口刷新一次的过程中所有图元同时显示到窗口,但也会有时间延迟,因为此时窗口刷新的时间间隔远小于图元每一次显示到窗口所用的时间。因此闪烁现象并不能完全消除!所以说,此时导致窗口闪烁现象的关键因素在于Paint事件发生的次数多少。
      解决此问题的关键在于:设置窗体或控件的几个关键属性。
 
解决双缓冲的关键技术:
1、设置显示图元控件的几个属性: 必须要设置,否则效果不是很明显!
this.SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.ResizeRedraw |ControlStyles.AllPaintingInWmPaint, true);
2、窗口刷新一次的过程中,让所有图元同时显示到窗口。
    可以通过以下几种方式实现,这几种方式都涉及到Graphics对象的创建方式。
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();//释放资源
至此,双缓冲问题解决,两种方式的实现效果都一样,但最后一种方式的占有的内存很少,不会出现内存泄露!

或者:

      1、在内存中建立一块“虚拟画布”:

Bitmap bmp = new Bitmap(600, 600);

  2、获取这块内存画布的Graphics引用:

Graphics g = Graphics.FromImage(bmp);

  3、在这块内存画布上绘图:

g.FillEllipse(brush, i * 10, j * 10, 10, 10);

  4、将内存画布画到窗口中

this.CreateGraphics().DrawImage(bmp, 0, 0);

还有的方式
在构造函数中加如下代码

代码一:
      SetStyle(ControlStyles.UserPaint, true);
      SetStyle(ControlStyles.AllPaintingInWmPaint, true); // 禁止擦除背景.
      SetStyle(ControlStyles.DoubleBuffer, true); // 双缓冲

代码二:
   this.SetStyle(ControlStyles.DoubleBuffer | ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint, true);
   this.UpdateStyles();

=======================================================================

使用 GDI+ 双缓冲 解决绘图闪烁问题

现在的问题是很多人不知道怎么怎么使用GDI+ 双缓冲

public partial class Form1 : Form
    {
        //记录矩形位置的变量
        Point p = Point .Empty ;
        Point location = new Point(0, 0);
        int x = 0;
        int y = 0;

public Form1()
        {
            InitializeComponent();
            //采用双缓冲技术的控件必需的设置
            this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
            this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
            this.SetStyle(ControlStyles.UserPaint, true);

        }
        protected override void OnPaint(PaintEventArgs e)
        {
            base.OnPaint(e);
            Graphics g = e.Graphics;
            g.FillRectangle(Brushes.Black, x, y, 200, 200);
        }
        private void Form1_MouseDown(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Right) return;
            p = e.Location;
        }
        private void Form1_MouseUp(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Right) return;
            location.X += e.X - p.X;
            location.Y += e.Y - p.Y;
            p = Point.Empty;
        }
        private void Form1_MouseMove(object sender, MouseEventArgs e)
        {
         if (p == Point.Empty) return;
            x = e.X - p.X + location.X;
            y = e.Y - p.Y + location.Y;
            this.Invalidate(true);//触发Paint事件
        }
     }
这个简单的例子实现了用鼠标拖动窗口中矩形,利用双缓冲技术使动画过程不会产生闪烁.
在这个例子上我犯的错误:
在 OnPaint(PaintEventArgs e)中,我使用下面两种方法获取graphics 对象
     Graphics g = this.CreateGraphics();
     Graphics g = Graphics.FromHwnd(this.Handle);
这都使双缓冲失效.
获得graphics 对象还有两种方法是
     Graphics g = Graphics.FromImage(image); //后面将用此方法实现双缓冲
     Graphics g = e.Graphics;  //这是唯一好使的方法

上面是在Form窗口直接绘制图形,那么如何在控件上(比如Panel)利用双缓冲技术绘图呢?
在窗口窗创建一个Panel , 并修改一下代码
        private void panel1_Paint(object sender, PaintEventArgs e)
        {
            Graphics g = e.Graphics;
            g.FillRectangle(Brushes.Black, x, y, 200, 200);
        }
运行后发现拖动在panel1上绘制的图形依然有闪烁,那么是不是应该这样设置
panel1.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);//这样并不行,
因为SetStyle()在Panel类中不是public方法

使用从Panel类继承的MyPanel类 的构造函数中设置双缓冲
    public class MyPanel:Panel
    {
        public MyPanel()
        {
            this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
            this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
            this.SetStyle(ControlStyles.UserPaint, true);
        }
    }

不管怎么说这个方法的确好用,不过注意以后你使用的是MyPanel类,而不是Panel.
把自己定义MyPanel从工具栏里拖到窗口上和Panle一样使用.

注意:在控件上绘制就必须设置该控件的DoubleBuffer,而不是Form的DoubleBuffer.

在此之前我采用自己的方法实现双缓冲而不是控件自身的DoubleBuffer
先在内存里绘制图形,包括清除旧画面和绘制新画面,然后将内存的图形绘制到屏幕上
public void Draw(System.Windows.Forms.Panel _panel, float  _x, float  _y)
{
  Graphics g = Graphics.FromHwnd(_panel.Handle);
  try{
     //在内存创建一块和panel一大小的区域
     Bitmap bitmap = new Bitmap(_panel.ClientSize.Width, _panel.ClientSize.Height);
     using (Graphics buffer = Graphics.FromImage(bitmap))
      {
        //buffer中绘图
        buffer.Clear(_panel.BackColor); //用背景色填充画面
        buffer.Transform = matrix;     
        buffer.DrawImage(source, _x/Scale , _y/Scale ); //绘制新画面
        //屏幕绘图
        g.DrawImage(bitmap, 0, 0); //将buffer绘制到屏幕上
       }
   }
  finally
  {
      g.Dispose();
   }
}
使用上面方法不需要任何设置.

总结一下
与绘图有关的ControlStyles
enum ControlStyles{
AllPainingInWmPaint, //将绘制阶段折叠入Paint事件
DoubleBuffer, //直到Paint返回,再显示绘制对象
UserPaint,  //用于自身有着特别绘制的控件

Opaque, //忽略OnPaintBackground,Paint事件绘制整个区域
ResizeRedraw,//当调整控件大小时使整个工作区无效
SupportsTransparentBackColor,//模拟透明控件
...
}

1.在OnPaint(PaintEventArgs e)或Paint中 使用e获取graphics,我之所以费了很大周折就是因为在网上找到一篇实现双缓冲文章介绍,不要使用e获取graphics,而用this.CreateGraphics(),还有的文章介绍了奇怪的方法居然最终也好使.

2.在继承了Form和control 的控件上利用双缓冲绘制的时候,可以在控件的构造函数里设置双缓冲属性,而在窗口Form 里设置doublebuffer,只针对窗口的绘制起作用.

3.使用自己的方法,双缓冲的原理都是一样的.

示例:

private void DrawRectBackImage()
        {
            Graphics g = Graphics.FromHwnd(_currentChart.Handle);
            try
            {
                //在内存创建一块和panel一大小的区域
                Bitmap bitmap = new Bitmap(_currentChart.ClientSize.Width, _currentChart.ClientSize.Height);
                using (Graphics buffer = Graphics.FromImage(bitmap))
                {
                    buffer.Transform =new  Matrix();

//要画的背景图片

                    buffer.DrawImage(curChartImage, _currentChart.ClientRectangle);

//背景图片上的内容
                    DrawFitAdjustRect(buffer);
                    //屏幕绘图
                    g.DrawImage(bitmap, 0, 0); //将buffer绘制到屏幕上
                }
            }
            finally
            {
                g.Dispose();
            }
        }

C#画图解决闪烁问题的更多相关文章

  1. ddraw 视频下画图 不闪烁的方法

    我们如果是在在RGB视频上画图(直线,矩形等),一般采用双缓冲区继续,使用内存MemoryDC,来实现画的图形在视频上显示不闪烁的功能,但是我们知道用RGB显示视频都是使用GDI进行渲染,这样很耗CP ...

  2. VC++ 实现VC程序启动时最小化到任务栏(完美解决闪烁问题)

    之前写的一个VC应用程序,是程序启动时就直接出现在任务栏, 窗体不出现,等用户点击任务栏图标再出现窗口.和一些防火墙什么的软件类似. 这种效果实现并不是很困难的,硬是找不到最好的.为什么呢? 首先,在 ...

  3. cefsharp解决闪烁

    CefSharp禁用GPU的命令行参数 其中,Major和Minor分别指代系统的主版本(大版本).次版本(小版本)版本号.其中指定了Windows7系统会禁用 GPU.,突发奇想,是否windows ...

  4. c# winfrom 更新控件时停止刷新,解决闪烁问题

    static Dictionary<Control, bool> m_lstFreezeControl = new Dictionary<Control, bool>(); / ...

  5. C# 实现PNG文件的背景透明显示,解决动态显示闪烁问题 【转】

    http://blog.sina.com.cn/s/blog_402c071e0102x4rl.html    以下内容,对于想要使用C#实现PNG图片背景透明显示,同时动态显示时无闪烁问题的人来说, ...

  6. winform画图闪烁问题

    问题:在winform程序的onpaint方法中画图, 连续画, 如鼠标移动时就要不断画图, 会闪烁. 解决方法:将要画图的部分放到一个自定义控件中, 自定义控件的onpaint方法里面画图, 然后再 ...

  7. vue解决加载闪烁问题

    <!DOCTYPE html><html> <head> <meta charset="utf-8"> <title>& ...

  8. C# 中DataGridView和ListView闪烁问题的解决方法

    C# 中DataGridView和ListView闪烁问题的解决方法 方法一首先定义类,将此类放在datagridview或ListView所在的窗体类外面,然后代码如下, <span styl ...

  9. 使用Three.js挖空安装门来解决重叠闪烁的问题

    一.挖空原理说明 subtract 用墙面减去与门重叠的部分,产生一个新的对象,导入材质安装门即可 //参与减去几何体 //平行于x轴门 var meshH4Door = new ThreeBSP( ...

随机推荐

  1. 使用IE10登录,URL出现SessionId的解决办法

    问题:用户登入之后,URL会出现一长串字符,类似SessionId,把这一长串字符删除之后重新进入页面,页面又会自动地跳转到登录页面,所以,应该是Session没记住用户已经登录的信息. 网站环境: ...

  2. ora-28002 the password will expire解决办法

    Oracle11g R2数据库提示ORA-28002: the password will expire within 5 days,是说密码过期,将Oracle密码设置成永不过期就可以了,不过并不推 ...

  3. mysql federated engine

    mysql)) -> engine=federated -> connection='mysql://root@localhost:3306/t1/t';

  4. c++ 继承多个类 及虚函数

    #include <iostream>using namespace std; class BaseA {public:    virtual void say() {        co ...

  5. 【BZOJ】【2982】Combination

    排列组合 Lucas定理模板题…… 感觉我做题顺序有点问题啊……应该是BZOJ 2982-->HDOJ 3037-->BZOJ 1272 好吧这个现在来看就有些水了…… 预处理一下fact ...

  6. Unique Binary Search Tree II

    Given n, generate all structurally unique BST's (binary search trees) that store values 1...n. For e ...

  7. 玩转图片Base64编码

    什么是 base64 编码? 图片的 base64 编码就是可以将一副图片数据编码成一串字符串,使用该字符串代替图像地址. 这样做有什么意义呢?我们知道,我们所看到的网页上的每一个图片,都是需要消耗一 ...

  8. POJ 1691 Painting A Board(DFS)

    链接 题意 : 看了好长时间终于看懂题目了,将一个大矩形划分成若干小矩形,告诉你每个小矩形的左上角那个点和右下角那个点的坐标,告诉你这个小矩形要涂的颜色,每个颜色对应一个刷子,问你最少要使用几次刷子. ...

  9. Oracle 学习笔记(二)

    1.创建用户,一般是具有dba权限的用户才能使用: create user 用户名 identified by 密码; 2.删除用户: drop user 用户名,注意,如果用户拥有对象,则不能直接删 ...

  10. 【转载】Dom篇

    一. 初探Dom     1. Dom介绍 二. Dom基础     1. window顶级对象     2. body.document对象事件     3. 通用的HTML元素的事件     4. ...