呈现 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 的讨论中详细介绍。 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;
}
}