列表控件示例
数据绑定控件通常为列表控件。列表控件通过为它的主框架边界内的每个绑定数据项重复固定的模板,生成它自己的用户界面。例如,CheckBoxList 控件只是为每个绑定数据项重复 CheckBox 控件。同样,DropDownList 控件遍历它的数据源,并且在 < select > 父标记内创建新的 < option > 元素。除了列表控件以外,ASP.NET 还提供了迭代控件。它们有什么不同?
列表控件和迭代控件的不同之处在于被应用于每个数据项的可重复模板允许具有的自定义级别。像 CheckBoxList 控件一样,Repeater 控件遍历绑定数据项并应用用户定义的模板。Repeater(以及更完善的 DataList 控件)极为灵活,但是在使代码保持模块化和分层化方面不能提供多少帮助。要使用 Repeater,您需要在该页(或外部用户控件)中定义模板,并使用 ASPX 源中的数据绑定属性。它是快速、有效的,有时还是必要的,但肯定不是整洁和优雅的。
在 ASP.NET 1.x 中,所有列表控件都从 ListControl(它是表 1 中唯一一个已经在 1.x 中定义的类)继承。让我们进入编码猴子模式,并且开始练习使用 ASP.NET 2.0 中的数据绑定控件。我将首先生成一个 HeadlineList 控件,以便为每个数据项呈现两行数据绑定文本。此外,该控件还将具备一些布局功能,例如,垂直或水平呈现。
列表控件示例:HeadlineList 示例控件
正如前面提到的那样,ListControl 是 ASP.NET 1.x 和 2.0 中所有列表控件的基类。非常令人愉快的是,可以用一种非常平滑的方式将在此为 ASP.NET 2.0 编写的 HeadlineList 控件向后移植到 ASP.NET 1.x。出于某种原因,当需要生成标题列表时,人们的大脑中涌现的第一个想法往往是使用 Repeater。的确,Repeater 会使这一工作变得非常简单。
- < asp:Repeater runat="server">
- < HeaderTemplate>
- < table>
- < /HeaderTemplate>
- < ItemTemplate>
- < tr>< td>
- < %# DataBinder.Eval(Container.DataItem, "Title") %>
- < hr>
- < %# DataBinder.Eval(Container.DataItem, "Abstract") %>
- < /td>< /tr>
- < /ItemTemplate>
- < FooterTemplate>
- < /table>
- < /FooterTemplate>
- < /asp:Repeater>
这段代码有什么问题?或者更准确地说,这段代码中有哪些可以改进的地方?
注:在 ASP.NET 2.0 中,您可以将 DataBinder.Eval(Container.DataItem, field) 替换为一个较短的表达式,该表达式受益于 Page 类上的一个新的公共方法 — Eval。这一新的表达式类似于 Eval(field)。在内部,Eval 调用 DataBinder 类上的静态 Eval 方法,并且确定要使用的正确绑定上下文。
字段的名称在 ASPX 页中硬编码。可以实现可重用性,但只能通过剪切和粘贴实现。您所添加的用于使 Repeater 的行为更加丰富多彩的代码越多,对该解决方案及其跨越页和项目的可重用性的危害就越大。如果标题列表控件恰恰是您需要的东西,则请改而尝试以下方法。
- public class HeadlineList : ListControl, IRepeatInfoUser
- {
- :
- }
ListControl 是列表控件的基类(它位于与 CheckBoxList、DropDownList 和类似控件相同的系列中);IRepeatInfoUser 是上述大多数控件加以实现以便用水平或垂直方式在列和行中呈现的几乎不为人所知的界面。请注意,ListControl 和 IRepeatInfoUser 还存在于 ASP.NET 1.x 中,并且以几乎与 2.0 相同的方式工作。
列表控件是围绕一个要重复的控件生成的;该控件(或控件图)是一个类属性,并且在加载时实例化以节省一些 CPU 时间。以下为私有 ControlToRepeat 属性的实现。
- private Label _controlToRepeat;
- private Label ControlToRepeat
- {
- get
- {
- if (_controlToRepeat == null)
- {
- _controlToRepeat = new Label();
- _controlToRepeat.EnableViewState = false;
- Controls.Add(_controlToRepeat);
- }
- return _controlToRepeat;
- }
- }
在该示例中,要重复的控件(标题)是一个在首次读取时实例化的 Label。HeadlineList 控件还应当向用户提供通过多种属性(如 RepeatLayout、RepeatColumns 和 RepeatDirection)影响外观的方式。很多标准列表控件上都定义了这些属性,因此开发人员不应该对它们感到陌生。它们的实现是类似的,并且看起来像下面的代码。
- public virtual RepeatDirection RepeatDirection
- {
- get
- {
- object o = ViewState["RepeatDirection"];
- if (o != null)
- return (RepeatDirection) o;
- return RepeatDirection.Vertical;
- }
- set
- {
- ViewState["RepeatDirection"] = value;
- }
- }
为完成 HeadlineList 控件而需要编写的另一段代码以呈现为中心。IRepeatInfoUser 接口对您可以用来控制呈现过程的各种属性进行计数。这方面的属性示例有 HasHeader、HasFooter 和 HasSeparator 布尔型属性。您可以像实现其他任何普通属性一样实现这些属性,并且根据需要在 RenderItem 接口方法中使用它们。
- public void RenderItem(ListItemType itemType, int repeatIndex,
- RepeatInfo repeatInfo, HtmlTextWriter writer)
- {
- string format = "< b>{0}< /b>< hr style='solid 1px black'>{1}";
- Label lbl = ControlToRepeat;
- int i = repeatIndex;
- lbl.ID = i.ToString();
- string text = String.Format(format, Items[i].Text, Items[i].Value);
- lbl.Text = text;
- lbl.RenderControl(writer);
- }
RenderItem 对向页提供的输出承担最终的责任。它获得要重复的控件,并且将其呈现到标记中。RenderItem 是从 Render 中调用的。
- protected override void Render(HtmlTextWriter writer)
- {
- if (Items.Count >0)
- {
- RepeatInfo ri = new RepeatInfo();
- Style controlStyle = (base.ControlStyleCreated
- ? base.ControlStyle : null);
- ri.RepeatColumns = RepeatColumns;
- ri.RepeatDirection = RepeatDirection;
- ri.RepeatLayout = RepeatLayout;
- ri.RenderRepeater(writer, this, controlStyle, this);
- }
- }
RepeatInfo 是一个 Helper 对象,它经过专门设计,以便通过重复现有的控件图来生成新控件。以上就是所需的全部代码。让我们准备一个示例页,并测试该控件。
- < expo:headlinelist id="HeadlineList1" runat="server"
- repeatlayout="Table" repeatdirection="Vertical" repeatcolumns="2"
- datatextfield="LastName" datavaluefield="Notes" />
图 2 显示了该控件的工作方式。
列表控件示例: HeadlineList 数据绑定控件
该控件在设计时工作正常,并且不需要插入其他任何代码。然而,这段代码的最令人愉快的边界效应并非免费的设计时支持。对我来说,它简直太美妙了,因为它能够使用 ADO.NET 数据源对象(例如,DataTable 或 DataSet)和数据源组件(如 SqlDataSource)。您可以取走这段代码,将其编译为 ASP.NET 1.x 项目,而它就可以使用基于 IEnumerable 的数据源。如果将这段代码引入到 ASP.NET 2.0 项目中,则它无须更改就同样可以使用数据源对象。
这一事实的意义是什么?
在 ASP.NET 1.x 中,ListControl 类是一个令人愉快的例外 — 但仍然是一个例外。在 ASP.NET 2.0 中,您可以使用类似的简单但有效的方法来生成任何数据绑定控件。在这样做的时候,您可以利用合并了大部分复杂性并且将大多数已知的最佳做法硬编码的新基类。
【编辑推荐】