在 GridView 控件中使用 TemplateField

本文档是 Visual C# 教程 (切换到 Visual Basic 教程)

为了实现灵活性,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 Event Handler 基于基础数据,自定义格式。另一种方式是在模板中调用格式设置方法。我们将在本教程中讨论这种方式。

在本教程中,我们会 使用 TemplateField 自定义一个员工列表的外观。具体地说,我们将列出所有员工,将他们的姓名放在一列中,入职日期放在一个 Calendar 控件中,另外再显示一个状态列指示他们进入公司的天数。

图1 :使用 3 个 TemplateField 自定义数据显示方式

步骤1 :将数据绑定到 GridView

在需要使用TemplateField 自定义外观的报表场景中,我发现最简单的方法是先创建一个只包含BoundField 的 GridView 控件,然后向它添加一些 TemplateField ,或根据需要将现有的 BoundField 转换为 TemplateField 。因此,本教程将首先通过设计器向页面添加一个 GridView ,并将其绑定到一个返回员工列表的 ObjectDataSource 。这将创建一个 GridView ,其中包含的 BoundField 对应员工信息的各个字段。

打开GridViewTemplateField.aspx 页面,从 Toolbox 中将 一个 GridView 拖放 到设计器上。从 GridView 的智能标记上,选择添加一个新的 ObjectDataSource 控件,使其调用 EmployeesBLL 类的 GetEmployees() 方法。

图2 :添加一个新的 ObjectDataSource 控件来调用 GetEmployees() 方法

以这种方式绑定 GridView 将自动为以下每个员工属性添加一个 BoundField :EmployeeID 、LastName 、FirstName 、Title 、HireDate 、ReportsTo 和Country 。在此报表中,我们不希望显示 EmployeeID 、 ReportsTo 和 Country 属性。要删除这些 BoundField ,我们可以:

  • 使用 Fields 对话框:单击 GridView 的智能标记上的 Edit Columns 链接来打开此对话框。然后,在左下角的列表中选择需要删除的 BoundField 并单击红色的 X 按钮,这个 BoundField 就被删除了。
  • 手动编辑 GridView 的声明式语法。操作方式为:在 Source 视图中找到希望删除的 BoundField 对应的 <asp:BoundField> 元素,删除这些元素即可。

删除 EmployeeID 、ReportsTo 和 Country 的 BoundField 后,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>

花些时间在浏览器中查看目前的成果。现在,我们能看到一张表格,表格中每条记录对应一位员工的信息,这些信息分为 4 列,分别是:员工的姓、名、职位以及入职日期。

图3 :显示了每位员工的 LastName 、FirstName 、Title 和 HireDate 字段

步骤2 : 在一列中显示姓名

现在,每位员工的姓和名分开在两列中显示。但将这两列合并为一列的效果会更好。要做到这一点,我们需要使用一个 TemplateField 。操作方法有两种: 1. 添加一个新的 TemplateField ,为其添加所需的标记和数据绑定语法,然后再删除 FirstName 和 LastName 两个 BoundField ; 2. 将 FirstName BoundField 转换为一个 TemplateField ,编辑该 TemplateField ,使其包含 LastName 值,然后再删除 LastName BoundField 。

这两种方法得到的结果是相同的,但我个人倾向于使用将 BoundField 转换为 TemplateField 的方法。因为这种转换会自动添加带有 Web 控件和数据绑定语法的 ItemTemplate 和 EditItemTemplate ,它们可以 实现与 BoundField 相似的外观和功能。这样做的好处在于,因为转换过程帮我们完成了一些工作,我们需要对 TemplateField 执行的操作就减少了。

要将现有的BoundField 转换为 TemplateField ,单击 GridView 智能标记上的 Edit Columns 链接打开 Fields 对话框。从左下角的列表中选择要转换的 BoundField ,然后单击右下角的 "Convert this field into a TemplateField" 链接。

图4 :在 Fields 对话框中,将一个 BoundField 转换为 TemplateField

继续操作,将 FirstName BoundField 转换为 TemplateField 。完成此更改后,我们在 设计器 中看不出有哪些不同。这是因为,将 BoundField 转换为 TemplateField 将创建一个外观与之前 BoundField 相似的 TemplateField 。但尽管目前在设计器中看不出任何区别,转换过程已将 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 数据字段值的Label )和一个 EditItemTemplate ( 带有一个 Text 属性也设为 FirstName 数据字段值的TextBox 控件 )。数据绑定语法 - <%# Bind("fieldName") %> - 表示数据字段 fieldName 被绑定到指定的 Web 控件属性。

要将 LastName 数据字段值添加到此 TemplateField ,我们需要在ItemTemplate 中添加另一个 Web Label 控件,并将其 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 的智能标记上的 Edit Templates 链接,显示GridView 的模板编辑界面。此界面的智能标记中列出了 GridView 中的模板。由于我们现在只有一个 TemplateField ,因此下拉列表中列出的模板只有 FirstName TemplateField 的模板,以及 EmptyDataTemplate 和 PagerTemplate 。如果指定了 EmptyDataTemplate 模板,该模板将在绑定到 GridView 的数据中没有结果时呈现 GridView 的输出。如果指定了 PagerTemplate ,该模板将用于呈现支持分页的 GridView 的分页界面。

图5 :可以通过 设计器 编辑GridView 的模板

如果还要在FirstName TemplateField 中显示 LastName ,则 从Toolbox 中 将 Label 控件 拖放到 GridView 模板编辑界面上的 FirstName TemplateField 的 ItemTemplate 中 。

图6 :向 FirstName TemplateField 的 ItemTemplate 添加一个 Web Label 控件

现在,添加到TemplateField 的 Web Label 控件的Text 属性为 "Label" 。我们需要更改该属性,将其绑定到LastName 数据字段的值。操作方式为:单击 Label 控件的智能标记,然后选择 Edit DataBindings 选项。

图7 :从 Label 的智能标记上选择 Edit DataBindings 选项

DataBindings 对话框弹出。从此对话框中,你可从左面的列表中选择要加入数据绑定的属性,并从右面的下拉列表中选择要将属性绑定到的字段。我们从左面列表中选择Text 属性 ,从 右面列表中选择 L astName 字段,然后点击 OK 。

图8 :将 Text 属性绑定到 LastName 数据字段

注意:通过 DataBindings 对话框,我们可指定是否进行双向数据绑定。如果不选择双向绑定,则将使用数据绑定语法 <%# Eval("LastName")%> 代替<%# Bind("LastName")%> 。本教程中,两种方法都可以使用。双向数据绑定在插入和编辑数据时非常重要。但如果只需要显示数据,两种方法都可胜任。我们将在今后的教程中详细讨论双向数据绑定。

花些时间在浏览器中查看本页。正如您所看到的那样,GridView 中仍包含 4 列。但不同的是,FirstName 列现在同时 列出了 FirstName 和 LastName 数据字段值。

图9 :FirstName 和 LastName 值同时显示在一列中

这一步的最后一个操作是删除 LastName BoundField ,并将FirstName TemplateField 的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 :使用 Calendar 控件显示 HiredDate 字段

在GridView 中将数据字段值显示为文本与使用 BoundField 一样简单。但在某些特定场景下,最好使用特定的 Web 控件而不是文本来呈现数据。使用 TemplateField 就 可以进行数据显示的自定义。例如,员工的入职日期可以在日历( 使用Calendar 控件 )中突出显示,而不是用文本显示。

操作方法为,首先将HiredDate BoundField 转换为 TemplateField 。只需进入 GridView 的智能标记,并单击 Edit Columns 链接来打开 Fields 对话框, 选择HiredDate BoundField 并单击 "Convert this field into a TemplateField" 。

图11 :将 HiredDate BoundField 转换为 TemplateField

正如我们在步骤 2 中看到的那样,此操作将 BoundField 替换为一个包含 ItemTemplate 和 EditItemTemplate 的 TemplateField 。其中的 ItemTemplate 和 EditItemTemplate 分别带有一个Label 和一个 TextBox ,它们的 Text 属性都通过数据绑定语法<%# Bind("HiredDate")%> 绑定到HiredDate 值。

要用Calendar 控件替换文本,应对模板进行编辑,删除Label ,添加一个 Calendar 控件。在设计器中选择 GridView 的智能标记上的 Edit Templates,并从下拉列表中选择 HireDate TemplateField 的 ItemTemplate 。然后,删除 Label 控件,并从 Toolbox 中将一个 Calendar 控件拖放到模板编辑界面。

图12 :向 HireDate TemplateField 的 ItemTemplate 添加一个 Calendar 控件

现在,GridView 每行的 HiredDate TemplateField 中都包含一个 Calendar 控件。但员工的实际 HiredDate 值并未在 Calendar 控件中设置,这使每个Calendar 控件都默认显示当前月份和日期。为解决此问题,需要将每个员工的 HiredDate 赋值给 Calendar 控件的 SelectedDateVisibleDate 属性。

在Calendar 控件的智能标记中选择 Edit DataBindings 。然后,将SelectedDate 和 VisibleDate 属性绑定到HiredDate 数据字段。

图13 :将 SelectedDate 和 VisibleDate 属性绑定到 HiredDate 数据字段

注意:Calendar 控件的选中日期不一定是可见的。例如,Calendar 的选中日期可能为 1999 年 8 月 1 日,但显示的可能是当前日期。选中的日期和显示的日期分别由 Calendar 控件的 SelectedDate 和 VisibleDate 属性指定。由于我们既想选中员工的 HiredDate ,又想确保将其显示出来,那么我们就需要将这两个属性都绑定到 HireDate 数据字段。

现在,我们再到浏览器中查看该页面。这次,日历中显示的是员工入职的月份,并突出显示了员工的入职日期。

图14 :Calendar 控件显示了员工的 HiredDate

注意:与前面看到的示例不同,本教程中没有 将 GridView 的 EnableViewState 属性设为 false 。这是因为,单击 Calendar 控件的日期会产生一次回传,将 Calendar 的选中日期设为刚才单击的日期。但如果禁用了 GridView 的视图状态,每一次回传都将导致 GridView 的数据重新绑定到基础数据源,从而导致 Calendar 的选中日期被重新设回 员工的 HireDate ,覆盖用户选中的日期。

本教程提出此观点仅供大家了解,因为用户不应该能够更新员工的 HireDate 。最好的办法可能是对 Calendar 控件进行配置,将其日期设为不可选。但无论如何,我们应该注意到,在某些情况下必须启用视图状态才能提供特定功能。

步骤4 :显示员工在公司的工作天数

现在我们已经学习了 TemplateField 的两种应用:

  • 将两个或多个数据字段值合并到一列中 ;
  • 使用 Web 控件代替文本显示数据字段值。

TemplateField 的第三种用途是显示关于 GridView 基础数据的元数据。例如,除了显示员工的入职时间,我们可能还想用一列显示员工在公司工作的总天数。

当基础数据在网页报表中的显示方式需要与它们在数据库中的存储格式不同时,TemplateField 也可以发挥作用。假设 Employees 表格有一个 Gender 字段,用于存储字符 M 或 F 来标识员工的性别。而在网页上显示时,我们可能希望将性别信息显示为 "Male" 或 "Female" ,而不是 "M" 或 "F" 。

要处理这些 场景,我们可以在ASP.NET 页面的code-behind 类中( 或在一个实现为静态方法的独立的类库中 )创建一个从模板调用的格式设置方法。这种格式设置方法从模板中调用,使用的数据绑定语法与前面用过的语法相同。格式设置方法可以携带任意数量的参数,但必须返回一个字符串。该字符串即为用于插入到模板中的 HTML 。

为演示此概念,让我们在报表中增加一列来显示员工在公司工作的总天数。此格式设置方法将携带一个 Northwind.EmployeesRow 对象,并以字符串的形式返回员工在公司工作的总天数。此方法可以添加到 ASP.NET 页面的code-behind类中,但必须 标记为受保护或公共方法,以便从模板访问。

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 :单击 GridView 的智能标记上的 Edit Columns 链接,然后添加一个新的 TemplateField 即可。

图15 :向 GridView 添加一个新的 TemplateField

将这个新的TemplateField 的 HeaderText 属性设为"Days on the Job" ,并将它的 ItemStyle 的HorizontalAlign 属性设为 Center 。要从模板调用 DisplayDaysOnJob 方法,添加一个 ItemTemplate 并使用以下数据绑定语法:

<%# DisplayDaysOnJob((Northwind.EmployeesRow) ((System.Data.DataRowView) Container.DataItem).Row) %>

Container.DataItem 返回一个 DataRowView 对象,该对象与绑定到 GridViewRow 的 DataSource 记录相对应。它的 Row 属性返回强类型的 Northwind.EmployeesRow ,后者将被传递给 DisplayDaysOnJob 方法。此数据绑定语法可以直接显示在 ItemTemplate 中(如下面的声明式语法所示),也可指定给 Web Label 控件的 Text 属性。

注意:除了传递一个 EmployeesRow 实例,我们也可以使用 <%# DisplayDaysOnJob(Eval("HireDate")) %> 来传递 HireDate 值。但由于 Eval 方法返回一个对象,因此我们必须修改 DisplayDaysOnJob 方法的签名,使其接受一个对象类型的输入参数。不能随意地将 Eval("HireDate") 调用的结果赋值给 DateTime ,因为 Employees 表的 HireDate 列可能包含 NULL 值。因此,我们需要接受一个对象作为 DisplayDaysOnJob 方法的输入参数,检查它是否有 NULL 数据库值(可使用 Convert.IsDBNull(objectToCheck) 进行检查),然后根据情况继续后面的步骤。

鉴于上述细微差别,我还是选择传递整个 EmployeesRow 实例。在下一篇教程中,我将介绍一个更合适使用Eval("columnName") 语法将输入参数传递给格式设置方法的示例。

在添加了 TemplateField 且从 ItemTemplate 调用了 DisplayDaysOnJob 方法之后,用于 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: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(CType(CType(Container.DataItem, DataRowView).Row, Northwind.EmployeesRow)) %>
                    </ItemTemplate>
                    <ItemStyle HorizontalAlign="Center" />
                </asp:TemplateField>
            </Columns>
        </asp:GridView>

在完成本教程的内容后,通过浏览器查看页面的效果如图 16 所示。

图16 :显示了员工在公司工作的总天数

小结

相对于其它字段控件,GridView 控件中的 TemplateField 可以更灵活地显示数据。TemplateField 尤其适用于以下场景 :

  • 需要在一个 GridView 列中显示多个数据字段
  • 使用 Web 控件展示数据比用纯文本的效果更好
  • 输出取决于基础数据 , 如显示元数据或重新设置数据的格式

除了自定义数据的显示方式,TemplateField 还可用于自定义用户编辑和插入数据的用户界面,后面的教程会对此作详细介绍。

接下来的两篇教程将继续介绍模板。我们会先讨论在 DetailsView 中使用 TemplateField 的情况。然后再看看 FormView ,该视图使用模板代替字段来提供更灵活的数据布局和结构。

快乐编程 !





下一篇教程