Sunday, March 7, 2010

GridView in GridView

There are many RAD controls on the market that can do very fancy presentation stuff. But even with the plain old ASP.NET controls, you can still exploit their interesting potentials. Embeding a GridView inside another GridView is not really difficult, but those examples I searched from Internet couldn't do me the favor. So here comes my solution.

The outer grid represents a session table of a user and each session contains a few transaction logs associated with the outer sessionId. The .aspx is straightforward: a TemplateField column is used to embed the inner GridView.
<asp:GridView ID="gvSession" CssClass="datagrid" runat="server" 
  AllowPaging="True" PageSize="6" AutoGenerateColumns="False" 
  DataSourceID="ldsSession" OnRowDataBound="gvSession_RowDataBound" Width="680px">
  <Columns>
    <asp:BoundField DataField="StaffName" HeaderText="Staff Name" />
    <asp:BoundField DataField="SessionId" HeaderText="Session Id" />
    <asp:BoundField DataField="SessionStartTime" HeaderText="Session Start Time" />
    <asp:BoundField DataField="SessionDuration" HeaderText="Session Duration" />
    <asp:TemplateField HeaderText="Tx Logs">
      <ItemTemplate >
        <asp:GridView ID="gvTxLog" runat="server" DataSourceID="ldsTxLog" AutoGenerateColumns="False" Width="100%">
          <Columns>
            <asp:BoundField DataField="TxType" HeaderText="Tx Type" ItemStyle-Width="70%" />
            <asp:BoundField DataField="Duration" HeaderText="Duration" />
          </Columns>
        </asp:GridView>
        <asp:ObjectDataSource ID="ldsTxLog" runat="server" SelectMethod="GetTxLogs" TypeName="DAO.ReportDAO">
          <SelectParameters>
            <asp:ControlParameter ControlID="txtStartDate" Name="start" PropertyName="Text" Type="String" />
            <asp:ControlParameter ControlID="txtEndDate" Name="end" PropertyName="Text" Type="String" />
            <asp:ControlParameter ControlID="ddlStaffs" Name="staffId" PropertyName="SelectedValue" Type="Int32" />
            <asp:Parameter Name="sessionId" Type="Int32" />
          </SelectParameters>
        </asp:ObjectDataSource>
      </ItemTemplate>
    </asp:TemplateField>
  </Columns>
</asp:GridView>
<asp:ObjectDataSource ID="ldsSession" runat="server" SelectMethod="GetSessions" TypeName="DAO.ReportDAO">
  <SelectParameters>
    <asp:ControlParameter ControlID="txtStartDate" Name="start" PropertyName="Text" />
    <asp:ControlParameter ControlID="txtEndDate" Name="end" PropertyName="Text" />
    <asp:ControlParameter ControlID="ddlStaffs" Name="staffId" PropertyName="SelectedValue" Type="Int32" />
  </SelectParameters>
</asp:ObjectDataSource>

To make it simple, the outer grid just uses declarative data binding. While the inner grid, although appears to be the same, has to be initialized manually because, like I mentioned earlier, it relies on the sessionId from the outer grid after outer's data binding. So here is the trick:
protected void gvSession_RowDataBound(object sender, GridViewRowEventArgs e)
{
    if (e.Row.RowType == DataControlRowType .DataRow)
    {
        ObjectDataSource s = (ObjectDataSource )e.Row.FindControl("ldsTxLog" );
        s.SelectParameters["sessionId" ].DefaultValue = e.Row.Cells[1].Text;
        GridView subView = (GridView )e.Row.FindControl("gvTxLog" );
        subView.DataBind();
    }
}

This resolution is not perfect though, because you have to use 2 separate data sources, one for each GridView. And that brings the N+1 problem. But there is a better solution coming...

No comments: