为 GridView 添加一列单选按钮

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

本教程将探讨如何向 GridView 控件添加一列单选按钮,为用户选择单行 GridView 提供更直观的方式。

« 前一篇教程  |  下一篇教程 »

Part 1

简介

GridView 控件提供多种内置功能。它含有多个显示文本、图像、超链接和按钮的不同字段。它还支持模板的进一步定制。只需轻轻的点击几下鼠标,您即可构造 GridView ,使其每行均可使用按钮进行选择,或者启用编辑或删除功能。尽管它提供了多种功能,但是很多情况下我们仍然需要添加一些附加功能和它不支持的功能。在本教程和接下来的两篇教程中,我们将探讨如何增强 GridView 的功能,使之包含更多的附加功能。

本教程和下一个教程重点探讨如何增强选行流程。正如在使用具有 Details DetailView 功能的可选主 GridView 的主/ 明细报表 中讨论的一样 , 我们可以向GridView 添加CommandField , 该 CommandField 包含一个 Select 按钮。单击后紧接着会回传 , 并且 GridView 的 SelectedIndex 属性将更新到行索引 , 此行的 Select 按钮为被点击的按钮。在使用具有 Details DetailView 功能的可选主 GridView 的主/ 明细报表 教程中 , 我们了解了如何使用此功能显示选定 GridView 行的详细情况。 ,

尽管 Select 按钮适用于多种情况,但是它可能并不适用于其它情况。并不经常使用按钮,而是通常使用其它两个用户界面元素来进行选择:单选按钮和复选框。我们可添加一个 GridView ,代替 Select 按钮,从而保证每行均包含一个单选按钮或者复选框。在用户仅可选择 GridView 记录中的一条记录的情况下,单选按钮可能要优于 Select 按钮。在用户可选择多条记录的情况下(例如基于 web 的电子邮件程序,用户可能希望选择删除多条信息),复选框可提供 Select 按钮和单选按钮用户界面无法提供的功能。

本教程将探讨如何向 GridView 添加一列单选按钮。下个教程还将探讨如何使用复选框。 .

步骤1 : 创建增强 GridView 的 Web 页面

在我们开始增强 GridView 功能 , 使之包含一列单选按钮之前 , 我们首先花点时间在我们的网站项目中创建本教程和下两个教程需要的ASP.NET 页面。首先 , 添加一个名为 EnhancedGridView 的新文件夹。接下来 , 将以下 ASP.NET 页面添加到该文件夹 , 确保每个页面与 Site.master 主页面相关联 :

  • Default.aspx
  • RadioButtonField.aspx
  • CheckBoxField.aspx
  • InsertThroughFooter.aspx

图1 : 为 SqlDataSourc 相关教程添加 ASP.NET 页

与其它文件夹一样 ,EnhancedGridView 文件夹中的 Default.aspx 页将列出教程。回想一下 ,SectionLevelTutorialListing.ascx 用户控件提供本功能。因此 , 请通过从Solution Explorer 中将此 用户 控件拖放到页面的 Design 视图来添加此 用户 控件。

图2 : 将SectionLevelTutorialListing.ascx 用户控件添加至 Default.aspx

最后 , 将这四个页面按条目添加到 Web.sitemap 文件中。具体地说 , 在 “Using the SqlDataSource Control” <siteMapNode> 后添加下列标记 :

<siteMapNode 
    title="Enhancing the GridView" 
    url="~/EnhancedGridView/Default.aspx" 
    description="Augment the user experience of the GridView control.">
    <siteMapNode 
        url="~/EnhancedGridView/RadioButtonField.aspx" 
        title="Selection via a Radio Button Column" 
        description="Explore how to add a column of radio buttons in the GridView." />
    <siteMapNode 
        url="~/EnhancedGridView/CheckBoxField.aspx" 
        title="Selection via a Checkbox Column" 
        description="Select multiple records in the GridView by using a column of 
            checkboxes." />
    <siteMapNode 
        url="~/EnhancedGridView/InsertThroughFooter.aspx" 
        title="Add New Records through the Footer" 
        description="Learn how to allow users to add new records through the 
            GridView's footer." />
</siteMapNode>

更新Web.sitemap 后 , 花点时间用浏览器查看一下教程网站。现在,左边的菜单包含用于编辑、插入和删除教程的各项。

图3 : 现在 , 网站地图中包含了增强 GridView 教程的条目

步骤2 : 在GridView 中显示供应商

在本教程中 , 我们将创建一个列出美国供应商的 GridView , 每个 GridView 行均提供了一个单选按钮。在使用单选按钮选择一个供应商之后,用户可以单击按钮查看供应商的产品。尽管此任务似乎微不足道,但是存在一些微妙之处,使它变得非常棘手。 在我们深入研究这些微妙之处之前,我们首先获得一个列出供应商的 GridView 。

首先 , 通过将GridView 从工具箱拖放到设计器中 , 在 EnhancedGridView 文件夹中打开RadioButtonField.aspx 页。将 GridView 的 ID 设置为 Suppliers , 从其智能标记中 , 选择创建新数据源。具体地说 , 创建一个名为SuppliersDataSource 的 ObjectDataSource ,ObjectDataSource 从 SuppliersBLL 对象提取数据。

图4 : 创建一个名称为 SuppliersDataSource 的新的 ObjectDataSource

 

图5 : 配置 ObjectDataSource , 使之使用 SuppliersBLL 类

由于我们只希望列出美国的供应商 , 因此 , 请从SELECT 选项卡的下拉列表中选择 GetSuppliersByCountry(country) 方法。

图6 : 配置 ObjectDataSource , 使之使用 SuppliersBLL 类

从UPDATE 选项卡选择 “(None)” 选项 , 并单击Next 。

图7 : 配置 ObjectDataSource , 使之使用 SuppliersBLL 类

由于GetSuppliersByCountry(country) 方法可接受参数 ,Configure Data Source 向导将提示我们输入这个参数的源。要想指定固定值 ( 此例中为 “USA” ), 请保留 Parameter source 下拉列表的None 设置 , 并在文本框中输入默认值。单击 Finish 完成向导。

图8 : 使用 “USA” 作为国家参数的默认值

完成向导之后 ,GridView 将为每个供应商数据字段包含一个BoundField 。删除 CompanyName 、City 和 Country BoundField 之外的所有 BoundField , 并把 CompanyName BoundFields HeaderText 属性重命名为 “Supplier” 。完成上述操作之后 ,GridView 和 ObjectDataSource 的 声明语法应类似如下。

<asp:GridView ID="Suppliers" runat="server" AutoGenerateColumns="False"
    DataKeyNames="SupplierID" DataSourceID="SuppliersDataSource" 
    EnableViewState="False">
    <Columns>
        <asp:BoundField DataField="CompanyName" HeaderText="Supplier" 
            SortExpression="CompanyName" />
        <asp:BoundField DataField="City" HeaderText="City" 
            SortExpression="City" />
        <asp:BoundField DataField="Country" HeaderText="Country" 
            SortExpression="Country" />
    </Columns>
</asp:GridView>
<asp:ObjectDataSource ID="SuppliersDataSource" runat="server" 
    OldValuesParameterFormatString="original_{0}"
    SelectMethod="GetSuppliersByCountry" TypeName="SuppliersBLL">
    <SelectParameters>
        <asp:Parameter DefaultValue="USA" Name="country" Type="String" />
    </SelectParameters>
</asp:ObjectDataSource>

在本教程中 , 我们允许用户在供应商列表或者其它页面上查看选定供应商的产品。要达到这一目的,请在页面上添加两个 Web 按钮控件。我已经将这两个Web 按钮 的 ID 设置为 ListProducts 和SendToProducts , 我的想法是:单击 ListProducts 时将出现回传 , 并且选定供应商的产品将被列在同一个页面上 , 但是 , 单击 SendToProduct 时 , 用户将被转到其它列出产品的页面上。

图9 显示通过浏览器查看页面时 , 页面上显示了 Suppliers GridView 和两个Web 按钮 控件。

图9 : 页面上列出了美国供应商的名称、城市和国家信息

步骤3 : 添加一列单选按钮

此时 ,Suppliers GridView 有三个 BoundField , 分别显示美国供应商的公司名称、城市和国家。但是 , 它还缺少一列单选按钮。遗憾的是 ,GridView 不包含内置的 RadioButtonField , 否则我们可以向网格中添加 RadioButtonField , 并完成这项工作。或者 , 我们可以添加一个TemplateField , 并将其 ItemTemplate 配置为提供单选按钮 , 从而为每个GridView 行添加单选按钮。

最初 , 我们可能假定所需的用户界面可以通过向TemplateField 的 ItemTemplate 添加一个 RadioButton Web 控件来实现。尽管这样实际上会向 GridView 的每一行都添加一个单选按钮,这些单选按钮不能分组,因此并不互相排斥。也就是说,最终用户可以同时从 GridView 选择多个单选按钮。

尽管使用RadioButton Web 控件的TemplateField 不提供我们所需的功能 , 我们还是要采用这种方式 , 因为它可以检查造成单选按钮不被分组的原因 , 因此是值得的。首先 , 向Suppliers GridView 添加一个 TemplateField , 使其成为最左边的字段。接下来 , 从GridView 的智能标记单击 Edit Templates 链接 , 并将RadioButton Web 控件从文本框拖放到 TemplateField 的 ItemTemplate ( 见图10 )。 将RadioButton 的 ID 属性设置为 RowSelector , 将 GroupName 属性设置为SuppliersGroup 。

图10 : 向 ItemTemplate 添加 RadioButton Web 控件

使用设计器完成添加操作之后 ,GridView 的标记应类似如下 :

<asp:GridView ID="Suppliers" runat="server" AutoGenerateColumns="False"
    DataKeyNames="SupplierID" DataSourceID="SuppliersDataSource" 
    EnableViewState="False">
    <Columns>
        <asp:TemplateField>
            <ItemTemplate>
                <asp:RadioButton ID="RowSelector" runat="server" 
                    GroupName="SuppliersGroup" />
            </ItemTemplate>
        </asp:TemplateField>
        <asp:BoundField DataField="CompanyName" HeaderText="Supplier" 
            SortExpression="CompanyName" />
        <asp:BoundField DataField="City" HeaderText="City" 
            SortExpression="City" />
        <asp:BoundField DataField="Country" HeaderText="Country" 
            SortExpression="Country" />
    </Columns>
</asp:GridView>

RadioButton 的 GroupName 属性 用于对一系列单选按钮进行分组。具有同一GroupName 值的所有 RadioButton 控件被认为已被分组 ; 每次只能从一个组中选择一个单选按钮。 GroupName 属性用于指定呈现的单选按钮的名称属性值。浏览器检查单选按钮的名称属性值,确定单选按钮的分组。

将RadioButton Web 控件添加到 ItemTemplate 之后 , 请通过浏览器访问此页面 , 并单击网格行中的单选按钮。请注意单选按钮并未分组,因此可选择所有的行,如图 11 所示。

图11 :GridView 的单选按钮并未分组

单选按钮并未分组的原因是它们呈现的名称属性值不同 , 尽管它们具有相同的GroupName 属性设置。要查看这些区别,在浏览器中选择 View/Source ,并检查单选按钮标记:

<input id="ctl00_MainContent_Suppliers_ctl02_RowSelector" 
    name="ctl00$MainContent$Suppliers$ctl02$SuppliersGroup" 
    type="radio" value="RowSelector" />
<input id="ctl00_MainContent_Suppliers_ctl03_RowSelector" 
    name="ctl00$MainContent$Suppliers$ctl03$SuppliersGroup" 
    type="radio" value="RowSelector" />
<input id="ctl00_MainContent_Suppliers_ctl04_RowSelector" 
    name="ctl00$MainContent$Suppliers$ctl04$SuppliersGroup" 
    type="radio" value="RowSelector" />
<input id="ctl00_MainContent_Suppliers_ctl05_RowSelector" 
    name="ctl00$MainContent$Suppliers$ctl05$SuppliersGroup" 
    type="radio" value="RowSelector" />

请注意 , 名称和ID 属性的值与 Properties 窗口中指定的值并不完全相同 , 而是预置了一些其它 ID 值。添加到呈现的 ID 和名称属性前头的附加 ID 值是单选按钮父控件的 ID – GridViewRows 的 ID 、GridView 的 ID 、Content 控件的 ID 和Web Form 的 ID 。添加这些 ID 是为了保证 GridView 中每个呈现的 Web 控件具有唯一的 ID 值和名称值。

每个呈现的控件需要具有不同的名称和ID , 因为这是浏览器在客户端区分每个控件以及在回传时确定网络服务器发生什么操作或者更改的唯一途径。例如,我们假设:一旦 RadioButton 的选定状态发生变更,希望运行某些服务器端代码。我们可以通过将RadioButton 的 AutoPostBack 属性设置为 True , 并为 CheckChanged 事件创建 Event Handler 来实现这一目标。但是,如果所有单选按钮呈现的名称和 ID 值都相同,回传时我们将无法确定到底单击了哪个特定的 RadioButton 。

总之 , 我们不能使用RadioButton Web 控件在 GridView 中创建一列单选按钮。而且 , 我们必须使用相当陈旧的技术保证标记被注入到每个GridView 行。

注意 : 和 RadioButton Web 控件一样,单选按钮 HTML 控件在添加到模板时将包含唯一的名称属性,保证网格中单选按钮未进行分组。如果您不熟悉HTML 控件 , 您可以按照您的意愿忽略本提示 , 因为HTML 控件很少使用 , 尤其是在 ASP.NET 2.0 中。但是如果您愿意了解更多内容 , 请参照 K. Scott Allen 的博客日志Web Controls and HTML Controls

使用Literal 控件插入单选按钮标记

为了能够将GridView 内的所有单选按钮正确分组 , 我们需要手工将单选按钮的标记插入 ItemTemplate 。每个单选按钮需要相同的名称属性,但是,每个单选按钮应该具有唯一的 ID 属性(假如我们希望通过客户端脚本访问单选按钮)。在用户选定单选按钮,并回传页面时,浏览器将送回选定单选按钮的 value 属性的值。因此,每个单选按钮需要具有唯一的 value 属性。最后,我们需要在回传时确保将 checked 属性添加到选定的单选按钮,否则,在用户完成选择并回传时,单选按钮将返回到它们的默认状态(全部取消选定)。

可以采用两种方式将低级标记插入到模板中。第一种方式是组合标记 , 并调用 代码文件 类中定义的格式化方法。该方法首次提及是在 GridView控件中使用 TemplateField 教程中。在我们的例子中 , 它可能类似如下 :

<input type="radio" id='<%# GetUniqueRadioButtonID(...) %>' 
    name='SuppliersGroup' value='<%# GetRadioButtonValue(...) %>' ... />

此处 ,GetUniqueRadioButton 和 GetRadioButtonValue 为 代码文件 类中定义的方法 ,它们 返回每个单选按钮的相应的 ID 和 value 属性的值。这种方法非常适用于分配id 和 value 值 , 但是在需要指定 checked 属性值时就不管用了 , 因为数据绑定语法仅当数据首次绑定到GridView 时才执行。因此,如果 GridView 启用了视图状态,格式化方法将仅在首次加载页面时(或者在 GridView 明确绑定到数据源时)才会触发,因此,在回传时不会调用设置 checked 属性的函数。这是一个非常棘手的问题,并有些超出本文的范围,所以本文不再讨论。但是,我推荐你们尝试一下使用上述方法,并进行到遇到困难为止。尽管这样的练习与使用版本还有一段距离,但它仍然会加深您对 GridView 和数据绑定生命周期的理解。

向模板中插入定制的低级标记的另外一种方法 ( 在本教程中使用的方法 ) 是向模板添加 Literal 控件 。然后 ,可以通过 在 GridView 的 RowCreated 或 RowDataBound Event Handler 中编码来访问 Literal 控件 , 并将其 Text 属性设置为要传输的标记。

首先 , 从TemplateField 的 ItemTemplat 删除RadioButton , 并替换为 Literal 控件。将 Literal 控件的 ID 设置为 RadioButtonMarkup 。

图12 :向 ItemTemplate 添加 Literal 控件

接下来 , 为GridView 的 RowCreated 事件创建Event Handler 。无论数据是否重新绑定到 GridView , 添加每一行时均将触发 RowCreated 事件。这意味着既使从视图状态重新加载数据产生回传时 , 仍会触发RowCreated , 这也是我们使用 RowCreated 而不用 RowDataBound 的原因 (RowDataBound 只有在数据明确绑定到 Web 数据 控件时才会触发 ) 。

在此 Event Handler 中 , 我们希望仅在我们处理数据行时才运行。对于每个数据行来说 , 我们希望通过编码引用 RadioButtonMarkup Literal 控件 , 并将其 Text 属性设置为要传输的标记。正如下列代码所示 , 传输的标记将创建一个单选按钮 , 此单选按钮的名称属性设置为 SuppliersGroup ,id 属性设置为 RowSelectorX , 其中 X 为 GridView 行的索引 , 并且其 value 属性被设置为 GridView 行的索引。

protected void Suppliers_RowCreated(object sender, GridViewRowEventArgs e)
{
    if (e.Row.RowType == DataControlRowType.DataRow)
    {
        // Grab a reference to the Literal control
        Literal output = (Literal)e.Row.FindControl("RadioButtonMarkup");
        // Output the markup except for the "checked" attribute
        output.Text = string.Format(
            @"<input type="radio" name="SuppliersGroup" " +
            @"id="RowSelector{0}" value="{0}" />", e.Row.RowIndex);
    }
}

选中GridView 行 , 并且发生回传时 , 我们感兴趣的是所选供应商的 SupplierID 。因此 , 人们不禁会认为每个单选按钮的值应为实际SupplierID ( 而不是 GridView 行的索引 ) 。尽管在某些情况下的确如此 , 但是 , 盲目的接受和处理 SupplierID 可能在安全性方面存在风险。例如 ,我们要求 GridView 仅列出了美国的供应商。但是 , 如果直接从单选按钮传递 SupplierID , 我们将如何阻止捣乱的用户对回传时发回的SupplierID 值造假呢 ? 通过使用行索引值 , 然后在回传时从DataKeys 集合获取 SupplierID 值 , 我们可以保证用户仅使用与某一 GridView 行关联的 SupplierID 值。

在添加完成此Event Handler 代码后 , 请花点时间在浏览器中测试该页面。首先,请注意每次只能在网格中选择一个单选按钮。但是,当选择一个单选按钮,并单击某个按钮时,将出现回传,并且单选按钮全部恢复到它们的初始值(也就是说,在回传时,选定的单选按钮已经不再选定)。要解决这个问题 , 我们需要扩充RowCreated Event Handler , 使其检查从回传发送的选定单选按钮的索引 , 并向传输的匹配行索引标记添加checked="checked" 属性。

出现回传时,浏览器将送回选定单选按钮的名称和值。单选按钮值可以通过编码使用Request.Form("name") 进行检索。Request.Form 属性 提供了一个 NameValueCollection 来代表表格变量。表格变量为 web 页面上的表格字段的名称和值,并且在回传时由 web 浏览器发回。由于 GridView 中呈现的单选按钮的 name 属性为 SuppliersGroup ,web 页面回传时 , 浏览器将把 SuppliersGroup=valueOfSelectedRadioButton 回传到web 服务器 ( 与其它表格字段一起 ) 。然后 , 此信息可从 Request.Form 属性访问 , 使用的访问方式为 : Request.Form("SuppliersGroup")。

由于我们需要不仅在 RowCreated Event Handler 中确定选定单选按钮索引 , 而且还要在 Web 按钮控件的 Click Event Handler 确定 ,因此 我们需要向 代码文件 类添加一个SuppliersSelectedIndex 属性 , 该属性在单选按钮未选定时返回-1 , 在选定某个单选按钮时返回选定的索引。

private int SuppliersSelectedIndex
{
    get
    {
        if (string.IsNullOrEmpty(Request.Form["SuppliersGroup"]))
            return -1;
        else
            return Convert.ToInt32(Request.Form["SuppliersGroup"]);
    }
}

添加该属性之后 , 当SuppliersSelectedIndex 等于e.Row.RowIndex 时 , 我们要在 RowCreated Event Handler 中添加checked="checked" 标记。 更新 Event Handler 使其包含下列逻辑:

protected void Suppliers_RowCreated(object sender, GridViewRowEventArgs e)
{
    if (e.Row.RowType == DataControlRowType.DataRow)
    {
        // Grab a reference to the Literal control
        Literal output = (Literal)e.Row.FindControl("RadioButtonMarkup");
        // Output the markup except for the "checked" attribute
        output.Text = string.Format(
            @"<input type="radio" name="SuppliersGroup" " +
            @"id="RowSelector{0}" value="{0}"", e.Row.RowIndex);
        // See if we need to add the "checked" attribute
        if (SuppliersSelectedIndex == e.Row.RowIndex)
            output.Text += @" checked="checked"";
        // Add the closing tag
        output.Text += " />";
    }
}

完成这一更改后 , 选定的单选按钮在回传后将保持选定。现在,我们具备了指定选定单选按钮的能力,我们可以更改行为,从而在首次访问页面时选定第一个 GridView 行的单选按钮(而不是默认情况下不选择任何单选按钮)。要默认选定第一个单选按钮 , 请将if SuppliersSelectedIndex = e.Row.RowIndex 语句更改为下列形式 :If SuppliersSelectedIndex = e.Row.RowIndex OrElse (Not Page.IsPostBack AndAlso e.Row.RowIndex = 0) Then 。

此时 , 我们已经向GridView 添加了一列分组单选按钮 , 分组单选按钮允许选择单个 GridView 行 , 并在回传时进行记录。下一步是显示选定供应商的产品。在步骤 4 中,我们将了解如何将用户重定向到其它页面,发送选定的 SupplierID 。在步骤 5 中,我们将了解如何在相同页面的 GridView 中显示选定供应商的产品。

注意 : 我们可以创建一个显示适当的用户界面和功能的定制DataControlField 类 , 而不是使用 TemplateField ( 这是稍显冗长的步骤 3 重点介绍的 ) 。DataControlField 类 是基类 ,BoundField 、CheckBoxField 、TemplateField 和其它内置 GridView 和 DetailsView 字段均从其派生出来。创建一个定制 DataControlField 类意味着只使用声明语法就可添加单选按钮列 , 并且可以方便地在其它web 页面和其它 web 应用程序上重用此功能。

如果您曾经在 ASP.NET 中创建过定制、编译控件 , 那么 , 您会知道这个过程需要数量可观的琐碎工作 , 以及必须小心处理多个微妙问题和边缘情况。因此 , 这里我们放弃通过定制 DataControlField 类的方案实施单选按钮列 , 坚持选择使用 TemplateField 。可能在后续教程中我们有机会探讨创建、使用和部署定制 DataControlField 类。





上一页 | 1 | 2 | 下一页