c# - 我应该如何缓冲绘制的矩形以提高性能(C#/.NET/WinForms/GDI+)

标签 c# .net winforms graphics gdi+

我在做什么

我正在开发一个 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/

相关文章:

c# - GetSharedDefaultFolder() 抛出错误 MAPI_E_NOT_FOUND - 赎回

c# - 在 .net 中以编程方式突出显示 PDF - 是否有任何 API 和库来生成偏移文件或是否有更好的方法?

c# - 如何在 .NET 中引发事件查看器 "Event"?

c# - 尝试创建一个动态委托(delegate)

wpf - 从 WPF 应用程序窗口获取位图?

c# - 有没有办法将类内的字段组划分为 "sub structures"

c# - 自定义面板内的项目未正确排列

c# - 如何根据数组中的值对对象列表进行排序?

c# - 服务器控件、客户端控件、面板、默认按钮和客户端事件

c# - 消息在进行事务处理时未到达 MSMQ