更新: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 窗体控件。
![]() |
---|
不要直接从控件调用 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;
}
}