在控件上绘图(Windows 窗体 .NET)

控件的自定义绘制是 Windows 窗体可以轻松完成的众多复杂任务之一。 创作自定义控件时,有许多选项可用于处理控件的图形外观。 如果要创作自定义控件(即从 Control 继承的控件),则必须提供代码以呈现其图形表示形式。

重要

面向 .NET 7 和 .NET 6 的桌面指南文档正在撰写中。

如果要创建复合控件(即从 UserControl 继承的控件或某个现有的 Windows 窗体控件),则可以替代标准图形表示形式,并提供你自己的图形代码。

如果要在不创建新控件的情况下为现有控件提供自定义呈现,选项会变得更为有限。 但是,对于控件和应用程序,仍有各种各样的图形。

控件呈现涉及以下元素:

  • 基类 System.Windows.Forms.Control 提供的绘图功能。
  • GDI 图形库的基本元素。
  • 绘图区域的几何图形。
  • 释放图形资源的过程。

控件提供的绘图

基类 Control 通过其 Paint 事件提供绘图功能。 每当控件需要更新其显示时,它都会引发 Paint 事件。 有关 .NET 中的事件的详细信息,请参阅处理和引发事件

Paint 事件的事件数据类 PaintEventArgs 包含绘制控件所需的数据 - 图形对象的句柄和表示绘制区域的矩形。

public class PaintEventArgs : EventArgs, IDisposable
{

    public System.Drawing.Rectangle ClipRectangle {get;}
    public System.Drawing.Graphics Graphics {get;}

    // Other properties and methods.
}
Public Class PaintEventArgs
    Inherits EventArgs
    Implements IDisposable

    Public ReadOnly Property ClipRectangle As System.Drawing.Rectangle
    Public ReadOnly Property Graphics As System.Drawing.Graphics

    ' Other properties and methods.
End Class

Graphics 是一个可封装绘图功能的托管类,如本文后面的 GDI 讨论中所述。 ClipRectangleRectangle 结构的实例,它定义了可在其中绘制控件的可用区域。 控件开发人员可以使用控件的 ClipRectangle 属性来计算 ClipRectangle,如本文后面的几何图形讨论中所述。

OnPaint

控件必须通过重写它从 Control 继承的 OnPaint 方法来提供呈现逻辑。 OnPaint 可访问图形对象和矩形,以通过传递给它的 PaintEventArgs 实例的 GraphicsClipRectangle 属性进行绘制。

下面的代码使用 System.Drawing 命名空间:

protected override void OnPaint(PaintEventArgs e)
{
    // Call the OnPaint method of the base class.
    base.OnPaint(e);

    // Declare and instantiate a new pen that will be disposed of at the end of the method.
    using var myPen = new Pen(Color.Aqua);

    // Create a rectangle that represents the size of the control, minus 1 pixel.
    var area = new Rectangle(new Point(0, 0), new Size(this.Size.Width - 1, this.Size.Height - 1));

    // Draw an aqua rectangle in the rectangle represented by the control.
    e.Graphics.DrawRectangle(myPen, area);
}
Protected Overrides Sub OnPaint(e As PaintEventArgs)
    MyBase.OnPaint(e)

    ' Declare and instantiate a drawing pen.
    Using myPen = New System.Drawing.Pen(Color.Aqua)

        ' Create a rectangle that represents the size of the control, minus 1 pixel.
        Dim area = New Rectangle(New Point(0, 0), New Size(Me.Size.Width - 1, Me.Size.Height - 1))

        ' Draw an aqua rectangle in the rectangle represented by the control.
        e.Graphics.DrawRectangle(myPen, area)

    End Using
End Sub

Control 基类的 OnPaint 方法不实现任何绘图功能,只是调用注册到 Paint 事件的事件委托。 重写 OnPaint 时,应确保调用基类的 OnPaint 方法,以便注册的委托可接收 Paint 事件。 但是,绘制整个表面的控件不应调用基类的 OnPaint,因为这会引起闪烁。

注意

请勿直接从控件调用 OnPaint;请改为调用 Invalidate 方法(从 Control 继承)或调用 Invalidate 的其他方法。 Invalidate 方法又会调用 OnPaint。 重载 Invalidate 方法,并根据提供给 Invalidatee 的参数重绘其部分屏幕区域或整个屏幕区域。

控件的 OnPaint 方法中的代码将在第一次绘制控件以及每次刷新该控件时执行。 若要确保在每次调整控件大小时都重新进行绘制,请将下面的行添加到控件的构造函数中:

SetStyle(ControlStyles.ResizeRedraw, true);
SetStyle(ControlStyles.ResizeRedraw, True)

OnPaintBackground

Control 基类定义了另一种可用于绘图的方法,即 OnPaintBackground 方法。

protected virtual void OnPaintBackground(PaintEventArgs e);
Protected Overridable Sub OnPaintBackground(e As PaintEventArgs)

OnPaintBackground 绘制窗口的背景(并以相同方式绘制形状),并保证速度较快,而 OnPaint 绘制详细信息,速度可能较慢,因为单个绘制请求会合并为一个 Paint 事件,其中涵盖了所有需要重新绘制的区域。 例如,如果想要为控件绘制颜色渐变的背景,则可能需要调用 OnPaintBackground

虽然 OnPaintBackground 具有类似事件的命名法并采用与 OnPaint 方法相同的参数,但 OnPaintBackground 不是真正的事件方法。 不存在 PaintBackground 事件,并且 OnPaintBackground 不调用事件委托。 重写 OnPaintBackground 方法时,无需使用派生类即可调用其基类的 OnPaintBackground 方法。

GDI+ 基础知识

Graphics 类提供用于绘制各种形状(如圆形、三角形、弧形和椭圆形)的方法,以及用于显示文本的方法。 System.Drawing 命名空间包含命名空间和类,它们可用于封装图形元素,如形状(圆形、矩形、弧形等)、颜色、字体、画笔等。

绘图区域的几何图形

控件的 ClientRectangle 属性指定可用于用户屏幕上的控件的矩形区域,而 PaintEventArgsClipRectangle 属性指定所绘制的区域。 当控件的一小部分显示发生变化时,控件可能只需要绘制部分可用区域。 在这些情况下,控件开发人员必须计算要在其中进行绘制的实际矩形,并将其传递到 Invalidate。 采用 RectangleRegion 作为参数的 Invalidate 的重载版本使用该参数生成 PaintEventArgsClipRectangle 属性。

释放图形资源

图形对象成本昂贵,因为它们使用系统资源。 此类对象包括 System.Drawing.Graphics 类的实例以及 System.Drawing.BrushSystem.Drawing.Pen 和其他图形类的实例。 请务必仅在需要时才创建图形资源,并在使用完之后立即释放。 如果创建实现 IDisposable 接口的类型的实例,请在使用完该实例之后,调用其 Dispose 方法来释放资源。

另请参阅