多窗体绘制大量线条性能问题:200+线条耗时约1秒求排查
嘿,看了你的代码,能猜到为啥绘制200多条线就卡1秒了——这里有不少可以优化的点,咱们一个个拆解:
1. 重复的属性/方法调用,纯纯浪费性能
你在循环里反复调用objects[i].getEdges()、getPoint1()、getPoint2(),这些每次调用都可能产生额外的开销(比如方法调用的栈操作,或是属性内部的额外逻辑)。应该把这些值提前缓存到局部变量里,减少重复计算。
2. 冗余的条件判断完全没必要
你在遍历ed的循环里加了if (objects[i].getEdges().Count > 0),但既然ed是从0到objects[i].getEdges().Count - 1,这个判断完全多余,只会增加循环里的分支开销,直接删掉就行。
3. 坐标计算重复做,白耗CPU
像X + 332、-(Y - 170)这种坐标转换,你每个绘制调用都算一遍,完全可以提前把转换后的点坐标算好,存到临时变量里,不用每次绘制都重复计算。
4. Pen的切换可以提前搞定
你现在在每个edge的循环里判断objects[i].selected来切换pAux,但这个值是针对整个object的,完全可以在遍历objects[i]的时候就确定好要用的Pen,不用在每个edge里重复判断。
5. 别用单个绘制API,试试批量调用
GDI+的单个DrawLine/DrawRectangle调用开销其实不小。如果能把一组线的点收集起来,用DrawLines代替多次DrawLine,或者用GraphicsPath把所有要画的图形加进去最后一次性绘制,能大幅减少API调用次数,提升性能。
6. 开启双缓冲解决闪烁+提速
WinForms的控件默认可能没开启双缓冲,这会导致绘制时的闪烁,也会间接影响绘制速度。你可以在窗体/控件的构造函数里加上DoubleBuffered = true;,或者手动实现双缓冲。
优化后的代码示例
先给你一个基础优化版本,解决前面提到的前几个问题:
public void drawObjects() { // 提前缓存常用Pen,减少重复赋值 Pen linePenCache = linePen; Pen selectedPenCache = selectedPen; foreach (var obj in objects) { // 针对当前Object确定Pen,只判断一次 Pen currentPen = obj.selected ? selectedPenCache : linePenCache; var edges = obj.getEdges(); // 缓存edges集合,避免重复调用方法 foreach (var edge in edges) { // 缓存两个端点,避免重复调用getPoint1/getPoint2 var point1 = edge.getPoint1(); var point2 = edge.getPoint2(); // 提前计算三个视图的坐标,避免重复运算 // 前视图坐标 int frontX1 = point1.X + 332; int frontY1 = -(point1.Y - 170); int frontX2 = point2.X + 332; int frontY2 = -(point2.Y - 170); // 右视图坐标 int rightX1 = point1.Z + 332; int rightY1 = -(point1.Y - 170); int rightX2 = point2.Z + 332; int rightY2 = -(point2.Y - 170); // 顶视图坐标 int topX1 = point1.X + 332; int topY1 = -(point1.Z - 170); int topX2 = point2.X + 332; int topY2 = -(point2.Z - 170); // 绘制端点矩形 frontGraphics.DrawRectangle(recPen, frontX1, frontY1, 2, 2); rightGraphics.DrawRectangle(recPen, rightX1, rightY1, 2, 2); topGraphics.DrawRectangle(recPen, topX1, topY1, 2, 2); // 绘制线条 frontGraphics.DrawLine(currentPen, frontX1, frontY1, frontX2, frontY2); rightGraphics.DrawLine(currentPen, rightX1, rightY1, rightX2, rightY2); topGraphics.DrawLine(currentPen, topX1, topY1, topX2, topY2); } } }
进阶优化:批量绘制
如果想进一步提速,可以把相同Pen的线条/矩形批量收集,再一次性调用绘制API(注意如果不同Object用不同Pen,需要按Pen分组收集):
public void drawObjects() { // 按视图和Pen分组收集绘制数据 Dictionary<Pen, List<Point>> frontLines = new Dictionary<Pen, List<Point>>(); Dictionary<Pen, List<Point>> rightLines = new Dictionary<Pen, List<Point>>(); Dictionary<Pen, List<Point>> topLines = new Dictionary<Pen, List<Point>>(); List<Rectangle> frontRects = new List<Rectangle>(); List<Rectangle> rightRects = new List<Rectangle>(); List<Rectangle> topRects = new List<Rectangle>(); foreach (var obj in objects) { Pen currentPen = obj.selected ? selectedPen : linePen; var edges = obj.getEdges(); // 初始化当前Pen的点列表(如果不存在) if (!frontLines.ContainsKey(currentPen)) { frontLines[currentPen] = new List<Point>(); rightLines[currentPen] = new List<Point>(); topLines[currentPen] = new List<Point>(); } foreach (var edge in edges) { var point1 = edge.getPoint1(); var point2 = edge.getPoint2(); // 前视图数据收集 int fx1 = point1.X + 332; int fy1 = -(point1.Y - 170); int fx2 = point2.X + 332; int fy2 = -(point2.Y - 170); frontLines[currentPen].Add(new Point(fx1, fy1)); frontLines[currentPen].Add(new Point(fx2, fy2)); frontRects.Add(new Rectangle(fx1, fy1, 2, 2)); // 右视图数据收集 int rx1 = point1.Z + 332; int ry1 = -(point1.Y - 170); int rx2 = point2.Z + 332; int ry2 = -(point2.Y - 170); rightLines[currentPen].Add(new Point(rx1, ry1)); rightLines[currentPen].Add(new Point(rx2, ry2)); rightRects.Add(new Rectangle(rx1, ry1, 2, 2)); // 顶视图数据收集 int tx1 = point1.X + 332; int ty1 = -(point1.Z - 170); int tx2 = point2.X + 332; int ty2 = -(point2.Z - 170); topLines[currentPen].Add(new Point(tx1, ty1)); topLines[currentPen].Add(new Point(tx2, ty2)); topRects.Add(new Rectangle(tx1, ty1, 2, 2)); } } // 批量绘制矩形 if (frontRects.Count > 0) { frontGraphics.DrawRectangles(recPen, frontRects.ToArray()); rightGraphics.DrawRectangles(recPen, rightRects.ToArray()); topGraphics.DrawRectangles(recPen, topRects.ToArray()); } // 批量绘制线条(按Pen分组绘制) foreach (var pair in frontLines) { if (pair.Value.Count >= 2) frontGraphics.DrawLines(pair.Key, pair.Value.ToArray()); } foreach (var pair in rightLines) { if (pair.Value.Count >= 2) rightGraphics.DrawLines(pair.Key, pair.Value.ToArray()); } foreach (var pair in topLines) { if (pair.Value.Count >= 2) topGraphics.DrawLines(pair.Key, pair.Value.ToArray()); } }
最后别忘了给你的绘制控件开启双缓冲——在控件构造函数里设置DoubleBuffered = true;,或者重写OnPaintBackground方法为空(避免重复绘制背景),这能解决闪烁问题,也能进一步提升绘制流畅度。
内容的提问来源于stack exchange,提问作者Dazentt Eduardo Dazentt55

