如果在使用双缓冲的过程中有一些疑问可以参考这个帖子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 有重新分配内存,如果这个做的比较频繁系统会越来越慢的,这是让人比较难过的地方。