Click here to Skip to main content
Click here to Skip to main content

Expandable Rows in GridView

By , 4 Dec 2012
 
expandablegridviewrows/contracted.PNG

expandablegridviewrows/expanded.PNG

Introduction

This article shows how to add functionality to the ASP.NET GridView control to allow the display of master/detail records with expanding rows. It probably isn't suited to scenarios where large number of records will be returned at a time.

This example is based on work I did for an equine hospital showing appointments where the cost of treatments had exceeded the limit agreed with the client. Expanding the details lists the transactions which make up the cost.

Background

I was asked for this functionality by a client and found a few articles online presenting different solutions, but I found all of them to be overly-complex. However, some of those other solutions, such as this one, are more suitable for large record sets as my solution renders all of the details for each record when the page loads, rather than only when they are requested.

Using the Code

This solution doesn't require any special classes or custom controls, just a bit of JavaScript. It simply adds a new row after each existing row in the main (master) GridView in the RowDataBound event and creates an expanded details (detail) GridView inside it.

The code for the ASP page looks like this:

<asp:GridView ID="grdOverLimitList" runat="server" AutoGenerateColumns="False" 
  DataSourceID="SQLOverLimitList" CssClass="gridview" AllowSorting="True" 
  AlternatingRowStyle-CssClass="alternating" 
  SortedAscendingHeaderStyle-CssClass="sortedasc" 
  SortedDescendingHeaderStyle-CssClass="sorteddesc" 
  FooterStyle-CssClass="footer" >
  <AlternatingRowStyle CssClass="alternating"></AlternatingRowStyle>
  <Columns>
    <asp:TemplateField>
      <ItemTemplate>
        <%--This is a placeholder for the details GridView--%>
      </ItemTemplate> 
    </asp:TemplateField>
    <asp:BoundField DataField="PetID" HeaderText="Pet ID" SortExpression="Pet ID" />
    <asp:BoundField DataField="AppointmentID" HeaderText="Appointment ID" 
		SortExpression="AppointmentID" />
    <asp:BoundField DataField="Horse Name" HeaderText="Horse Name" 
		SortExpression="Horse Name" />
    <asp:BoundField DataField="Client Surname" HeaderText="Client Surname" 
		SortExpression="Client Surname" />
    <asp:BoundField DataField="Senior Clinician" HeaderText="Senior Clinician" 
		SortExpression="Senior Clinician" />
    <asp:BoundField DataField="Warning Limit" 
	DataFormatString="{0:£#,##0.00;(£#,##0.00);''}" 
	HeaderText="Warning Limit" SortExpression="Warning Limit" />
    <asp:BoundField DataField="Cost" 
	DataFormatString="{0:£#,##0.00;(£#,##0.00);''}" 
	HeaderText="Cost" SortExpression="Cost" />
  </Columns>
  <EmptyDataTemplate>
    No data to display
  </EmptyDataTemplate>
  <FooterStyle CssClass="footer"></FooterStyle>
  <SortedAscendingHeaderStyle CssClass="sortedasc"></SortedAscendingHeaderStyle>
  <SortedDescendingHeaderStyle CssClass="sorteddesc"></SortedDescendingHeaderStyle>
</asp:GridView>

<asp:ToolkitScriptManager ID="ToolkitScriptManager1" runat="server">
</asp:ToolkitScriptManager>

<asp:SqlDataSource ID="SQLOverLimitList" runat="server" 
  ConnectionString="<%$ ConnectionStrings:DatabaseConnectionString %>" 
  SelectCommand="sp_Equine_OverLimitList" SelectCommandType="StoredProcedure">
</asp:SqlDataSource> 

<asp:SqlDataSource ID="SQLOverLimitDetail" runat="server"
  ConnectionString="<%$ ConnectionStrings:DatabaseConnectionString %>"
  SelectCommand="sp_Equine_OverLimitDetail" SelectCommandType="StoredProcedure">
</asp:SqlDataSource> 

That just sets up a simple GridView, which is the master table. Note that it includes an empty ItemTemplate column where the 'Show/Hide' button will go.

Next is the code behind for the RowDataBound event of this GridView:

Protected Sub grdOverLimitList_RowDataBound(ByVal sender As Object, _
	ByVal e As System.Web.UI.WebControls.GridViewRowEventArgs) _
	Handles grdOverLimitList.RowDataBound

  If e.Row.RowType = DataControlRowType.DataRow Then

    'Configure the datasource for the expanded details
    Dim appID As String = Convert.ToString(DataBinder.Eval_
	(e.Row.DataItem, "AppointmentID")) 'Get the unique ID for this record
    SQLOverLimitDetail.SelectParameters.Clear() 'Remove all select parameters 
				'from the datasource for the expanded details
    SQLOverLimitDetail.SelectParameters.Add("AppID", appID) 'Add the select parameter 
	'to the datasource for the expanded details using the unique ID of the record

    'Create a new GridView for displaying the expanded details
    Dim gv As New GridView
    gv.DataSource = SQLOverLimitDetail
    gv.ID = "grdSQLOverLimitDetail" & e.Row.RowIndex 'Since a gridview is 
	'being created for each row they each need a unique ID, so append the row index
    gv.AutoGenerateColumns = False
    gv.CssClass = "subgridview"
    AddHandler gv.RowDataBound, AddressOf grdOverLimitDetails_RowDataBound 'Add a 
					'rowdatabound method for the new GridView

    'Add fields to the expanded details GridView
    Dim bf1 As New BoundField
    bf1.DataField = "Date Added"
    bf1.DataFormatString = "{0:d}"
    bf1.HeaderText = "Date Added"
    gv.Columns.Add(bf1)

    Dim bf2 As New BoundField
    bf2.DataField = "Treatment"
    bf2.HeaderText = "Treatment"
    gv.Columns.Add(bf2)

    Dim bf3 As New BoundField
    bf3.DataField = "Total Cost"
    bf3.HeaderText = "Total Cost"
    bf3.DataFormatString = "{0:c}"
    gv.Columns.Add(bf3)

    'Create the show/hide button which will be displayed on each row of the main GridView
    Dim btn As Web.UI.WebControls.Image = New Web.UI.WebControls.Image
    btn.ID = "btnDetail"
    btn.ImageUrl = "~/Images/detail.gif"
    btn.Attributes.Add("onclick", "javascript: gvrowtoggle_
	(" & e.Row.RowIndex + (e.Row.RowIndex + 2) & ")") 'Adds the javascript 
	'function to the show/hide button, passing the row to be toggled as a parameter

    'Add the expanded details row after each record in the main GridView
    Dim tbl As Table = DirectCast(e.Row.Parent, Table)
    Dim tr As New GridViewRow(e.Row.RowIndex + 1, -1, _
	DataControlRowType.EmptyDataRow, DataControlRowState.Normal)
    tr.CssClass = "hidden"
    Dim tc As New TableCell()
    tc.ColumnSpan = grdOverLimitList.Columns.Count
    tc.BorderStyle = BorderStyle.None
    tc.BackColor = Drawing.Color.AliceBlue
    tc.Controls.Add(gv) 'Add the expanded details GridView to the newly-created cell
    tr.Cells.Add(tc) 'Add the newly-created cell to the newly-created row
    tbl.Rows.Add(tr) ' Add the newly-ccreated row to the main GridView
    e.Row.Cells(0).Controls.Add(btn) 'Add the show/hide button to the main GridView row

    gv.DataBind() 'Bind the expanded details GridView to its datasource

  End If

End Sub 

This is heavily annotated so hopefully it explains itself but it contains these basic steps:

  1. Clear the select parameters from the datasource for the details view and add a new one using the unique ID for the current record.
  2. Create a new GridView instance and specify its datasource, ID, CSS class and RowDataBound method. This will be our details GridView.
  3. Add bound fields to the new GridView.
  4. Create the show/hide button in the master record row, adding the 'OnClick' attribute to point to the JavaScript coming up below. Note that it is actually an image, not a button, to remove any question of postback.
  5. Create a new, empty row after the current row in the master GridView, then populate it with the new GridView, and add the show/hide button to the master record row.
  6. Bind the new GridView to its datasource.

You will notice that in creating the show/hide button, this code passes the index of the relevant details row (I know the formula looks a bit crazy, but trust me!) as the variable rows in the JavaScript below. This script is at the top of my ASP.NET page in the usual way.

<script type="text/javascript">

  function gvrowtoggle(row) {
    try {
      row_num = row; //row to be hidden
      ctl_row = row - 1; //row where show/hide button was clicked
      rows = document.getElementById('<%= grdOverLimitList.ClientID %>').rows; 
      rowElement = rows[ctl_row]; //elements in row where show/hide button was clicked
      img = rowElement.cells[0].firstChild; //the show/hide button

      if (rows[row_num].className !== 'hidden') //if the row is not currently hidden 
						//(default)...
      {
        rows[row_num].className = 'hidden'; //hide the row
        img.src = '../Images/detail.gif'; //change the image for the show/hide button
      } 
      else {
        rows[row_num].className = ''; //set the css class of the row to default 
				//(to make it visible)
        img.src = '../Images/close.gif'; //change the image for the show/hide button
      } 
    } 
    catch (ex) {alert(ex) }
  }
</script> 

Some pretty simple code, when the show/hide button is clicked this just checks the current class of the details row, setting it to 'hidden' if it's visible and setting it to the default class if it's hidden.

Finally, we just need to create a CSS class called 'hidden' which will hide the detail row until it's requested. I've also included my CSS for styling the GridViews, in case you're interested:

.hidden
{
    display: none;
} 
/*GridView---------------------------------------------*/

.gridview
{
width: 100%;
border: 1px solid black;
background: white;
text-align: center;

}
.gridview th
{
text-align: center;
background: #013b82;
color: white;
}
.gridview .pager
{
text-align: center;
background: #013b82;
color: White;
font-weight: bold;
border: 1px solid #013b82;
}
.gridview .pager a
{
color: #666;
}

.gridview a
{
text-decoration: none;
color: White;
}
.gridview a:hover
{
color: Silver;
}
.gridview .sortedasc
{
background-color: #336699;
}
.gridview .sortedasc a
{
padding-right: 15px;
background-image :url(../images/up_arrow.png);
background-repeat: no-repeat;
background-position: right center;
}
.gridview .sortedasc a:hover
{
color:White;
}
.gridview .sorteddesc
{
background-color: #336699;
}
.gridview .sorteddesc a
{
padding-right: 15px;
background-image :url(../images/down_arrow.png);
background-repeat: no-repeat;
background-position: right center;
}
.gridview .sorteddesc a:hover
{
color:White;
}
.gridview .alternating
{
background: #d8d8d8;
}

/*-----------------------------------------------------*/

/*SubGridView------------------------------------------*/
.subgridview
{
width:80%;
border: none;
text-align:left;
margin: 0px 0px 5px 25px;
background: whitesmoke;
}
.subgridview th
{
background: silver;
color: Black;
}
/*-----------------------------------------------------*/ 

Points of Interest

There isn't anything particularly difficult about this, it just took some thinking through. The one thing that did drive me a bit mad was that, whereas in Firefox/Chrome the default style for a GridViewRow is 'table-row', in Internet Explorer it's 'block'. That's why in the JavaScript when the details row is being made visible, the className is left blank rather, causing the browser to use whatever is default.

History

  • 22nd February, 2011: Initial post

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

phil2415
Other
United Kingdom United Kingdom
Member
IT Manager for a large higher education institution in the North West of England with responsibility and experience in everything from networking, systems administration, database administration, helpdesk support, web development, etc. since 2003.
 
I tend to develop web applications in ASP.NET and VB.NET, with a bit of JavaScript and C# thrown in for good measure at times.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionI converted the project to VB.NETmemberMember 99274443 May '13 - 5:07 
https://github.com/gbam/ExpandablePanel-VB/blob/master/README.md[^]
 
If anyone is looking for the project in VB, look above.
QuestionRowDataBound for the Detail GridViewmemberEkjon7 Mar '13 - 12:13 
You did not show the RowDataBound event handler added for the detail gridview.
I'm having some problem and trying to figure out whats causing it.
I'll ask you again if needed.
Thanks.
BugPaging & Mastergridview viewstate="true" problemmemberKashif.Khan.in11 Feb '13 - 0:19 
Hi Phil,
 
The code that you have demonstrated has paging problem. Paging does not work if the viewstate of the mastergridview is enabled. The probable reason i googled is that the controls are being created after the page viewstate is loaded. Now if i disable the viewstate of the master gridview the paging works.
 
But i need viewstate enabled for master gridview because i have added one more template column in the gridview which contains one checkbox column. This checkbox column does not give appropriate values for even rows with this configuration. Please help me how to proceed with this.
 
Any help from any user would be appreciated a lot.
GeneralRe: Paging & Mastergridview viewstate="true" problemmemberKashif.Khan.in11 Feb '13 - 20:34 
I solved the problem to paging with mastergridview. How? answer is: Just disable childgridview viewstate by setting enableviewstateproperty to false
QuestionSub Gridview minimizing on postbackmemberJames Culpepper15 Jan '13 - 3:19 
Great code. Just one question. On post back the sub-grid minimizes. Is it possible to keep the grid expanded OR have the grid fully expanded on load? It can be fully displayed in the inital page load as well.
QuestionTroble in RowCommandmemberimchanchalsingh7 Jan '13 - 0:57 
please help us to find value in row command
QuestionRowCommand troublememberMember 848442421 Dec '12 - 2:53 
Nice article, but using your approach I'm having trouble with RowCommands. The buttons on the rows of the lower half of the grid, doesn't trigger GridView-RowCommand. I'm using a GridView with paging. I guess it has something to do with the GridView still believing it only has X rows when it actually has 2X rows.
 
Have yet to figure out a workaround for this...
GeneralMy vote of 5memberVarun Sareen10 Dec '12 - 18:19 
Excellent work. May help in my future project is the reason for voting it as 5
GeneralMy vote of 1memberraj@aia10 Dec '12 - 17:07 
Use javascript, use only gridview control with asp.net, it is possible try once
Suggestionpost source codegroupjmohanbe6 Dec '12 - 22:30 
hi,
nice code.
pls post the entire code. that will be helpful for us.
 
Thanks in advance.
GeneralMy vote of 5membermaris984 Dec '12 - 8:59 
Easy steps, very interesting, helpful
GeneralMy vote of 5memberJitendra20054 Dec '12 - 7:14 
It's good.
QuestionHow does Javascript function work?memberCorey Blair4 Dec '12 - 5:43 
In the code-behind you have
btn.Attributes.Add("onclick", "javascript: gvrowtoggle_
	(" & e.Row.RowIndex + (e.Row.RowIndex + 2) & ")") 'Adds the javascript 
	'function to the show/hide button, passing the row to be toggled as a parameter
But I don't understand how the following is supposed to work?
e.Row.RowIndex + (e.Row.RowIndex + 2)

AnswerRe: How does Javascript function work?memberphil24154 Dec '12 - 6:17 
It's passing the index of the row which should be expanded/contracted when the button is clicked to the Javascript function as a variable. I can't exactly remember why that's the formula, it's a long time since I wrote this, but I remember it took a long time to get it right, so go with it and, if it doesn't work, tweak it!
GeneralMy vote of 5memberdracopql4 Dec '12 - 3:21 
Excellent article, well written, lots of comments and a step by step structure easy to follow and put immediately in practice.

Thanks a lot.
GeneralMy vote of 5memberdracopql4 Dec '12 - 3:21 
Excellent article, well written, lots of comments and a step by step structure easy to follow and put immediately in practice.
 
Thanks a lot.
GeneralMy vote of 5memberSavalia Manoj M2 Dec '12 - 17:29 
Good work...!!
QuestionHow do we query for child Grid view?memberMember 964536830 Nov '12 - 4:35 
I tried this
foreach (GridViewRow parentrow in ParentGridView.Rows)
{
GridView gv = (GridView)parentrow.FindControl("ChildGridView");
}

QuestionHow to add insert functionality for the sub gridviewmemberveesbee114 Aug '12 - 2:56 
How to add insert functionality for the sub gridview
GeneralMy vote of 5memberTimDwg5 Aug '12 - 13:08 
Good article but it really needs a source code download to facilitate implementation or research scenarios.
GeneralMy vote of 4memberTejas_Vaishnav19 Jul '12 - 0:27 
Nice
SuggestionAdd your working source code for end usermemberTejas_Vaishnav19 Jul '12 - 0:27 
Add your working source code for end user to better understand what you want to demonstrate.
Thanks & Regards
Tejas Vaishnav
Find me on Facebook | Blog

GeneralRe: Add your working source code for end usermemberStrange_Pirate28 Sep '12 - 3:13 
Well he is a well wisher of developers. He only provides hints and tips not the whole source code. So that any other developer can learn something new and face some COMPLEXITY to learn new things. Laugh | :laugh: Laugh | :laugh:
 
વેલ તેણે વિકાસકર્તાઓ એક શુભેચ્છકે છે. તેમણે માત્ર સંકેતો અને સમગ્ર સ્રોત કોડ નથી ટીપ્સ પૂરી પાડે છે. જેથી કોઇ પણ અન્ય વિકાસકર્તા કંઈક નવું જાણવા અને કેટલાક નવા વસ્તુઓ શીખવા જટિલતા સામનો કરી શકે છે. : :: હસવા હસવા:
 
Well also posted in your language based on your location. So that you can unstd. well WTF | :WTF:
Strange_Pirate

GeneralMy vote of 1memberBhushan Prod12 Apr '12 - 3:13 
Does not provided all method definitions
QuestionAttach codememberDorababu74316 Mar '12 - 5:26 
Can you attach the code please

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web02 | 2.6.130516.1 | Last Updated 4 Dec 2012
Article Copyright 2011 by phil2415
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid