且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

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

更新时间:2022-06-19 05:16:56

这是一个不需要任何缓冲的快速简便的解决方案.要复制这一点,请从一个新的 Windows 窗体项目开始.我只画了两个矩形,但你想画多少就画多少.

Here's a quick and easy solution that doesn't require any buffering. To replicate this, start with a fresh Windows Forms project. I only draw two rectangles, but you can have as many as you want.

如果您使用这两个成员变量和这两个处理程序创建一个新的 WinForms 项目,您将获得一个工作示例.

If you create a new WinForms project with these two member variables and these two handlers, you will get a working sample.

首先,为您的表单添加几个成员变量:

First, a couple of member variables for your form:

private bool _started = false;
private Point _lastPoint;

第一次移动鼠标后,started 标志将变为 true._lastPoint 字段将跟踪绘制最后一个十字准线的点(这主要是 _started 存在的原因).

The started flag will turn to true after the first mouse move. The _lastPoint field will track the point at which the last cross-hairs was drawn (that's mostly why _started exists).

Paint 处理程序将在每次调用时绘制十字准线(您将了解为什么使用 MouseMove 处理程序可以这样做):

The Paint handler will draw the cross hairs every time it's called (you'll see why this is ok with the MouseMove handler):

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 处理程序.这就是魔法发生的地方.

Now comes the MouseMove handler. It's where the magic happens.

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 坐标为中心.

If the cursor is within the form, I do a bunch of work. First I declare two rectangles that I will be using for invalidate. The horizontal one will be a rectangle that fills the width of the client rectangle, but is only 3 pixels high, centered on the Y coordinate of the area that I want to invalidate. The vertical one is as high as the client rectangle, but only 3 pixels wide. It's centered on the X coordinate of the area that I want to invalidate.

当 Paint 处理程序运行时,它实际上会绘制整个客户区,但实际上只有整个无效区域中的像素被绘制在屏幕上.无效区域之外的任何内容都将被保留.

When the Paint handler runs, it virtually paints the entire client area, but only the pixels in the total invalidated area actually get drawn on the screen. Anything outside the invalidate area is left alone.

因此,当鼠标移动时,我创建了两个矩形(一个垂直,一个水平),围绕最后一组十字准线所在的位置(这样当绘制这些矩形中的像素(包括背景)时,旧的十字准线被有效地擦除),然后我创建了两个新的矩形,围绕当前十字准线的位置(导致背景和新的十字准线被绘制).

So, when the mouse moves, I create two rectangles (one vertical, one horizontal) that surround where the last set of cross-hairs were (so that when the pixels in those rectangles are drawn (including the background), the old cross-hairs are effectively erased) and then I create two new rectangles surrounding where the current cross-hairs should go (causing the background and the new cross-hairs to be drawn).

如果您有一个复杂的绘图应用程序,您将会想要了解失效矩形.例如,当表单被调整大小时,你想要做的只是使新展开的矩形无效,这样就不需要渲染整个绘图.

You are going to want to learn about invalidation rectangles if you have a complicated drawing app. For example, when the form is resized, what you want to do is invalidate only the newly unveiled rectangle(s), so that the whole drawing doesn't need to be rendered.

这可行,但为十字准线选择颜色(或画笔)以使其始终显示可能很困难.使用我的另一个建议(你画线两次(一个擦除,一个绘制)使用 INVERT(即 XOR)画笔更快,并且它总是显示.

This works, but picking a color (or a brush) for the cross-hairs so that they always show can be difficult. Using my other suggestion (that you draw the lines twice (one to erase, one to draw) using an INVERT (i.e. XOR) brush is faster, and it always shows.