在 GridView 控件中使用 TemplateField (C#)
为了提供灵活性,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 控件实现更高程度的自定义。 在前面的教程中,我们了解了如何使用 和 RowDataBound
事件处理程序基于基础数据DataBound
自定义格式设置。 基于基础数据自定义格式设置的另一种方法是从模板中调用格式设置方法。 本教程中还将介绍此方法。
在本教程中,我们将使用 TemplateFields 自定义员工列表的外观。 具体而言,我们将列出所有员工,但会在一列中显示员工的名字和姓氏、日历控件中的雇用日期以及指示他们在公司工作天数的状态列。
图 1:三个 TemplateField 用于自定义显示 (单击以查看全尺寸图像)
步骤 1:将数据绑定到 GridView
在需要使用 TemplateFields 来自定义外观的报告方案中,我发现最简单的方法是先创建仅包含 BoundFields 的 GridView 控件,然后根据需要添加新的 TemplateFields 或将现有 BoundField 转换为 TemplateFields。 因此,让我们通过 Designer 将 GridView 添加到页面并将其绑定到返回员工列表的 ObjectDataSource 开始本教程。 这些步骤将为每个员工字段创建包含 BoundFields 的 GridView。
打开页面,GridViewTemplateField.aspx
将 GridView 从工具箱拖到Designer。 从 GridView 的智能标记中,选择添加调用类GetEmployees()
方法的新 ObjectDataSource 控件EmployeesBLL
。
图 2:添加新的 ObjectDataSource 控件,该控件调用 GetEmployees()
方法 (单击以查看全尺寸图像)
以这种方式绑定 GridView 将自动为每个员工属性添加 BoundField:EmployeeID
、、LastName
、FirstName
Title
、HireDate
、 ReportsTo
和 Country
。 对于此报表,我们无需费心显示 EmployeeID
、 ReportsTo
或 Country
属性。 若要删除这些 BoundField,可以:
- 使用“字段”对话框,单击 GridView 智能标记中的“编辑列”链接,打开此对话框。 接下来,从左下角列表中选择“BoundFields”,然后单击红色的“X”按钮删除 BoundField。
- 从“源”视图中手动编辑 GridView 的声明性语法,删除
<asp:BoundField>
要删除的 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
HireDate
FirstName
Title
每个员工 (单击查看全尺寸图像)
步骤 2:在单个列中显示名字和姓氏
目前,每个员工的名字和姓氏都显示在单独的列中。 最好改为将它们合并到单个列中。 为此,需要使用 TemplateField。 我们可以添加新的 TemplateField,向其添加所需的标记和数据绑定语法,然后删除 FirstName
和 LastName
BoundFields,也可以将 BoundField 转换为 FirstName
TemplateField,编辑 TemplateField 以包含 LastName
值,然后删除 LastName
BoundField。
这两种方法的结果相同,但就我个人而言,我喜欢尽可能将 BoundFields 转换为 TemplateFields,因为转换会自动添加 一个 ItemTemplate
, EditItemTemplate
使用 Web 控件和数据绑定语法来模拟 BoundField 的外观和功能。 好处是,我们需要减少使用 TemplateField 的工作,因为转换过程将为我们执行一些工作。
若要将现有 BoundField 转换为 TemplateField,请单击 GridView 智能标记中的“编辑列”链接,打开“字段”对话框。 从左下角的列表选择要转换的 BoundField,然后单击右下角的“将此字段转换为 TemplateField”链接。
图 4:从“字段”对话框中将 BoundField 转换为 TemplateField, (单击以查看全尺寸图像)
继续将 BoundField 转换为 FirstName
TemplateField。 完成此更改后,Designer没有明显的差异。 这是因为将 BoundField 转换为 TemplateField 会创建一个保持 BoundField 外观的 TemplateField。 尽管Designer目前没有视觉差异,但此转换过程已将 BoundField 的声明性语法 <asp:BoundField DataField="FirstName" HeaderText="FirstName" SortExpression="FirstName" />
替换为以下 TemplateField 语法:
<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,需要在 中添加另一个 Label Web 控件,ItemTemplate
并将其属性LastName
绑定到 Text
。 这可以通过手动或Designer来实现。 若要手动执行此操作,只需将适当的声明性语法添加到 :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>
若要通过Designer添加它,请单击 GridView 智能标记中的“编辑模板”链接。 这将显示 GridView 的模板编辑界面。 在此接口的智能标记中,是 GridView 中的模板列表。 由于目前只有一个 TemplateField,因此下拉列表中列出的唯一模板是 TemplateField EmptyDataTemplate
的那些模板FirstName
以及 和 PagerTemplate
。 如果已指定,则 EmptyDataTemplate
模板用于呈现 GridView 的输出(如果绑定到 GridView 的数据没有结果); PagerTemplate
如果指定,则使用 模板呈现支持分页的 GridView 的分页接口。
图 5:可以通过Designer (单击以查看全尺寸图像)
若要在 TemplateField 中FirstName
同时显示 LastName
,请将“标签”控件从“工具箱”拖到 FirstName
GridView 的ItemTemplate
模板编辑界面中的 TemplateField 中。
图 6:将标签 Web 控件添加到 FirstName
TemplateField 的 ItemTemplate (单击以查看全尺寸图像)
此时,添加到 TemplateField 的 Label Web 控件的 Text
属性设置为“Label”。 我们需要更改此属性,以便将此属性改为绑定到数据字段的值 LastName
。 若要完成此操作,请单击“标签”控件的智能标记,然后选择“编辑数据绑定”选项。
图 7:从标签的智能标记中选择“编辑数据绑定”选项 (单击以查看全尺寸图像)
此时会显示“数据绑定”对话框。 在此处,你可以从左侧列表中选择要参与数据绑定的属性,并从右侧的下拉列表中选择要将数据绑定到的字段。 Text
从左侧选择属性,LastName
从右侧选择字段,然后单击“确定”。
图 8:将 Text
属性绑定到 LastName
数据字段 (单击 以查看全尺寸图像)
注意
使用“数据绑定”对话框可以指示是否执行双向数据绑定。 如果保持此未选中状态,将使用数据绑定语法 <%# Eval("LastName")%>
而不是 <%# Bind("LastName")%>
。 对于本教程,这两种方法都不错。 插入和编辑数据时,双向数据绑定变得非常重要。 但是,对于仅显示数据,两种方法都同样效果良好。 我们将在将来的教程中详细介绍双向数据绑定。
花点时间通过浏览器查看此页面。 如你所看到的,GridView 仍然包含四列;但是,该FirstName
列现在同时FirstName
列出 和 LastName
数据字段值。
图 9:和 LastName
值都FirstName
显示在单个列中 (单击以查看全尺寸图像)
若要完成第一步,请删除 LastName
BoundField 并将 TemplateField 的 HeaderText
属性重命名FirstName
为“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
边界字段转换为 TemplateField (单击以查看全尺寸图像)
正如我们在步骤 2 中看到的,这会将 BoundField 替换为包含 ItemTemplate
和 EditItemTemplate
的 TemplateField,并使用 Label 和 TextBox(其Text
属性使用数据绑定语法 <%# Bind("HiredDate")%>
绑定到 HiredDate
值)。
若要将文本替换为 Calendar 控件,请通过删除 Label 并添加 Calendar 控件来编辑模板。 从Designer,从 GridView 的智能标记中选择“编辑模板”,然后从下拉列表中选择HireDate
“TemplateFieldItemTemplate
”。 接下来,删除“标签”控件,并将“日历”控件从“工具箱”拖动到模板编辑界面中。
图 12:将日历控件添加到 HireDate
TemplateField 的 ItemTemplate
(单击以查看全尺寸图像)
此时,GridView 中的每一行都将在其 HiredDate
TemplateField 中包含一个 Calendar 控件。 但是,员工的实际 HiredDate
值未在 Calendar 控件中的任何位置设置,导致每个 Calendar 控件默认显示当前月份和日期。 若要解决此问题,我们需要将每个员工的 HiredDate
分配给 Calendar 控件的 SelectedDate 和 VisibleDate 属性。
从“日历”控件的智能标记中,选择“编辑数据绑定”。 接下来,将 SelectedDate
和 VisibleDate
属性绑定到 HiredDate
数据字段。
图 13:将 SelectedDate
和 VisibleDate
属性绑定到 HiredDate
数据字段 (单击以查看全尺寸图像)
注意
日历控件的所选日期不一定可见。 例如,日历可能将 1999 年 8 月 1日作为所选日期,但显示当前月份和年份。 所选日期和可见日期由 Calendar 控件的 SelectedDate
和 VisibleDate
属性指定。 由于我们想要同时选择员工的 HiredDate
,并确保显示该属性,因此需要将这两个属性 HireDate
绑定到数据字段。
在浏览器中查看页面时,日历现在会显示员工雇用日期的月份,并选择该特定日期。
图 14:员工 HiredDate
显示在“日历控件” (单击以查看全尺寸图像)
注意
与到目前为止看到的所有示例相反,在本教程中,我们没有为此 GridView 将 属性false
设置为 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
或 public
才能从模板访问。
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 的 HeaderText
属性设置为“作业日期”,将其 ItemStyle
的 HorizontalAlign
属性设置为 Center
。 若要从模板调用 DisplayDaysOnJob
方法,请 ItemTemplate
添加 并使用以下数据绑定语法:
<%# DisplayDaysOnJob((Northwind.EmployeesRow)
((System.Data.DataRowView) Container.DataItem).Row) %>
Container.DataItem
返回一个DataRowView
对象,该对象对应于绑定到 的DataSource
GridViewRow
记录。 其 Row
属性返回传递给 DisplayDaysOnJob
方法的强类型 Northwind.EmployeesRow
。 此数据绑定语法可以直接显示在 ItemTemplate
(中,如下面的声明性语法所示) ,也可以分配给 Text
Label Web 控件的 属性。
注意
或者,我们可以使用 直接传入 HireDate
值<%# DisplayDaysOnJob(Eval("HireDate")) %>
,而不是传入 EmployeesRow
实例。 但是, 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,它使用模板代替字段,以在数据的布局和结构方面提供更大的灵活性。
编程愉快!
关于作者
Scott Mitchell 是七本 ASP/ASP.NET 书籍的作者, 4GuysFromRolla.com 的创始人,自 1998 年以来一直从事 Microsoft Web 技术工作。 Scott 担任独立顾问、培训师和作家。 他的最新书是 山姆斯在24小时内 ASP.NET 2.0自学。 可以在 上联系 mitchell@4GuysFromRolla.com他, 也可以通过他的博客联系到他,该博客可在 http://ScottOnWriting.NET中找到。
特别感谢
本教程系列由许多有用的审阅者查看。 本教程的首席审阅者是 Dan Jagers。 有兴趣查看我即将发布的 MSDN 文章? 如果是,请在 处放置一行 mitchell@4GuysFromRolla.com。