如何在已创建的Panel图形上添加新绘制内容?
解决Panel重绘闪烁与增量绘制问题
先纠正代码里的错误
不要手动调用g.Dispose()——PaintEventArgs提供的Graphics对象由.NET框架管理生命周期,手动释放会引发异常或绘制异常。
方案1:开启双缓冲消除闪烁(推荐)
闪烁的根源是Panel重绘时会先擦除背景,再绘制内容。开启双缓冲可以让控件在内存中完成全部绘制后再一次性显示到界面,彻底解决闪烁。
有两种方式开启双缓冲:
- 直接通过反射设置Panel的双缓冲属性:
public static void EnableDoubleBuffering(Control control) { typeof(Control).InvokeMember("DoubleBuffered", System.Reflection.BindingFlags.SetProperty | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic, null, control, new object[] { true }); } // 在创建Panel后调用 EnableDoubleBuffering(Panel1);
- 自定义继承自Panel的控件,默认开启双缓冲:
public class DoubleBufferedPanel : Panel { public DoubleBufferedPanel() { DoubleBuffered = true; SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.UserPaint, true); UpdateStyles(); } }
之后用DoubleBufferedPanel代替原生Panel即可。
这种方式下,你依然可以用Panel1.Refresh()触发重绘,只要在Paint事件里绘制所有需要的内容(比如维护一个图形元素集合,每次Paint遍历绘制),就不会有闪烁,也符合Windows GDI+的绘制模型。
方案2:增量绘制(仅特殊场景使用)
如果一定要在不清空现有内容的前提下添加新绘制,有两种思路:
- 直接在Panel的Graphics上绘制:
获取Panel的Graphics对象直接绘制,内容会立即显示,但控件被重绘(比如窗口最小化恢复)时会丢失,所以需要把绘制内容记录下来,在Paint事件里重新绘制:// 记录所有需要绘制的矩形 private List<Rectangle> _drawnRects = new List<Rectangle>(); // 新增绘制的方法 public void AddRectangle(Rectangle rect) { _drawnRects.Add(rect); // 直接绘制到Panel using (var g = Panel1.CreateGraphics()) using (var pen = new Pen(Color.Gray, 1F)) { g.DrawRectangle(pen, rect); } } // 修改Paint事件,绘制所有记录的矩形 private void Panel1Paint(object sender, PaintEventArgs e) { var g = e.Graphics; using (var pen = new Pen(Color.Gray, 1F)) { foreach (var rect in _drawnRects) { g.DrawRectangle(pen, rect); } } // 不要释放g! } - 使用内存位图作为缓冲区:
创建一个和Panel大小一致的Bitmap,所有绘制都先画到这个Bitmap上,然后在Paint事件里把Bitmap画到Panel上。这样既可以增量绘制,也不会丢失内容:private Bitmap _bufferBitmap; // 初始化缓冲区 private void InitBuffer() { _bufferBitmap = new Bitmap(Panel1.Width, Panel1.Height); } // 增量绘制方法 public void DrawRectangleToBuffer(Rectangle rect) { using (var g = Graphics.FromImage(_bufferBitmap)) using (var pen = new Pen(Color.Gray, 1F)) { g.DrawRectangle(pen, rect); } // 触发Paint事件显示缓冲区内容 Panel1.Invalidate(); } // Paint事件绘制缓冲区 private void Panel1Paint(object sender, PaintEventArgs e) { if (_bufferBitmap != null) { e.Graphics.DrawImage(_bufferBitmap, 0, 0); } } // 记得在Panel大小改变时重新创建缓冲区,释放旧的Bitmap private void Panel1_SizeChanged(object sender, EventArgs e) { _bufferBitmap?.Dispose(); InitBuffer(); }
关键提醒
Windows的GDI+绘制模型是**“重绘所有内容”**,控件随时可能被系统强制重绘(比如被其他窗口遮挡后恢复),所以增量绘制的内容如果不记录下来,一定会丢失。因此最可靠的方式还是方案1:开启双缓冲,在Paint事件里绘制所有内容,用Refresh()或Invalidate()触发重绘,既解决闪烁,又保证内容不会丢失。
内容的提问来源于stack exchange,提问作者Paul WWJD
相关产品推荐
相关产品推荐

