[摘要]构造函数 构造函数传递三个参数:包含圆的中心坐标的点、圆的半径以及一个 System.Drawing.Color 结构(包含用于绘制圆轮廓的颜色)。 然后我们根据中心和半径计算边框,并将笔颜色项设置为我们传递的颜色对象。 绘图代码 Draw 方法重载实际上非常简单:它根...
构造函数
构造函数传递三个参数:包含圆的中心坐标的点、圆的半径以及一个 System.Drawing.Color 结构(包含用于绘制圆轮廓的颜色)。
然后我们根据中心和半径计算边框,并将笔颜色项设置为我们传递的颜色对象。
绘图代码
Draw 方法重载实际上非常简单:它根据我们保存在构造函数中的颜色对象创建一个笔对象,然后使用该笔,调用 Graphics.DrawEllipse 方法绘制圆,同时传递了我们早先创建的边框。
图形、笔和画笔
这里我们需要解释一下 Graphics、Pen 和 Brush 对象。(在开始填充我们的可填充对象时,就会看到 Brush 对象。)
Graphics 对象代表一个与某个真实绘图空间相关联的虚拟化的绘图空间。虚拟化是指,通过在 Graphics 对象上绘图,我们可以使用相同的 Graphics 方法在与该对象相关联的任何类型的实际表面上绘图。对于那些习惯于使用 MFC 或 Microsoft Windows? SDK 编程的用户,Graphics 对象相当于 Windows 中称为“设备上下文”(或 DC)的 .NET 版本。
在此 Windows 窗体应用程序中,传递到 Draw 方法的 Graphics 对象将与屏幕上的一个窗口相关联 -- 这里是 PictureBox。在我们的 Microsoft ASP.NET 应用程序中使用这一代码时,传递给 Draw 方法的 Graphics 对象将与一个位图图像相关联。它也可以和打印机或其他设备相关联。
这个方案的优点是我们可以使用相同的绘图代码在不同的表面上绘图。在我们的绘图代码中,我们不需要知道任何有关屏幕、位图、打印机等等之间的不同 -- .NET Framework(以及底层的操作系统)可以为我们处理所有细节。
利用相同的标记,笔和画笔成为虚拟化的绘图工具。笔代表线条属性 -- 颜色、宽度、样式,甚至可以是用来绘制线的位图。画笔代表一个填充区域的属性 -- 颜色、样式,甚至可以是用来填充区域的位图。
在使用 Using 后清除(或至少 Dispose)
Graphics、Pen 和 Brush 对象都与相似类型的 Windows 对象相关联。这些 Windows 对象分配在操作系统的内存中 -- 这些内存尚未被 .NET 运行时管理。长时间将这些对象驻留在内存中会导致性能问题,并且在 Microsoft Windows 98 下,当图形堆填满时会导致绘图问题。因此,我们应尽快释放这些 Windows 对象。
当相应的 .NET Framework 对象完成操作并回收内存后,.NET 运行时会自动释放 Windows 对象。但回收内存的时间会很长 -- 如果我们不迅速释放这些对象,所有不幸的事情(包括填满 Windows 堆)都可能发生。在该应用程序的 ASP.NET 版本中,由于很多用户在同一台服务器上访问该应用程序,所以这种现象会更加严重。
因为这些对象与未管理的资源相关联,所以它们实现 IDisposable 接口。该接口有一个方法,即 Dispose,它将 .NET Framework 对象从 Windows 对象中分离出来,并释放 Windows 对象,从而使计算机处于良好的状态。
这时您只需完成一项任务:确保在使用完该对象后,调用 Dispose 方法。
Visual Basic .NET 代码中显示了这一内容的经典形式:首先创建对象,然后在一个 Try 块中使用该对象,最后在 Finally 块中清理该对象。Try/Finally 能够确保即使出现异常也会清理对象。(在本例中,我们调用的绘图方法可能不会引发异常,所以可能并不需要 Try/Finally 块。但掌握这些技巧很有用,因此 Dr. GUI 也希望向您演示这一正确方法。)
这种形式很常见,因此 C# 为其提供了一个私有语句:using。C# 代码中的 using 语句等同于 Visual Basic .NET 代码中的声明和 Try/Finally -- 但更为简洁、方便,并减少了发生错误的可能性。(Dr. GUI 不清楚为什么 Visual Basic .NET 不包含一些诸如 using 的语句。)
接口
有些(但不是全部)可绘制对象可以被填充。某些对象(如点和线)不能被填充,因为它们不是封闭的区域,而矩形和圆等对象可以是中空的,或者被填充。
另一方面,就象将所有可绘制对象作为多态处理一样,我们也可以将所有可填充对象作为多态处理,这会很方便。例如,如同我们将所有可绘制对象放到一个集合中,通过遍历集合并在每个对象上调用 Draw 来绘制对象一样,我们可以将所有可填充对象放到一个集合中,而不考虑这些可填充对象的实际类型。因此,我们使用某种机制(如继承)来获得真正的多态。
因为不是所有的可绘制对象都可以被填充,因此不能将 Fill 方法的声明放在抽象基类中。.NET Framework 不允许类的多重继承,所以也不能将其放在另一个抽象基类中。并且如果我们从不是非可填充类的其他基类中派生出可填充对象,则不能将所有可绘制对象作为多态处理。
但 .NET Framework 支持接口 -- 并提供了一个可实现任意数量的接口的类。接口不具有任何实现 -- 没有代码,也没有任何数据。因此,实现接口的类必须提供所有内容。
接口所能包含的只有声明。以下是我们在 C# 中的接口 IFillable。单击此处在新窗口中查看全部源文件。
C#
public interface IFillable {
void Fill(Graphics g);
Color FillBrushColor { get; set; }
}
以下是等同的 Visual Basic .NET 代码。单击此处在新窗口中查看全部源文件。
Visual Basic .NET
Public Interface IFillable
Sub Fill(ByVal g As Graphics)
Property FillBrushColor() As Color
End Interface
我们不需要声明方法或者 virtual/Overridable 或 abstract/MustOverride 属性以及任何其他项,因为接口中的所有方法和属性都自动设置为公开的和 abstract/MustOverride。
使用一个属性:不能在接口中包含数据
请注意,虽然我们不能在接口中声明字段,但可以声明一个属性,因为属性实际上是作为方法实现的。
但这样做会给接口的实现者带来负担,下面就会看到。实现者必须实现 get 和 set 方法,以及实现该属性所必需的任何数据。如果实现非常复杂,则可以编写一个 helper 类以封装某些部分。在本文稍后我们将就一个略微不同的上下文环境显示如何使用 helper 类。
实现接口
我们已经定义了接口,现在可以在类中实现它了。请注意,我们必须提供所实现接口的完整实现:不能只从中选取一部分。
下面我们看看 C# 中 DFilledRectangle 类的代码。
C#
public class DFilledCircle : DHollowCircle, IFillable
{
public DFilledCircle(Point center, int radius, Color penColor,
Color brushColor) : base(center, radius, penColor) {
this.brushColor = brushColor;
}
public void Fill(Graphics g) {
using (Brush b = new SolidBrush(brushColor)) {
g.FillEllipse(b, bounding);
}
}
protected Color brushColor;
public Color FillBrushColor {
get {
return brushColor;
}
set {
brushColor = value;
}
}
public override void Draw(Graphics g) {
Fill(g);
base.Draw(g);
}
}
以下是 Visual Basic .NET 中 DFilledRectangle 类的代码。
Visual Basic .NET
Public Class DFilledRectangle
Inherits DHollowRectangle
Implements IFillable
Public Sub New(ByVal rect As Rectangle, _
ByVal penColor As Color, ByVal brushColor As Color)
MyBase.New(rect, penColor)
Me.brushColor = brushColor
End Sub
Public Sub Fill(ByVal g As Graphics) Implements IFillable.Fill
Dim b = New SolidBrush(FillBrushColor)
Try
g.FillRectangle(b, bounding)
Finally
b.dispose()
End Try
End Sub
Protected brushColor As Color
Public Property FillBrushColor() As Color _
Implements IFillable.FillBrushColor
Get
Return brushColor
End Get
Set(ByVal Value As Color)
brushColor = Value
End Set
End Property
Public Overrides Sub Draw(ByVal g As Graphics)
Dim p = New Pen(penColor)
Try
Fill(g)
MyBase.Draw(g)
Finally
p.Dispose()
End Try
End Sub
End Class
以下是有关这些类的注意事项。
从 HollowRectangle 中派生
我们从这个类的空心版本中派生出填充类。这个类中的多数内容都发生了改变:Draw 方法和构造函数都是新的(但两者都调用基类的版本),并且为 IFillable 接口的 Fill 方法以及 FillBrushColor 属性添加了实现。
需要新构造函数的原因是我们在这个类中包含了需要初始化的其他数据,即填充画笔。(您可以回顾我们前面讨论的画笔。)请注意此构造函数是如何调用基类构造函数的:在 C# 中,该调用被内置到声明 (: base(center, radius, penColor)) 中;在 Visual Basic .NET 中,我们将它明确放在 New 方法(即构造函数)的第一行 (MyBase.New(rect, penColor))。
因为我们已经向基类构造函数中传递了三个参数中的两个,现在只需初始化最后的字段即可。
……