作者 :斯科特·米切尔
本教程扩展了主/细节关系以添加第三层,使用两个 DropDownList 控件选择所需的父记录和祖父记录。
介绍
在 上一教程中 ,我们研究了如何使用填充了类别的单个 DropDownList 显示简单的主/详细信息报表,以及显示属于所选类别的产品的 GridView。 此报表模式在显示具有一对多关系的记录时效果良好,并且可以轻松扩展,以便适用于包含多个一对多关系的场景。 例如,订单输入系统将具有对应于客户、订单和订单行项的表。 给定的客户可能有多个订单,每个订单都包含多个项。 此类数据可以通过两个下拉列表和一个网格视图呈现给用户。 第一个 DropDownList 将在数据库中为每个客户提供一个列表项,第二个 DropDownList 的内容是所选客户的订单。 GridView 将列出所选订单中的行项。
虽然 Northwind 数据库在其Customers
、Orders
和Order Details
表中包含规范的客户/订单/订单详细信息,但这些表不会在我们的体系结构中捕获。 尽管如此,我们仍然可以演示如何使用两个互相依赖的下拉列表。 第一个 DropDownList 将列出类别,第二个属于所选类别的产品。 然后,DetailsView 将列出所选产品的详细信息。
步骤 1:创建和填充类别 DropDownList
我们的第一个目标是添加列出类别的 DropDownList。 在前面的教程中详细介绍了这些步骤,但在此处进行了总结,了解完整性。
在MasterDetailsDetails.aspx
文件夹中打开Filtering
页面,将 DropDownList 添加到页面,设置其ID
属性为Categories
,然后单击其智能标记中的“配置数据源”链接。 从“数据源配置向导”中选择添加新数据源。
图 1:为 DropDownList 添加新数据源(单击以查看全尺寸图像)
新数据源自然应为 ObjectDataSource。 将此新的 ObjectDataSource CategoriesDataSource
命名,并使其调用 CategoriesBLL
对象的 GetCategories()
方法。
图 2:选择使用 CategoriesBLL
类(单击以查看全尺寸图像)
图 3:将 ObjectDataSource 配置为使用 GetCategories()
方法(单击可查看全尺寸图像)
配置 ObjectDataSource 后,我们仍需要指定应在 DropDownList 中显示的 Categories
数据源字段,以及哪些数据源字段应配置为列表项的值。 将 CategoryName
字段设置为显示项和 CategoryID
每个列表项的值。
图 4:让 DropDownList 显示 CategoryName
字段并用作 CategoryID
值(单击以查看全尺寸图像)
此时,我们有一个 DropDownList 控件(Categories
),该控件填充了表中的记录 Categories
。 当用户从 DropDownList 中选择新类别时,我们希望进行回发,以便刷新要在步骤 2 中创建的产品 DropDownList。 因此,从 categories
DropDownList 的智能标记中选择“Enable AutoPostBack”选项。
图 5:为 Categories
DropDownList 启用 AutoPostBack(单击以查看全尺寸图像)
步骤 2:在第二个 DropDownList 中显示所选类别的产品
在完成 Categories
的 DropDownList 后,我们的下一步是显示所选类别的产品下拉菜单。 为此,请将另一个 DropDownList 添加到名为 ProductsByCategory
的页面。 与 Categories
DropDownList 一样,为ProductsByCategory
DropDownList 创建一个名为ProductsByCategoryDataSource
的新的 ObjectDataSource。
图 6:为 ProductsByCategory
DropDownList 添加新数据源(单击以查看全尺寸图像)
图 7:创建名为 ProductsByCategoryDataSource
的新 ObjectDataSource (单击可查看全尺寸图像)
ProductsByCategory
由于 DropDownList 只需要显示属于所选类别的产品,请让 ObjectDataSource 从GetProductsByCategoryID(categoryID)
对象调用ProductsBLL
该方法。
图 8:选择使用 ProductsBLL
类(单击以查看全尺寸图像)
图 9:将 ObjectDataSource 配置为使用 GetProductsByCategoryID(categoryID)
方法(单击以查看全尺寸图像)
在向导的最后一步中,我们需要指定参数的值 categoryID
。 将此参数分配给 DropDownList 中的 Categories
选定项。
图 10:从 categoryID
DropDownList 拉取Categories
参数值(单击以查看全尺寸图像)
配置好 ObjectDataSource 后,剩下要做的就是指定哪些数据源字段用于 DropDownList 项的显示和值。 显示字段 ProductName
并使用 ProductID
字段作为值。
图 11:指定用于 DropDownList 的 ListItem
的 Text
和 Value
属性的数据源字段(点击查看全尺寸图像)
配置 ObjectDataSource 和 ProductsByCategory
DropDownList 后,页面将显示两个 DropDownList:第一个将列出所有类别,而第二个将列出属于所选类别的产品。 当用户从第一个 DropDownList 中选择新类别时,将触发回发事件,第二个 DropDownList 将重新绑定,显示属于新选择类别的产品。 通过浏览器查看时,图 12 和 13 展示了MasterDetailsDetails.aspx
的实际效果。
图 12:首次访问页面时,“饮料类别”处于选中状态(单击以查看全尺寸图像)
图 13:选择其他类别显示新类别的产品(单击以查看全尺寸图像)
当前更改productsByCategory
DropDownList 时,不会导致回发。 但是,在添加 DetailsView 以显示所选产品的详细信息(步骤 3)后,我们需要进行回发。 因此,请选中 DropDownList 智能标记中的 productsByCategory
“启用 AutoPostBack”复选框。
图 14:为 productsByCategory
DropDownList 启用 AutoPostBack 功能(单击以查看全尺寸图像)
步骤 3:使用 DetailsView 显示所选产品的详细信息
最后一步是在 DetailsView 中显示所选产品的详细信息。 为此,请将 DetailsView 添加到页面中,将其 ID
属性设置为 ProductDetails
,并为其创建一个新的 ObjectDataSource。 将此 ObjectDataSource 配置为从 ProductsBLL
类的 GetProductByProductID(productID)
方法中提取数据,使用 ProductsByCategory
DropDownList 的选定值作为 productID
参数的值。
图 15:选择使用 ProductsBLL
类(单击以查看全尺寸图像)
图 16:将 ObjectDataSource 配置为使用 GetProductByProductID(productID)
方法(单击以查看全尺寸图像)
图 17:从 productID
DropDownList 拉取ProductsByCategory
参数值(单击以查看全尺寸图像)
可以选择在 DetailsView 中显示任何可用字段。 我已选择删除 ProductID
字段 SupplierID
和 CategoryID
字段,并重新排序并设置其余字段的格式。 此外,我清除了 DetailsView 的 Height
和 Width
属性,允许 DetailsView 扩展到最佳显示其数据所需的宽度,而不是将其限制为指定大小。 完整标记如下所示:
<asp:DetailsView ID="ProductDetails" runat="server"
AutoGenerateRows="False" DataKeyNames="ProductID"
DataSourceID="ObjectDataSource1" EnableViewState="False">
<Fields>
<asp:BoundField DataField="ProductName"
HeaderText="Product" SortExpression="ProductName" />
<asp:BoundField DataField="CategoryName"
HeaderText="Category" ReadOnly="True"
SortExpression="CategoryName" />
<asp:BoundField DataField="SupplierName"
HeaderText="Supplier" ReadOnly="True"
SortExpression="SupplierName" />
<asp:BoundField DataField="QuantityPerUnit"
HeaderText="Qty/Unit" SortExpression="QuantityPerUnit" />
<asp:BoundField DataField="UnitPrice"
DataFormatString="{0:c}" HeaderText="Price"
HtmlEncode="False" SortExpression="UnitPrice" />
<asp:BoundField DataField="UnitsInStock"
HeaderText="UnitsInStock" SortExpression="Units In Stock" />
<asp:BoundField DataField="UnitsOnOrder"
HeaderText="UnitsOnOrder" SortExpression="Units On Order" />
<asp:BoundField DataField="ReorderLevel"
HeaderText="ReorderLevel" SortExpression="Reorder Level" />
<asp:CheckBoxField DataField="Discontinued"
HeaderText="Discontinued" SortExpression="Discontinued" />
</Fields>
</asp:DetailsView>
花点时间在浏览器中试用 MasterDetailsDetails.aspx
页面。 一目了然,似乎一切都按预期工作,但有一个微妙的问题。 选择新类别时, ProductsByCategory
DropDownList 会更新为包含所选类别的这些产品,但 ProductDetails
DetailsView 继续显示以前的产品信息。 选择所选类别中的不同产品时,DetailsView会更新。 此外,如果测试得足够充分,你会发现,如果你不断选择新类别(如先从Categories
DropDownList中选择饮料,然后选择调味品,然后选择糖果),每隔一个类别的选择都会导致ProductDetails
DetailsView刷新。
为了帮助具体化这个问题,让我们看看一个特定示例。 首次访问页面时,会选择“饮料”类别,并在 DropDownList 中 ProductsByCategory
加载相关产品。 Chai 是所选产品,其详细信息显示在 DetailsView 中 ProductDetails
,如图 18 所示。
图 18:所选产品的详细信息显示在 DetailsView 中(单击以查看全尺寸图像)
如果将类别选择从饮料更改为调味品,则会发生回发,并相应地更新 ProductsByCategory
DropDownList,但 DetailsView 仍然显示 Chai 的详细信息。
图 19:仍显示以前选择的产品的详细信息(单击可查看全尺寸图像)
从列表中选择新产品会按预期刷新 DetailsView。 如果在更改产品后选取新类别,则 DetailsView 将再次不刷新。 但是,如果不是选择新产品而是选择新类别,DetailsView 将刷新。 世界上发生了什么?
问题是页面生命周期中的计时问题。 每当请求页面时,它会按照一系列步骤进行渲染。 在以下步骤之一中,ObjectDataSource 控件检查其 SelectParameters
值是否已更改。 如果是这样,绑定到 ObjectDataSource 的数据 Web 控件知道它需要刷新其显示。 例如,选择新类别时, ProductsByCategoryDataSource
ObjectDataSource 会检测到其参数值已更改, ProductsByCategory
DropDownList 会重新绑定自身,以获取所选类别的产品。
在这种情况下出现的问题在于,ObjectDataSources 检查更改参数的页面生命周期中的点在重新绑定关联的数据 Web 控件 之前 发生。 因此,选择新类别时, ProductsByCategoryDataSource
ObjectDataSource 会检测其参数值的变化。 但是,DetailsView 使用的 ProductDetails
ObjectDataSource 不会注意到任何此类更改,因为 ProductsByCategory
DropDownList 尚未反弹。 在生命周期的后期,ProductsByCategory
DropDownList 重新绑定到 ObjectDataSource,提取新选择类别的产品。
ProductsByCategory
虽然 DropDownList 的值已更改,但 ProductDetails
DetailsView 的 ObjectDataSource 已完成其参数值检查;因此,DetailsView 会显示其以前的结果。 图 20 中描述了此交互。
ProductDetails DetailsView 的 ObjectDataSource 检查是否存在更改时,ProductsByCategory DropDownList 的值会发生变化。
图 20: ProductsByCategory
在 DetailsView 的 ObjectDataSource 检查更改后 ProductDetails
,DropDownList 值发生更改(单击以查看全尺寸图像)
为了补救这一问题,我们需要在绑定好ProductDetails
DropDownList 之后,显式重新绑定ProductsByCategory
DetailsView。 我们可以通过在 DropDownList 的ProductDetails
事件触发时调用 DetailsView 的DataBind()
方法来实现此目的。 将以下事件处理程序代码添加到 MasterDetailsDetails.aspx
页面的代码隐藏类(请参阅“以编程方式设置 ObjectDataSource 的参数值”,了解如何添加事件处理程序):
protected void ProductsByCategory_DataBound(object sender, EventArgs e)
{
ProductDetails.DataBind();
}
在对ProductDetails
DetailsView的DataBind()
方法添加显式调用后,本教程按预期工作。 图 21 重点介绍了这一更改如何纠正我们先前的问题。
图 21:当 ProductDetails
DropDownList 的 ProductsByCategory
事件触发时,DetailsView 的DataBound
会被显式刷新(单击以查看全尺寸图像)
概要
DropDownList 用作主/详细信息报表的理想用户界面元素,其中主记录与详细信息记录之间存在一对多关系。 在前面的教程中,我们了解了如何使用单个 DropDownList 筛选所选类别显示的产品。 在本教程中,我们将产品的 GridView 替换为 DropDownList,并使用 DetailsView 显示所选产品的详细信息。 本教程中讨论的概念可以轻松扩展到涉及多个一对多关系的数据模型,例如客户、订单和订单项。 一般情况下,您始终可以为一对多关系中的每个“一个”实体添加一个下拉列表(DropDownList)。
快乐编程!
关于作者
斯科特·米切尔,七本 ASP/ASP.NET 书籍的作者和 4GuysFromRolla.com 的创始人,自1998年以来一直在与Microsoft Web 技术合作。 斯科特担任独立顾问、教练和作家。 他的最新书是 《Sams Teach Yourself ASP.NET 2.0 in 24 Hours》。 可以通过 mitchell@4GuysFromRolla.com 联系到他。
特别致谢
本教程系列由许多有用的审阅者审阅。 本教程的主要审阅者是希尔顿·吉森诺。 有兴趣查看即将发布的 MSDN 文章? 如果是这样,请给我写信。mitchell@4GuysFromRolla.com