ASP.NET可交互式位图窗体设计(上)

发表于:2007-07-01来源:作者:点击数: 标签:
请您检查作为 Microsoft ASP.NET 应用程序运行的示例(带有源代码)。或者仅在新窗口中查看源代码。请注意, 程序员 的注释在示例程序中是英文的,而在本文中被翻译成中文,以便更好地解释该程序。另外,使用了此新功能后(在此感谢 MSDN Web Publishing Tea


  请您检查作为 Microsoft ASP.NET 应用程序运行的示例(带有源代码)。或者仅在新窗口中查看源代码。请注意,程序员的注释在示例程序中是英文的,而在本文中被翻译成中文,以便更好地解释该程序。另外,使用了此新功能后(在此感谢 MSDN Web Publishing Team!),您可以将两个窗口都放在屏幕上,这样便可以方便地查看相应代码。

  简介

  在本文,我们将通过一个灵活的绘图应用程序提供一个有关继承、abstract (MustInherit) 基类和接口的更为完整的示例。这不是一个控制台应用程序;由于其图形化的特征,更适合作为一个 Microsoft Windows 窗体应用程序。(这就给了我们一个了解 Windows 窗体的机会。)

  该 ASP.NET 版本将演示如何在 Web 页上使用自定义绘制的位图 -- 这在大多数 Web 编程系统中是非常难以实现的,但使用 ASP.NET 则很简单。Dr. GUI 相信您会喜欢这一点。而且您还可以运行该应用程序。

  经典的多态示例

  在教授编程时,有一些常用的、非常标准的示例程序。而我最初曾发誓不使用这些示例:我不会使用一个字符串类作为示例,也不会使用复杂的数字或绘图应用程序。毕竟,这样做就不是原创了。

  然而随着事情的发展,使用这些示例显得很有必要(不仅仅是因为懒惰):这些示例非常丰富,易于解释和理解,并且可以非常清晰地揭示核心概念。

  以下是该程序 Windows 窗体版本的屏幕快照:

图 1:经典多态示例的 Windows 窗体版本

  以下是 ASP.NET 版本在浏览器中的显示:

图 2:经典多态示例的 ASP.NET 版本

  您可以运行上面显示的 ASP.NET 版本。

  我们的任务

  这个程序的基本思想如下:我们有一个 abstract 基类(在 Microsoft Visual Basic? 中是 MustInherit),其中包含公共数据(如边框)和一套虚拟方法,虚拟方法多数是抽象的(在 Visual Basic 中是 MustOverride),例如 Draw。请注意,Draw 的多态性很重要,因为每个可绘制对象类型(如点、线、矩形、圆等)都是用完全不同的代码绘制的。

  虽然方法可以是多态的,但数据不能。因此,我们只将确实应用于所有可能的可绘制对象的数据放在程序中 -- 在本例中,放置了一个边框和颜色(在其中绘制对象的线)。

  与特定类型的可绘制对象相关的数据(例如圆的中心和半径、矩形相对点的坐标,或者一条线的端点)都应该在与该类型的可绘制对象对应的特定类(从抽象基类中派生)中声明。请注意,可以使用二次派生合并相似的对象。例如,可以从椭圆中派生出圆,因为所有的圆都是椭圆。与此类似,也可以从矩形中派生出方形,因为所有的方形都是矩形(也都是四边形、多边形)。所选择的派生树会反映类之间的关系,以及常用的预期使用模式,这样您经常执行的操作便会非常快速、方便。

  以下是我们的类派生图:

图 3:类派生图

  因为构造函数(在 Visual Basic 中为 New)存在的主要原因是用于初始化数据,因此构造函数不是(实际上也不能是)多态的。这意味着初始创建操作不能是多态的,因为数据要求随类型的不同而不同。但是,一个好的设计在对象创建后,可在之后的使用中将对象作为多态处理,这里我们就是这样做的。

  让我们看看这个类集中包含什么,从根抽象基类开始:

  抽象 (MustInherit) 基类

  以下是 C# 中抽象基类的代码。单击此处在新窗口中查看全部源文件。

  C#

public abstract class DShape {
public abstract void Draw(Graphics g);
protected Rectangle bounding;
protected Color penColor; // 还应具有属性
// 还应具有移动、调整大小等方法。
}

  以下是等同的 Visual Basic .NET 代码。单击此处在新窗口中查看全部源文件。

  Visual Basic .NET

Public MustInherit Class DShape
Public MustOverride Sub Draw(ByVal g As Graphics)
Protected bounding As Rectangle
Protected penColor As Color @# 还应具有属性
@# 还应具有移动、调整大小等方法。
End Class

  语法虽然不同,但很明显这是相同的类。

  请注意,Draw 方法被暗示为 virtual (Overridable),因为它被声明为 abstract (MustOverride)。还要注意在这个类中我们并没有提供一个实现。因为我们尚不知道在这个类中执行的对象,因此不可能写出绘图代码。

  包含哪些数据?

  另请注意,这里并没有很多数据 -- 但我们已经为这样一个抽象类声明了所有数据。

  每一个可绘制对象(无论其形状如何)都有一个边框 -- 即可以完全包含该对象的最小可能矩形。边框用于绘制点(作为很小的矩形)、长方形和圆 -- 并且对于其他形状,可以作为第一个用于点击或碰撞测试的快速估计。

  适用于所有对象的其他共同点并没有很多;中心对于某些对象有用,例如圆和长方形,对于其他对象(如三角形)则没有意义。并且通常都是使用角来表示矩形,而不是使用中心。但您不能使用角来指定圆,因为圆没有角。Dr. GUI 确信您已经看到了为一个普通可绘制对象指定其他数据的困难之处。

  每个可绘制对象还有一个与绘制它的线相关联的颜色,这里我们也做了声明。

  某些派生类

  如上所述,我们不能真正创建一个抽象基类类型的对象,虽然我们可以将从抽象基类(或任何基类)中派生的任何对象作为基类对象处理。

  所以,为创建一个绘图对象,我们必须从抽象基类中派生一个新类 -- 并确保覆盖所有 abstract/MustOverride 方法。

  在本例中我们将使用 DHollowCircle 类。DHollowRectangle 类和 DPoint 类非常相似。

  以下是 C# 中的 DHollowCircle。单击此处在新窗口中查看其他类。

  C#

public class DHollowCircle : DShape
{
public DHollowCircle(Point p, int radius, Color penColor) {
p.Offset(-radius, -radius); // 需要转换到左上角
int diameter = radius * 2;
bounding = new Rectangle(p, new Size(diameter, diameter));
this.penColor = penColor;
}

public override void Draw(Graphics g) {
using (Pen p = new Pen(penColor)) {
g.DrawEllipse(p, bounding);
}
}
}

以下是等同的 Visual Basic .NET 类。单击此处在新窗口中查看其他类。

Visual Basic .NET

Public Class DHollowCircle
Inherits DShape

Public Sub New(ByVal p As Point, ByVal radius As Integer, _
ByVal penColor As Color)
p.Offset(-radius, -radius) @# 需要转换到左上角
Dim diameter As Integer = radius * 2
bounding = New Rectangle(p, New Size(diameter, diameter))
Me.penColor = penColor
End Sub

Public Overrides Sub Draw(ByVal g As Graphics)
Dim p = New Pen(penColor)
Try
g.DrawEllipse(p, bounding)
Finally
p.Dispose()
End Try
End Sub
End Class

  请注意,我们没有为这个类声明其他数据 -- 它给出的边框和笔已经足够了。(对于点和矩形是这样,但对于三角形和其他多边形就不够了。)我们的应用程序不需要在设置圆后知道圆的中心或半径,因此将它们忽略掉。(如果需要中心和半径,我们可以存储这些数据,或者根据边框计算得出。)

  但我们确实需要边框,因为它是用于绘制圆的 Graphics.DrawEllipse 方法的一个参数。因此我们根据在构造函数中传递的中心点和半径计算边框。

  下面我们深入了解每一个方法。

  绘图如何改变

  您会注意到,Draw 方法与基类基本相同 -- 主要差别在于它调用了 Fill 方法,因为要完成绘制一个填充对象,所以需要对其进行填充。我们没有为绘制轮廓重写代码,而是再次调用了基类的方法:Visual Basic .NET 中的 MyBase.Draw(g) 或 C# 中的 base.Draw(g);。

  因为我们正在指派用于绘制轮廓的笔,因此需要使用 usingTry/FinallyDispose 以确保迅速释放 Windows 笔对象。(同样,如果非常确信所调用的方法不会引发异常,可以在完成笔的处理后,跳过异常处理,而只调用 Dispose。但我们必须调用 Dispose,无论是直接调用,还是通过 using 语句。

  实现 Fill 方法

  Fill 方法很简单:指派一个画笔,然后在屏幕上填充对象 -- 并确保 Dispose 画笔。

  请注意,在 Visual Basic .NET 中,您必须明确指定实现一个接口的方法 (... Implements IFillable.Fill);而在 C# 中,实现接口中的方法或属性由方法或属性的签名确定(因为您编写了一个称为 Fill 的方法,该方法不返回任何内容并接受一个 Graphics,因此它必须是 IFillable.Fill 的实现)。非常奇怪,Dr. GUI 通常喜欢简洁的编程结构(如果不可能通过简单的编写完成),但实际上却倾向使用 Visual Basic 的语法,因为这种语法既清晰又灵活(Visual Basic 实现类中的方法名称不必与接口中的名称匹配,并且一个给定方法通常能够实现多个接口方法)。

  实现属性

  IFillable 接口还包含一个属性,从中可以 setget 画笔颜色。(我们在 Change fills to hot pink [将填充色更改为粉红] 按钮处理程序中使用该属性。)

  为实现公开属性,我们需要一个私有或保护的字段。这里我们选择了保护字段,以便能够方便地从派生类(而不允许任何类)对其进行访问。

  具有该字段后,我们可以轻松地编写一个很简单的 setget 方法对以实现属性。

  请再次注意,在 Visual Basic .NET 中,必须明确指定所实现的属性。

  接口还是抽象 (MustInherit) 基类?

  在面向对象的编程中,最常见的争论之一就是,是使用抽象基类还是使用接口。

  接口可以提供一些额外的灵活性,但也要付出一定代价:对于实现该接口的每一个类,必须实现其中的所有内容。我们可以使用一个 helper 类来协助这项工作(稍后会提供一个相关示例),但您仍然必须在所有地方实现所有内容。并且接口不能包含数据(虽然如此,与在 Brand J 的系统中不同,它们可以包含属性,因此它们可以看起来好象包含了数据)。

  在本例中,Dr. GUI 为 DShape 选择了使用一个抽象基类而不是一个接口,因为他不想在每个类中将数据作为属性重复实现。此外,还因为从 DShape 派生出的所有内容都是形状,由于可填充对象仍然是形状,因而也可以进行填充。

  您的选择可能有所不同,但 Dr. GUI 认为他在此做出的选择非常正确。

  绘图对象的容器

  因为要重复绘制我们的对象(在 Windows 窗体版本中,每次都将绘制图像;在 ASP.NET 版本中,每次都将重新加载 Web 页),因此需要将它们放在一个容器中,以便能够反复访问它们。

  Dr. GUI 更进一步,将容器变得智能化,使其知道如何绘制所包含的对象。以下是这个容器类的 C# 代码:

  C#

public class DShapeList {
ArrayList wholeList = new ArrayList();
ArrayList filledList = new ArrayList();

public void Add(DShape d) {
wholeList.Add(d);
if (d is IFillable)
filledList.Add(d);
}

public void DrawList(Graphics g) {
if (wholeList.Count == 0)
{
Font f = new Font("Arial", 10);
g.DrawString("没有任何要绘制的内容;列表为空...",
f, Brushes.Gray, 50, 50);
}
else
{
foreach (DShape d in wholeList)
d.Draw(g);
}
}

public IFillable[] GetFilledList() {
return (IFillable[])filledList.ToArray(typeof(IFillable));
}
}

  以下为等同类的 Visual Basic .NET 代码:

  Visual Basic

.NET Public Class DShapeList
Dim wholeList As New ArrayList()
Dim filledList As New ArrayList()

Public Sub Add(ByVal d As DShape)
wholeList.Add(d)
If TypeOf d Is IFillable Then filledList.Add(d)
End Sub

Public Sub DrawList(ByVal g As Graphics)
If wholeList.Count = 0 Then
Dim f As New Font("Arial", 10)
g.DrawString("没有任何要绘制的内容;列表为空...", _
f, Brushes.Gray, 50, 50)
Else
Dim d As DShape
For Each d In wholeList
d.Draw(g)
Next
End If
End Sub

Public Function GetFilledList() As IFillable()
Return filledList.ToArray(GetType(IFillable))
End Function
End Class


原文转自:http://www.ltesting.net