更新:2007 年 11 月
呈现是指在用户屏幕上创建可视化表示的过程。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 的讨论中详细介绍。ClipRectangle 是 Rectangle 结构的一个实例,它定义绘制控件的可用区域。控件开发者可以使用控件的 ClipRectangle 属性计算 ClipRectangle,详情将在本主题稍后的几何图形论述中进行说明。
控件必须通过重写从 Control 继承的 OnPaint 方法来提供呈现逻辑。OnPaint 通过传递给它的 PaintEventArgs 实例的 Graphics 和 ClipRectangle 属性来访问图形对象以及要在其中进行绘制的矩形。
Protected Overridable Sub OnPaint(pe As PaintEventArgs)
protected virtual void OnPaint(PaintEventArgs pe);
Control 基类的 OnPaint 方法不实现任何绘制功能,而仅仅是调用使用 Paint 事件注册的事件委托。当重写 OnPaint 时,通常应调用基类的 OnPaint 方法,以便已注册的委派可以接收到 Paint 事件。但是,绘制其整个图面的控件不应调用基类的 OnPaint,因为这样会引起闪烁。有关重写 OnPaint 事件的示例,请参见 如何:创建显示进度的 Windows 窗体控件。
| .gif) 说明: | 
|---|
| 不要直接从控件调用 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 的更多信息,请参见 使用托管图形类。GDI 的要点在 如何:创建显示进度的 Windows 窗体控件 中也有介绍。
绘图区域的几何图形
控件的 ClientRectangle 属性指定用户屏幕上控件可用的矩形区域,而 PaintEventArgs 的 ClipRectangle 属性指定实际绘制的区域。(切记,绘制是在 Paint 事件方法中完成的,该方法的参数中包含一个 PaintEventArgs 实例)。在控件的一小部分显示发生变化时,控件可能仅需要绘制其可用区域的一部分。在这些情况下,控件开发者必须计算要在其中绘图的实际矩形并将其传递给 Invalidate。将 Rectangle 或 Region 作为一个参数的 Invalidate 的重载版本使用该参数生成 PaintEventArgs 的 ClipRectangle 属性。
以下代码片段展示了 FlashTrackBar 自定义控件如何计算要在其中进行绘制的矩形区域。client 变量表示 ClipRectangle 属性。有关完整示例,请参见 如何:创建显示进度的 Windows 窗体控件。
Dim invalid As Rectangle = New Rectangle( _
    client.X + lmin, _
    client.Y, _
    lmax - lmin, _
    client.Height)
Invalidate(invalid)
Rectangle invalid = new Rectangle(
    client.X + min, 
    client.Y, 
    max - min, 
    client.Height);
Invalidate(invalid);
释放图形资源
图形对象非常昂贵,因为它们占用很多系统资源。此类对象包括 System.Drawing.Graphics 类的实例以及 System.Drawing.Brush、System.Drawing.Pen 和其他图形类的实例。应该仅在需要时才创建图形资源,并在使用完毕后立即将其释放,这一点非常重要。如果创建一个实现 IDisposable 界面的类型,请在使用完毕后调用其 Dispose 方法以释放资源。
下面的代码片段展示了 FlashTrackBar 自定义控件如何创建和释放 Brush 资源。有关完整的源代码,请参见 如何:创建显示进度的 Windows 窗体控件。
Private baseBackground As Brush
private Brush baseBackground = null;
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
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);
    }
}
Protected Overrides Sub OnResize(ByVal e As EventArgs)
    MyBase.OnResize(e)
    If (baseBackground IsNot Nothing) Then
        baseBackground.Dispose()
        baseBackground = Nothing
    End If
End Sub
protected override void OnResize(EventArgs e) {
    base.OnResize(e);
    if (baseBackground != null) {
        baseBackground.Dispose();
        baseBackground = null;
    }
}