ASP.NET GridView TemplateField controls lost after PostBack - asp.net

solving the problem with the GridView control and template field. I have defined the GridView like this:
<asp:GridView ID="gridView" runat="server" ShowFooter="True" onrowdatabound="onRowDataBound" AutoGenerateColumns="False" onrowcreated="onRowCreated" onrowcommand="onRowCommand" onselectedindexchanged="onSelectedIndexChanged">
<Columns>
<asp:CommandField SelectText="cmdSelectRow" ShowSelectButton="True" />
<asp:TemplateField AccessibleHeaderText="treeController" HeaderText="">
<ItemTemplate>
<asp:ImageButton ID="btnShow" runat="server" ImageUrl="~\\Images\\treePlus.png" CommandName="TreeShow" UseSubmitBehavior="False"/>
<asp:ImageButton ID="btnHide" runat="server" Visible="False" ImageUrl="~\\Images\\treeMinus.png" CommandName="TreeHide" UseSubmitBehavior="False" />
</ItemTemplate>
</asp:TemplateField>
<asp:BoundField DataField="treeLevel" HeaderText="Tree Level" />
<asp:BoundField DataField="parentTaskId" HeaderText="parent_task_id" />
<asp:BoundField DataField="taskId" HeaderText="task_id" />
<asp:BoundField DataField="groupId" HeaderText="group_id" />
<asp:BoundField DataField="hasTiming" HeaderText="" />
... much more BoundFiels ...</Columns>
I suppose you understood that using this gridView I implement the treeView ... those two ImageButtons are buttons to expland/collapse child items.
If I do NOTHING with the grid, works perfect, event after PostBacks. But, because there is a lot of columns, I have customization allowing to adjust the gridView layout defining the order of the columns and visibility. If I do anything with the GridView columns (re-order columns, or just remove the column and insert it at the same position), TemplateField buttons are lost on PostBack. Even if I do nothing with TemplateField column definition but reordering BoundFields columns, TemplateField columns are lost after PostBack.
It looks there is some problem with the ViewState (I do not know). The fact is, that:
1. I make the GridView customization on Page_Init method
2. I do Data Binding (because of some reasons) on Page_PreRender method, only if NOT PostBack
I saw several questions solving the issues with TemplateField items after postback, but I did not find the solution.
Does have enybody the idea, where should be the issue? Why it works, when nothing is done with the gridview structure (columns) and DOES NOT WORK when the same column is taken out and reinserted into the grid columns?
Thank you for any help or ideas.
To demonstrate the "flow" of the page, I am adding more details ...
The GridView is part of my custom control.
protected void Page_Init (object sender, EventArgs e)
{
/* customize grid control */
/* here I load the customization defined by user and change GridView.Columns */
layoutCustomize(gridControl, accountId);
}
As shown, I am changing the GridView structure (on first page load or postbacks)
protected override void OnDataBinding(EventArgs e)
{
/* declarations */
DbModel.TaskView [] tasks = null;
DataTable tasksTable = null;
/* call parent method */
base.OnDataBinding(e);
/* get data */
if ((tasks = TasksView.Data) != null)
{
/* build data table */
tasksTable = TsGridView.BuildDataTable(TasksTreeView, tasks, typeof(TaskView));
/* apply filter */
DataTable viewTable = Filter(tasksTable);
/* bound the data source to the gird */
TasksTreeView.DataSource = viewTable;
TasksTreeView.DataBind();
}
}
This is custom control's DataBind event, the main purpose is to bind data to the grid :-)
The event is triggered by call to DataBind in parent control's Page_PreRender method, like this:
protected void Page_PreRender(object sender, System.EventArgs e)
{
/* set active view */
if (IsPostBack == false)
SetView(tasksMultiView.ActiveViewIndex);
}
protected void SetView (int viewIndex)
{
/* declarations */
Control viewControl = null;
View selectedView = null;
ListItem selectedItem = null;
/* get control */
selectedView = tasksMultiView.Views[viewIndex];
selectedItem = View.Items[viewIndex];
/* get control */
if ((viewControl = selectedView.FindControl(selectedItem.Value)) != null)
/* bind data */
viewControl.DataBind();
}
Hopes this help.

Your Viewstate, is "lost" on postback because you have changed the structure of the GridView. The TemplateField buttons are still there but...If you add/remove columns after OnInit but before your GridView is databound you willnot have this problem. I think for your issue though you will need to rebind the data to the GridView after removing the columns to refresh the Viewstate.
OR, I found this possible solution too, looks like calling myGridView.Columns.Clear(); before adding/removing columns might do the trick for you.
http://forums.asp.net/p/1229438/2216336.aspx#2216336

Set EnableViewState to false on your .aspx page. I was having this issue and this resolved the issue for me.

Based on the code behind you added to your question, the issue might be that your binding data too late to the GridView to work correctly. It should be done in Page_Load. I think there is a red flag here too that you call TasksTreeView.DataBind() in OnDataBinding and also call DataBind again in the custom control itself, in SetView, based on an event in the ASP.NET Lifecycle.
Also why are you calling DataBind again in protected override void OnDataBinding. You called DataBind somewhere already to trigger OnDataBinding. Are you triggering the protected override void OnDataBinding in your Parent page ultimately via the custom control's SetView call to viewControl.DataBind()? If that is the case, that is some convoluted, unmaintainable code and you should restructure so your parent page and custom control are loosely coupled so you can reuse the custom control without a developer having to have knowledge of the custom control's internal workings.
If your CustomControl does not have a public DataBind method that works the way you want it to than create a new Public method that mimics the DataBind parameters of the GridView DataBind and then call the appropriate GridView with the passed in data argument.
Perhaps you could restructure your code and eliminate the SetView method all together. You shouldn't be calling the DataBind in the custom control itself. That should be up to the parent user of the custom control, when DataBind is called that is. Override DataBind in your custom control instead:
public override void DataBind()
{
//...some implementation here...
base.DataBind()
}

Related

Populate gridview based on databound dropdown list selectedvalue change

I have an asp.net application for membership management. One page needs to have a gridview which is populated based on a dropdown list of statuses. I initially thought about hard-coding with a Select Case, but then remembered that the dropdown list is databound and needs to be dynamic (because the admin-level users have another page to change the statuses). I'm still new at this, and my searches are not turning up anything.
Any links or examples would be helpful. Thanks.
I would suggest to use OnSelectedIndexChanged event of dropdownlist for your purpose with AutoPostBack property set to true, something like this
<asp:DropDownList runat="server" ID="ddlStatus" OnSelectedIndexChanged="ddlStatus_SelectedIndexChanged" AutoPostBack="True"></asp:DropDownList>
And on your code behind page you can bind your grid differently for different selected values in your event handler, something like this
protected void ddlStatus_SelectedIndexChanged(object sender, EventArgs e)
{
if (ddlStatus.SelectedItem.Value == "RequiredValue")
{
// bind grid in some way
}
else
{
// bind grid in some other way
}
}
This will work irrespective of your binding the dropdownlist options dynamically or having them hard coded.

How to avoid repetition of data in Gridview?

In a web application I am binding the data to a GridView. In the GridView some of data is repeating. I want to not display the data again and again.
For example Empid is displaying more than one time in the same column. I want to not display the empid again in that column.
You can implement the OnDataBinding event for the specific column you are using. I never use AutoGenerateColumns so having fine control of each cell is pretty simple to implement.
Eg:
// Create global in your .cs file
string _currentEmpID = string.Empty;
Define your column like:
<Columns>
<asp:TemplateField>
<ItemTemplate>
<asp:Literal ID="ltEmpID" runat="server"
OnDataBinding="ltEmpID_DataBinding" />
</ItemTemplate>
</asp:TemplateField>
<!-- Your other columns... -->
</Columns>
Then just implement your DataBinding event:
protected void ltEmpID_DataBinding(object sender, System.EventArgs e)
{
Literal lt = (Literal)(sender);
string empID = Eval("EmpID").ToString();
if (!empID.Equals(_currentEmpID))
{
lt.Text = empID;
_currentEmpID = empID;
}
else
{
lt.Text = string.Empty;
}
}
The RowDataBound forces you to search for controls and if changes are required in the future you have the possibility of breaking other things being modified within the event. Because of this, I prefer to use the control's DataBinding event whenever possible as it localizes functionality to only the control and gives you the flexability to swap out controls and functionality easily without the worry off affecting other things.
If you group your data by the columns you don't want to repeat before binding it to your datasource you can bind an event to RowDataBound and check if the current value equals the previous and then hide the cell.
Check this for an example.
Just add the property AutoGenerateColumns in the gridview and assign it the value of false.
AutoGenerateColumns="false"

ASP.NET Repeater not binding after ItemCommand

I have a repeater that is looping a user control, like this:
<asp:Repeater ID="repItems" runat="server" EnableViewState="false"
OnItemCommand="repItems_ItemCommand">
<ItemTemplate>
<dmg:confirmItem runat="server"
OnDataBinding="confirmitemItem_DataBinding"
Basket="<%# Container.DataItem %>" />
</ItemTemplate>
</asp:Repeater>
My code behind looks like this:
public void BindItems(List<ShopBasket> baskets)
{
_baskets = baskets;
repItems.DataSource = baskets;
repItems.DataBind();
}
My custom user control looks like this:
public ShopBasket Basket;
protected void Page_Load(object sender, EventArgs e)
{
imgItem.ImageUrl = ShopImagePath + Basket.ImageFilename;
...etc...
}
All works brilliantly the first time around, the basket items are bound to Basket objects and everything is great.
However, when I receive an ItemCommand from my repeater, and update the basket contents (Note: No adding or removing is done here, just updates the quantity) then I rebind to see the latest values, and BOOM! Null reference - no Basket object in the user control Page_Load. This is despite tracing through to see that the BindItems() method is called as usual, and the Baskets are there.
I presume this has something to do with the life cycle but it has me beat.
Any ideas?
Thanks
Duncan
It's a little dangerous to have a public field store an item being bound, especially when you have multiple items. A safer way to do it is to extract the Basket as the DataItem bound to the repeater, and do something with it that way in the repeater ItemCommand event. ItemDataBound would be even safer as you know the basket would be existing (since it's data bound), Page_Load is not a safe option here...
HTH.

Repeater databound loses data & event on postback - is there a best practice solution?

Currently struggling with a problem that I've encountered variations on in the past. At the moment a worthwhile solution escapes me, but it seems such an obvious issue that I can't help wondering whether or not there's a "best practice" approach I should adopt.
Without code, here's the issues in a nutshell:
page has databound control (a repeater) which isn't populated until user inputs data and clicks a button.
Repeater item template contains a button
User clicks button, page posts back. On load, the repeater is actually empty so event is never handled because the originating control no longer exists
Go back to the beginning of wretched cycle
I've confirmed that this is the problem because if you provide the repeater with some static data on page load, everything works fine. But of course that's no use because it has to be populated dynamically.
Is there a commonly approved way round this headache? I can store the data in session and re-use it on page load, but it seems terribly clumsy.
Cheers,
Matt
If the event is being fired by a button within a repeater then this would bubble up to the repeaters ItemCommand event. Using a buttons CommandName and CommandArgument parameters you can then identify which button was clicked and act accordingly. Below is some basic markup and code behind to demonstrate the approach:
HTML:
<asp:Repeater ID="rptTest" runat="server" onitemcommand="rptTest_ItemCommand"
onitemdatabound="rptTest_ItemDataBound">
<ItemTemplate>
<p>
<asp:Button ID="btnTest" runat="server" />
</p>
</ItemTemplate>
</asp:Repeater>
<asp:Button ID="btnLoad" runat="server" Text="Load" onclick="btnLoad_Click" />
Code behind events:
protected void rptTest_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
Button button = (Button)e.Item.FindControl("btnTest");
button.Text = string.Format("Button {0}", e.Item.DataItem.ToString());
button.CommandName = e.Item.ItemIndex.ToString();
}
protected void rptTest_ItemCommand(object source, RepeaterCommandEventArgs e)
{
Response.Write(string.Format("Postback from button {0}", e.CommandName));
}
protected void btnLoad_Click(object sender, EventArgs e)
{
List<int> list = new List<int>();
list.Add(1);
list.Add(2);
list.Add(3);
list.Add(4);
rptTest.DataSource = list;
rptTest.DataBind();
}
Hopefully i've understood the problem and this helps.
If any of your controls are created dynamically, then they have to be recreated during post back in order for the events etc to get hooked back up.
If this is the case, take a look at a control built by a guy named Denis Bauer. We use this with just some slight modifications and it's perfect.

Telerik RadGrid doesn't display on first Page_Load but does on postback

I have a page with a drop-down. Based on the selection in the drop-down, data gets loaded and populates a RadGrid. I am using a custom user control for the EditTemplate, so I can't use radGrid.DataBind(). Instead, I have to use radGrid.MasterTableView.Rebind() in association with a NeedDataSource event handler.
My problem is that when I load the page initially, I populate the drop-down and automatically select a value (first item in the list) which triggers the databinding on the RadGrid. I can step through the code in debug mode and see that the grid is being populated with data, but when the page displays, it doesn't get rendered. When I then manually choose an item from the drop-down, which triggers the same grid databinding code, it displays properly the second time.
How do I get it to display the grid the first time the page loads?
I have a very similar issue with nested Multipage with RadGrid in RadGrid
aspx:
<telerik:RadTabStrip><Tabs><!-- ... --></Tabs></telerik:RadTabStrip>
<telerik:RadMultiPage>
<telerik:RadPageView>
<!-- ChildRadGrid1 doesn't display on first time but does on postback -->
<telerik:RadGrid ID="ChildRadGrid1"><!-- ... --></telerik:RadGrid>
<telerik:RadPageView>
</telerik:RadMultiPage>
</NestedViewTemplate>
<!-- Columns... -->
</MasterTableView>
</telerik:RadGrid>
In my case, only Rebind() in ItemCommand of parent grid helps me:
aspx.cs:
class MyPage : Page
{
protected void RadGrid1_ItemCommand(object source, GridCommandEventArgs e)
{
if (e.CommandName == RadGrid.ExpandCollapseCommandName && e.Item is GridDataItem)
{
var dataItem = e.Item as GridDataItem;
// rebiding fix situation
(dataItem.ChildItem.FindControl("ChildRadGrid1") as RadGrid).Rebind();
}
}
}
I can't answer WHY it was happening, but the solution that works for me is to bind the grid to an ObjectDataSource.
<asp:ObjectDataSource ID="gridData" runat="server"/>
I was already binding the grid to a property on the page which was a collection of type List:
protected List<EquipmentGridItem> GridItems { get; set; }
In order to use the ObjectDataSource, I created a wrapper method to return the list.
public object GetGridData()
{
return GridItems;
}
Then I bound the grid to the object data source.
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
grdUnits.DataSourceID = "gridData";
gridData.TypeName = typeof (ReservationEdit).ToString();
gridData.SelectMethod = "GetGridData";
}
Kind of a convoluted solution, but it works.

Resources