我应该如何缓冲绘制的矩形以提高性能 (C#/.NET/WinForms/GDI+)
How should I buffer drawn rectangles to improve performance (C#/.NET/WinForms/GDI+)
我在做什么
我正在开发一个 C#/.NET 4.7.2/WinForms 应用程序,该应用程序使用 Graphics.FillRectangle
在表单上绘制大量填充矩形。
目前,矩形是在表单的 Paint
事件中绘制的。绘制完所有矩形后,根据鼠标位置绘制十字准线。
只要鼠标移动,就会在窗体上调用 Invalidate
以强制重新绘制,以便十字准线出现在新位置。
问题
这是低效的,因为矩形不会改变,只有十字线位置,但每次都会重新绘制矩形。 CPU 鼠标移动期间的使用很重要。
下一步
我相信解决这个问题的方法是先将矩形绘制到缓冲区(在 Paint
事件之外)。然后,Paint
事件只需要渲染缓冲区并在顶部绘制十字准线。
由于我是 GDI+ 和手动缓冲的新手,所以我很担心误入歧途。 Google 搜索显示大量关于手动缓冲的文章,但每篇文章似乎采用不同的方法,这增加了我的困惑。
如果有利于简单性和效率的建议方法,我将不胜感激。如果有一种惯用的 .NET 方法可以做到这一点 — 意味着 的方式 — 我很想知道。
这是一个不需要任何缓冲的快速简单的解决方案。要复制它,请从一个新的 Windows Forms 项目开始。我只画了两个矩形,你想画多少就画多少。
如果您使用这两个成员变量和这两个处理程序创建一个新的 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 处理程序运行时,它实际上会绘制整个客户区,但实际上只有整个无效区域中的像素才会绘制在屏幕上。无效区域之外的任何东西都被保留。
因此,当鼠标移动时,我创建了两个矩形(一个垂直,一个水平)围绕着最后一组十字准线所在的位置(以便在绘制这些矩形中的像素时(包括背景) ,旧的十字准线被有效地擦除)然后我在当前十字准线应该去的地方创建两个新的矩形(导致背景和新的十字准线被绘制)。
如果您有一个复杂的绘图应用程序,您将想要了解失效矩形。例如,当窗体调整大小时,您要做的只是使新出现的矩形无效,这样就不需要渲染整个图形。
这可行,但是为十字准线选择一种颜色(或画笔)以使其始终显示可能很困难。使用我的另一个建议(你画线两次(一次擦除,一次绘制)使用 INVERT(即 XOR)画笔更快,并且它总是显示。
我在做什么
我正在开发一个 C#/.NET 4.7.2/WinForms 应用程序,该应用程序使用 Graphics.FillRectangle
在表单上绘制大量填充矩形。
目前,矩形是在表单的 Paint
事件中绘制的。绘制完所有矩形后,根据鼠标位置绘制十字准线。
只要鼠标移动,就会在窗体上调用 Invalidate
以强制重新绘制,以便十字准线出现在新位置。
问题
这是低效的,因为矩形不会改变,只有十字线位置,但每次都会重新绘制矩形。 CPU 鼠标移动期间的使用很重要。
下一步
我相信解决这个问题的方法是先将矩形绘制到缓冲区(在 Paint
事件之外)。然后,Paint
事件只需要渲染缓冲区并在顶部绘制十字准线。
由于我是 GDI+ 和手动缓冲的新手,所以我很担心误入歧途。 Google 搜索显示大量关于手动缓冲的文章,但每篇文章似乎采用不同的方法,这增加了我的困惑。
如果有利于简单性和效率的建议方法,我将不胜感激。如果有一种惯用的 .NET 方法可以做到这一点 — 意味着 的方式 — 我很想知道。
这是一个不需要任何缓冲的快速简单的解决方案。要复制它,请从一个新的 Windows Forms 项目开始。我只画了两个矩形,你想画多少就画多少。
如果您使用这两个成员变量和这两个处理程序创建一个新的 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 处理程序运行时,它实际上会绘制整个客户区,但实际上只有整个无效区域中的像素才会绘制在屏幕上。无效区域之外的任何东西都被保留。
因此,当鼠标移动时,我创建了两个矩形(一个垂直,一个水平)围绕着最后一组十字准线所在的位置(以便在绘制这些矩形中的像素时(包括背景) ,旧的十字准线被有效地擦除)然后我在当前十字准线应该去的地方创建两个新的矩形(导致背景和新的十字准线被绘制)。
如果您有一个复杂的绘图应用程序,您将想要了解失效矩形。例如,当窗体调整大小时,您要做的只是使新出现的矩形无效,这样就不需要渲染整个图形。
这可行,但是为十字准线选择一种颜色(或画笔)以使其始终显示可能很困难。使用我的另一个建议(你画线两次(一次擦除,一次绘制)使用 INVERT(即 XOR)画笔更快,并且它总是显示。