作者 :斯科特·米切尔
为了提供灵活性,GridView 提供了 TemplateField,它使用自定义模板进行呈现。 模板可以包含静态 HTML、Web 控件和数据绑定语法的组合。 本教程介绍如何使用 TemplateField 通过 GridView 控件实现更大的自定义度。
介绍
GridView 由一组字段组成,这些字段指示要包含在呈现的输出中的属性 DataSource
以及数据的显示方式。 最简单的字段类型是 BoundField,它以文本形式显示数据值。 其他字段类型使用备用 HTML 元素显示数据。 例如,CheckBoxField 呈现为复选框,其选中状态取决于指定数据字段的值;ImageField 呈现其图像源基于指定数据字段的图像。 可以使用 HyperLinkField 和 ButtonField 字段类型呈现其状态依赖于基础数据字段值的超链接和按钮。
虽然 CheckBoxField、ImageField、HyperLinkField 和 ButtonField 字段类型允许数据的备用视图,但它们在格式设置方面仍然相当有限。 CheckBoxField 只能显示单个复选框,而 ImageField 只能显示单个图像。 如果特定字段需要根据不同的数据字段值显示某些文本、复选框 和 图像,该怎么办? 或者,如果想要使用 CheckBox、Image、HyperLink 或 Button 以外的 Web 控件显示数据,该怎么办? 此外,BoundField 将其显示限制为单个数据字段。 如果要在单个 GridView 列中显示两个或多个数据字段值,该怎么办?
为了适应此级别的灵活性,GridView 提供了 TemplateField,该字段使用 模板进行呈现。 模板可以包含静态 HTML、Web 控件和数据绑定语法的组合。 此外,TemplateField 还具有各种模板,可用于自定义不同情况的呈现。 例如,默认情况下, ItemTemplate
该模板用于呈现每行的单元格,但 EditItemTemplate
模板可用于在编辑数据时自定义接口。
本教程介绍如何使用 TemplateField 通过 GridView 控件实现更大的自定义度。 在前面的教程中,我们了解了如何使用DataBound
和RowDataBound
事件处理程序基于底层数据自定义格式。 基于基础数据自定义格式的另一种方法是从模板中调用格式设置方法。 本教程还将介绍此技术。
在本教程中,我们将使用 TemplateFields 自定义员工列表的外观。 具体而言,我们将列出所有员工,但将在一列中显示员工的名字和姓氏,其雇用日期在日历控件中,以及一个状态列,指示他们在公司雇用了多少天。
图 1:三个 TemplateField 用于自定义显示(单击以查看全尺寸图像)
步骤 1:将数据绑定到 GridView
在需要使用 TemplateFields 自定义外观的报告方案中,我发现首先创建一个仅包含 BoundFields 的 GridView 控件,然后根据需要添加新的 TemplateFields 或将现有 BoundFields 转换为 TemplateFields,这是最简单的方法。 因此,让我们通过设计器将 GridView 添加到页面并将其绑定到返回员工列表的 ObjectDataSource 来开始本教程。 这些步骤将为每个员工字段创建具有 BoundFields 的 GridView。
打开 GridViewTemplateField.aspx
页面,将 GridView 从工具箱拖到设计器上。 从 GridView 的智能标记中,选择添加一个新 ObjectDataSource 控件,该控件调用 EmployeesBLL
类的 GetEmployees()
方法。
图 2:添加一个调用GetEmployees()
方法的新 ObjectDataSource 控件(单击以查看全尺寸图像)
以这种方式绑定 GridView 将自动为每个员工属性添加 BoundField:EmployeeID
、LastName
、FirstName
、Title
、HireDate
、ReportsTo
和Country
。 对于此报表,我们不要费心显示EmployeeID
或ReportsTo
Country
属性。 若要删除“BoundFields”,您可以:
- 使用“字段”对话框单击 GridView 智能标记中的“编辑列”链接来显示此对话框。 接下来,从左下列表中选择 BoundFields,然后单击红色 X 按钮以删除 BoundField。
- 在源视图中手动编辑 GridView 的声明性语法,然后删除要删除的 BoundField 对应的
<asp:BoundField>
元素。
删除EmployeeID
ReportsTo
Country
和 BoundFields 后,GridView 的标记应如下所示:
<asp:GridView ID="GridView1" runat="server"
AutoGenerateColumns="False" DataKeyNames="EmployeeID"
DataSourceID="ObjectDataSource1">
<Columns>
<asp:BoundField DataField="LastName" HeaderText="LastName"
SortExpression="LastName" />
<asp:BoundField DataField="FirstName" HeaderText="FirstName"
SortExpression="FirstName" />
<asp:BoundField DataField="Title" HeaderText="Title"
SortExpression="Title" />
<asp:BoundField DataField="HireDate" HeaderText="HireDate"
SortExpression="HireDate" />
</Columns>
</asp:GridView>
花点时间在浏览器中查看进度。 此时,应看到一个表,其中包含每个员工的记录和四列:一个用于员工的姓氏,一个用于他们的名字,一个用于他们的职务,一个用于他们的雇用日期。
图 3: LastName
每个员工显示“、 FirstName
” Title
和 HireDate
“字段”(单击以查看全尺寸图像)
步骤 2:在单个列中显示名字和姓氏
目前,每个员工的名字和姓氏都显示在单独的列中。 最好将它们合并成单个列。 为此,需要使用 TemplateField。 我们可以添加新的 TemplateField,向其添加所需的标记和数据绑定语法,然后删除 FirstName
和 LastName
BoundFields,也可以将 FirstName
BoundField 转换为 TemplateField,编辑 TemplateField 以包含 LastName
值,然后删除 LastName
BoundField。
这两种方法都能得到相同的结果,但我个人更喜欢在可能的情况下将 BoundFields 转换为 TemplateFields,因为转换会自动添加一个带有 Web 控件和数据绑定语法的 ItemTemplate
和 EditItemTemplate
,以便模拟 BoundField 的外观和功能。 好处是,我们对于 TemplateField 的工作量会减少,因为转换过程已经为我们完成了一部分工作。
若要将现有 BoundField 转换为 TemplateField,请单击 GridView 智能标记中的“编辑列”链接,打开“字段”对话框。 选择要从左下角的列表转换的 BoundField,然后单击右下角的“将此字段转换为 TemplateField”链接。
图 4:从“字段”对话框将 BoundField 转换为 TemplateField(单击可查看全尺寸图像)
继续将 FirstName
BoundField 转换为 TemplateField。 在此更改后,设计器中没有感知性的差异。 这是因为将 BoundField 转换为 TemplateField 会创建一个 TemplateField,用于维护 BoundField 的外观。 尽管设计器目前没有视觉差异,但此转换过程已将 BoundField 的声明性语法替换为以下 TemplateField 语法 <asp:BoundField DataField="FirstName" HeaderText="FirstName" SortExpression="FirstName" />
:
<asp:TemplateField HeaderText="FirstName" SortExpression="FirstName">
<EditItemTemplate>
<asp:TextBox ID="TextBox1" runat="server"
Text='<%# Bind("FirstName") %>'></asp:TextBox>
</EditItemTemplate>
<ItemTemplate>
<asp:Label ID="Label1" runat="server"
Text='<%# Bind("FirstName") %>'></asp:Label>
</ItemTemplate>
</asp:TemplateField>
如你所见,TemplateField 由两个模板组成,一个 ItemTemplate
模板包含一个标签,其 Text
属性设置为 FirstName
数据字段的值;另一个 EditItemTemplate
模板包含一个 TextBox 控件,其 Text
属性也设置为 FirstName
数据字段。 数据绑定语法 - <%# Bind("fieldName") %>
指示数据字段 fieldName
绑定到指定的 Web 控件属性。
若要将 LastName
数据字段值添加到此 TemplateField,我们需要在 ItemTemplate
中添加另一个标签 Web 控件,并将其 Text
属性绑定到 LastName
。 可以通过手动操作或使用设计器来实现这一点。 若要手动执行此作,只需将适当的声明性语法添加到 ItemTemplate
:
<asp:TemplateField HeaderText="FirstName" SortExpression="FirstName">
<EditItemTemplate>
<asp:TextBox ID="TextBox1" runat="server"
Text='<%# Bind("FirstName") %>'></asp:TextBox>
</EditItemTemplate>
<ItemTemplate>
<asp:Label ID="Label1" runat="server"
Text='<%# Bind("FirstName") %>'></asp:Label>
<asp:Label ID="Label2" runat="server"
Text='<%# Bind("LastName") %>'></asp:Label>
</ItemTemplate>
</asp:TemplateField>
若要通过设计器添加它,请单击 GridView 智能标记中的“编辑模板”链接。 这将显示 GridView 的模板编辑界面。 这个接口的智能标记里有 GridView 的模板列表。 由于目前只有一个 TemplateField,因此下拉列表中列出的唯一模板是 TemplateField 的FirstName
模板以及EmptyDataTemplate
PagerTemplate
如果指定了EmptyDataTemplate
模板,则用于在没有数据结果绑定到 GridView 时呈现其输出;如果指定了PagerTemplate
,则用于呈现支持分页的 GridView 的分页界面。
图 5:可以通过设计器编辑 GridView 的模板(单击以查看全尺寸图像)
若要在LastName
TemplateField中显示FirstName
,请将“标签”控件从工具箱拖动到 GridView 模板编辑界面的FirstName
TemplateField的ItemTemplate
中。
图 6:向 TemplateField 的 ItemTemplate 添加标签 Web 控件 FirstName
(单击以查看全尺寸图像)
此时,添加到 TemplateField 的标签 Web 控件的属性 Text
设置为“Label”。 我们需要更改此属性,以便此属性改为绑定到数据字段的值 LastName
。 为了完成此操作,请单击标签控件的智能标签,然后选择“编辑数据绑定”选项。
从标签智能标记中选择“编辑数据绑定”选项
图7:从标签的智能标记中选择“编辑数据绑定”选项(单击以查看全尺寸图像)
此时会显示“DataBindings”对话框。 从此处,你可以从左侧列表中选择要参与数据绑定的属性,并从右侧的下拉列表中选择将数据绑定到的字段。 选择 Text
左侧的属性和右侧的 LastName
字段,然后单击“确定”。
图 8:将 Text
属性绑定到 LastName
数据字段(单击可查看全尺寸图像)
注释
使用 DataBindings 对话框可以指示是否执行双向数据绑定。 如果不选中此项,将使用数据绑定语法 <%# Eval("LastName")%>
,而不是 <%# Bind("LastName")%>
。 这两种方法都适用于本教程。 插入和编辑数据时,双向数据绑定变得非常重要。 但是,为了简单地显示数据,这两种方法同样能正常工作。 我们将在将来的教程中详细讨论双向数据绑定。
花点时间通过浏览器查看此页面。 如你所看到的,GridView 仍然包含四列,但是,该 FirstName
列现在 同时 列出 FirstName
和 LastName
数据字段值。
图 9:同时显示 FirstName
值和 LastName
值在单个列中(单击以查看全尺寸图像)
若要完成第一步,请删除 LastName
BoundField 并将 TemplateField 的属性FirstName
重命名HeaderText
为“Name”。 这些更改后,GridView 的声明性标记应如下所示:
<asp:GridView ID="GridView1" runat="server"
AutoGenerateColumns="False" DataKeyNames="EmployeeID"
DataSourceID="ObjectDataSource1">
<Columns>
<asp:TemplateField HeaderText="Name" SortExpression="FirstName">
<EditItemTemplate>
<asp:TextBox ID="TextBox1" runat="server"
Text='<%# Bind("FirstName") %>'></asp:TextBox>
</EditItemTemplate>
<ItemTemplate>
<asp:Label ID="Label1" runat="server"
Text='<%# Bind("FirstName") %>'></asp:Label>
<asp:Label ID="Label2" runat="server"
Text='<%# Eval("LastName") %>'></asp:Label>
</ItemTemplate>
</asp:TemplateField>
<asp:BoundField DataField="Title" HeaderText="Title"
SortExpression="Title" />
<asp:BoundField DataField="HireDate" HeaderText="HireDate"
SortExpression="HireDate" />
</Columns>
</asp:GridView>
图 10:每个员工的名字和姓氏显示在一列中(单击以查看全尺寸图像)
步骤 3:使用日历控件显示HiredDate
字段
将数据字段值显示为 GridView 中的文本与使用 BoundField 一样简单。 但是,对于某些方案,最好使用特定的 Web 控件而不是文本来表示数据。 可以使用 TemplateFields 对数据的显示进行此类自定义。 例如,我们可以显示日历(使用 日历控件)并突出显示其雇佣日期,而不是将员工的雇用日期显示为文本。
为此,首先将 HiredDate
BoundField 转换为 TemplateField。 只需转到 GridView 的智能标记并单击“编辑列”链接,打开“字段”对话框。 选择 HiredDate
BoundField,然后单击“将此字段转换为 TemplateField”。
图 11:将 HiredDate
BoundField 转换为 TemplateField(单击以查看全尺寸图像)
正如我们在步骤 2 中看到的,这将用 TemplateField 替换 BoundField,该 TemplateField 中包含 ItemTemplate
和 EditItemTemplate
,其中有一个 Label 和一个 TextBox,其 Text
属性使用数据绑定语法 HiredDate
绑定到 <%# Bind("HiredDate")%>
值。
若要将文本替换为日历控件,请通过删除标签并添加日历控件来编辑模板。 从设计器中,从 GridView 的智能标记中选择“编辑模板”,然后从下拉列表中选择 HireDate
TemplateField。ItemTemplate
接下来,删除标签控件,并将“日历”控件从工具箱拖动到模板编辑界面中。
图 12:向 TemplateField 添加HireDate
日历控件ItemTemplate
(单击以查看全尺寸图像)
此时,GridView 中的每个行都将在其 HiredDate
TemplateField 中包含日历控件。 但是,员工的实际 HiredDate
值未在日历控件中的任何位置设置,导致每个日历控件默认显示当前月份和日期。 为了纠正此问题,我们需要将每位员工的 HiredDate
设置到日历控件的 SelectedDate 和 VisibleDate 属性。
从日历控件的智能标记中,选择“编辑数据绑定”。 接下来,将这两个属性 SelectedDate
和 VisibleDate
属性绑定到 HiredDate
数据字段。
图 13:将SelectedDate
“属性”VisibleDate
绑定到HiredDate
数据字段(单击可查看全尺寸图像)
注释
日历控件的选定日期不一定可见。 例如,日历可能有 1999 年 8 月 1日作为所选日期,但显示当前月份和年份。 所选日期和可见日期由日历控件和SelectedDate
VisibleDate
属性指定。 由于我们希望同时选择员工的HiredDate
并确保其显示,因此我们需要将这两个属性绑定到HireDate
数据字段。
在浏览器中查看页面时,日历现在显示员工雇用日期的月份,并选择该特定日期。
图 14:员工的 HiredDate
显示在日历控件中(单击以查看全尺寸图像)
注释
与到目前为止我们所看到的所有示例相反,在本教程中我们没有为此 GridView 设置 属性 EnableViewState
。 之所以做出这个决定,是因为单击日历控件上的日期会触发页面回传,并将日历的选定日期设置为刚刚单击的日期。 但是,如果禁用 GridView 的视图状态,则在每个回发中,GridView 的数据将反弹到其基础数据源,这会导致日历的选定日期 重新 设置为员工的 HireDate
日期,覆盖用户选择的日期。
对于本教程,这是一个无关紧要的讨论,因为用户无法更新员工的HireDate
。 最好配置日历控件,以便其日期不可选择。 不管怎样,本教程都表明,在某些情况下必须启用视图状态才能提供某些功能。
步骤 4:显示员工为公司工作的天数
到目前为止,我们看到了 TemplateFields 的两个应用程序:
- 将两个或多个数据字段值合并为一列,以及
- 使用 Web 控件而不是文本表示数据字段值
TemplateFields 的第三个用途是显示有关 GridView 基础数据的元数据。 例如,除了显示员工的雇佣日期外,我们还可能还希望有一列显示他们在工作中的总天数。
另外,当基础数据需要在网页报表中以不同于存储在数据库中的格式显示时,会再次使用 TemplateFields。 假设Employees
表有一个Gender
字段,该字段存储字符M
或F
,以指示员工的性别。 在网页中显示此信息时,我们可能希望将性别显示为“男性”或“女性”,而不是“M”或“F”。
这两种方案都可以通过在 ASP.NET 页的代码隐藏类(或在从模板调用的单独类库中实现为方法)中创建static
来处理。 使用前面看到的相同数据绑定语法从模板调用此类格式设置方法。 格式设置方法可以采用任意数量的参数,但必须返回字符串。 此返回的字符串是注入到模板中的 HTML。
为了说明这一概念,让我们扩充我们的教程,以显示一列,其中列出了员工在职的天数。 此格式化方法将接收一个Northwind.EmployeesRow
对象,并返回员工已工作天数的字符串。 可以将此方法添加到 ASP.NET 页的代码隐藏类中,但必须标记为 或 protected
,才能从模板中访问。
protected string DisplayDaysOnJob(Northwind.EmployeesRow employee)
{
// Make sure HiredDate is not null... if so, return "Unknown"
if (employee.IsHireDateNull())
return "Unknown";
else
{
// Returns the number of days between the current
// date/time and HireDate
TimeSpan ts = DateTime.Now.Subtract(employee.HireDate);
return ts.Days.ToString("#,##0");
}
}
在进行计算之前,由于HiredDate
字段可以包含NULL
数据库值,必须首先确保该值不是NULL
。
HiredDate
如果值为NULL
,则只返回字符串“Unknown”;如果不是NULL
,则计算当前时间和HiredDate
值之间的差异并返回天数。
若要利用此方法,我们需要使用数据绑定语法从 GridView 中的 TemplateField 调用它。 首先,通过单击 GridView 智能标记中的“编辑列”链接并添加新的 TemplateField,将新的 TemplateField 添加到 GridView。
图 15:向 GridView 添加新 TemplateField(单击以查看全尺寸图像)
将此新 TemplateField HeaderText
的属性设置为“在职天数”,并将其 ItemStyle
的 HorizontalAlign
属性设置为 Center
。 若要从模板调用 DisplayDaysOnJob
该方法,请添加 ItemTemplate
并使用以下数据绑定语法:
<%# DisplayDaysOnJob((Northwind.EmployeesRow)
((System.Data.DataRowView) Container.DataItem).Row) %>
Container.DataItem
返回一个与DataRowView
记录绑定到DataSource
的GridViewRow
记录对应的对象。 其Row
属性返回强类型的Northwind.EmployeesRow
,并将其传递给DisplayDaysOnJob
方法。 此数据绑定语法可以直接显示在 ItemTemplate
(如下声明性语法所示)中,也可以分配给 Text
标签 Web 控件的属性。
注释
或者,我们可以使用 EmployeesRow
来传入 HireDate
值,而不是传入 <%# DisplayDaysOnJob(Eval("HireDate")) %>
实例。 但是,Eval
方法返回一个 object
,因此我们必须更改 DisplayDaysOnJob
方法签名以接受一个类型为 object
的输入参数。 无法盲目地将Eval("HireDate")
调用强制转换为DateTime
,因为HireDate
表中的Employees
列可以包含NULL
值。 因此,我们需要接受一个 object
作为 DisplayDaysOnJob
方法的输入参数,检查它是否有数据库 NULL
值(这可以通过使用 Convert.IsDBNull(objectToCheck)
来完成),然后相应地继续。
由于这些细微之处,所以我选择传入整个 EmployeesRow
实例。 在下一教程中,我们将看到一个更合适的示例,用于使用 Eval("columnName")
语法将输入参数传递到格式化方法。
下面显示了在添加 TemplateField 之后,GridView 的声明性语法,以及从 DisplayDaysOnJob
调用 ItemTemplate
方法:
<asp:GridView ID="GridView1" runat="server"
AutoGenerateColumns="False" DataKeyNames="EmployeeID"
DataSourceID="ObjectDataSource1">
<Columns>
<asp:TemplateField HeaderText="Name" SortExpression="FirstName">
<EditItemTemplate>
<asp:TextBox ID="TextBox1" runat="server"
Text='<%# Bind("FirstName") %>'></asp:TextBox>
</EditItemTemplate>
<ItemTemplate>
<asp:Label ID="Label1" runat="server"
Text='<%# Bind("FirstName") %>'></asp:Label>
<asp:Label ID="Label2" runat="server"
Text='<%# Eval("LastName") %>'></asp:Label>
</ItemTemplate>
</asp:TemplateField>
<asp:BoundField DataField="Title" HeaderText="Title"
SortExpression="Title" />
<asp:TemplateField HeaderText="HireDate"
SortExpression="HireDate">
<EditItemTemplate>
<asp:TextBox ID="TextBox2" runat="server"
Text='<%# Bind("HireDate") %>'></asp:TextBox>
</EditItemTemplate>
<ItemTemplate>
<asp:Calendar ID="Calendar1" runat="server"
SelectedDate='<%# Bind("HireDate") %>'
VisibleDate='<%# Eval("HireDate") %>'>
</asp:Calendar>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Days On The Job">
<ItemTemplate>
<%# DisplayDaysOnJob((Northwind.EmployeesRow)
((System.Data.DataRowView) Container.DataItem).Row) %>
</ItemTemplate>
<ItemStyle HorizontalAlign="Center" />
</asp:TemplateField>
</Columns>
</asp:GridView>
图 16 显示了通过浏览器查看的已完成教程。
图 16:显示员工已上岗的天数(单击可查看全尺寸图像)
概要
GridView 控件中的 TemplateField 允许比其他字段控件更灵活地显示数据。 TemplateFields 非常适合以下情况:
- 需要在一个 GridView 列中显示多个数据字段
- 最好使用 Web 控件而不是纯文本来表示数据
- 输出取决于基础数据,例如显示元数据或重新格式化数据
除了自定义数据的显示之外,TemplateFields 还用于自定义用于编辑和插入数据的用户界面,我们将在将来的教程中看到。
接下来的两个教程继续探索模板,从在 DetailsView 中使用 TemplateFields 开始。 接下来,我们将转到 FormView,它使用模板代替字段,以在数据的布局和结构方面提供更大的灵活性。
快乐编程!
关于作者
斯科特·米切尔,七本 ASP/ASP.NET 书籍的作者和 4GuysFromRolla.com 的创始人,自1998年以来一直在与Microsoft Web 技术合作。 斯科特担任独立顾问、教练和作家。 他的最新书是 《Sams Teach Yourself ASP.NET 2.0 in 24 Hours》。 可以通过 mitchell@4GuysFromRolla.com 联系到他。
特别致谢
本教程系列由许多有用的审阅者审阅。 本教程的主要审阅者是 Dan Jagers。 有兴趣查看即将发布的 MSDN 文章? 如果是这样,请给我写信。mitchell@4GuysFromRolla.com