自定义数据修改界面 (VB)
作者 :斯科特·米切尔
本教程介绍如何通过将标准 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,该控件提供两个选项:“Active”和“Discontinued”。
图 1:GridView 的编辑界面包括 DropDownLists 和 RadioButtons(单击以查看全尺寸图像)
步骤 1:创建适当的UpdateProduct
重载
在本教程中,我们将生成一个可编辑的 GridView,该视图允许编辑产品的名称、类别、供应商和已停用状态。 因此,我们需要一个重载,它接受这四个产品值加上ProductID
五个UpdateProduct
输入参数。 就像在以前的重载中一样,此重载将:
- 从数据库中检索指定的
ProductID
产品信息, ProductName
更新 、CategoryID
、SupplierID
和Discontinued
字段以及- 通过 TableAdapter
Update()
的方法将更新请求发送到 DAL。
为了简洁起见,对于此特定重载,我省略了业务规则检查,确保标记为已停用的产品不是其供应商提供的唯一产品。 如果愿意,或者最好将逻辑重构为单独的方法,可以随意将其添加到其中。
以下代码显示了类中的ProductsBLL
新UpdateProduct
重载:
<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 控件。 接下来,从 GridView 的智能标记创建新的 ObjectDataSource。 将 ObjectDataSource 配置为通过 ProductBLL
类 GetProducts()
的方法检索产品信息,并使用刚刚创建的重载更新产品数据 UpdateProduct
。 在“插入”和“删除”选项卡中,从下拉列表中选择“无”。
图 2:将 ObjectDataSource 配置为使用 UpdateProduct
刚刚创建的重载(单击以查看全尺寸图像)
正如我们在整个数据修改教程中看到的,由 Visual Studio 创建的 ObjectDataSource 的声明性语法将属性original_{0}
分配给 OldValuesParameterFormatString
。 当然,这不适用于业务逻辑层,因为我们的方法不希望传入原始 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
该属性已被删除,并且重载所需的UpdateProduct
每个输入参数的集合中有UpdateParameters
一个Parameter
。
虽然 ObjectDataSource 配置为仅更新产品值的子集,但 GridView 当前显示 所有 产品字段。 花点时间编辑 GridView,以便:
- 它仅包括
ProductName
,SupplierName
CategoryName
BoundFields 和Discontinued
CheckBoxField CategoryName
SupplierName
要出现在 CheckBoxField 之前(左侧)Discontinued
的字段SupplierName
和CategoryName
BoundFields 的属性HeaderText
分别设置为“Category”和“Supplier”- 已启用编辑支持(选中 GridView 智能标记中的“启用编辑”复选框)
这些更改后,设计器将类似于图 3,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 中呈现为一行,其中显示了产品的名称、类别、供应商和已停用状态。
图 4:GridView 的只读界面已完成(单击以查看全尺寸图像)
注意
如“插入、更新和删除数据概述”教程中所述,已启用 GridView 的视图状态(默认行为)至关重要。 如果将 GridView 属性 EnableViewState
设置为 false
,则可能会无意中删除或编辑记录并发用户。
步骤 3:对类别和供应商编辑接口使用 DropDownList
回想一下,该ProductsRow
对象包含CategoryID
、CategoryName
属性SupplierID
和SupplierName
属性,它们提供数据库表中的实际外键 ID 值Products
以及表中的相应Name
值Categories
Suppliers
。 和ProductRow
CategoryID
SupplierID
可以读取和写入,而CategoryName
属性SupplierName
标记为只读。
由于属性的CategoryName
只读状态,SupplierName
相应的 BoundFields 已设置True
其ReadOnly
属性,从而防止在编辑行时修改这些值。 虽然我们可以将 ReadOnly
属性 False
设置为 ,在编辑过程中将 CategoryName
和 SupplierName
BoundFields 呈现为 TextBoxes,但当用户尝试更新产品时,此方法将导致异常,因为没有 UpdateProduct
占用 CategoryName
和 SupplierName
输入的重载。 事实上,出于两个原因,我们不想创建这样的重载:
- 该
Products
表没有SupplierName
或CategoryName
字段,但SupplierID
具有和CategoryID
。 因此,我们希望我们的方法传递这些特定的 ID 值,而不是它们的查阅表的值。 - 要求用户键入供应商或类别的名称并不理想,因为它要求用户知道可用的类别和供应商及其正确的拼写。
当处于只读模式时,供应商和类别字段应显示类别和供应商的名称(如现在所示),并在编辑时显示适用选项的下拉列表。 使用下拉列表,最终用户可以快速查看可在其中选择哪些类别和供应商,并且可以更轻松地进行选择。
为了提供此行为,我们需要将SupplierName
CategoryName
和 BoundFields 转换为 TemplateFields,其ItemTemplate
发出SupplierName
和CategoryName
值,并使用 EditItemTemplate
DropDownList 控件列出可用的类别和供应商。
Categories
添加和Suppliers
DropDownLists
首先将和 BoundFields 转换为 SupplierName
CategoryName
TemplateFields,方法是:单击 GridView 智能标记中的“编辑列”链接;从左下角列表中选择 BoundField;然后单击“将此字段转换为 TemplateField”链接。 转换过程将创建包含一个 ItemTemplate
和一个 EditItemTemplate
的 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 被标记为只读, ItemTemplate
并且 EditItemTemplate
包含一个标签 Web 控件,其 Text
属性绑定到适用的数据字段(CategoryName
在上面的语法中)。 我们需要修改标签 EditItemTemplate
Web 控件,将 Label Web 控件替换为 DropDownList 控件。
如前面的教程中所述,可以通过设计器或直接从声明性语法编辑模板。 若要通过设计器对其进行编辑,请单击 GridView 智能标记中的“编辑模板”链接,然后选择使用“类别”字段。EditItemTemplate
删除标签 Web 控件并将其替换为 DropDownList 控件,并将 DropDownList 的 ID 属性设置为 Categories
。
图 5:删除 TexBox 并将 DropDownList 添加到 EditItemTemplate
(单击以查看全尺寸图像)
接下来,我们需要使用可用类别填充 DropDownList。 单击 DropDownList 智能标记中的“选择数据源”链接,并选择创建名为 CategoriesDataSource
的新 ObjectDataSource。
图 6:新建名为 CategoriesDataSource
ObjectDataSource 控件(单击以查看全尺寸图像)
若要让此 ObjectDataSource 返回所有类别,请将其 CategoriesBLL
绑定到类 GetCategories()
的方法。
图 7:将 ObjectDataSource 绑定到 CategoriesBLL
's GetCategories()
方法(单击以查看全尺寸图像)
最后,配置 DropDownList 的设置,以便字段 CategoryName
显示在每个 DropDownList ListItem
中,且 CategoryID
字段用作值。
图 8:显示字段并CategoryID
用作值(单击以查看全尺寸图像CategoryName
)
进行这些更改后,TemplateField 中的CategoryName
声明性标记EditItemTemplate
将同时包括 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
SupplierName
TemplateField 的 EditItemTemplate
DropDownList。 这涉及到将 DropDownList 添加到 EditItemTemplate
另一个 ObjectDataSource 并创建。 Suppliers
但是,应将 DropDownList 的 ObjectDataSource 配置为调用SuppliersBLL
类GetSuppliers()
的方法。 此外,将 Suppliers
DropDownList 配置为显示 CompanyName
字段,并将 SupplierID
该字段用作其 ListItem
值。
将 DropDownLists 添加到两 EditItemTemplate
个 s 后,将页面加载到浏览器中,然后单击 Chef Anton 的 Cajun 调味品的“编辑”按钮。 如图 9 所示,产品的类别和供应商列呈现为下拉列表,其中包含可供选择的可用类别和供应商。 但是,请注意 ,默认情况下,这两个下拉列表中的第一 个项目都是选择的(类别饮料和异国液体作为供应商),即使厨师安东的卡琼调味是新奥尔良卡琼喜悦提供的调味品。
图 9:下拉列表中的第一项默认处于选中状态(单击以查看全尺寸图像)
此外,如果单击“更新”,你将发现产品的 CategoryID
和 SupplierID
值已设置为 NULL
。 这两种不想要的行为都是由于 s 中的 EditItemTemplate
DropDownList 不绑定到基础产品数据中的任何数据字段而引起的。
将 DropDownList 绑定到CategoryID
和数据SupplierID
字段
为了使已编辑的产品类别和供应商下拉列表设置为适当的值,并在单击“更新”时将这些值发送回 BLL UpdateProduct
的方法,我们需要使用双向数据绑定将 DropDownLists SelectedValue
的属性绑定到CategoryID
SupplierID
数据字段和数据字段。 若要使用 Categories
DropDownList 完成此操作,可以直接添加到 SelectedValue='<%# Bind("CategoryID") %>'
声明性语法。
或者,可以通过设计器编辑模板并单击 DropDownList 智能标记中的“编辑 DataBindings”链接来设置 DropDownList 的数据绑定。 接下来,指示 SelectedValue
该属性应使用双向数据绑定绑定到 CategoryID
字段(请参阅图 10)。 重复声明性或设计器过程,将数据 SupplierID
字段 Suppliers
绑定到 DropDownList。
图 10:使用双向数据绑定绑定到 CategoryID
DropDownList SelectedValue
的属性(单击以查看全尺寸图像)
将绑定应用于 SelectedValue
两个 DropDownList 的属性后,编辑的产品类别和供应商列将默认为当前产品的值。 单击“更新”后,CategoryID
SupplierID
所选下拉列表项的值将传递给UpdateProduct
该方法。 图 11 显示了添加数据绑定语句后的教程;请注意厨师安东的卡琼调味料的所选下拉列表项是如何正确调味品和新奥尔良的 Cajun 喜悦。
图 11:默认情况下已编辑产品的当前类别和供应商值处于选中状态(单击以查看全尺寸图像)
处理NULL
值
CategoryID
表中的列SupplierID
Products
可以是NULL
,但 DropDownLists EditItemTemplate
中不包含表示NULL
值的列表项。 这有两个后果:
- 用户无法使用界面将产品的类别或供应商从非
NULL
值更改为非NULL
值 - 如果产品有
NULL
CategoryID
或SupplierID
,单击“编辑”按钮将导致异常。 这是因为NULL
语句中Bind()
由CategoryID
(或SupplierID
) 返回的值不映射到 DropDownList 中的值(当 DropDownList 设置为非列表项集合中的值时SelectedValue
,DropDownList 将引发异常)。
为了支持NULL
CategoryID
和SupplierID
值,我们需要为每个 DropDownList 添加另一个ListItem
值来表示NULL
值。 在“使用 DropDownList 筛选母版/详细信息筛选”教程中,我们了解了如何向数据绑定的 DropDownList 添加附加ListItem
内容,该列表涉及将 DropDownList AppendDataBoundItems
的属性设置为True
并手动添加其他ListItem
属性。 但是,在上一篇教程中,我们添加了一个 Value
-1
教程。 但是,ASP.NET 中的数据绑定逻辑会自动将空白字符串转换为 NULL
值,反之亦然。 因此,在本教程中,我们希望 ListItem
其 Value
为空字符串。
首先将 DropDownLists AppendDataBoundItems
的属性设置为 True
. 接下来,NULL
ListItem
将以下<asp:ListItem>
元素添加到每个 DropDownList,以便声明性标记如下所示:
<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
文本值,但如果愿意,则可以将其更改为空白字符串。
注意
正如我们在 Master/Detail Filtering With a DropDownList 教程中看到的那样,ListItem
可以通过设计器将 s 添加到 DropDownList,方法是单击 属性窗口 中的 DropDownList Items
属性(这将显示ListItem
集合编辑器)。 但是,请务必通过声明性语法添加NULL
ListItem
本教程。 如果使用ListItem
集合编辑器,则生成的声明性语法将在分配空白字符串时完全省略Value
该设置,并创建声明性标记,如下所示。 <asp:ListItem>(None)</asp:ListItem>
虽然这可能看起来无害,但缺少的值会导致 DropDownList 在其位置使用 Text
属性值。 这意味着,如果选择此选项NULL
ListItem
,将尝试将值“(None)”分配给该CategoryID
值,这将导致异常。 通过显式设置Value=""
,将在选择值时分配给CategoryID
该值NULL
ListItem
。NULL
对供应商 DropDownList 重复这些步骤。
ListItem
此外,编辑界面现在可以将值分配给 NULL
Product 和CategoryID
SupplierID
字段,如图 12 所示。
图 12:选择(无)为产品类别或供应商分配 NULL
值(单击以查看全尺寸图像)
步骤 4:对已停止状态使用 RadioButtons
当前,产品 Discontinued
的数据字段使用 CheckBoxField 表示,该字段为只读行呈现禁用复选框,并为正在编辑的行呈现已启用复选框。 虽然此用户界面通常适用,但如果需要,可以使用 TemplateField 对其进行自定义。 在本教程中,让我们将 CheckBoxField 更改为使用 RadioButtonList 控件的 TemplateField,其中包含两个选项“Active”和“Discontinued”,用户可以从中指定产品 Discontinued
的值。
首先,将 Discontinued
CheckBoxField 转换为 TemplateField,这将创建包含 and 的 ItemTemplate
EditItemTemplate
TemplateField。 这两个模板都包含一个 CheckBox,其 Checked
属性绑定到 Discontinued
数据字段,这两个模板的唯一区别是 ItemTemplate
“CheckBox Enabled
”属性设置为 False
。
将两者ItemTemplate
EditItemTemplate
中的 CheckBox 替换为 RadioButtonList 控件,并将 RadioButtonLists ID
的属性设置为 DiscontinuedChoice
。 接下来,指示 RadioButtonLists 应包含两个单选按钮,一个标记为“Active”,值为“False”,一个标记为“已停用”,值为“True”。 为此,可以直接通过声明性语法输入 <asp:ListItem>
元素,也可以使用 ListItem
设计器中的集合编辑器。 图 13 显示了 ListItem
指定两个单选按钮选项后集合编辑器。
图 13:向 RadioButtonList 添加活动选项和已停用选项(单击可查看全尺寸图像)
由于不应编辑 RadioButtonList ItemTemplate
,因此将其 Enabled
属性 False
设置为 ,将 Enabled
属性保留为 True
RadioButtonList 中的 EditItemTemplate
属性(默认值)。 这将将非编辑行中的单选按钮设为只读,但允许用户更改已编辑行的 RadioButton 值。
我们仍然需要分配 RadioButtonList 控件 SelectedValue
的属性,以便根据产品 Discontinued
的数据字段选择相应的单选按钮。 与本教程前面所述的 DropDownLists 一样,此数据绑定语法可以直接添加到声明性标记中,也可以通过 RadioButtonLists 智能标记中的“编辑 DataBindings”链接添加。
添加两个 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:已用单选按钮对替换已停止使用的复选框(单击可查看全尺寸图像)
注意
由于数据库中的Discontinued
Products
列不能有NULL
值,因此无需担心在接口中捕获NULL
信息。 但是 Discontinued
,如果列可能包含 NULL
我们要将第三个单选按钮添加到列表中,其 Value
设置为空字符串(Value=""
就像类别和供应商 DropDownLists 一样)。
总结
虽然 BoundField 和 CheckBoxField 自动呈现只读、编辑和插入接口,但它们缺乏自定义功能。 不过,我们通常需要自定义编辑或插入界面,例如添加验证控件(如前面的教程所示),或通过自定义数据收集用户界面(如本教程所示)。 可以使用 TemplateField 自定义接口,可按以下步骤进行总结:
- 添加 TemplateField 或将现有 BoundField 或 CheckBoxField 转换为 TemplateField
- 根据需要扩充接口
- 使用双向数据绑定将适当的数据字段绑定到新添加的 Web 控件
除了使用内置 ASP.NET Web 控件之外,还可以使用自定义、编译的服务器控件和用户控件自定义 TemplateField 的模板。
快乐编程!
关于作者
斯科特·米切尔,七本 ASP/ASP.NET 书籍的作者和 4GuysFromRolla.com 的创始人,自1998年以来一直在与Microsoft Web 技术合作。 斯科特担任独立顾问、教练和作家。 他的最新书是 山姆斯在24小时内 ASP.NET 2.0。 他可以通过他的博客联系到mitchell@4GuysFromRolla.com他,可以在该博客中找到http://ScottOnWriting.NET。