winform的双缓冲绘图

如果在使用双缓冲的过程中有一些疑问可以参考这个帖子https://bbs.csdn.net/topics/390673684/ 他的问题问的非常好,解决了我心中的一些疑惑。 另外写winform的gdi的代码可以参考一本书上的代码《GDI+图形程序设计》书中有一个项目GDIPainter

很多人应该都熟悉win32的双缓冲,但是c#的却比较诡异。比如这段代码

//绘图完毕 都应该调用这段代码。比如你绘制直线,你mousemove的时候是直接在显示器上绘制的,并没有绘制在位图

上(win32里面可能叫兼容位图) ,你mouseup的时候才去真实绘制在内存里(位图或者说画布或者你的理解)的 ,

在mouseup绘制在位图上之后调用下面的这个方法。

 private void RefreshFormBackground()
        {
            curBitmap = m_bitmap.Clone(  // 刚开始不太理解这里为什么要clone位图 new一个新内存出来 而不是引用
              new Rectangle(0, 0, width, height), //那个贴里就说了 如果是同样的内存地址(同样的对象)那么无法触发界面的paint绘//制 事件 
              m_bitmap.PixelFormat);
            m_parentWnd.BackgroundImage = curBitmap; // 必须创建一个新的对象
           //对backgroudImage赋新的对象 会导致界面刷新 调用OnPaint方法
        }

//简单代码示意

 public void Form_Load()

{

          //这几个SetStyle方法得添加上 使用winform的双缓存

           SetStyle(ControlStyles.UserPaint, true);
            SetStyle(ControlStyles.AllPaintingInWmPaint, true); // 避免
            SetStyle(ControlStyles.DoubleBuffer, true);
 

             m_bitmap = new Bitmap(width, height);            
            m_memGraphic = Graphics.FromImage(m_bitmap);
            m_memGraphic.Clear(this.BackColor); //用窗体的 背景色去清位图,假如之前有绘制线条那么会被擦除,如果后续还想显示需要再次调用绘制函数 然后RefreshFormBackground()方法 方可显示

}

 

//鼠标左键点击表示要开始实时绘制图元了,这里会设定一个标记 方便在mousemove中 做一些逻辑

//真正的保存绘制是在mouseup中执行的 绘制到缓存(位图)上,然后将缓存(位图)设置窗口背景显示出来 

    private void Form1_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e)

{
      m_lineStart.X = e.X;
      m_lineStart.Y = e.Y;     
      isRuntimeDraw = true;
    }

public void OnMouseMove(object sender, MouseEventArgs e)
        {
            if(isRuntimeDraw) // 这是一个标记 当鼠标点下的时候 设置一个标记  你需要监听mouseDown的消息
            {//我那边是鼠标右键触发的绘制开始,所以这里不列出来  这里挡一下避免额外的cpu开销
                m_lineEnd.X = e.X;
                m_lineEnd.Y = e.Y;              
                this.Refresh(); // 触发onpaint 界面重绘 
            }
        }

public void OnMouseMove(object sender, MouseEventArgs e)

{

           if(isRuntimeDraw) // 和上面要一一对应 
                    {

                        isRuntimeDraw = false; //在onpaint方法中会判断这个标记  如果是false什么都不做 如果为true onpaint那边会直接在Graphic上绘制 图元

                        //这里是我自己的逻辑大概就是给2个按钮添加了一个“连接线” 并且调用Draw绘制这个连接线

//                          //最后一定不忘 调用RefreshFormBackground()方法
                        m_connectLineManager.Add(LineStartEnd.Instance.StartNode.m_node,
                            LineStartEnd.Instance.EndNode.m_node);
                        m_connectLineManager.Draw(m_memGraphic, pen);
                        RefreshFormBackground(); 
                    }


}

  public void OnPaint(object sender, PaintEventArgs e)
        {
            if(isRuntimeDraw)
            {
                e.Graphics.DrawLine(pen, LineStartEnd.Instance.StartNode.m_node.PT
                    , m_lineEnd);
            }
        }

 

可能我们要绘制的是树形结点之间的连线,使用gdi绘制的那种,每个节点对应了一个数据结构 ,连线也是一种数据结构,并且设计了一个管理类来管理。比如你的绘制是这样的

//添加连接线 (选中的结点A和结点B之间 ) Add内部会做一些逻辑 比如设定他们的父子关系 构建连接线映射(2个map c#中叫Dictionary 称呼不同而已)。

m_connectLineManager.Add(LineStartEnd.Instance.StartNode.m_node,
                            LineStartEnd.Instance.EndNode.m_node);

//调用绘制线条函数 
                        m_connectLineManager.Draw(m_memGraphic, pen);

如果你使用了删除 你就需要重新调用m_connectLineManager.Draw(m_memGraphic, pen); 这个方法绘制 ,不过切记你需要首先

清理一下位图 使用 m_memGraphic.Clear(this.BackColor) 就可以做到。

这整个流程不爽的地方在于RefreshFormBackground 有重新分配内存,如果这个做的比较频繁系统会越来越慢的,这是让人比较难过的地方。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值