自定义数据修改界面 (VB)

作者 :Scott Mitchell

下载 PDF

本教程介绍如何通过将标准 TextBox 和 CheckBox 控件替换为备用输入 Web 控件来自定义可编辑 GridView 的界面。

简介

GridView 和 DetailsView 控件使用的 BoundFields 和 CheckBoxFields 简化了修改数据的过程,因为它们能够呈现只读、可编辑和可插入的接口。 无需添加任何其他声明性标记或代码即可呈现这些接口。 但是,BoundField 和 CheckBoxField 的接口缺乏实际方案中通常需要的可自定义性。 若要在 GridView 或 DetailsView 中自定义可编辑或可插入的界面,我们需要改用 TemplateField。

前面的教程中 ,我们了解了如何通过添加验证 Web 控件来自定义数据修改接口。 本教程介绍如何自定义实际数据收集 Web 控件,将 BoundField 和 CheckBoxField 的标准 TextBox 和 CheckBox 控件替换为备用输入 Web 控件。 具体而言,我们将构建一个可编辑的 GridView,允许更新产品名称、类别、供应商和停产状态。 编辑特定行时,类别和供应商字段将呈现为 DropDownLists,其中包含一组可供选择的可用类别和供应商。 此外,我们将用 RadioButtonList 控件替换 CheckBoxField 的默认 CheckBox,该控件提供两个选项:“活动”和“已停用”。

GridView 的编辑界面包括 DropDownLists 和 RadioButtons

图 1:GridView 的编辑界面包括 DropDownLists 和 RadioButtons (单击以查看全尺寸图像)

步骤 1:创建适当的UpdateProduct重载

在本教程中,我们将生成一个可编辑的 GridView,允许编辑产品名称、类别、供应商和停产状态。 因此,我们需要一个重载,它接受这四个乘积值加上 的ProductID五个UpdateProduct输入参数。 与前面的重载一样,此重载将:

  1. 从数据库中检索指定 ProductID的 产品信息
  2. 更新 、ProductNameCategoryIDSupplierIDDiscontinued 字段,以及
  3. 通过 TableAdapter 的 Update() 方法将更新请求发送到 DAL。

为简洁起见,对于此特定重载,我省略了业务规则检查,以确保标记为已停产的产品不是其供应商提供的唯一产品。 如果愿意将逻辑重构为单独的方法,或者最好将逻辑重构为单独的方法,请随意将其添加到 中。

以下代码显示了 类中的ProductsBLLUpdateProduct重载:

<System.ComponentModel.DataObjectMethodAttribute(System.ComponentModel.DataObjectMethodType.Update, False)>
Public Function UpdateProduct(
    ByVal productName As String, ByVal categoryID As Nullable(Of Integer), 
    ByVal supplierID As Nullable(Of Integer), ByVal discontinued As Boolean, 
    ByVal productID As Integer)
    As Boolean
    Dim products As Northwind.ProductsDataTable = Adapter.GetProductByProductID(productID)
    If products.Count = 0 Then
        Return False
    End If
    Dim product As Northwind.ProductsRow = products(0)
    product.ProductName = productName
    If Not supplierID.HasValue Then
        product.SetSupplierIDNull()
    Else
        product.SupplierID = supplierID.Value
    End If
    If Not categoryID.HasValue Then
        product.SetCategoryIDNull()
    Else
        product.CategoryID = categoryID.Value
    End If
    product.Discontinued = discontinued
    Dim rowsAffected As Integer = Adapter.Update(product)
    Return rowsAffected = 1
End Function

步骤 2:创建可编辑的 GridView

添加重载后 UpdateProduct ,我们已准备好创建可编辑的 GridView。 CustomizedUI.aspx打开 文件夹中的页面,EditInsertDelete并将 GridView 控件添加到Designer。 接下来,从 GridView 的智能标记创建新的 ObjectDataSource。 配置 ObjectDataSource 以通过 ProductBLL 类的 GetProducts() 方法检索产品信息,并使用 UpdateProduct 刚刚创建的重载更新产品数据。 从“插入”和“删除”选项卡中,从下拉列表中选择“ (无) ”。

将 ObjectDataSource 配置为使用刚刚创建的 UpdateProduct 重载

图 2:将 ObjectDataSource 配置为使用 UpdateProduct 刚刚创建的重载 (单击以查看全尺寸图像)

正如我们在数据修改教程中所看到的那样,Visual Studio 创建的 ObjectDataSource 的声明性语法将 OldValuesParameterFormatString 属性 original_{0}分配给 。 当然,这不适用于我们的业务逻辑层,因为我们的方法不需要传入原始 ProductID 值。 因此,正如我们在前面的教程中所做的那样,请花点时间从声明性语法中删除此属性赋值,或者将此属性的值设置为 {0}

进行此更改后,ObjectDataSource 的声明性标记应如下所示:

<asp:ObjectDataSource ID="ObjectDataSource1" runat="server"
    SelectMethod="GetProducts" TypeName="ProductsBLL"
    UpdateMethod="UpdateProduct">
    <UpdateParameters>
        <asp:Parameter Name="productName" Type="String" />
        <asp:Parameter Name="categoryID" Type="Int32" />
        <asp:Parameter Name="supplierID" Type="Int32" />
        <asp:Parameter Name="discontinued" Type="Boolean" />
        <asp:Parameter Name="productID" Type="Int32" />
    </UpdateParameters>
</asp:ObjectDataSource>

请注意, OldValuesParameterFormatString 属性已被删除,并且集合中UpdateParameters针对我们的UpdateProduct重载所需的每个输入参数都有 Parameter

虽然 ObjectDataSource 配置为仅更新一部分产品值,但 GridView 当前会显示 所有 产品字段。 花点时间编辑 GridView,以便:

  • 它仅包括 ProductName、、 SupplierNameCategoryName BoundFields 和 Discontinued CheckBoxField
  • CategoryName checkBoxField) 左侧 Discontinued (之前显示的 和 SupplierName 字段
  • CategoryNameSupplierName BoundFields 的属性HeaderText分别设置为“Category”和“Supplier”
  • (检查 GridView 智能标记中的“启用编辑”复选框启用编辑支持)

进行这些更改后,Designer将类似于图 3,GridView 的声明性语法如下所示。

从 GridView 中删除不需要的字段

图 3:从 GridView 中删除不需要的字段 (单击以查看全尺寸图像)

<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False"
    DataKeyNames="ProductID" DataSourceID="ObjectDataSource1">
    <Columns>
        <asp:BoundField DataField="ProductName"
           HeaderText="ProductName" SortExpression="ProductName" />
        <asp:BoundField DataField="CategoryName" HeaderText="Category"
           ReadOnly="True"
           SortExpression="CategoryName" />
        <asp:BoundField DataField="SupplierName" HeaderText="Supplier"
           ReadOnly="True"
           SortExpression="SupplierName" />
        <asp:CheckBoxField DataField="Discontinued"
           HeaderText="Discontinued" SortExpression="Discontinued" />
    </Columns>
</asp:GridView>

此时,GridView 的只读行为已完成。 查看数据时,每个产品在 GridView 中呈现为一行,显示产品名称、类别、供应商和停产状态。

GridView 的 Read-Only 接口已完成

图 4:GridView 的 Read-Only 接口已完成 (单击以查看全尺寸图像)

注意

插入、更新和删除数据概述教程中所述,启用 GridView 视图状态非常重要, (默认行为) 。 如果将 GridView 的 EnableViewState 属性设置为 false,则存在并发用户无意中删除或编辑记录的风险。

步骤 3:对类别和供应商编辑接口使用 DropDownList

回想一下,ProductsRow对象包含 CategoryIDCategoryNameSupplierIDSupplierName 属性,它们提供数据库表中的实际外键 ID 值Products和 和 Suppliers 表中的相应NameCategoriesProductRowCategoryIDSupplierID 都可以从 中读取和写入,而 CategoryNameSupplierName 属性标记为只读。

由于 和 SupplierName 属性的CategoryName只读状态,相应的 BoundFields 已将其ReadOnly属性设置为 True,以防止在编辑行时修改这些值。 虽然我们可以将 属性设置为 ReadOnlyFalse,在编辑期间将 和 SupplierName BoundFields 呈现CategoryName为 TextBoxes,但当用户尝试更新产品时,此方法将导致异常,因为没有UpdateProduct输入和SupplierName输入的重载CategoryName。 事实上,我们不希望创建这样的重载,原因有两个:

  • Products 表没有 SupplierNameCategoryName 字段,而是 SupplierIDCategoryID。 因此,我们希望方法传递这些特定的 ID 值,而不是其查找表的值。
  • 要求用户键入供应商或类别的名称并不理想,因为它要求用户知道可用的类别和供应商及其正确的拼写。

在只读模式下,供应商和类别字段应显示类别和供应商的名称 (,因为编辑时) 和适用选项的下拉列表。 使用下拉列表,最终用户可以快速查看哪些类别和供应商可供选择,并且可以更轻松地进行选择。

若要提供此行为,我们需要将 和 BoundFields 转换为 SupplierName TemplateFields,后者ItemTemplate发出 SupplierNameCategoryName 值,并使用 EditItemTemplate DropDownList 控件列出可用的类别和供应商。CategoryName

Categories添加 和SuppliersDropDownLists

首先,将 和 CategoryName BoundFields 转换为 SupplierName TemplateFields,方法是:单击 GridView 智能标记中的“编辑列”链接;从左下角的列表中选择 BoundField;然后单击“将此字段转换为 TemplateField”链接。 转换过程将创建包含 ItemTemplateEditItemTemplate的 TemplateField,如以下声明性语法所示:

<asp:TemplateField HeaderText="Category" SortExpression="CategoryName">
    <EditItemTemplate>
        <asp:Label ID="Label1" runat="server"
          Text='<%# Eval("CategoryName") %>'></asp:Label>
    </EditItemTemplate>
    <ItemTemplate>
        <asp:Label ID="Label1" runat="server"
          Text='<%# Bind("CategoryName") %>'></asp:Label>
    </ItemTemplate>
</asp:TemplateField>

由于 BoundField 被标记为只读,因此 和 EditItemTemplate 都包含ItemTemplate一个标签 Web 控件,其Text属性绑定到 (适用的数据字段CategoryName,按照上述语法) 。 我们需要修改 , EditItemTemplate将 Label Web 控件替换为 DropDownList 控件。

正如我们在前面的教程中看到的那样,可以通过Designer或直接从声明性语法编辑模板。 若要通过Designer对其进行编辑,请单击 GridView 智能标记中的“编辑模板”链接,然后选择使用“类别”字段的 EditItemTemplate。 删除 Label Web 控件并将其替换为 DropDownList 控件,并将 DropDownList 的 ID 属性设置为 Categories

删除 TexBox 并将 DropDownList 添加到 EditItemTemplate

图 5:删除 TexBox 并将 DropDownList 添加到 EditItemTemplate (单击以查看全尺寸图像)

接下来,我们需要使用可用类别填充 DropDownList。 单击 DropDownList 智能标记中的“选择数据源”链接,并选择创建名为 CategoriesDataSource的新 ObjectDataSource。

新建一个名为 CategoriesDataSource 的 ObjectDataSource 控件

图 6:新建一 CategoriesDataSource 个名为 的 ObjectDataSource 控件 (单击以查看全尺寸图像)

若要使此 ObjectDataSource 返回所有类别,请将其绑定到 CategoriesBLL 类的 GetCategories() 方法。

将 ObjectDataSource 绑定到 CategoriesBLL 的 GetCategories () 方法

图 7:将 ObjectDataSource 绑定到 CategoriesBLL方法 GetCategories() (单击以查看全尺寸图像)

最后,配置 DropDownList 的设置,使 CategoryName 字段显示在每个 DropDownList ListItem 中,并将 CategoryID 字段用作值。

显示 CategoryName 字段并将 CategoryID 用作值

图 8:显示 CategoryName 字段和 CategoryID 用作值 (单击以查看全尺寸图像)

进行这些更改后,TemplateField 中 CategoryNameEditItemTemplate声明性标记将同时包含 DropDownList 和 ObjectDataSource:

<asp:TemplateField HeaderText="Category" SortExpression="CategoryName">
    <EditItemTemplate>
        <asp:DropDownList ID="Categories" runat="server"
          DataSourceID="CategoriesDataSource"
          DataTextField="CategoryName" DataValueField="CategoryID">
        </asp:DropDownList>
        <asp:ObjectDataSource ID="CategoriesDataSource" runat="server"
            OldValuesParameterFormatString="original_{0}"
            SelectMethod="GetCategories" TypeName="CategoriesBLL">
        </asp:ObjectDataSource>
    </EditItemTemplate>
    <ItemTemplate>
        <asp:Label ID="Label1" runat="server"
          Text='<%# Bind("CategoryName") %>'></asp:Label>
    </ItemTemplate>
</asp:TemplateField>

注意

中的 EditItemTemplate DropDownList 必须启用其视图状态。 我们很快会将数据绑定语法添加到 DropDownList 的声明性语法和数据绑定命令(如 Eval()Bind() 中,只能在启用了视图状态的控件中显示。

重复这些步骤,将名为 的 Suppliers DropDownList 添加到 SupplierName TemplateField 的 EditItemTemplate。 这将涉及将 DropDownList 添加到 并 EditItemTemplate 创建另一个 ObjectDataSource。 Suppliers但是,DropDownList 的 ObjectDataSource 应配置为调用SuppliersBLL类的 GetSuppliers() 方法。 此外,将 Suppliers DropDownList 配置为显示 CompanyName 字段,并使用 字段 SupplierID 作为其 ListItem 的值。

将 DropDownLists 添加到这两 EditItemTemplate 个 s 后,在浏览器中加载页面,然后单击 Chef Anton 的 Cajun 调味品的“编辑”按钮。 如图 9 所示,产品的类别和供应商列呈现为下拉列表,其中包含可供选择的可用类别和供应商。 但是,请注意,默认情况下,这两个下拉列表中的 第一 项都选中 (饮料类别和异国液体作为供应商) ,即使主厨安东的卡琼调味料是由新奥尔良卡琼喜悦提供的调味品。

默认情况下,Drop-Down Lists 中的第一项处于选中状态

图 9:默认选中 Drop-Down Lists 中的第一项 (单击以查看全尺寸图像)

此外,如果单击“更新”,你会发现产品的 CategoryIDSupplierID 值设置为 NULL。 这两种不需要的行为都是由于 中的 DropDownLists EditItemTemplate 未绑定到基础产品数据中的任何数据字段。

将 DropDownLists 绑定到CategoryIDSupplierID数据字段

若要将已编辑产品的类别和供应商下拉列表设置为适当的值,并在单击“更新”时将这些值发送回 BLL UpdateProduct 的方法,我们需要使用双向数据绑定将 DropDownLists 的属性 SelectedValue 绑定到 CategoryIDSupplierID 数据字段。 若要使用 Categories DropDownList 实现此目的,可以直接将 添加到 SelectedValue='<%# Bind("CategoryID") %>' 声明性语法。

或者,可以通过Designer编辑模板,然后单击 DropDownList 的智能标记中的“编辑数据绑定”链接来设置 DropDownList 的数据绑定。 接下来,指示 SelectedValue 应使用双向数据绑定将 属性绑定到 CategoryID 字段 (见图 10) 。 重复声明性或Designer过程,将数据SupplierID字段绑定到 Suppliers DropDownList。

使用 Two-Way 数据绑定将 CategoryID 绑定到 DropDownList 的 SelectedValue 属性

图 10:使用 Two-Way 数据绑定将 绑定到 CategoryID DropDownList 的属性 SelectedValue (单击以查看全尺寸图像)

将绑定应用于 SelectedValue 两个 DropDownList 的属性后,编辑的产品类别和供应商列将默认为当前产品的值。 单击“更新”时, CategoryID 所选下拉列表项的 和 SupplierID 值将传递给 UpdateProduct 方法。 图 11 显示了添加数据绑定语句后的教程;请注意主厨安东的卡琼调味料的选定下拉列表项是如何正确调味品和新奥尔良卡琼喜悦。

默认情况下,已编辑产品的当前类别和供应商值处于选中状态

图 11:已编辑产品的当前类别和供应商值默认处于选中状态 (单击以查看全尺寸图像)

处理NULL

表中CategoryIDProductsSupplierID 列可以是 NULL,但 中的 EditItemTemplate DropDownLists 不包括表示NULL值的列表项。 这会产生两个后果:

  • 用户无法使用我们的界面将产品的类别或供应商从非值更改为NULLNULL
  • 如果产品具有 NULLCategoryIDSupplierID,则单击“编辑”按钮将导致异常。 这是因为NULL语句中 Bind() (或 SupplierID) 返回CategoryID的值不会映射到 DropDownList 中的值, (当其 SelectedValue 属性设置为不在其列表项集合中的值时,DropDownList 会引发异常) 。

为了支持 NULLCategoryIDSupplierID 值,我们需要将另一个 ListItem 添加到每个 DropDownList 以表示 NULL 值。 在 使用 DropDownList 筛选母版/细节筛选 教程中,我们了解了如何向数据绑定的 DropDownList 添加一个附加 ListItem 项,其中涉及将 DropDownList 的 AppendDataBoundItems 属性设置为 True 并手动添加其他 ListItem。 但是,在上一 ListItem 教程中,我们添加了 ,其 Value-1。 但是,ASP.NET 中的数据绑定逻辑会自动将空白字符串转换为 NULL 值,反之亦然。 因此,在本教程中, ListItem我们希望 的 Value 为空字符串。

首先将两个 DropDownLists 的 AppendDataBoundItems 属性都设置为 True。 接下来,通过将以下<asp:ListItem>元素添加到每个 DropDownList 来添加 NULLListItem ,使声明性标记如下所示:

<asp:DropDownList ID="Categories" runat="server"
    DataSourceID="CategoriesDataSource" DataTextField="CategoryName"
    DataValueField="CategoryID" SelectedValue='<%# Bind("CategoryID") %>'
    AppendDataBoundItems="True">
    <asp:ListItem Value="">(None)</asp:ListItem>
</asp:DropDownList>

我已选择使用“ (None) ”作为此 ListItem的 Text 值,但如果需要,也可以将其更改为空白字符串。

注意

正如我们在使用 DropDownList 筛选母版/细节筛选教程中看到的,ListItem可以通过Designer将 添加到 DropDownList 中,方法是在属性窗口 (中单击 DropDownList 的 Items 属性,该属性将显示ListItem集合编辑器) 。 但是,请务必通过声明性语法为本教程添加 NULLListItem 。 如果使用ListItem集合编辑器,在分配空白字符串时,生成的声明性语法将完全省略Value设置,从而创建声明性标记,例如:<asp:ListItem>(None)</asp:ListItem>。 虽然这看起来可能无害,但缺少 Value 会导致 DropDownList 在其位置使用 Text 属性值。 这意味着,如果 NULLListItem 选择此选项,将尝试将值“ (None) ”分配给 CategoryID,这将导致异常。 通过显式设置 Value="",当选择 时NULLListItem,将分配给 CategoryID 一个NULL值。

对 Suppliers DropDownList 重复这些步骤。

借助此附加 ListItem功能,编辑界面现在可以将值分配给 NULL 产品的 CategoryIDSupplierID 字段,如图 12 所示。

选择“ (无”) 为产品的类别或供应商分配 NULL 值

图 12:选择“无 (”) 为产品的类别或供应商分配 NULL 值 (单击以查看全尺寸图像)

步骤 4:将 RadioButtons 用于停止使用状态

目前,产品的数据字段是使用 CheckBoxField 表示的 Discontinued ,该字段为只读行呈现禁用复选框,为正在编辑的行呈现已启用的复选框。 虽然此用户界面通常适用,但如果需要,可以使用 TemplateField 对其进行自定义。 在本教程中,让我们将 CheckBoxField 更改为 TemplateField,该模板使用具有两个选项“Active”和“Discontinued”的 RadioButtonList 控件,用户可以从中指定产品 Discontinued 的值。

首先将 Discontinued CheckBoxField 转换为 TemplateField,这将创建具有 和 EditItemTemplateItemTemplate TemplateField。 这两个模板都包含一个 CheckBox,其 属性绑定到Discontinued数据字段,这两个模板之间的唯一区别是ItemTemplate,的 CheckBox 的 Enabled 属性设置为 FalseChecked

将 和 中的 ItemTemplate CheckBox 替换为 RadioButtonList 控件,同时将两个 RadioButtonLists 的属性ID都设置为 DiscontinuedChoiceEditItemTemplate 接下来,指示 RadioButtonLists 应包含两个单选按钮,一个标记为“活动”,值为“False”,一个标记为“已停止”,值为“True”。 为此,可以通过声明性语法直接在 中输入<asp:ListItem>元素,或使用ListItemDesigner中的集合编辑器。 图 13 显示了ListItem指定两个单选按钮选项后的集合编辑器。

将 Active 和 Discontinued Options 添加到 RadioButtonList

图 13:向 RadioButtonList 添加活动选项和停用选项 (单击以查看全尺寸图像)

由于 中的 ItemTemplate RadioButtonList 不应可编辑,因此将其 Enabled 属性设置为 False,使 Enabled 属性 True (中 EditItemTemplateRadioButtonList 的默认) 。 这会使未编辑行中的单选按钮成为只读按钮,但允许用户更改已编辑行的 RadioButton 值。

我们仍然需要分配 RadioButtonList 控件 SelectedValue 的属性,以便根据产品 Discontinued 的数据字段选择适当的单选按钮。 与本教程前面介绍的 DropDownList 一样,此数据绑定语法可以直接添加到声明性标记中,也可以通过 RadioButtonLists 智能标记中的“编辑数据绑定”链接添加。

添加两个 RadioButtonList 并对其进行配置后, Discontinued TemplateField 的声明性标记应如下所示:

<asp:TemplateField HeaderText="Discontinued" SortExpression="Discontinued">
    <ItemTemplate>
        <asp:RadioButtonList ID="DiscontinuedChoice" runat="server"
          Enabled="False" SelectedValue='<%# Bind("Discontinued") %>'>
            <asp:ListItem Value="False">Active</asp:ListItem>
            <asp:ListItem Value="True">Discontinued</asp:ListItem>
        </asp:RadioButtonList>
    </ItemTemplate>
    <EditItemTemplate>
        <asp:RadioButtonList ID="DiscontinuedChoice" runat="server"
            SelectedValue='<%# Bind("Discontinued") %>'>
            <asp:ListItem Value="False">Active</asp:ListItem>
            <asp:ListItem Value="True">Discontinued</asp:ListItem>
        </asp:RadioButtonList>
    </EditItemTemplate>
</asp:TemplateField>

通过这些更改,列 Discontinued 已从复选框列表转换为单选按钮对列表 (见图 14) 。 编辑产品时,会选择相应的单选按钮,并通过选择另一个单选按钮并单击“更新”来更新产品的停产状态。

停产的复选框已被单选按钮对替换

图 14:停产的复选框已被单选按钮对替换 (单击以查看全尺寸图像)

注意

由于数据库中的DiscontinuedProducts列不能有NULL值,因此无需担心在接口中捕获NULL信息。 但是,如果列可能包含值,Discontinued我们希望将第三个单选按钮添加到列表,并将其Value设置为空字符串 (Value="") ,就像类别和供应商 DropDownLists 一NULL样。

总结

虽然 BoundField 和 CheckBoxField 会自动呈现只读、编辑和插入接口,但它们缺乏自定义功能。 但是,我们通常需要自定义编辑或插入界面,如我们在前面的教程) 或通过自定义数据收集用户界面 ((如本教程) 所示) (添加验证控件。 使用 TemplateField 自定义接口可以归纳为以下步骤:

  1. 添加 TemplateField 或将现有 BoundField 或 CheckBoxField 转换为 TemplateField
  2. 根据需要扩充接口
  3. 使用双向数据绑定将适当的数据字段绑定到新添加的 Web 控件

除了使用内置 ASP.NET Web 控件外,还可以使用自定义、编译的服务器控件和用户控件自定义 TemplateField 的模板。

编程愉快!

关于作者

Scott Mitchell 是七本 ASP/ASP.NET 书籍的作者, 4GuysFromRolla.com 的创始人,自 1998 年以来一直从事 Microsoft Web 技术工作。 Scott 担任独立顾问、培训师和作家。 他的最新书是 山姆斯在24小时内 ASP.NET 2.0自学。 可以在 上联系 mitchell@4GuysFromRolla.com他, 也可以通过他的博客联系到他,该博客可在 http://ScottOnWriting.NET中找到。