65.9K
CodeProject 正在变化。 阅读更多。
Home

ASP.NET 4 实战 - 复合控件

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.93/5 (9投票s)

2011 年 5 月 17 日

CPOL

7分钟阅读

viewsIcon

38993

摘自《ASP.NET 4 实战》一章

本文基于 2011 年 5 月 15 日出版的 ASP.NET 4.0 in Practice。经 Manning Publications 许可在此转载。Manning 出版 MEAP(Manning 抢先体验计划)、电子书和纸质书。MEAP 仅通过 Manning.com 销售。所有纸质书购买均包含免费的 PDF、mobi 和 epub。当移动格式可用时,所有客户都将收到通知并获得升级。访问 Manning.com 获取更多信息。

image002.jpg ASP.NET 4 实战

作者:Daniele Bochicchio, Stefano Mostarda, and Marco De Sanctis

自定义控件通常通过组合现有控件并增强其功能来创建。组合控件比从头开始创建新控件更具挑战性。在本书《ASP.NET 4.0 实战》的这篇文章中,您将学习如何使用 ASP.NET 构建复合控件。

您可能还对以下内容感兴趣…

自定义控件通常通过组合现有控件并增强其功能来创建。在大多数情况下,这个过程包括选择两个或多个控件并将它们组合以产生一个结果。了解如何做到这一点很重要,因为您可以重用现有控件并添加更多功能以简化常见、重复情况的使用。

组合控件比从头开始创建新控件更具挑战性。当您创建复合控件时,您会遇到特殊问题。例如,控件需要被封装,并且它们的成员需要在相应的控件中暴露。这项任务执行起来很简单,但也很耗时。实际情况是,您只会映射最常用和最有用的成员,并根据需要添加其他成员。

这类控件的问题在于,您将它们从外部隐藏起来,决定外部世界可以使用和不能使用什么。因此,这些控件内部处理的事件可能会成为一场噩梦。您需要实现事件冒泡技术(让事件通过控件树传播),或者选择定义新事件以仅将现有事件暴露到封装的控件之外。为了充分理解所有这些将如何影响您创建复合控件的方式,我们的场景将涵盖如何使用 ASP.NET 构建复合控件。

问题

假设您需要创建一个特殊的 DropDownList,它可以在单个声明中用于插入描述和用户要选择的选项。

通过使用此控件,您可以在编写标记方面节省大量时间,并且可以在项目中重复使用相同的功能。

解决方案

复合控件通常通过从 System.Web.UI.WebControls 中的 CompositeControl 派生而来。这个类实现了实现也是 Web 控件的自定义控件所需的大量逻辑——例如,复合控件支持样式。如果您不需要这些功能,您可以选择 System.Web.UI 中的简单 Control 类。使用 Control 类将确保生成的标记保持简单,但您需要手动添加 Composite-Control 已经提供的缺失功能。

图 1 说明了复合控件的概念。无论您使用 CompositeControl 类还是 Control 类,您都需要操纵页面的控件树并在运行时动态实例化控件。

image003.gif

图 1 复合控件组合了其他控件。在外部,它们被视为一个封装了所有逻辑的单个控件。

复合控件通过组合控件来工作,因此控件是使用 CreateChildControls 方法添加的。

每当需要子控件时,CreateChildControls 方法都会通过调用 EnsureChildControls 方法来调用。当您操作控件树时,您需要小心并记住这些控件将嵌套到控件本身,然后再嵌套到页面中。要将控件添加到另一个控件中,您必须访问其 Controls 属性并通过 Add 方法添加它,如以下列表所示。

列表 1 CreateChildControl 包含嵌套控件声明

C#

public class SuperDropDownList: CompositeControl, INamingContainer
{
  protected override void CreateChildControls()
  {
    if (ChildControlsCreated)                                        #1
      return;
 
    Controls.Clear();                                                #A
 
    Controls.Add(new LiteralControl("<p>"));
 
    Label labelText = new Label();
    labelText.Text = Description;
    Controls.Add(labelText);
 
    Controls.Add(new LiteralControl(
                      string.IsNullOrEmpty(Description)?
 
                             string.Empty:": "));
    DropDownList listControl = new DropDownList();
 
    Controls.Add(listControl);
 
    Controls.Add(new LiteralControl("</p>"));
 
    ChildControlsCreated = true;                                     #1
  }
...                                                                  #B
}

VB

Public Class SuperDropDownList
  Inherits CompositeControl
  Implements INamingContainer
  Protected Overrides Sub CreateChildControls()
    If ChildControlsCreated Then                                     #1
      Return
    End If
 
    Controls.Clear()                                                  #A
 
    Controls.Add(New LiteralControl("<p>"))
 
    Dim labelText As New Label()
    labelText.Text = Description
    Controls.Add(labelText)
 
    Controls.Add(New LiteralControl(If(String.IsNullOrEmpty(Description),
                                       String.Empty, ": ")))
 
    Dim listControl As New DropDownList()
    Controls.Add(listControl)
 
    Controls.Add(New LiteralControl("</p>"))
 
    ChildControlsCreated = True                                        #1
  End Sub
...                                                                    #B
End Class

#1 避免控件创建
#A 删除现有控件
#B 继续代码

如您在此列表中所见,我们基本上添加了一些控件以显示 DropDownList 和一个描述。为了从控件树中删除不需要的控件(可能是可以在标记中添加的 Literal 控件),我们调用了 Controls.Clear 来重置控件树。#1 中的代码实际上不是必需的,因为它已经包含在 Composite-Control 中。列表 1 展示了当使用另一个更简单的基控件(如 Control)时如何处理这个问题。请参阅图 2 以查看结果。

image004.gif

图 2 新的 SuperDrop-DownList 控件正在运行。此控件组合了不同的控件以提供简单的实现。

为了简洁起见,我们省略了列表 1 中属性的声明。当您需要为内部控件设置属性时,您必须使用一种特殊的方法:您需要从控件外部访问内部对象的属性。在这些情况下,首选的方法如下面的代码片段所示

C#

public IList DataSource
{
  get
  {
    EnsureChildControls();                                          #A
    return ((DropDownList)Controls[3]).DataSource as IList;
  }
 
  set
  {
    EnsureChildControls();
    ((DropDownList)Controls[3]).DataSource = value;
  }
}

VB

Public Property DataSource() As IList
  Get
    EnsureChildControls()
    Return TryCast(DirectCast(Controls(3), DropDownList).DataSource, IList)
  End Get
 
  Set
    EnsureChildControls()                                          #A
    DirectCast(Controls(3), DropDownList).DataSource = value
  End Set
End Property

#A 将调用 CreateChildControls

如您所见,我们引用了我们在列表 1 中创建的控件(在本例中是 DropDownList),通过位置找到它,并直接公开其内部属性。因为您不必保持内部属性同步(使用此模式会自动执行),所以此示例向您展示了处理这种情况的最佳方法。

如何避免按位置引用控件 为了生成更简洁的代码,您还可以将对控件的引用保存在 CreateChildControls 中,然后使用此语法(而不是按位置查找它们)引用控件。

EnsureChildControls 的调用不仅重要——它们是强制性的。这些调用确保在访问控件之前创建它们。

现在我们控件的基础设施已经就位,让我们看看如何在复合控件中使用事件。

复合控件中的事件

事件在自定义控件中用于简化处理状态所需的代码。复合控件隐藏了子控件,因此您需要通过实现事件包装器将它们的事件传播到容器外部。

重定向事件是一种简单的技术。事件首先在本地拦截,然后传播到外部。请看下面的代码片段以了解其工作原理。在这种情况下,代码胜过千言万语。

C#

public event EventHandler SelectedValueChanged;

protected void OnSelectedValueChanged(EventArgs e)
{
  if (SelectedValueChanged != null)
    SelectedValueChanged(this, e);
}

VB

Public Event SelectedValueChanged As EventHandler

Protected Sub OnSelectedValueChanged(e As EventArgs)
  RaiseEvent SelectedValueChanged(Me, e)
End Sub

此代码片段将暴露一个名为 SelectedValueChanged 的新事件和一个名为 OnSelectedValueChanged 的新方法,用于在标记中定义事件处理程序。为了将事件附加到内部控件,我们还需要在 CreateChildControls 方法中,紧接在 DropDownList 实例之后添加这段简单的代码

C#

DropDownList listControl = new DropDownList();
listControl.SelectedIndexChanged += (object sender, EventArgs e) => {
  OnSelectedValueChanged(e);
};

VB

Dim listControl as New DropDownList()
listControl.SelectedIndexChanged += Function(sender As Object,
                                             e As EventArgs) Do
     OnSelectedValueChanged(e)
End Function

此代码片段确保当 DropDownListSelectedIndexChanged 事件触发时,我们的事件也会触发。结果是,在页面内部创建的事件处理程序也将被调用,并且我们的事件将传播到包含的控件外部。

讨论

当您构建复合控件时,您需要注意这样一个事实:您不是在生成标记,而是在组合您的控件,将它们混合在一起,并操作页面的控件树。在一个像我们这里介绍的简单场景中,这项任务当然很容易实现,因为您正在利用现有控件,但它也容易出错。正如您在此场景中所了解的那样,您需要了解 CreateChildControlsEnsureChildControls 的工作原理。

摘要

开始使用自定义控件并不困难,但高级场景涉及对 ASP.NET 的深入理解。在更简单的情况下,自定义控件可以通过实现和支持重复性任务来帮助您避免代码重复。您可以轻松地为每个控件添加 PostBack 和模板,并且实现对数据绑定的支持也并非那么困难。

 

您可能还会对以下 Manning 图书感兴趣:

image005.jpg

ASP.NET MVC 2 实战
Jeffrey Palermo, Ben Scheirman, Jimmy Bogard, Eric Hexter, and Matthew Hinze

 

image005.jpg

ASP.NET AJAX 实战
Alessandro Gallo, David Barkol, and Rama Krishna Vavilala

image007.jpg

IronRuby 实战
Ivan Porto Carrero and Adam Burmister

 

© . All rights reserved.