呈现 Windows 窗体控件

呈现是指在用户屏幕上创建视觉对象表示形式的过程。 Windows 窗体使用 GDI(新的 Windows 图形库)进行呈现。 提供 GDI 访问权限的托管类位于 System.Drawing 命名空间及其子命名空间中。

控件呈现涉及以下元素:

  • 基类 System.Windows.Forms.Control 提供的绘图功能。

  • GDI 图形库的基本元素。

  • 绘图区域的几何图形。

  • 释放图形资源的过程。

控件提供的绘图功能

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

Paint 事件的事件数据类 PaintEventArgs 包含绘制控件所需的数据,即图形对象的句柄和表示绘制区域的矩形对象。 这些对象在以下代码片段中以粗体显示。

Public Class PaintEventArgs  
   Inherits EventArgs  
   Implements IDisposable  
  
   Public ReadOnly Property ClipRectangle() As System.Drawing.Rectangle  
      ...  
   End Property  
  
   Public ReadOnly Property Graphics() As System.Drawing.Graphics  
      ...  
   End Property  
   ' Other properties and methods.  
   ...  
End Class  
public class PaintEventArgs : EventArgs, IDisposable {  
public System.Drawing.Rectangle ClipRectangle {get;}  
public System.Drawing.Graphics Graphics {get;}  
// Other properties and methods.  
...  
}  

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

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

Protected Overridable Sub OnPaint(pe As PaintEventArgs)  
protected virtual void OnPaint(PaintEventArgs pe);  

Control 基类的 OnPaint 方法不实现任何绘图功能,只是调用注册到 Paint 事件的事件委托。 重写 OnPaint 时,应确保调用基类的 OnPaint 方法,以便注册的委托可接收 Paint 事件。 但是,绘制整个表面的控件不应调用基类的 OnPaint,因为这会引起闪烁。 有关重写 OnPaint 事件的示例,请参阅如何:创建显示进度的 Windows 窗体控件

注意

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

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

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

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

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

GDI+ 基础知识

Graphics 类提供用于绘制各种形状(如圆形、三角形、弧形和椭圆形)的方法,以及用于显示文本的方法。 System.Drawing 命名空间及其子命名空间包含类,可用于封装图形元素,如形状(圆形、矩形、弧形等)、颜色、字体、画笔等。 有关 GDI 的详细信息,请参阅使用托管图形类如何:创建显示进度的 Windows 窗体控件中还介绍了 GDI 的基本信息。

绘图区域的几何图形

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

以下代码片段显示了 FlashTrackBar 自定义控件如何计算要绘制的矩形区域。 client 变量表示 ClipRectangle 属性。 有关完整示例,请参阅如何:创建显示进度的 Windows 窗体控件

Rectangle invalid = new Rectangle(
    client.X + min,
    client.Y,
    max - min,
    client.Height);

Invalidate(invalid);
Dim invalid As Rectangle = New Rectangle( _
    client.X + lmin, _
    client.Y, _
    lmax - lmin, _
    client.Height)

Invalidate(invalid)

释放图形资源

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

以下代码片段显示了 FlashTrackBar 自定义控件如何创建和释放 Brush 资源。 有关完整的源代码,请参阅如何:创建显示进度的 Windows 窗体控件

private Brush baseBackground = null;
Private baseBackground As Brush
base.OnPaint(e);
if (baseBackground == null) {
    if (showGradient) {
        baseBackground = new LinearGradientBrush(new Point(0, 0),
                                                 new Point(ClientSize.Width, 0),
                                                 StartColor,
                                                 EndColor);
    }
    else if (BackgroundImage != null) {
        baseBackground = new TextureBrush(BackgroundImage);
    }
    else {
        baseBackground = new SolidBrush(BackColor);
    }
}
MyBase.OnPaint(e)

If (baseBackground Is Nothing) Then

    If (myShowGradient) Then
        baseBackground = New LinearGradientBrush(New Point(0, 0), _
                                                 New Point(ClientSize.Width, 0), _
                                                 StartColor, _
                                                 EndColor)
    ElseIf (BackgroundImage IsNot Nothing) Then
        baseBackground = New TextureBrush(BackgroundImage)
    Else
        baseBackground = New SolidBrush(BackColor)
    End If

End If
protected override void OnResize(EventArgs e) {
    base.OnResize(e);
    if (baseBackground != null) {
        baseBackground.Dispose();
        baseBackground = null;
    }
}
Protected Overrides Sub OnResize(ByVal e As EventArgs)
    MyBase.OnResize(e)
    If (baseBackground IsNot Nothing) Then
        baseBackground.Dispose()
        baseBackground = Nothing
    End If
End Sub

另请参阅