我在做什么
我正在开发一个 C#/.NET 4.7.2/WinForms 应用程序,该应用程序使用 Graphics.FillRectangle
在表单上绘制大量填充矩形。 .
目前,矩形是在表单的 Paint
事件中绘制的。绘制完所有矩形后,根据鼠标位置绘制十字准线。
每当鼠标移动时,都会在表单上调用 Invalidate
来强制重新绘制,以便十字线出现在新位置。
问题
这是低效的,因为矩形不会改变,只有十字线位置改变,但每次都会重新绘制矩形。鼠标移动期间的 CPU 使用率很高。
下一步
我相信这个问题的解决方案是首先将矩形绘制到缓冲区(在 Paint
事件之外)。然后,Paint
事件只需要渲染缓冲区并在顶部绘制十字准线。
由于我是 GDI+ 和手动缓冲的新手,因此我很警惕走入错误的轨道。 Google 搜索显示了大量有关手动缓冲的文章,但每篇文章似乎都采用了不同的方法,这增加了我的困惑。
如果有利于简单和高效的建议方法,我将不胜感激。如果有一种惯用的 .NET 方法可以做到这一点(即它意味着的完成方式),我很想知道。
最佳答案
这是一个快速简单的解决方案,不需要任何缓冲。要复制此操作,请从一个新的 Windows 窗体项目开始。我只画了两个矩形,但你可以画任意多个。
如果您使用这两个成员变量和这两个处理程序创建一个新的 WinForms 项目,您将获得一个工作示例。
首先,您的表单有几个成员变量:
private bool _started = false;
private Point _lastPoint;
第一次鼠标移动后,started
标志将变为 true
。 _lastPoint
字段将跟踪最后绘制十字准线的点(这就是 _started
存在的主要原因)。
Paint
处理程序将在每次调用时绘制十字准线(您将了解为什么 MouseMove
处理程序可以这样做):
private void Form1_Paint(object sender, PaintEventArgs e)
{
var graphics = e.Graphics;
var clientRectangle = this.ClientRectangle;
//draw a couple of rectangles
var firstRectangle = clientRectangle;
firstRectangle.Inflate(-20, -40);
graphics.FillRectangle(Brushes.Aqua, firstRectangle);
var secondRectangle = clientRectangle;
secondRectangle.Inflate(-100, -4);
graphics.FillRectangle(Brushes.Red, secondRectangle);
//draw Cross-Hairs
if (_started)
{
//horizontal
graphics.DrawLine(Pens.LightGray, new Point(clientRectangle.X, _lastPoint.Y),
new Point(ClientRectangle.Width + clientRectangle.X, _lastPoint.Y));
//vertical
graphics.DrawLine(Pens.LightGray, new Point(_lastPoint.X, clientRectangle.Y),
new Point(_lastPoint.X, ClientRectangle.Height + clientRectangle.Y));
}
}
现在出现了 MouseMove
处理程序。这就是魔法发生的地方。
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
var clientRectangle = this.ClientRectangle;
var position = e.Location;
if (clientRectangle.Contains(position))
{
Rectangle horizontalInvalidationRect;
Rectangle verticalInvalidationRect;
if (_started)
{
horizontalInvalidationRect = new Rectangle(clientRectangle.X,
Math.Max(_lastPoint.Y - 1, clientRectangle.Y), clientRectangle.Width, 3);
verticalInvalidationRect = new Rectangle(Math.Max(_lastPoint.X - 1, clientRectangle.X),
clientRectangle.Y, 3, clientRectangle.Height);
Invalidate(horizontalInvalidationRect);
Invalidate(verticalInvalidationRect);
}
_started = true;
_lastPoint = position;
horizontalInvalidationRect = new Rectangle(clientRectangle.X,
Math.Max(_lastPoint.Y - 1, clientRectangle.Y), clientRectangle.Width, 3);
verticalInvalidationRect = new Rectangle(Math.Max(_lastPoint.X, clientRectangle.X - 1),
clientRectangle.Y, 3, clientRectangle.Height);
Invalidate(horizontalInvalidationRect);
Invalidate(verticalInvalidationRect);
}
}
如果光标位于表单内,我会做很多工作。首先,我声明两个将用于无效的矩形。水平矩形将是一个填充客户端矩形宽度的矩形,但只有 3 个像素高,以我想要无效的区域的 Y 坐标为中心。垂直矩形与客户端矩形一样高,但只有 3 像素宽。它以我想要无效的区域的 X 坐标为中心。
当 Paint 处理程序运行时,它实际上会绘制整个客户区域,但实际上只有整个无效区域中的像素才会绘制在屏幕上。无效区域之外的任何内容都会被保留。
因此,当鼠标移动时,我创建两个矩形(一个垂直,一个水平),围绕最后一组十字线所在的位置(这样,当绘制这些矩形中的像素(包括背景)时,旧的十字准线被有效删除),然后我在当前十字准线应该去的位置周围创建两个新的矩形(导致绘制背景和新的十字准线)。
如果您有一个复杂的绘图应用程序,您将需要了解失效矩形。例如,当调整表单大小时,您要做的只是使新显示的矩形无效,这样就不需要渲染整个绘图。
这可行,但为十字线选择颜色(或画笔)以便它们始终显示可能很困难。使用我的其他建议(使用反转(即异或)画笔绘制线条两次(一次删除,一次绘制)速度更快,并且始终显示。
关于c# - 我应该如何缓冲绘制的矩形以提高性能(C#/.NET/WinForms/GDI+),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/67541811/