闪烁和内存溢出主因是Graphics/Pen/Brush生命周期管理不当且未启用双缓冲;应设ControlStyles.OptimizedDoubleBuffer与AllPaintingInWmPaint,仅用e.Graphics或FromImage(并Dispose),Pen/Brush须using或复用,Bitmap需DPI适配与复用。

直接说结论:闪烁和内存溢出不是“画得太多”导致的,而是Graphics、Pen、Brush生命周期管理不当 + 双缓冲未启用的组合结果。
OnPaint里不加双缓冲,重绘就必然闪烁
WinForms默认用单缓冲:先Clear背景(白闪一下),再Draw内容(覆盖上去),两步之间用户能感知到撕裂。尤其当控件尺寸大或绘制逻辑稍复杂时,闪烁肉眼可见。
- 正确做法是在控件构造函数末尾调用:
this.SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint, true) - 不要用
ControlStyles.UserPaint单独开启——它会禁用系统背景擦除,但没配双缓冲,反而更容易出现残影 - 如果继承自
Panel等容器控件,记得同时设DoubleBuffered = true(这是快捷方式,底层也是调SetStyle)
Graphics不能new,也不能长期持有
new Graphics()直接抛NotSupportedException;而Control.CreateGraphics()返回的对象是临时的,窗口最小化再还原后内容全丢,且绕过双缓冲机制。
- 稳定来源只有三个:
e.Graphics(来自OnPaint或Paint事件)、Graphics.FromImage(bitmap)(离屏绘制)、Control.CreateGraphics()(仅限调试/瞬时标注) - 从
Bitmap创建的Graphics必须显式Dispose(),否则GDI句柄泄漏——Windows每进程句柄数上限通常为10000,跑几小时就可能卡死或报OutOfMemoryException - 别把
Graphics存成字段反复用;每次OnPaint都应使用新传入的e.Graphics
Pen和Brush不释放,几天就崩
Pen和Brush都实现了IDisposable,内部封装了Windows GDI对象句柄。不释放=句柄持续增长,不是内存泄漏,是资源耗尽。
- 错误写法:
var pen = new Pen(Color.Red, 2); g.DrawLine(pen, ...);——pen没释放 - 安全写法:
using (var pen = new Pen(Color.Red, 2)) { g.DrawLine(pen, ...); } - 例外:静态预定义对象如
Brushes.Red、Pens.Blue可直接用,不用Dispose;但所有new LinearGradientBrush()、new SolidBrush()都必须释放 - 高频绘制场景(如鼠标拖动画线),避免在循环里
new——复用一个Pen实例,或用using确保退出即释放
离屏绘制要防位图爆炸
用Bitmap做缓存画布(比如实现撤销/缩放/图层)时,容易忽略分辨率和DPI适配,导致内存占用飙升。
- 别无脑按
ClientSize创建位图:new Bitmap(Width, Height)在高DPI屏上可能实际分配4倍内存 - 正确做法:用
CreateGraphics().DpiX和DpiY算出物理像素尺寸,或直接用GraphicsUnit.Pixel配合GraphicsPageScale控制 - 位图一旦创建,尽量复用;频繁
new Bitmap()+Graphics.FromImage()+Dispose()会产生GC压力,建议池化管理 - 保存前务必调
bitmap.SetResolution(dpiX, dpiY),否则导出图片模糊或拉伸
真正难的不是“怎么画”,而是“什么时候释放”和“在哪一帧画”。GDI+没有自动垃圾回收机制,每个Graphics、Pen、Brush、Bitmap都是裸露的操作系统资源——漏掉一个,程序就多一分不可靠性。



















