使用 GridView 进行多级嵌套主/详细数据显示






4.36/5 (43投票s)
2006年7月18日
6分钟阅读

534798

8511
GridView 如何用于显示具有多级主/详细关系的数据。
引言
本文介绍了如何嵌套 GridView
控件来显示多级层次化数据的 Master/Detail 关系。在本文中,我实现了三级,通过维护父 GridView
控件的编辑索引信息,可以很容易地实现 n 级。
本文提供了一种简单、易于理解且清晰的解决方案,用于显示具有多级主/详细关系的数据。本文还展示了如何通过使用模板字段来有效地使用 GridView
,以及如何访问和绑定模板字段内的控件。更重要的是,您将认识到 GridView
编辑模式的强大功能,并学习如何使用它来实现除编辑以外的其他功能,从而摆脱其默认用途!!
背景
我曾经在一个公司的 HRM 系统上工作,其中我需要显示员工日志事件的详细信息。这是一个三级主/详细关系。在第一级,我需要显示公司所有员工(在一个 ParentGridView
中)。点击任何一个员工时,在第二级,我需要显示该员工在一个月内的出勤日期(在一个 ChildGridView
中),然后在第三级,当点击某个特定日期时,需要显示当天的相应日志事件(登录或登出)(在一个 GrandChildGridView
中)。我通过处理 GridView
的 OnRowEditing
事件来实现这种多级主/详细关系,以获取父 GridView
的新编辑索引并将其保存在 Session 中供子 GridView
使用。
使用代码
在此特定演示版本中,我使用了 Northwind 的 Pubs 数据库。Publishers、Titles 和 Roysched 表具有三级主/详细层次关系:每个出版商出版了多个标题,每个标题有多个版税计划。有一个父 GridView
名为 ParentGridView
,显示 Publishers 表;一个子 GridView
名为 ChildGridView
,显示 Titles 表的相应记录;还有一个孙子 GridView
名为 GrandChildGridView
,显示特定标题的相应版税计划。所有父 GridView
都需要进行模板化,以便在编辑模式下显示子 GridView
。ParentGridView
的 SelectCommand
表明它将具有三个模板字段:pub_id
、pub_name
和 city
,但还将有一个第四个模板字段,用于显示子 GridView
。
SelectCommand="SELECT [pub_id], pub_name], [city] FROM [publishers]";
为了清晰起见,我已移除格式代码,并单独解释了所有三个 GridView
的实现。我将通过写出带星号的子 GridView
名称来展示子 GridView
应嵌套的位置,以提高可读性和理解性。
父 GridView
父 GridView
已为 SelectCommand
中选择的所有列进行了模板化。正如您在代码中看到的,还有一个名为“View
”的模板字段,这是唯一神奇之处!在其 IetmTemplate
中,有一个按钮,其 Text
为“+”,CommandName
为“Edit”。此加号按钮被点击时,将 GridView
置于编辑模式,并触发已处理的 OnRowEditing
事件。在编辑模式下显示的内容在 EditItemTemplate
中实现。如代码所示,它包含一个 CommandName
为 'Cancel'、Text
为“-" 的按钮(通过运行 Cancel
命令即可折叠 GridView
,这将编辑索引重置为 -1,ParentGridView
退出编辑模式;显然,在编辑模式下显示的 ChildGridView
将不再可见,从而产生可折叠的概念)。紧挨着 Cancel "-" 按钮的是一个 ChildGridView
。
<asp:GridView ID="ParentGridView" runat="server"
DataSourceID="ParentSqlDataSource"
AutoGenerateColumns="False"
DataKeyNames="pub_id"
OnRowEditing= "ParentGridView_OnRowEditing"
<Columns>
<asp:TemplateField HeaderText="PublisherID">
<ItemTemplate>
<asp:Label id="pubid_lbl" Runat="Server"
Text='<%# Eval("pub_id") %>'/>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Name">
<ItemTemplate>
<asp:Label id="name_lbl" Runat="Server"
Text='<%# Eval("pub_name") %>'/>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="City">
<ItemTemplate>
<asp:Label id="city_lbl" Runat="Server"
Text='<%# Eval("city") %>'/>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="View">
<ItemTemplate>
<asp:Button ID="ViewChild_Button"
runat="server" Text="+" CommandName="Edit" />
</ItemTemplate>
<EditItemTemplate>
<asp:Button ID="CancelChild_Button"
runat="server" Text="-" CommandName="Cancel" />
*ChildGridView…will come here
</EditItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
OnRowEditing
的事件处理代码很容易理解,但重要的是要知道 OnRowEditing
事件没有任何编辑代码。相反,它将 ParentGridView
的编辑索引设置为当前行,并将其绑定。它将索引和 pub_id
保存在 Session 中,因为稍后我们将两者都用到:pub_id
用于获取与其对应的 Titles 元组,而 ParentGridViewIndex
需要用于查找包含已选择行的 ChildGridView
的行。
protected void ParentGridView_OnRowEditing(object sender,
GridViewEditEventArgs e)
{
int parent_index = e.NewEditIndex;
//to set the edit index of the Parent
//grid with that of the current row
ParentGridView.EditIndex = parent_index;
ParentGridView.DataBind();
//find the pubid_lbl containing pub_id in that
//particular row by using findcontrol method
GridViewRow row = ParentGridView.Rows[parent_index];
Label pub_id_lbl = (Label)row.FindControl("pubid_lbl");
//save pub_id and edit_index in session for childgridview's use
Session["PubID"] = Convert.ToInt32(pub_id_lbl.Text);
Session["ParentGridViewIndex"] = parent_index;
}
子 GridView
让我们看一下 ChildGridView
的 DataSource
的 SelectCommand
。其 WHERE
子句需要 pub_id
,我们已将其存储在 Session[“Pub_ID”]
中。
<SelectCommand="SELECT [title_id],[title], [type],[price]
FROM [titles] WHERE ([pub_id] = ?) ORDER BY [price]">
<SelectParameters>
<asp:SessionParameter Name="PubID" SessionField="PubID" Type="Int32" />
</SelectParameters>
GrandChildView
的模板化方式与 ParentGridView
相同。它也有一个名为“View”的模板字段,用于显示其自己的子网格(GrandChildGridView
),按钮和命令位置相同。它还实现了 OnRowEditing
,这与其父级略有不同。在这里,子网格视图无法直接访问,因为它嵌套在父级中。要将其编辑索引设置为新的编辑索引,我们首先需要从 ParentGridView
的特定行中找到它。包含 ChildGridView
的 ParentGridView
的行是使用 Session 变量中的 ParentGridViewIndex
找到的。
int parent_index =(int)Session["ParentGridViewIndex"];
GridViewRow parent_row = ParentGridView.Rows[parent_index];
我们现在拥有包含 ChildGridView
的 ParentGirdView
的行。现在,我们只需使用 FindControl
方法来查找 ChildGridView
。
GridView ChildGridiView =
(GridView)Parent_row.FindControl("ChildGridView");
所选标题的 title_id
存储在 Session 中,这将由其自己的子网格,即 GrandChildGridView
使用。事件的完整代码如下:
protected void ChildGridView_OnRowEditing(object sender,
GridViewEditEventArgs e)
{
//set the edit index of the child
//gridview with that of the current row
int parent_index =(int)Session["ParentGridViewIndex"];
GridViewRow parent_row = ParentGridView.Rows[parent_index];
GridView ChildGridiView =
(GridView)parent_row.FindControl("ChildGridView");
int child_index = e.NewEditIndex;
ChildGridiView.EditIndex = child_index;
ChildGridiView.DataBind();
//find the titleid_lbl in that particular
//row by using findcontrol method
GridViewRow child_row = ChildGridiView.Rows[child_index];
Label titleid_lbl = (Label)child_row.FindControl("titleid_lbl");
// save the title_id in session for grandchildgridview's use
Session["TitleID"] = titleid_lbl.Text;
}
这是 ChildGridView
的 aspx 源代码。名为 View
的模板字段显示 GrandChildGridView
,并带有一个星号,该星号将被替换到此处。
<asp:GridView ID="ChildGridView"
runat="server" AllowPaging="true" PageSize="4"
AutoGenerateColumns="false"
DataSourceID="ChildSqlDataSource"
OnRowEditing= "ChildGridView_OnRowEditing" >
<Columns>
<asp:TemplateField HeaderText="TitleID">
<ItemTemplate>
<asp:Label id="titleid_lbl" Runat="Server"
Text='<%# Eval("title_id") %>'/>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Title">
<ItemTemplate>
<asp:Label id="title_lbl" Runat="Server"
Text='<%# Eval("title") %>'/>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Type">
<ItemTemplate>
<asp:Label id="type_lbl" Runat="Server"
Text='<%# Eval("type") %>'/>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Price">
<ItemTemplate>
<asp:Label id="price_lbl" Runat="Server"
Text='<%# Eval("price") %>'/>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="View">
<ItemTemplate>
<asp:Button ID="ViewGrandChild_Button"
runat="server" Text="+" CommandName="Edit"/>
</ItemTemplate>
<EditItemTemplate>
<asp:Button ID="CancelGrandChild_Button"
runat="server" Text="-" CommandName="Cancel" />
*GrandChildGridView will come here
</EditItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
孙子 GridView
GrandChildDataSource
的 SelectCommand
的 WHERE
子句需要来自其直接父表的值 title_id
,该值是在选择特定标题时存储在 Session 中的。现在它被用于在 GrandChildGridView
中显示 roysched 表中针对它的所有行。
<SelectCommand="select [lorange],[hirange],roysched.royalty
from [roysched], [titles] where
titles.title_id=roysched.title_id
and (roysched.title_id=?)">
<SelectParameters>
<asp:SessionParameter Name="title_id"
SessionField="TitleID" Type="String"/>
</SelectParameters>
由于不需要与 GrandChildGridView
进行进一步的子关系,因此无需使用模板字段。否则,我们将不得不使用模板字段来实现。
<asp:GridView ID="GrandChildGridView" runat="server"
AllowPaging="true" PageSize="4"
DataSourceID="GrandChildSqlDataSource">
</asp:GridView>
关注点
重要的是要知道,我使用了 GridView
的编辑模式来显示子 GridView
。编辑模式下不进行任何编辑,但同时,如果需要,也可以进行编辑。OnRowEditing
事件不包含任何编辑代码,而是执行所需的操作;编辑索引和存储在 Session 中的主键值将在子 GridView
的 OnRowEditing
事件中使用,用于搜索父 GridView
的特定行并从中找到子 GridView
,之后将用于子 GridView
的 SelectCommand
以获取针对该特定父 ID 的所有子行。Cancel
命令已经执行了我们想要的操作;它取消编辑,父 GridView
退出编辑模式,隐藏其子 GridView
。
如果层级中的任何父 GridView
被折叠,其所有子项将自动折叠。
更重要的是,通过跟踪子 GridView
的所有父 GridView
的主键和编辑索引,可以实现任意数量的层级。每个子 GridView
都知道自己的编辑索引,但它需要知道其所有父 GridView
的编辑索引,这可以通过将每个父 GridView
的编辑索引存储在 Session 中并在需要时使用它们来查找包含子 GridView
的特定行来实现。最后,始终将 SqlDataSource
控件放在 GridView
旁边,否则子 GridView
将无法访问它。