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

C# 专栏收录该内容
3 篇文章 0 订阅

 这几日要用C#做一个简单的图形处理工具.但刚开始就遇见一个头疼的问题.图片用鼠标移动时闪烁很明显,使用双缓冲来解决这个问题,对图形处理有经验的人都能想到.

    现在的问题是很多人不知道怎么怎么使用GDI+ 双缓冲,我从网上搜到很多资料,加上手里的一本书也介绍了一两页,就这样我依然被折磨了整整一天,就在要崩溃前我终于搞明白.本来很简单的东西按照网上的还是书上的方法怎么试都不行!这到底是什么原因?
    看看我做的例子(vs2005):
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.使用自己的方法,双缓冲的原理都是一样的.

  • 1
    点赞
  • 5
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值