ASP.NET 4 实战 - 复合控件
摘自《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 获取更多信息。
![]() |
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
类,您都需要操纵页面的控件树并在运行时动态实例化控件。
复合控件通过组合控件来工作,因此控件是使用 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 以查看结果。
为了简洁起见,我们省略了列表 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
此代码片段确保当 DropDownList
的 SelectedIndexChanged
事件触发时,我们的事件也会触发。结果是,在页面内部创建的事件处理程序也将被调用,并且我们的事件将传播到包含的控件外部。
讨论
当您构建复合控件时,您需要注意这样一个事实:您不是在生成标记,而是在组合您的控件,将它们混合在一起,并操作页面的控件树。在一个像我们这里介绍的简单场景中,这项任务当然很容易实现,因为您正在利用现有控件,但它也容易出错。正如您在此场景中所了解的那样,您需要了解 CreateChildControls
和 EnsureChildControls
的工作原理。
摘要
开始使用自定义控件并不困难,但高级场景涉及对 ASP.NET 的深入理解。在更简单的情况下,自定义控件可以通过实现和支持重复性任务来帮助您避免代码重复。您可以轻松地为每个控件添加 PostBack 和模板,并且实现对数据绑定的支持也并非那么困难。
![]() |
ASP.NET MVC 2 实战
|
![]() |
ASP.NET AJAX 实战 |
![]() |
IronRuby 实战
|