自定义数据编辑界面
本文档是 Visual Basic 教程 (切换到 Visual C# 教程)
本教程中,我们将了解怎样自定义可编辑的 GridView 界面,解决方法是将标准的 TextBox 与 CheckBox 控件替换为其他的 Web 输入控件。
简介因为GridView 与 DetailsView 控件使用的BoundField 与 CheckBoxField 能够呈现只读、可编辑、可插入的界面 , 所以它们能简化修改数据的过程。这些界面不需要加入任何附加的声明式标记或代码 , 就可以被呈现出来。然而 , 实际场景中经常需要自定义的界面 ,BoundField 与 CheckBoxField 的界面却无法做到这点。为了在GridView 或 DetailsView 中自定义编辑或插入界面 , 我们需要转为使用 TemplateField 。 在前一篇教程 中 ,我们看到了怎样通过添加 Web 验证控件来对数据修改界面进行自定义。本教程中,我们将了解怎样自定义实际的Web 数据集合 控件 , 将BoundField 与 CheckBoxField 的标准 TextBox 与 CheckBox 控件替换为其他的 Web 输入 控件。尤其是 , 我们将构建一个可编辑的GridView , 它允许对产品的名称、类别、供应商、断货状态进行更新。当对特定一行进行编辑时,类别与供应商字段将呈现为 DropDownList ,其中包含了可用的类别与供应商集合以供选择。此外,我们还会把 CheckBoxField 的默认 CheckBox 替换为 RadioButtonList 控件,它能提供两个选项:“ Active ”与“ Discontinued ”。 图1:GridView 的编辑界面包括了 DropDownList 与 RadioButton 步骤1 : 创建适当的UpdateProduct 重载本教程中,我们将构建一个可编辑的 GridView ,它允许编辑产品的名称、类别、供应商、断货状态。因此,我们需要一个 UpdateProduct 重载,它要接受五个输入参数 — 这四个产品值加上 ProductID 。与我们之前的重载一样,这个重载将:
为简单起见 , 对这个重载 , 我没有加上 “ 如果产品标为断货 , 确保它不是供应商提供的唯一产品 ” 这条业务规则检查。如果您想要的话,可以随意加上,或者,理想做法是,将这个逻辑分离成一个单独的方法。 下面的代码显示的是 ProductsBLL 类中这个新的 UpdateProduct 重载 :
步骤2 : 设计可编辑的GridView添加完 UpdateProduct 重载之后,我们就开始创建我们的可编辑 GridView 。打开 EditInsertDelete 文件夹中的 CustomizedUI.aspx 页面,为 设计器 添加一个 GridView 控件。接下来,从 GridView 的智能标记中创建一个新的 ObjectDataSource 。对 ObjectDataSource 进行配置,使得通过 ProductBLL 类的 GetProducts() 方法得到产品信息,使用我们刚才创建的 UpdateProduct 重载更新产品数据。在INSERT 与 DELETE 选项卡中,从下拉列表中选择 (None)。 图2:将 ObjectDataSource 配置为使用刚才创建的 UpdateProduct 重载 与我们在数据修改教程一直看到的一样,Visual Studio 创建的 ObjectDataSource 的声明式语法会将 OldValuesParameterFormatString 属性赋值为original_{0} 。当然,这在我们的 Business Logic Layer (业务逻辑层) 无法正常工作,因为我们的方法并不期待输入原来的 ProductID 值。因此,与我们在之前教程中所做的一样,花点时间将这个属性赋值从声明式语法中删除,或者,将这个属性的值设为 {0} 。 更改之后 ,ObjectDataSource 的声明式标记应该如下所示 :
注意,OldValuesParameterFormatString 属性已经被删除了,并且对于我们UpdateProduct 重载期待的每个输入参数,在UpdateParameters 集合中都有一个 Parameter 与之对应。 虽然ObjectDataSource 被配置为仅对产品值的一个子集进行更新,但是现在 GridView 会显示所有产品字段。花点时间编辑 GridView ,使得:
进行这些更改之后 , 编辑器将如图 3 所示 ,GridView 的声明式语法如下所示。 图3:从 GridView 中删除不需要的字段
此时,GridView 的只读功能就完成了。当查看数据时,每个产品都呈现为 GridView 中的一行,显示为产品的名称、类别、供应商、断货状态。 图4 :GridView 的只读界面完成了
注意 : 在插入、更新、删除数据概述教程中讨论过 , 必须启用 GridView 的view state ( 默认 ), 这点非常重要。如果将GridView 的EnableViewState 属性设置为 false , 则可能使多个用户无意中同时删除或编辑记录。有关更多信息 , 请参见警告:在使用支持编辑和/或删除功能并禁用了查看状态的ASP.NET 2.0 GridViews/DetailsView/FormViews 时的并发问题 。 步骤3 :为Category 与Supplier编辑界面使用DropDownList回忆一下 ,ProductsRow 对象包含 CategoryID 、CategoryName 、SupplierID 、SupplierName 属性 , 这提供了 Products 数据库表中的实际外键 ID 值 ,以及Categories 与 Suppliers 表中的相应Name 值。ProductRow 的CategoryID 与 SupplierID 都可读可写 ,CategoryName 与 SupplierName 属性被标为只读。 因为CategoryName 与 SupplierName 属性状态为只读 , 所以对应BoundField 的 ReadOnly 属性也设为True , 当编辑一行时 , 这些值不会被修改。虽然我们可以将 ReadOnly 属性设为 False , 在编辑时将 CategoryName 与SupplierName BoundField 呈现为 TextBox , 但是当用户试图更新产品时 , 这种设置会导致异常 , 原因是没有能够输入 CategoryName 与 SupplierName 的 UpdateProduct 重载。实际上,我们不想创建这种重载,原因有两个:
供应商与类别字段应该在只读模式时显示供应商与类别的名称 ( 现在就是这么做的 ), 在被编辑时显示适用选项的下拉列表。通过使用下拉列表,终端用户可以很快看到可选的类别与供应商,这样更容易进行选择。 要实现该功能 , 我们需要将SupplierName 与 CategoryName BoundField 转换为TemplateField , 它的 ItemTemplate 显示 SupplierName 与 CategoryName 值,它的 EditItemTemplate 使用DropDownList 控件来列出可选的类别与供应商。 添加Categories 与SuppliersDropDownList首先,将SupplierName 与 CategoryName BoundField 转换为TemplateField ,方法是:在GridView 的智能标记中,单击 Edit Columns 链接;选择左下方列表中的 BoundField ;单击 “Convert this field into a TemplateField ” 链接。转换过程将创建一个TemplateField ,它有一个ItemTemplate 与一个 EditItemTemplate ,见下面的声明式语法 :
因为BoundField 被标记为只读 , 所以 ItemTemplate 与 EditItemTemplate 都包含一个 Web 标签 控件 , 控件的Text 属性被绑定为相应的数据字段 ( 在上面的语法中是 CategoryName ) 。我们需要修改 EditItemTemplate , 将 Web 标签 控件替换为 DropDownList 控件。 我们在之前的教程中看到过 , 模板可以通过 设计器 编辑 , 也可以直接在声明式语法编辑。 要通过 设计器 编辑 , 请在GridView 的智能标记中单击 Edit Templates 链接 , 然后选为使用Category 字段的 EditItemTemplate 。删除Web 标签 控件并替换为 DropDownList 控件 , 将 DropDownList 的ID 属性设为 Categories 。 图5 : 删除 TexBox 并向 EditItemTemplate 添加一个DropDownList 接下来,我们需要为DropDownList 填充可选的类别。从 DropDownList 的智能标记中单击 Choose Data Source 链接,然后选择创建一个新的 ObjectDataSource ,命名为CategoriesDataSource 。 图6 :创建一个新的 ObjectDataSource 控件,命名为 CategoriesDataSource 要让这个ObjectDataSource 返回所有的类别,请将它绑定到 CategoriesBLL 类的 GetCategories() 方法。 图7 :将 ObjectDataSource 绑定到 CategoriesBLL 的 GetCategories() 方法 最后,配置DropDownList 的设置,使得每个 DropDownList ListItem 中的显示都是CategoryName ,使用CategoryID 字段赋值。 图8 :显示为 CategoryName 字段,使用 CategoryID 赋值 当做完这些更改之后,CategoryName TemplateField 中的 EditItemTemplate 声明式标记将会包含一个 DropDownList 与一个 ObjectDataSource :
注意 : 必须启用 EditItemTemplate 中 DropDownList 的查看状态。我们马上将向DropDownList 的声明式语法添加数据绑定语法,类似 Eval() 与 Bind() 的数据绑定命令只能出现在启用查看状态的控件中。 重复这些步骤,向SupplierName TemplateField 的 EditItemTemplate 添加一个名为 Suppliers 的 DropDownList 。这包括向EditItemTemplate 添加一个 DropDownList ,以及创建另一个 ObjectDataSource 。然而,应该将 Suppliers DropDownList 的 ObjectDataSource 配置为调用 SuppliersBLL 类的 GetSuppliers() 方法。此外,请将 Suppliers DropDownList 配置为显示 CompanyName 字段,使用 SupplierID 字段为其ListItem 赋 值。 将DropDownList 添加到两个 EditItemTemplate 之后,在浏览器中加载页面,单击Chef Anton's Cajun Seasoning 产品的 Edit 按钮。如图 9 所示,产品的类别和供应商列呈现为下拉列表,包含了可选的类别与供应商。然而,注意两个下拉列表的默认选项都是第一项 ( 类别为Beverages , 供应商为 Exotic Liquids ),即使 Chef Anton's Cajun Seasoning 是一种 Condiment ,由 New Orleans Cajun Delights 提供。 图9 :默认选项为下拉列表中的第一项 此外,如果单击Update ,将发现产品的 CategoryID 与 SupplierID 都赋 值为NULL 。这两种不期望的行为的产生,是由于 EditItemTemplate 中的DropDownList 没有绑定到底层产品数据的任何数据字段。 将DropDownList 绑定到CategoryID 与SupplierID 数据字段为了对所编辑产品的类别与供应商下拉列表适当赋值,以及当单击Update 时,将这些值送回到 BLL 的 UpdateProduct 方法,我们需要使用双向数据绑定,将DropDownList 的 SelectedValue 属性绑定到CategoryID 与 SupplierID 数据字段。为了使Categories DropDownList 达到这点,您可以直接将SelectedValue='<%# Bind("CategoryID") %>' 添加到声明式语法。 或者,您可以设置DropDownList 的数据绑定,方法是通过 设计器 来编辑模板,并从DropDownList 的智能标记中单击 Edit DataBindings 链接。接下来,使用双向数据绑定指明 SelectedValue 属性应该被绑定到 CategoryID 字段( 见图 10 )。重复声明式代码或设计器过程,将 SupplierID 数据字段绑定到 Suppliers DropDownList 。 图10 : 使用双向数据绑定将 CategoryID 绑定到 DropDownList 的 SelectedValue 属性 将绑定应用于两个 DropDownList 的 SelectedValue 属性之后,所编辑产品的类别与供应商列将默认为当前产品的值。单击Update 时,所选下拉列表项的 CategoryID 与 SupplierID 值将被传送到 UpdateProduct 方法。图 11 显示了添加数据绑定语句之后的本教程;请留意 Chef Anton's Cajun Seasoning 的下拉列表选项是怎样正确显示为 Condiment 与 New Orleans Cajun Delights 的。 图11 :默认选项为所编辑产品的当前类别与供应商 处理NULL 值Products 表中的 CategoryID 与 SupplierID 列可以为 NULL,但是EditItemTemplate 中的 DropDownList 并不包含一个能表示 NULL 值的列表项。这有两个后果 :
为了支持NULL CategoryID 与 SupplierID 值,我们需要为每个 DropDownList 添加另一个 ListItem,以表示NULL 值。在使用 DropDownList 的主/明细筛选教程中,我们看到了怎样为数据绑定的 DropDownList 添加另外一个 ListItem,这包括将 DropDownList 的 AppendDataBoundItems 属性设为 True 以及手动添加另外的 ListItem 。然而,在前一篇教程中,我们添加了Value 为 -1 的 ListItem。但是,ASP.NET 中的数据绑定逻辑会自动将空字符串转换为 NULL 值,反之亦然。因此对于本教程,我们需要将 ListItem 的 Value 设为空字符串。 首先,将两个 DropDownList 的 AppendDataBoundItems 属性设为 True。接下来,添加 NULL ListItem,方法是为每个DropDownList 添加下面的 <asp:ListItem> 元素,声明式标记类似下面 :
我选择了使用 “(None) ” 作为这个ListItem 的 Text 值 , 但是如果您愿意 , 可以将它也变为空字符串。 注意 :我们在使用DropDownList 的主/ 明 细筛选 教程中看到,可以通过设计器 将ListItem 添加到 DropDownList ,方法是单击 Properties 窗口中的DropDownList 的 Items 属性( 这将显示 ListItem Collection Editor )。然而对于本教程,务必通过声明式语法添加NULL ListItem 。如果您使用 ListItem Collection Editor ,那么当赋值为空字符串时,生成的声明式语法将完全忽略Value 设置,创建出这样的声明式标记:<asp:ListItem>(None)</asp:ListItem>.尽管看起来没有什么问题,丢失的Value 会使得 DropDownList 使用Text 属性顶替它。这意味着如果选择了这个NULL ListItem,那么就会试图将值 “(None) ” 赋给CategoryID,这就会导致异常。通过明确设置 Value="" ,当选择 NULL ListItem 时,才会将NULL 值赋给 CategoryID 。 对Suppliers DropDownList 重复这些步骤。 有了这个附加的 ListItem , 现在编辑界面可以将 NULL 值赋给 Product 的 CategoryID 与 SupplierID 字段了 , 如图12 中所示。 图12 :选择 (None) 会将产品的类别或供应商赋值为 NULL 步骤4 : 为断货状态使用RadioButton目前,使用的是CheckBoxField 来表现产品的 Discontinued 数据字段,这将在只读行呈现一个禁用的复选框,在正在编辑行呈现一个启用的复选框。虽然这个用户界面通常是合适的,但是如果需要,我们可以使用TemplateField 来对它自定义。对于本教程,我们将CheckBoxField 变为 TemplateField ,它使用 RadioButtonList 控件,控件有两个选项 — “Active ” 与 “Discontinued ” — 从这里,用户可以指定产品的 Discontinued 值。 首先,将Discontinued CheckBoxField 转换为 TemplateField ,这需要创建一个 TemplateField ,它有一个 ItemTemplate 与一个EditItemTemplate 。两个模板都包括一个 CheckBox ,它的Checked 属性绑定到 Discontinued 数据字段,两者的唯一区别是 ItemTemplate 的 CheckBox 的 Enabled 属性被设为 False 。 请将ItemTemplate 与 EditItemTemplate 中的CheckBox 替换为 RadioButtonList 控件,将两个RadioButtonList 的 ID 属性设为 DiscontinuedChoice 。接下来,表明每个RadioButtonList 都应该包含两个单选按钮,其中一个的标签为 “Active ”,值为 “False ”,一个的标签为 “Discontinued ”,值为 “True ” 。要达到这个目标,您或者可以直接通过声明式语法输入 <asp:ListItem> 元素,或者通过设计器,使用 ListItem Collection Editor 。图13 显示的是指定两个单选按钮选项后的 ListItem Collection Editor 。 图13 :为 RadioButtonList 添加 “Active ” 与 “Discontinued ” 选项 因为ItemTemplate 中的 RadioButtonList 应该是不可编辑的, 所以请将它的Enabled 属性设为 False , 将 EditItemTemplate 中RadioButtonList 的 Enabled 属性保持为 True 不变( 默认 )。这将使得非编辑行中的单选按钮为只读,但是允许用户更改编辑行的RadioButton 值。 我们仍然需要为 RadioButtonList 控件的 SelectedValue 属性赋值,使得根据产品的 Discontinued 数据字段,选中适当的单选按钮。与本教程前面探讨过的DropDownList 一样,或者可以直接将这个数据绑定语法添加到声明式标记中,或者可以使用RadioButtonList 智能标记中的 Edit DataBindings 链接做到。 添加完两个 RadioButtonList 并配置它们之后,Discontinued TemplateField 的声明式标记看起来应该像这样 :
做完这些更改之后,Discontinued 列就从一列复选框转化为了一列单选按钮对( 见图14 )。当编辑产品时,选择适当的单选按钮,通过选择另一个单选按钮并单击 Update 就可以更新产品的断货状态。 图14 :Discontinued 复选框被替换成了单选按钮对 注意:因为 Products 数据库中的 Discontinued 列不可能有NULL 值,所以我们不需要考虑在界面中捕捉 NULL 信息。然而,如果 Discontinued 列可能包含 NULL 值,我们就需要为列表添加第三个单选按钮,将它的Value 设为空字符串 (Value="" ),与我们对类别与供应商的 DropDownList 所做的一样。 小结虽然BoundField 与 CheckBoxField 可以自动呈现只读、编辑、插入界面 , 但是它们缺少自定义能力。然而,我们通常需要自定义编辑或插入界面,可能要添加验证控件(如我们在前面教程中看到的)或自定义数据集用户界面(如我们在本教程中看到的)。使用 TemplateField 来自定义界面可以归纳为如下步骤:
除了使用内置的 ASP.NET Web 控件之外 , 您还可以使用自定义的、编译的服务器控件与用户控件对TemplateField 模板进行自定义。 快乐编程 !
|