I'm dynamically adding BoundFields and TemplateFields to a GridView. This involves expensive (database-)operations, so I can't do it on each postback. Is there a possible way to persist the final state across postbacks?
I always run into errors on restoring the GridView object from the session or cache. I already did this for simple dynamic controls, but it doesn't seem to work for the GridView as soon as it involves dynamically added columns.
If you create the grid dynamically, you have to recreate it every time unless you create it in the PreInit event (which is unlikely). The control's tree viewstate is loaded before the OnLoad event so it can't recreate dynamically created controls, however,
the form will persist the ViewState of the grid.
I wouldn't store the gridview in session or cache, I woulr rather store the datasource there (whatever it is) and rebind the grid.
I finally figured out an acceptable solution.
The TemplateFields themselves are automatically restored from viewstate, but the templates themselves aren't (the field's Item/Header/FooterTemplate properties are null).
Note: I can't explain it, but I have to call gv.Columns.Clear() before adding dynamic columns, otherwise it doesn't even remember the TemplateFields...
So my solution is to cache the gridview's columns collection and call the following method in Page_Load (or earlier):
protected void restore_columns() {
DataControlFieldCollection columns = (DataControlFieldCollection)Cache["cols"];
gvSchluessel.Columns.Clear(); // gvSchluessel is the GridView
foreach (DataControlField field in columns) {
gvSchluessel.Columns.Add(field);
if (field.GetType() == typeof(TemplateField)) {
TemplateField tf = (TemplateField)field;
int i = gvSchluessel.Columns.IndexOf(tf);
if (tf.HeaderTemplate != null) {
tf.HeaderTemplate.InstantiateIn(gvSchluessel.HeaderRow.Cells[i]);
}
foreach (GridViewRow row in gvSchluessel.Rows)
{
if (tf.ItemTemplate != null) {
tf.ItemTemplate.InstantiateIn(row.Cells[i]);
}
}
if (tf.FooterTemplate != null) {
tf.FooterTemplate.InstantiateIn(gvSchluessel.FooterRow.Cells[i]);
}
}
}
}
Note that I call the template's InstantiateIn() method manually instead of binding the gridview, so I don't need to cache any data source for it.
If your grid also contains many predefined fields (bound fields etc.), you could further improve performance by only caching and replacing the TemplateFields.
I was thinking how stupid it was to have to databind on every postback simply because i'm creating template fields dynamically for the gridview.
After all, if you have a gridview which can work with multiple datasources, you want to generate the gridview at runtime based on the results from the query to the relevant datasource.
In addition, i'm doing some complex design with n nested gridviews, and the databind was fraking with updates to nested gridviews - because when updating the nested gridviews, the databind effectively re-creates the entire parent and child gridviews making
adding rows to child gridviews pretty much impossible. i.e. it gets re-generated from the databind before it even has a chance to add a row to itself.
Anyway this is great!
I think it is better in the session as well -
private DataControlFieldCollection _columnCache
{
get { return (DataControlFieldCollection)Session["_columnCache"]; }
set { Session["_columnCache"] = value; }
}
Also, note the inclusion of the EditItemTemplate to the foreach row, this is required if editing
private void RepairGridViewTemplateColumns()
{
DataControlFieldCollection columns = _columnCache; //getting columns back from session where we stored them in GenerateGridView
gvData.Columns.Clear();
foreach (DataControlField field in columns)
{
gvData.Columns.Add(field);
if (field.GetType() == typeof(TemplateField))
{
TemplateField tf = (TemplateField)field;
int i = gvData.Columns.IndexOf(tf);
if (tf.HeaderTemplate != null)
tf.HeaderTemplate.InstantiateIn(gvData.HeaderRow.Cells[i]);
foreach (GridViewRow row in gvData.Rows)
{
if (gvData.EditIndex == row.RowIndex && tf.EditItemTemplate != null)
tf.EditItemTemplate.InstantiateIn(row.Cells[i]);
else if (tf.ItemTemplate != null)
tf.ItemTemplate.InstantiateIn(row.Cells[i]);
}
if (tf.FooterTemplate != null)
tf.FooterTemplate.InstantiateIn(gvData.FooterRow.Cells[i]);
}
}
}
Doctor Sid
Member
156 Points
46 Posts
Persisting dynamic GridView across postbacks
Mar 05, 2008 08:39 PM|LINK
I'm dynamically adding BoundFields and TemplateFields to a GridView. This involves expensive (database-)operations, so I can't do it on each postback. Is there a possible way to persist the final state across postbacks?
I always run into errors on restoring the GridView object from the session or cache. I already did this for simple dynamic controls, but it doesn't seem to work for the GridView as soon as it involves dynamically added columns.
Ricardojb
Member
439 Points
144 Posts
Re: Persisting dynamic GridView across postbacks
Mar 06, 2008 02:25 AM|LINK
If you create the grid dynamically, you have to recreate it every time unless you create it in the PreInit event (which is unlikely). The control's tree viewstate is loaded before the OnLoad event so it can't recreate dynamically created controls, however, the form will persist the ViewState of the grid.
I wouldn't store the gridview in session or cache, I woulr rather store the datasource there (whatever it is) and rebind the grid.
Doctor Sid
Member
156 Points
46 Posts
Re: Persisting dynamic GridView across postbacks
Mar 06, 2008 12:28 PM|LINK
I finally figured out an acceptable solution.
The TemplateFields themselves are automatically restored from viewstate, but the templates themselves aren't (the field's Item/Header/FooterTemplate properties are null).
Note: I can't explain it, but I have to call gv.Columns.Clear() before adding dynamic columns, otherwise it doesn't even remember the TemplateFields...
So my solution is to cache the gridview's columns collection and call the following method in Page_Load (or earlier):
protected void restore_columns() { DataControlFieldCollection columns = (DataControlFieldCollection)Cache["cols"]; gvSchluessel.Columns.Clear(); // gvSchluessel is the GridView foreach (DataControlField field in columns) { gvSchluessel.Columns.Add(field); if (field.GetType() == typeof(TemplateField)) { TemplateField tf = (TemplateField)field; int i = gvSchluessel.Columns.IndexOf(tf); if (tf.HeaderTemplate != null) { tf.HeaderTemplate.InstantiateIn(gvSchluessel.HeaderRow.Cells[i]); } foreach (GridViewRow row in gvSchluessel.Rows) { if (tf.ItemTemplate != null) { tf.ItemTemplate.InstantiateIn(row.Cells[i]); } } if (tf.FooterTemplate != null) { tf.FooterTemplate.InstantiateIn(gvSchluessel.FooterRow.Cells[i]); } } } }Note that I call the template's InstantiateIn() method manually instead of binding the gridview, so I don't need to cache any data source for it.
If your grid also contains many predefined fields (bound fields etc.), you could further improve performance by only caching and replacing the TemplateFields.
I hope this is of use for some of you.
ViewState Cache postback GridView Caching lost dynamic templatefield
Filip123
Member
46 Points
27 Posts
Re: Persisting dynamic GridView across postbacks
Apr 24, 2008 11:55 AM|LINK
I try this Solution and is still not working. Where do you make the cache of columns. Could you post the entire solution.
Thanks
</div></div>ChandanaMand...
Member
2 Points
1 Post
Re: Persisting dynamic GridView across postbacks
Jan 20, 2009 03:18 AM|LINK
Hi DoctorSid,
Thanks a lot, This has really helped me
Best Regards,
Chandana
shrukge
Member
8 Points
7 Posts
Re: Persisting dynamic GridView across postbacks
Oct 21, 2009 01:05 AM|LINK
Hi, I find this solution did help but I can't put this in page init because all the data binded will be gone if I put it under that!
Is there someway to put this after load view state so that I dont need to call this on every postback?
DaManJ
Member
2 Points
1 Post
Re: Persisting dynamic GridView across postbacks
Feb 26, 2011 01:26 PM|LINK
WOW!!! this solutions is awesome!
I was thinking how stupid it was to have to databind on every postback simply because i'm creating template fields dynamically for the gridview.
After all, if you have a gridview which can work with multiple datasources, you want to generate the gridview at runtime based on the results from the query to the relevant datasource.
In addition, i'm doing some complex design with n nested gridviews, and the databind was fraking with updates to nested gridviews - because when updating the nested gridviews, the databind effectively re-creates the entire parent and child gridviews making adding rows to child gridviews pretty much impossible. i.e. it gets re-generated from the databind before it even has a chance to add a row to itself.
Anyway this is great!
I think it is better in the session as well -
private DataControlFieldCollection _columnCache { get { return (DataControlFieldCollection)Session["_columnCache"]; } set { Session["_columnCache"] = value; } }Also, note the inclusion of the EditItemTemplate to the foreach row, this is required if editing
private void RepairGridViewTemplateColumns() { DataControlFieldCollection columns = _columnCache; //getting columns back from session where we stored them in GenerateGridView gvData.Columns.Clear(); foreach (DataControlField field in columns) { gvData.Columns.Add(field); if (field.GetType() == typeof(TemplateField)) { TemplateField tf = (TemplateField)field; int i = gvData.Columns.IndexOf(tf); if (tf.HeaderTemplate != null) tf.HeaderTemplate.InstantiateIn(gvData.HeaderRow.Cells[i]); foreach (GridViewRow row in gvData.Rows) { if (gvData.EditIndex == row.RowIndex && tf.EditItemTemplate != null) tf.EditItemTemplate.InstantiateIn(row.Cells[i]); else if (tf.ItemTemplate != null) tf.ItemTemplate.InstantiateIn(row.Cells[i]); } if (tf.FooterTemplate != null) tf.FooterTemplate.InstantiateIn(gvData.FooterRow.Cells[i]); } } }