自定义数据修改界面 (C#)
本教程介绍如何通过将标准 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,该控件提供两个选项:“活动”和“已停止”。
图 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 bool UpdateProduct(string productName, int? categoryID,
int? supplierID, bool discontinued, int productID)
{
Northwind.ProductsDataTable products = Adapter.GetProductByProductID(productID);
if (products.Count == 0)
// no matching record found, return false
return false;
Northwind.ProductsRow product = products[0];
product.ProductName = productName;
if (supplierID == null) product.SetSupplierIDNull();
else product.SupplierID = supplierID.Value;
if (categoryID == null) product.SetCategoryIDNull();
else product.CategoryID = categoryID.Value;
product.Discontinued = discontinued;
// Update the product record
int rowsAffected = Adapter.Update(product);
// Return true if precisely one row was updated, otherwise false
return rowsAffected == 1;
}
步骤 2:创建可编辑的 GridView
添加重载后 UpdateProduct
,即可创建可编辑的 GridView。 CustomizedUI.aspx
打开 文件夹中的页面,EditInsertDelete
并将 GridView 控件添加到Designer。 接下来,从 GridView 的智能标记创建新的 ObjectDataSource。 将 ObjectDataSource 配置为通过 ProductBLL
类的 GetProducts()
方法检索产品信息,并使用 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
属性已被移除,并且对于重UpdateProduct
载所需的每个输入参数,集合中UpdateParameters
都有 Parameter
。
虽然 ObjectDataSource 配置为仅更新一部分产品值,但 GridView 当前显示 所有 产品字段。 花点时间编辑 GridView,以便:
- 它仅包括
ProductName
、、SupplierName
CategoryName
BoundFields 和Discontinued
CheckBoxField - 显示在
CategoryName
checkBoxField) 左侧Discontinued
(之前显示的 和SupplierName
字段 CategoryName
和SupplierName
BoundFields 的HeaderText
属性分别设置为“Category”和“Supplier”- (检查 GridView 的智能标记中的“启用编辑”复选框启用编辑支持)
完成这些更改后,Designer将类似于图 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 的 Read-Only 界面已完成 (单击以查看全尺寸图像)
注意
如 插入、更新和删除数据概述教程中所述,启用 GridView 视图状态 (默认行为) 至关重要。 如果将 GridView 的 EnableViewState
属性设置为 false
,则存在并发用户无意中删除或编辑记录的风险。
步骤 3:对类别和供应商编辑接口使用 DropDownList
回想一下, ProductsRow
对象包含 CategoryID
、CategoryName
、 SupplierID
和 SupplierName
属性,这些属性提供数据库表中的实际外键 ID 值Products
和 和 Suppliers
表中的相应Name
值Categories
。 ProductRow
的 CategoryID
和 SupplierID
可以读取和写入,而 CategoryName
和 SupplierName
属性标记为只读。
由于 和 SupplierName
属性的CategoryName
只读状态,相应的 BoundFields 已将其ReadOnly
属性设置为 true
,以防止在编辑行时修改这些值。 虽然我们可以将 ReadOnly
属性设置为 false
,但在编辑过程中将 和 SupplierName
BoundFields 呈现CategoryName
为 TextBoxes,但当用户尝试更新产品时,此方法将导致异常,因为没有任何UpdateProduct
重载会占用 CategoryName
和 SupplierName
输入。 事实上,我们不希望创建这样的重载,原因有两个:
- 该
Products
表没有SupplierName
或CategoryName
字段,但SupplierID
具有 和CategoryID
。 因此,我们希望方法传递这些特定的 ID 值,而不是其查找表的值。 - 要求用户键入供应商或类别的名称不太理想,因为它要求用户知道可用的类别和供应商及其正确的拼写。
当处于只读模式时,供应商和类别字段应显示类别和供应商的名称 (,因为它现在) ,并在编辑时显示适用选项的下拉列表。 使用下拉列表,最终用户可以快速查看哪些类别和供应商可供选择,并且可以更轻松地进行选择。
若要提供此行为,我们需要将 和 BoundField 转换为 SupplierName
TemplateFields,后者ItemTemplate
发出 SupplierName
和 CategoryName
值,并使用 EditItemTemplate
DropDownList 控件列出可用的类别和供应商。CategoryName
Categories
添加 和Suppliers
DropDownLists
首先,通过以下方式将 SupplierName
和 CategoryName
BoundField 转换为 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 被标记为只读,因此 和 EditItemTemplate
都包含ItemTemplate
一个标签 Web 控件,该控件的 Text
属性绑定到 (适用的数据字段CategoryName
,按照上述语法) 。 我们需要修改 EditItemTemplate
,将 Label Web 控件替换为 DropDownList 控件。
正如我们在前面的教程中看到的那样,可以通过 Designer或直接从声明性语法编辑模板。 若要通过Designer对其进行编辑,请单击 GridView 智能标记中的“编辑模板”链接,然后选择使用“类别”字段的 EditItemTemplate
。 删除标签 Web 控件并将其替换为 DropDownList 控件,并将 DropDownList 的 ID 属性设置为 Categories
。
图 5:删除 TexBox 并将 DropDownList 添加到 EditItemTemplate
(单击以查看全尺寸图像)
接下来,需要使用可用类别填充 DropDownList。 单击 DropDownList 的智能标记中的“选择数据源”链接,并选择创建名为 CategoriesDataSource
的新 ObjectDataSource。
图 6:创建名为 CategoriesDataSource
的新 ObjectDataSource 控件 (单击以查看全尺寸图像)
若要使此 ObjectDataSource 返回所有类别,请将其绑定到 CategoriesBLL
类的 GetCategories()
方法。
图 7:将 ObjectDataSource 绑定到 CategoriesBLL
的 GetCategories()
方法 (单击以查看全尺寸图像)
最后,配置 DropDownList 的设置,使 CategoryName
字段显示在每个 DropDownList ListItem
中, CategoryID
并将字段用作值。
图 8:显示 CategoryName
字段和 CategoryID
用作值 (单击以查看全尺寸图像)
进行这些更改后,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
DropDownList 添加到 SupplierName
TemplateField 的 EditItemTemplate
。 这将涉及将 DropDownList 添加到 EditItemTemplate
并创建另一个 ObjectDataSource。 Suppliers
但是,DropDownList 的 ObjectDataSource 应配置为调用SuppliersBLL
类的 GetSuppliers()
方法。 此外,配置 Suppliers
DropDownList 以显示字段 CompanyName
,并使用 字段 SupplierID
作为其 ListItem
的值。
将 DropDownLists 添加到这两 EditItemTemplate
个 s 后,在浏览器中加载页面,然后单击 Chef Anton 的 Cajun 调味品的“编辑”按钮。 如图 9 所示,产品的类别和供应商列呈现为下拉列表,其中包含可供选择的可用类别和供应商。 但是,请注意,默认情况下,两个下拉列表中的 第一 个项都选择 (饮料作为类别,异国情调的液体作为供应商) ,即使主厨安东的Cajun调味料是由新奥尔良卡琼喜悦提供的调味品。
图 9:默认选中 Drop-Down Lists 中的第一项 (单击以查看全尺寸图像)
此外,如果单击“更新”,你会发现产品的 CategoryID
和 SupplierID
值设置为 NULL
。 这两种不需要的行为都是由于 中的 DropDownLists EditItemTemplate
未绑定到基础产品数据中的任何数据字段而引起的。
将 DropDownList 绑定到CategoryID
和SupplierID
数据字段
若要将编辑的产品类别和供应商下拉列表设置为适当的值,并在单击“更新”时将这些值发送回 BLL 的方法 UpdateProduct
,我们需要使用双向数据绑定将 DropDownLists 的属性 SelectedValue
绑定到 CategoryID
和 SupplierID
数据字段。 若要使用 Categories
DropDownList 完成此操作,可以直接将 添加到 SelectedValue='<%# Bind("CategoryID") %>'
声明性语法。
或者,可以通过Designer编辑模板并单击 DropDownList 的智能标记中的“编辑数据绑定”链接来设置 DropDownList 的数据绑定。 接下来,指示 SelectedValue
应使用双向数据绑定 (属性绑定到 CategoryID
字段,请参阅图 10) 。 重复声明性或Designer过程,将数据SupplierID
字段绑定到 Suppliers
DropDownList。
图 10:使用 Two-Way 数据绑定将 绑定到 CategoryID
DropDownList 的 SelectedValue
属性 (单击 以查看全尺寸图像)
将绑定应用于 SelectedValue
两个 DropDownList 的属性后,已编辑的产品类别和供应商列将默认为当前产品的值。 单击“更新”时, CategoryID
所选下拉列表项的 和 SupplierID
值将传递到 UpdateProduct
方法。 图 11 显示了添加数据绑定语句后的教程;请注意主厨安东的 Cajun 调味料的选定下拉列表项如何正确调味品和新奥尔良 Cajun 欢乐。
图 11:默认选择已编辑产品的当前类别和供应商值 (单击以查看全尺寸图像)
处理NULL
值
CategoryID
表中的 Products
和 SupplierID
列可以是 NULL
,但 中的 DropDownLists EditItemTemplate
不包含表示NULL
值的列表项。 这会产生两个后果:
- 用户无法使用我们的界面将产品的类别或供应商从非值更改为
NULL
非NULL
值 - 如果产品具有
NULL
CategoryID
或SupplierID
,则单击“编辑”按钮将导致异常。 这是因为NULL
(或SupplierID
) 在 语句中Bind()
返回CategoryID
的值不会映射到 DropDownList 中的值, (当其SelectedValue
属性设置为不在) 列表项集合中的值时,DropDownList 将引发异常。
为了支持 NULL
CategoryID
和 SupplierID
值,我们需要将另一个 ListItem
添加到每个 DropDownList 以表示 NULL
值。 在 使用 DropDownList 筛选母版/详细信息筛选 教程中,我们了解了如何向数据绑定的 DropDownList 添加附加 ListItem
项,其中涉及将 DropDownList 的 AppendDataBoundItems
属性设置为 true
并手动添加附加 ListItem
的 。 但是,在上一教程中,我们添加了 ListItem
的 , Value
为 -1
。 但是,ASP.NET 中的数据绑定逻辑会自动将空白字符串转换为 NULL
值,反之亦然。 因此,在本教程中, ListItem
我们希望 的 Value
为空字符串。
首先,将两个 DropDownLists 属性 AppendDataBoundItems
都设置为 true
。 接下来,通过将以下<asp:ListItem>
元素添加到每个 DropDownList 来添加 NULL
ListItem
,使声明性标记如下所示:
<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
集合编辑器) 。 但是,请务必通过声明性语法为本教程添加 NULL
ListItem
。 如果使用ListItem
集合编辑器,在分配空白字符串时,生成的声明性语法将完全省略Value
该设置,从而创建声明性标记,如:<asp:ListItem>(None)</asp:ListItem>
。 虽然这看起来可能无害,但缺少 Value 会导致 DropDownList 在其位置使用 Text
属性值。 这意味着,如果 NULL
ListItem
选择此选项,将尝试将值“ (None) ”分配给 CategoryID
,这将导致异常。 通过显式设置 Value=""
,当选择 时ListItem
NULL
,将向其分配CategoryID
一个NULL
值。
对“供应商”DropDownList 重复这些步骤。
借助此附加 ListItem
功能,编辑界面现在可以将值分配给 NULL
Product 的 CategoryID
和 SupplierID
字段,如图 12 所示。
图 12:选择“无 (”) 为产品的类别或供应商分配 NULL
值 (单击以查看全尺寸图像)
步骤 4:将 RadioButtons 用于停产状态
目前,产品 Discontinued
的数据字段使用 CheckBoxField 表示,该字段为只读行呈现禁用复选框,为正在编辑的行呈现已启用复选框。 虽然此用户界面通常适用,但如果需要,可以使用 TemplateField 对其进行自定义。 在本教程中,让我们将 CheckBoxField 更改为使用 RadioButtonList 控件的 TemplateField,其中包含两个选项“活动”和“已停用”,用户可以从中指定产品 Discontinued
的值。
首先,将 Discontinued
CheckBoxField 转换为 TemplateField,这将创建具有 和 EditItemTemplate
的 ItemTemplate
TemplateField。 这两个模板都包含一个 CheckBox,其属性绑定到Discontinued
数据字段,这两个模板的唯一区别是ItemTemplate
,“CheckBox”的 Enabled
属性设置为 false
。Checked
将 和 中的 ItemTemplate
CheckBox 替换为 RadioButtonList 控件,并将两个 RadioButtonLists 属性ID
都设置为 DiscontinuedChoice
。EditItemTemplate
接下来,指示 RadioButtonLists 应各包含两个单选按钮,一个按钮标记为“活动”,值为“False”,一个标记为“已停用”,值为“True”。 为此,可以通过声明性语法直接在 中输入<asp:ListItem>
元素,或使用ListItem
Designer中的集合编辑器。 图 13 显示了ListItem
指定了两个单选按钮选项后的集合编辑器。
图 13:将“活动”和“已停用”选项添加到 RadioButtonList (单击以查看全尺寸图像)
由于 中的 ItemTemplate
RadioButtonList 不应是可编辑的,因此将其 Enabled
属性设置为 false
,使 Enabled
属性 true
(中 EditItemTemplate
RadioButtonList 的默认) 。 这会使未编辑行中的单选按钮成为只读按钮,但允许用户更改已编辑行的 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:停用的复选框已被单选按钮对替换 (单击以查看全尺寸图像)
注意
由于数据库中的Discontinued
Products
列不能包含NULL
值,因此无需担心在接口中捕获NULL
信息。 但是,如果列可能包含值,Discontinued
我们希望将第三个单选按钮添加到列表,并将其Value
设置为空字符串 (Value=""
) ,就像类别和供应商 DropDownLists 一NULL
样。
总结
虽然 BoundField 和 CheckBoxField 自动呈现只读、编辑和插入接口,但它们缺乏自定义功能。 但是,我们通常需要自定义编辑或插入界面,可能添加验证控件 (,如前面的教程) 所示,或通过自定义数据收集用户界面 (,如本教程) 所示。 可通过以下步骤总结使用 TemplateField 自定义接口:
- 添加 TemplateField 或将现有 BoundField 或 CheckBoxField 转换为 TemplateField
- 根据需要扩充接口
- 使用双向数据绑定将适当的数据字段绑定到新添加的 Web 控件
除了使用内置 ASP.NET Web 控件外,还可以使用自定义、已编译的服务器控件和用户控件自定义 TemplateField 的模板。
编程快乐!
关于作者
斯科特·米切尔是七本 ASP/ASP.NET 书籍的作者和 4GuysFromRolla.com 的创始人,自 1998 年以来一直在使用 Microsoft Web 技术。 Scott 担任独立顾问、培训师和作家。 他的最新一本书是 山姆斯在 24 小时内 ASP.NET 2.0。 可以在 上mitchell@4GuysFromRolla.com联系他,也可以通过他的博客(可在 中找到http://ScottOnWriting.NET)。
反馈
https://aka.ms/ContentUserFeedback。
即将发布:在整个 2024 年,我们将逐步淘汰作为内容反馈机制的“GitHub 问题”,并将其取代为新的反馈系统。 有关详细信息,请参阅:提交和查看相关反馈