In most cases, I always put the page setup code and data loading code in the page load event. I suppose the page "init" event is ok, but keep in mind that both events FIRE EACH and EVERY post back.
As a result, for the last 200+ web pages I built? You actually can't build a working page with your data binding running each and every time.
In other words, for a listbox, a combo box (dropdown list), and just about anything else?
That page will not work if you re-bind the data sources each and every time. So, say you bind data to a drop-down list. The user selects some value, and then you hit submit, or any other page post back occurring?
Well, since page init, and page load runs BEFORE the button or any other event code stub you have?
Then the re-binding of the simple dropdown list will occur first, and THEN the on-changed event, or button event code stub will run. But, since you re-bind the dropdown list "again", then you can't grab the value that the user selected, since you re-running your data binding code each and every time.
So, to build a working web page?
Such data load, data setup etc. that needs to ONLY run on first page load?
Then such code needs to be placed inside of a code block that checks for if this is the REAL first page load, or just one of many post-backs that typically will occur.
So, your design pattern is thus this:
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
LoadGrid();
}
void LoadGrid()
{
string strSQL =
@"SELECT * FROM Fighters
ORDER BY FighterName";
GridView1.DataSource = General.MyRst(strSQL);
GridView1.DataBind();
}
So, note how the code to load up the data, such as Grids, combo box, or whatever?
It is placed inside of the if (!IsPostBack) code stub. If you don't follow this design pattern, then you really can't build a working page, since as I pointed out, any button clicks, any other control with an auto-post back?
Well, the page load (and page inti) runs first, and THEN the event code stub for that button or whatever other event runs AFTER. Thus, if you re-bind your data each time, then I fail to see in the past how you managed to get any page working - since they will 99% of the time will not!!!
Ok, now that we cleared up how to build a working page (you MUST follow and use the above design patter, and you setup and data loading code MUST use that !IsPostBack block)?
The that! IsPostBack next issue is dealing with your expand code.
You may well be able to get away with a post-back, but using an update panel. It depends on what you attempting to do.
With the re-binding not occurring each time, then the GridView (or ListView), or your DevExpress control will in theory persist expanded row content.
For this GridView example?
This effect:

In above, I do allow (have) a post-back, but since I'm not re-binding the Grid on that row button click, then I'm able to get the PK row id, load up a hidden div, and then pop the popup client side.
The code for above is this:
Markup:
<asp:GridView ID="GridView1" runat="server"
AutoGenerateColumns="False" DataKeyNames="ID"
CssClass="table table-hover" Width="800px"
>
<Columns>
<asp:BoundField DataField="FirstName" HeaderText="FirstName" />
<asp:BoundField DataField="LastName" HeaderText="LastName" />
<asp:BoundField DataField="City" HeaderText="City" />
<asp:BoundField DataField="HotelName" HeaderText="HotelName" />
<asp:BoundField DataField="Description" HeaderText="Descripiton" />
<asp:TemplateField HeaderText="Edit">
<ItemTemplate>
<button id="cmdEdit" runat="server"
onserverclick="cmdEdit_ServerClick"
>
<span aria-hidden="true" class="fa fa-pencil-square-o fa-lg"></span>
</button>
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
And code behind to load, and the row button click:
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
LoadData();
}
void LoadData()
{
string strSQL =
@"SELECT * FROM tblHotels
WHERE Description is not null
ORDER BY HotelName";
GridView1.DataSource = General.MyRst(strSQL);
GridView1.DataBind();
}
protected void cmdEdit_ServerClick(object sender, EventArgs e)
{
HtmlButton btn = (HtmlButton)sender;
GridViewRow gRow = (GridViewRow)btn.NamingContainer;
int PK = (int)GridView1.DataKeys[gRow.RowIndex]["ID"];
Session["PK"] = PK; // we use this for our save edits code
string strSQL =
@"SELECT * FROM tblHotels
WHERE ID = @id";
SqlCommand cmdSQL = new SqlCommand(strSQL);
cmdSQL.Parameters.Add("@id",SqlDbType.Int).Value = PK;
DataRow rHotel = General.MyRstP(cmdSQL).Rows[0];
General.FLoader(HotelInfo, rHotel); // load row into controls in div HotelINfo
// call the client side pop div
// (we pass hotel name for the pop title)
string sHotelName = rHotel["HotelName"].ToString();
string sRowIndex = gRow.RowIndex.ToString();
string sJava = $@"popinfo('{sHotelName}',{sRowIndex})";
ScriptManager.RegisterStartupScript(Page, Page.GetType(), "mypop",sJava, true);
}
I posted that sample code, since it not a lot of code. And it's server side. I do on the row button click event "eventually" call a client-side jQuery.UI popup routine called popinfo().
That client-side code is this:
<script>
function popinfo(strHotelName, sRowIndex) {
var MyDialog = $("#HotelInfo")
strHotelName = strHotelName + " (Row index = " + sRowIndex + ")"
MyDialog.dialog({
title: strHotelName,
modal: true,
width: "940px",
dialogClass: "dialogWithDropShadow",
})
}
</script>
So, I would first fix your page load event, (well, in your case the page init event - not sure why you using that event??????).
Keep in mind, that for every new page, in code behind, you have the page load event created for you. Hence this:
public partial class WebForm1 : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
}
}
Since the above is automatic created for each page, then it makes sense to use that pre-made event for loading up page controls and data (of course with that required all important !IsPostBack code block).
So, between fixing your data loading code, then you may well be able to use an update panel, and not require client-side JavaScript code to eliminate the page post-backs.
Ok, with above, what about 2 "nested" tables/grids?
So, with the following example, we will display some rows of data (some hotels).
And when we click on the row to "expand", the nested table below will show the people booked in the hotel.
NOTE VERY close, if we expand a row, and pull the data? Then if we collapse, and then re-expand the nested row?
We do NOT have to re-pull the data, since GridView and most asp.net controls have automatic viewstate. Hence, once we load the main table/grid?
And then if we hit the "+" button to expand a given row to show child records?
Then if we collapse the row, and then re-expand? We actually don't have to hit the database and re-pull the data.
Now, keep in mind that while I could try and "nest" 2 GridViews?
Well, the nested child table will have to be "stuffed" into one of the columns of that hotel (parent) row. And GridViews don't really have great support for "spanning" multiple columns.
Thus, when markup and display requirements become complex? Then hands down I suggest using a ListView, since it far more flexible in terms of having markup (such as a GridView/table nested inside).
So, for the parent rows, I used a ListView, and for the child (nested) rows, I used a GridView. As noted, I suppose for the child GridView, I could have also used a ListView, but GridViews are less markup then a ListView.
If I required a 3rd level of nesting then I would of course use ListViews for all levels of display.
Ok, so our markup will have the "main" table, and nested inside a child table.
Hence this markup:
<asp:ListView ID="ListView1" runat="server" DataKeyNames="ID" >
<ItemTemplate>
<tr style="">
<td><asp:Button ID="cmdView" runat="server" Text="+" OnClick="cmdView_Click" /></td>
<td><asp:Label ID="HotelNameLabel" runat="server" Text='<%# Eval("HotelName") %>' /></td>
<td><asp:Label ID="CityLabel" runat="server" Text='<%# Eval("City") %>' /></td>
<td><asp:Label ID="ProvinceLabel" runat="server" Text='<%# Eval("Province") %>' /></td>
<td><asp:Label ID="DescriptionLabel" runat="server" Text='<%# Eval("Description") %>' /></td>
</tr>
<%-- below above row, we nest our GridView--%>
<tr>
<td colspan="5">
<asp:GridView ID="GridView2" runat="server" AutoGenerateColumns="False"
DataKeyNames="ID" CssClass="table table-hover borderhide"
style="display:none;margin-left:20px" >
<Columns>
<asp:BoundField DataField="Firstname" HeaderText="Firstname" SortExpression="Firstname" />
<asp:BoundField DataField="LastName" HeaderText="LastName" SortExpression="LastName" />
<asp:BoundField DataField="City" HeaderText="City" SortExpression="City" />
<asp:TemplateField HeaderText="Province">
<ItemTemplate>
<asp:TextBox ID="txtProvince" runat="server"
Text= '<%# Eval("Province") %>' ></asp:TextBox>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Btn Row">
<ItemTemplate>
<asp:Button ID="btnRowClick" runat="server"
Text="row click" CssClass="btn"
OnClick="btnRowClick_Click" />
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
</td>
</tr>
</ItemTemplate>
<LayoutTemplate>
<table id="itemPlaceholderContainer" runat="server" Class = "table table-hover" >
<tr runat="server" style="">
<th runat="server">View</th>
<th runat="server">HotelName</th>
<th runat="server">City</th>
<th runat="server">Province</th>
<th runat="server">Description</th>
</tr>
<tr id="itemPlaceholder" runat="server">
</tr>
</table>
</LayoutTemplate>
</asp:ListView>
Now, above is about the max amount of markup I would include in a post here, but now the use of "span" for the child nested table (a GridView in this example). This allows the "one" column of the ListView (parent) to span all columns.
And the effect of above is thus this:

And as I pointed out, when user expands to display the child records?
It checks if that child table been already loaded, and if yes, then we only display the child rows. If no, then we load the child table, and then display.
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
LoadGrid();
}
public void LoadGrid()
{
string strSQL = @"SELECT * FROM tblHotels
WHERE ID in (select hotel_Id from People)
ORDER BY HotelName";
ListView1.DataSource = MyRst(strSQL);
ListView1.DataBind();
}
protected void cmdView_Click(object sender, EventArgs e)
{
// button expand + display child rows click
Button cmd = (Button)sender;
// get list view row of button click
ListViewDataItem gVR = (ListViewDataItem)cmd.NamingContainer;
// get GridView for this row - our sub group
GridView gChild = (GridView)gVR.FindControl("GridView2"); // pluck gv control for this row
if (gChild.Style["display"] == "normal")
{
// if grid is already display, then hide it, and exit
gChild.Style["display"] = "none";
cmd.Text = "+"; // change button back to "+"
return;
}
gChild.Style["display"] = "normal";
// get data PK of row clicked
int HotelPK = (int)ListView1.DataKeys[gVR.DataItemIndex]["ID"];
// only re-load if never loaded
if (gChild.Rows.Count == 0)
{
SqlCommand cmdSQL =
new SqlCommand("SELECT * from People where hotel_id = @HPK");
cmdSQL.Parameters.Add("@HPK", SqlDbType.Int).Value = HotelPK;
gChild.DataSource = General.MyRstP(cmdSQL);
gChild.DataBind();
cmd.Text = "-"; // change button text to "-"
}
}
Note how the viewstate persists our style. In fact, we use the persisted "display" setting of the style to "persist" if the child row is expanded or not (saves having to use code and extra variables to determine if the given parent row is expanded to display child row).
And once again, note how we have that all important !IsPostBack code block. As a result, I'm free to click on buttons etc., and the main parent table (a ListView in this example) thus is not re-loaded each time for expanding a child table.
And, I point out that the amount of code written here is very little, since we went with server-side code.
Of course, once such data is loaded, then we could with ease have the expand, and contract for display of child rows 100% occur client side. If you wish, I can show how this could be done with the above example (it would only be a few lines of JavaScript code).