Reputation: 1528
I have a .ascx
user control on an .aspx
page. When the user clicks a button, information is gathered and stored in the database. Then a Gridview
is databinded and in the Gridview
is a dropdownlist
. This dropdownlist
's Items
are created dynamically based on the users previous input, now in the database. This is all good and the Gridview
is displayed with the dropdownlist
with the dynamically created Items
.
The problem is on the postback for this dropdownlist
. I obviously have to recreate the Gridview
with the dynamically created Items
in the dropdownlist
. This does not work. The postback happens and calls the Page_Init
and then the Page_InitComplete
. This has the databind on the Gridview
which calls the SectionGV_OnRowDataBound
method. The dropdownlists
are recreated. But the SectionDD_OnSelectedIndexChanged
method is never hit and then the dropdownlist
just reverts back to its original value. I can not change the dropdownlist's
selected value.
.ASCX
<asp:GridView ID="MyGV" runat="server" AutoGenerateColumns="False" DataSourceID="MyDS" Width="100%" OnRowDataBound="MyGV_OnRowDataBound">
<Columns>
<asp:TemplateField >
<ItemTemplate>
<asp:DropDownList runat="server" ID="SectionDD" AppendDataBoundItems="True" AutoPostBack="True" OnSelectedIndexChanged="SectionDD_OnSelectedIndexChanged" >
</asp:DropDownList>
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
Code Behind of .ASCX
protected void Page_Init(object sender,EventArgs e)
{
this.Page.InitComplete += Page_InitComplete;
}
private void Page_InitComplete(object sender, EventArgs e)
{
MyGV.DataBind();
}
protected void SectionDD_OnSelectedIndexChanged(object sender, EventArgs e)
{
}
protected void MyGV_OnRowDataBound(object sender, GridViewRowEventArgs e)
{
using (EntitiesModel dbContext = new EntitiesModel())
{
// get array[][] array from database
if (e.Row.RowType == DataControlRowType.DataRow)
{
DropDownList sectionDd = (DropDownList)e.Row.FindControl("SectionDD");
sectionDd.Items.Clear();
if (array.Length == 3)
{
if(array[0][2].ToDecimal() > 0) sectionDd.Items.Add(new ListItem(array[0][0], array[0][1]));
if(array[1][2].ToDecimal() > 0) sectionDd.Items.Add(new ListItem(array[1][0], array[1][1]));
if(array[2][2].ToDecimal() > 0) sectionDd.Items.Add(new ListItem(array[2][0], array[2][1]));
}
else if (array.Length == 2)
{
if (array[0][2].ToDecimal() > 0) sectionDd.Items.Add(new ListItem(array[0][0], array[0][1]));
if (array[1][2].ToDecimal() > 0) sectionDd.Items.Add(new ListItem(array[1][0], array[1][1]));
}
else if (array.Length == 1)
{
if (array[0][2].ToDecimal() > 0) sectionDd.Items.Add(new ListItem(array[0][0], array[0][1]));
}
}
sectionDd.DataBind();
}
}
}
}
private void SaveToDB()
{
//save information to database
NOTE
This is one of many ways I have tried to solve this issue. The reason I have saved the information in the database is just a temporary fix. I just want to solve the issue outlined above and then I will add a ViewState
solution.
Upvotes: 1
Views: 896
Reputation: 48989
First up, no, you do not have to re-create or load the grid again, or the dropdown list in the post back.
The user can type into the grid - change values, select drop downs. At that point you can loop all the grid rows and get/grab the dropdown values selected.
You CAN of course fire/trigger a event for the dropdown, but it not at all clear if you really need that event here.
If your grid is getting messed up on post-backs?, then it means you not limiting the load up in the FIRST page load - after that, it should not matter.
Say we have this grid markup:
<asp:GridView ID="MyGrid" runat="server" CssClass="table table-hover"
DataKeyNames="ID" AutoGenerateColumns="false" OnRowDataBound="MyGrid_RowDataBound" >
<Columns>
<asp:BoundField DataField="FirstName" HeaderText="FirstName" />
<asp:BoundField DataField="LastName" HeaderText="Last Name" />
<asp:BoundField DataField="HotelName" HeaderText="Hotel Name" />
<asp:TemplateField HeaderText="City">
<ItemTemplate>
<asp:DropDownList ID="DropDownList1" runat="server"
DataTextField="City"
DataValueField="City"
>
</asp:DropDownList>
</ItemTemplate>
</asp:TemplateField>
<asp:BoundField DataField="Province" HeaderText="Province" />
</Columns>
</asp:GridView>
And of course non templated fields (such as the first few boundField - they appear in the .Cells collection. but for templated columns, you use findcontrol.
However, in EVERY and NEAR ALL web pages, as a general rule, you ONLY load + mess + create the grid ONE time, and you ONLY do this on the first page load - END OF STORY! You don't follow this rule, then you are in a world of hurt big time.
Ok, so lets load up the grid. Since we have a dropdown, then we have to fill that out on item data bound.
So, our code will look like this:
public DataTable rstCity = new DataTable();
protected void Page_Load(object sender, EventArgs e)
{
if (IsPostBack == false)
{
LoadGrid();
}
}
public void LoadGrid()
{
using (SqlCommand cmdSQL = new SqlCommand("SELECT City from City Order by City",
new SqlConnection(Properties.Settings.Default.TEST4)))
{
// locd city for drop down list
cmdSQL.Connection.Open();
rstCity.Load(cmdSQL.ExecuteReader());
// now load grid
cmdSQL.CommandText = "SELECT * from tblHotels ORDER BY HotelName";
DataTable rst = new DataTable();
rst.Load(cmdSQL.ExecuteReader());
MyGrid.DataSource = rst;
MyGrid.DataBind();
}
}
Note how I did scope the city table to the class level (no reason to load it over and over for each row - and after the first page load, it will go out of scope - we don't care - it will live during the first page load and the data binding.
Ok, now, lets do the item data bind for the grid.
We have this:
protected void MyGrid_RowDataBound(object sender, GridViewRowEventArgs e)
{
if (e.Row.RowType == DataControlRowType.DataRow)
{
DropDownList MyDrop = (DropDownList)e.Row.FindControl("DropDownList1");
MyDrop.DataSource = rstCity;
MyDrop.DataBind();
// get City from current data row source - set the drop to data row City
MyDrop.SelectedValue = ((DataRowView)e.Row.DataItem)["City"].ToString();
}
}
Ok, that's it. (note how the "dataitem" exists ONLY during data binding - a handy tip, since then you have use of the FULL data row - including PK values, and columns that you don't even include in the grid markup. But, once data binding is over, then DataItem can NOT be used - it only EVER exists during the bind process. But this information is VERY valuable, since you have full use of the Actual data SOURCE you used to bind. In above, I needed the City from that Row to set the drop list.
Ok, the output now looks like this:
Ok, at this point since we ONLY bind on first page load. The view state of that grid is 100% automatic handled by asp.net for you at this point in time.
You can drop other buttons on this form - post backs should NOT matter. The BIG lesson here is that gridview does persist (at least it will if we don't re-load it each time on post-back - you should NOT have to re-load).
Ok, next issue:
In most cases, I don't see the need for the dropdown list event in the grid?
So, you can as a general rule select and change any row combo. Once done, then you can get values of each row by looping the data grid rows - including that of dropdown selected.
However, Lets wire up the dropdown list event anyway.
tip of the day: Since we can't select the dropdown and use the property sheet?
Then in markup do this:
You can in the markup type in OnSelectedIndexChanged=
WHEN you hit the "=" sign, note VERY close how intel-sense pops up a event create option:
So, click on CreateNewEvent - it "seems" like nothing occurs, but in fact if you flip over to code behind, you have a nice event stub created.
So lets put our code in that event - grab the row - show the value just slected.
protected void DropDownList1_SelectedIndexChanged(object sender, EventArgs e)
{
// user changed the combo box
DropDownList MyDrop = (DropDownList)sender;
GridViewRow gRow = (GridViewRow)MyDrop.Parent.Parent;
Response.Write("<h2>Row index is = " + gRow.RowIndex.ToString() + "</h2>");
Response.Write("<h2>Hotel Name is = " + gRow.Cells[2].Text + "</h2>");
Response.Write("<h2>City from Drop selected = " + MyDrop.SelectedValue + "</h2>");
}
Of course we set auto post back = true in the drop markup also - right?
And note how we pick up the "sender", and then use .Parent.Parent to get the grid row that we are operating on. The first parent is some cell or some such.
I actually built recursive function to get that value, but for here we just used .parent.Parent (often with a extra markup, then you need to go op one more .parent).
Anyway, now say I select the last drop down in above, and change it.
I get this output:
In summary:
Only EVER load the grid - first page load - (you check IsPostBack).
You can have all kinds of additional post backs - the grid should survive and just be fine - no need to re-load at all. (GridView has built in viewstate).
Now, after I have fun, change the drop down on many rows?
I can loop the gridview - and any changes to that grid will persist.
In fact, if you make the columns text boxes (in templated fields), then you can tab around and edit almost like Excel, and again the values will persist for you, and they survive post backs. (and then you can send the whole grid of changes back to the database in one update command - I can show how to do this, but this post already has lots of good stuff anyway.
Now, having said the above, having done the above?
Well now that we have this working, then one can go back to building a custom user control - but if done right, it also should behave correctly, and should also survive post-backs.
Upvotes: 1