帝国软件
  设为首页 加入收藏 关于我们
 
解密帝国网站管理系统v4.6
栏 目:
 
您的位置:首页 > 技术文档 > ASP.NET编程 >
ASP.NET可交互式位图窗体设计(5)
作者:Microsoft 发布时间:2005-03-12 来源:ASPCOOL
     维护两个列表
    因为我们要改变对象的填充颜色以实现 Change fill to hot pink 按钮,因此维护了两个可绘制对象列表:一个列表是全部对象,另一个列表是可填充对象。我们为这两个列表都使用了 ArrayList 类。ArrayList 对象包含一组 Object 引用 -- 这样一个 ArrayList 可以包含系统中任何类型的混合。
  
    这实际上并没有什么帮助 -- 我们希望 ArrayList 仅仅包括可绘制/可填充对象。为此,我们将 ArrayList 对象设为私有;然后将向列表添加对象的过程设为一个方法,该方法只接受一个 DShape。
  
    当使用 Add 方法向列表中添加对象时,我们将所有对象添加到 wholeList 中,然后检查对象是否还应添加到 filledList 集合中。
  
    请记住,Add 方法(以及列表)具有类型安全特性:它只接受 DShape(或者从 DShape 派生的类型,例如我们在上面创建的所有类型)。您不能将整数或字符串添加到列表中,这样我们便可以知道这个列表只包含可绘制对象。能够确知这一点是很方便的!
  
    绘制项
  
    我们还有一个 DrawList 方法,用于在它作为参数传递的 Graphics 对象上绘制列表中的对象。此方法具有两种情况:如果列表为空,它绘制一个字符串,说明列表为空。如果列表不为空,它使用一个 for each 构造函数遍历该列表,并在每个对象上调用 Draw。实际的遍历和绘图代码再简单不过了,如下面的 Visual Basic 所示。
  
  
    Visual Basic
  .NET Dim d As DShape
  For Each d In wholeList
  d.Draw(g)
  Next
  
  
    C# 代码几乎完全相同(当然,其行数更少)。
  
  
    C#
  foreach (DShape d in wholeList)
  d.Draw(g);
  
  
    由于列表是封装的,我们知道它具有类型安全特性,因此可以仅调用 Draw 方法而不必检查对象的类型。
  
    返回可填充列表
    最后,我们的 Change fills to hot pink(将填充色更改为粉红)按钮需要一个对所有可填充对象的引用数组,以便更改其 FillBrushColor 属性。虽然可以编写一个方法遍历列表并将颜色更改为传入的值,但这一次 Dr. GUI 选择了返回一个对象引用数组。幸运的是,ArrayList 类具有一个 ToArray 方法,利用它可以创建一个传递数组。该方法获取我们需要的数组元素类型 -- 从而可以传递回所需的类型 -- IFillable 数组。
  
  
    C#
  
  public IFillable[] GetFilledList() {
  return (IFillable[])filledList.ToArray(typeof(IFillable));
  }
  
    Visual Basic
  
  .NET Public Function GetFilledList() As IFillable()
  Return filledList.ToArray(GetType(IFillable))
  End Function
  
  
    在两种语言中,我们都使用了一个内置运算符获取给定类型的 Type 对象 -- 在 C# 中,是 typeof(IFillable);在 Visual Basic 中,是 GetType(IFillable)。
  
    调用程序使用此数组在可填充对象引用数组中遍历。例如,将填充颜色更改为粉红的 Visual Basic 代码如下所示:
  
  
  Dim filledList As IFillable() = drawingList.GetFilledList()
  Dim i As IFillable
  For Each i In filledList
  i.FillBrushColor = Color.HotPink
  Next
  
    用于分解出公共代码的 Helper 方法和类
    您可能注意到,Draw 和 Fill 方法有很多共同的代码。确切地说,每个类中创建笔或画笔的代码、建立 Try/Finally 块的代码以及清理笔或画笔的代码都是相同的 -- 唯一的区别是进行绘图或填充时调用的实际方法。(由于 C# 中 using 语法非常简洁,因而多余代码的数量并不明显。)在 Visual Basic .NET 中,每五行代码中可能有一行特殊的代码在所有实现中都是相同的。
  
    总之,如果存在大量重复代码,就需要寻求分解出公共的代码,以便形成为所有类所共享的公共子例程。这类方法有很多,Dr. GUI 非常高兴为您展示其中的两种。第一种方法仅用于类,第二种方法可用于类或接口,在本例中只用于接口。
  
    方法 1:公共入口点调用虚拟方法
    在第一个方法中,我们利用了类(不同于接口)可以包含代码这一事实。所以我们提供了一个用于创建笔的 Draw 方法的实现,以及一个异常处理程序和 Dispose,然后调用实际进行绘图的 abstract/MustOverride 方法。确切地说,我们更改了 DShapes 类以适应新的 Draw 方法,然后声明了新的 JustDraw 方法:
  
  
  Public MustInherit Class DShape
  ' Draw 不是虚拟的,这似乎有些不寻常……
  ' Draw 本应是抽象的 (MustOverride)。
  ' 但此方法是绘图的框架,而不是绘图代码本身,
  ' 绘图代码在 JustDraw 中完成。
  ' 还请注意,这意味着同原版本相比,这些类具有
  ' 不同的接口,虽然它们完成的工作相同。
  Public Sub Draw(ByVal g As Graphics)
  Dim p = New Pen(penColor)
  Try
  JustDraw(g, p)
  Finally
  p.Dispose()
  End Try
  End Sub
  ' 这里是需要成为多态的部分 -- 因此是抽象的
  Protected MustOverride Sub JustDraw(ByVal g As Graphics, _
  ByVal p As Pen)
  Protected bounding As Rectangle
  Protected penColor As Color ' 还应具有属性
  ' 还应具有移动、调整大小等方法。
  End Class
  
    一个值得注意的有趣的地方:Draw 方法并不是 virtual/Overridable。因为所有派生类都将以相同的方式完成这部分绘图(如果在 Graphics 上绘图 [如本例中的定义],则必须指派并清理笔),因此它不需要是 virtual/Overridable。
  
    实际上,Dr. GUI 认为在本例中,Draw 不应该是 virtual/Overridable。如果确实要覆盖 Draw 的行为(而不仅是 JustDraw 的行为),则可以将它设置为 virtual/Overridable。但在本例中,没有理由覆盖 Draw 的行为,如果鼓励程序员进行覆盖还会带来隐患 -- 他们可能不会正确处理笔,或者使用其他方法绘制对象而不是调用 JustDraw,这就违反了我们内置到类中的假设。因此,将 Draw 设置为非虚拟(顺便说一下,在 Brand J 中没有这个选项)可能会降低代码的灵活性,但会更加可靠 -- Dr. GUI 认为在本例中,这样做非常值得。
  
    JustDraw 的典型实现如下所示:
  
  
  Protected Overrides Sub JustDraw(ByVal g As Graphics, ByVal p As Pen)
  g.DrawEllipse(p, bounding)
  End Sub
  
    如您所见,我们获得了所希望的简洁的派生类实现。(可填充类中的实现只是略微复杂一些 -- 稍后会看到。)
  
    请注意,我们在接口中添加了一个额外的公开方法 JustDraw,除了要绘制的 Graphics 对象外,该方法还引用我们在 Draw 中创建的 Pen 对象。因为该方法需要是 abstract/MustOverride,因此必须是公开的。
  
    这并不是一个大问题,但它确实更改了类的公开接口。所以即使这个分解出公共代码的方法非常简单方便,也应当尽可能选择其他方法以避免更改公开接口。
  
    方法 2:虚拟方法调用公共 helper 方法,使用回调
    在实现接口的 Fill 方法时,代码的复杂程度也很类似:每六行代码中可能有一行特殊的代码在所有实现中都是相同的。但是我们不能将公共的实现放到接口中,因为接口只是声明,它们不包含代码或数据。此外,上面列出的方法是不能接受的,因为它会更改接口 -- 我们可能并不希望这样,或者因为是其他人创建的接口,我们根本不可能更改!
  
    所以,我们需要编写一个 helper 方法以设置并回调我们的类,以便进行实际的填充。对于本例,Dr. GUI 将代码放在一个单独的类中,这样任何类都可以使用该代码。(如果采用该方法来实现 Draw,则可以将 helper 方法作为抽象基类中的私有方法实现。)
  
    暂时不进一步展开,以下是我们创建的类:
  
  
  ' 请注意,该 delegate 提供的帮助仍然具有多态行为。
  Class FillHelper
  Public Delegate Sub Filler(ByVal g As Graphics, ByVal b As Brush)
  Shared Sub SafeFill(ByVal i As IFillable, ByVal g As Graphics, _
  ByVal f As Filler)
  Dim b = New SolidBrush(i.FillBrushColor)
  Try
  f(g, b)
  Finally
  b.dispose()
  End Try
  End Sub
  End Class
  
    我们的 helper 方法调用了 SafeFill,该方法接受一个可填充对象(请注意,这里我们使用了 IFillable 接口类型,而不是 DShape,从而只能传递可填充对象)、一个要在其上进行绘图的 Graphics 和一个称为 delegate 的私有变量。我们可以将 delegate 视为一个对方法(而不是对象)的引用 -- 如果您经常使用 C 或 C++ 编程,则可以将其视为具有类型安全特性的函数指针。可以将 delegate 设置为指向任何具有相同参数类型和返回值的方法,无论是实例方法还是 static/Shared 方法。将 delegate 设置为指向相应的方法后(例如在调用 SafeFill 时),我们可以通过 delegate 间接调用该方法。(顺便说一下,Brand J 中没有 delegate,这时如果使用此方法,会非常困难并且很不灵活)。
  
    delegate 类型 Filler 的声明位于类声明之上 -- 它被声明为一个不返回任何内容(在 Visual Basic .NET 中是一个 Sub)并且将 Graphics 和 Brush 作为参数传递的方法。我们会在将来的专栏中深入讨论 delegate。
  
    SafeFill 的操作非常简单:它指派画笔并将 Try/Finally 和 Dispose 设置为公共代码。它通过调用我们作为参数接收的 delegate 所引用的方法进行各种操作:f(g, b)。
  
    要使用这个类,需要向可填充对象类中添加一个可以通过 delegate 调用的方法,并确保将该方法的引用(地址)传递到 SafeFill,我们将在接口的 Fill 实现中调用 SafeFill。以下是 DFilledCircle 的代码:
  
  
  Public Sub Fill(ByVal g As Graphics) Implements IFillable.Fill
  FillHelper.SafeFill(Me, g, AddressOf JustFill)
  End Sub
  Private Sub JustFill(ByVal g As Graphics, ByVal b As Brush)
  g.FillEllipse(b, bounding)
  End Sub
  
    这样,当需要填充对象时,便在该对象上调用 IFillable.Fill。它将调用我们的 Fill 方法,而 Fill 方法调用 FillHelper.SafeFill,后者传递一个对我们的可填充对象的引用、所传递的要在其上进行绘图的 Graphics 对象以及一个对实际完成填充的方法的引用 -- 在本例中,该方法是私有的 JustFill 方法。
  
    然后,SafeFill 通过 delegate -- JustFill 方法来设置画笔和调用,JustFill 方法通过调用 Graphics.FillEllipse 进行填充并返回值。SafeFill 将清理画笔并返回到 Fill,Fill 再返回到调用者。
  
    最后是 JustDraw,它和原始版本中的 Draw 很类似,因为我们都调用了 Fill,并调用了基类的 Draw 方法(这是我们以前所做的)。以下是相关代码:
  
  
  Protected Overrides Sub JustDraw(ByVal g As Graphics, ByVal p As Pen)
  Fill(g)
  MyBase.JustDraw(g, p)
  End Sub
  
    请记住,指派画笔和笔的复杂之处在于它在 helper 函数中的处理 -- 在 Draw 中,它位于基类中;在 Fill 中,它位于 helper 类中。
  
    如果您认为这比以前复杂了,那么确实如此。如果您认为由于额外的调用和需要处理 delegate,速度比以前缓慢了,也确实如此。在生活中总是有很多东西需要进行权衡。
  
    那么,这样做值得吗?也许值得。这取决于公共代码的复杂程度,以及该代码需要重复的次数。也就是说,需要权衡。如果我们决定删除 Try/Finally,而只在完成绘图后清理笔和画笔,代码便会非常简单,这些方法也就用不上。并且在 C# 中,using 语句非常简洁,我们也不必费神使用这些方法。Dr. GUI 认为,在 Visual Basic 中使用 Try/Finally 时,可以使用、也可以不使用这些方法,这里旨在向大家展示这些方法,以便在遇到具有大量公共代码的情况时使用。
  
  

  
评论】【加入收藏夹】【 】【打印】【关闭
※ 相关链接
 ·带你走进ASP.NET(3)  (2005-03-12)
 ·带你走进ASP.NET(2)  (2005-03-12)
 ·带你走进ASP.NET(1)  (2005-03-12)
 ·使用ASP.NET开发移动通讯的几种方  (2005-03-12)
 ·ASP.NET可交互式位图窗体设计(9)  (2005-03-12)
 ·ASP.NET可交互式位图窗体设计(8)  (2005-03-12)
 ·ASP.NET可交互式位图窗体设计(7)  (2005-03-12)
 ·ASP.NET可交互式位图窗体设计(6)  (2005-03-12)
 ·ASP.NET应用程序规划与设计(4)  (2005-03-12)
 ·ASP.NET应用程序规划与设计(3)  (2005-03-12)

   栏目导行
  PHP编程
  ASP编程
  ASP.NET编程
  JAVA编程
   站点最新
·致合作伙伴的欢迎信
·媒体报道
·帝国软件合作伙伴计划协议
·帝国网站管理系统采集使用教程
·帝国网站管理系统模板制作教程
·帝国CMS模板下载
·Discuz6.0.1UC版通行证发布
·帝国网站管理系统之网站迁移教程
·帝国网站管理系统实例入门教程下载
·《帝国网站管理系统》实例入门教程
   类别最新
·ASP.NET中为DataGrid添加合计字段
·.text urlRewrite介绍
·利用 ASP.NET 的内置功能抵御 Web
·ASP.NET Cache
·用 WebClient.UploadData 方法 上载
·ASP.NET 程序设计-序
·什么是客户端/伺服端(Client/Serve
·因特网应用程序的开发
·网页的种类
·.NET Framework-Microsoft Visual
 
关于帝国 | 广告服务 | 联系我们 | 程序开发 | 网站地图 | 留言板 帝国网站管理系统