65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.36/5 (43投票s)

2006年7月18日

6分钟阅读

viewsIcon

534798

downloadIcon

8511

GridView 如何用于显示具有多级主/详细关系的数据。

Sample Image

引言

本文介绍了如何嵌套 GridView 控件来显示多级层次化数据的 Master/Detail 关系。在本文中,我实现了三级,通过维护父 GridView 控件的编辑索引信息,可以很容易地实现 n 级。

本文提供了一种简单、易于理解且清晰的解决方案,用于显示具有多级主/详细关系的数据。本文还展示了如何通过使用模板字段来有效地使用 GridView,以及如何访问和绑定模板字段内的控件。更重要的是,您将认识到 GridView 编辑模式的强大功能,并学习如何使用它来实现除编辑以外的其他功能,从而摆脱其默认用途!!

背景

我曾经在一个公司的 HRM 系统上工作,其中我需要显示员工日志事件的详细信息。这是一个三级主/详细关系。在第一级,我需要显示公司所有员工(在一个 ParentGridView 中)。点击任何一个员工时,在第二级,我需要显示该员工在一个月内的出勤日期(在一个 ChildGridView 中),然后在第三级,当点击某个特定日期时,需要显示当天的相应日志事件(登录或登出)(在一个 GrandChildGridView 中)。我通过处理 GridViewOnRowEditing 事件来实现这种多级主/详细关系,以获取父 GridView 的新编辑索引并将其保存在 Session 中供子 GridView 使用。

使用代码

在此特定演示版本中,我使用了 Northwind 的 Pubs 数据库。Publishers、Titles 和 Roysched 表具有三级主/详细层次关系:每个出版商出版了多个标题,每个标题有多个版税计划。有一个父 GridView 名为 ParentGridView,显示 Publishers 表;一个子 GridView 名为 ChildGridView,显示 Titles 表的相应记录;还有一个孙子 GridView 名为 GrandChildGridView,显示特定标题的相应版税计划。所有父 GridView 都需要进行模板化,以便在编辑模式下显示子 GridViewParentGridViewSelectCommand 表明它将具有三个模板字段:pub_idpub_namecity,但还将有一个第四个模板字段,用于显示子 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

让我们看一下 ChildGridViewDataSourceSelectCommand。其 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 的特定行中找到它。包含 ChildGridViewParentGridView 的行是使用 Session 变量中的 ParentGridViewIndex 找到的。

int parent_index =(int)Session["ParentGridViewIndex"];
GridViewRow parent_row = ParentGridView.Rows[parent_index];

我们现在拥有包含 ChildGridViewParentGirdView 的行。现在,我们只需使用 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;
}

这是 ChildGridViewaspx 源代码。名为 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

GrandChildDataSourceSelectCommandWHERE 子句需要来自其直接父表的值 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 中的主键值将在子 GridViewOnRowEditing 事件中使用,用于搜索父 GridView 的特定行并从中找到子 GridView,之后将用于子 GridViewSelectCommand 以获取针对该特定父 ID 的所有子行。Cancel 命令已经执行了我们想要的操作;它取消编辑,父 GridView 退出编辑模式,隐藏其子 GridView

如果层级中的任何父 GridView 被折叠,其所有子项将自动折叠。

更重要的是,通过跟踪子 GridView 的所有父 GridView 的主键和编辑索引,可以实现任意数量的层级。每个子 GridView 都知道自己的编辑索引,但它需要知道其所有父 GridView 的编辑索引,这可以通过将每个父 GridView 的编辑索引存储在 Session 中并在需要时使用它们来查找包含子 GridView 的特定行来实现。最后,始终将 SqlDataSource 控件放在 GridView 旁边,否则子 GridView 将无法访问它。

© . All rights reserved.