ASP.NET Repeater not binding after ItemCommand - asp.net

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.

Related

ASP.NET GridView TemplateField controls lost after PostBack

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()
}

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.

Creating dynamic DataList controls with ID's based on bound data

As a workaround for the fact that asp:Checkboxes don't have values, I am attempting to dynamically create the ID's of checkboxes in a DataList so that it inserts the primary keys into the control ID. This is surprisingly difficult.
I have placed a PlaceHolder in my DataList ItemTemplate, then in the ItemCreated I create the checkboxes using string.Format("Checkbox{0}", DataBinder(e.Item.DataItem, "ID")). The problem is that this only works in a non-postback condition, as on postback the DataItem is null. And of course ItemDataBound isn't called on PostBack so that won't work either.
I can't seem to find a good way to handle this short of if (IsPostback) dataList.Bind(), which i don't think is a good way to do it.
Can anyone provide me with any alternatives here?
EDIT:
Some additional information. I just realized that part of the problem was because I actually have a DataList within a DataList. The reason DataItem is null is because there is no databinding on postback, and the child data is not saved to viewstate.
Basically, what i'm doing is This, although it's using a DataList rather than Repeater. So, on postback, the Children collection doesn't get set because ItemDataBound isn't called on postback.
EDIT2: To clarify, the problem is largely because of the nested datalists. I have to set the datasource of the nested datalist to a collection field of the first datalist's individual rows fields. On postback, there is no databinding, so it doesn't work.
You could use a similar technique to the one I wrote up in this answer - add a regular CheckBox, and a HiddenField control in the ItemTemplate, and bind the HiddenField to the primary key value e.g.
<ItemTemplate>
<tr>
<td>
<asp:CheckBox runat="server" ID="MyCheckBox" AutoPostBack="true" oncheckedchanged="MyCheckBox_CheckedChanged" />
<asp:HiddenField runat="server" id="DatabaseKeyHiddenField" Value='<%# Eval("DatabaseKey") %>' />
</td>
</tr>
</ItemTemplate>
protected void MyCheckBox_CheckedChanged(object sender, EventArgs e)
{
CheckBox selectedCheckBox;
DataListItem selectedDataListItem;
HiddenField databaseKeyHiddenField;
string databaseKey;
// Cast the sender object to a CheckBox
selectedCheckBox = (CheckBox)sender;
// Walk up the tree one level so we get the container for both controls
selectedDataListItem = (DataListItem)selectedCheckBox.Parent;
// Get the HiddenField control ...
databaseKeyHiddenField = (HiddenField)selectedDataListItem.FindControl("DatabaseKeyHiddenField");
// ... and read the value
databaseKey = databaseKeyHiddenField.Value;
// Go off and do a database update based on the key we now have
...
}
It's a bit of a workaround rather than exactly what you want to do, but it works!

How do I get the whole object from a ListView?

I have a asp:ListView control that I bind with a List<CustomObject>.
When Editing records in this ListView control, I can always get the Unique Id of record being edited by using:
int id = Convert.ToInt32(lstView1.DataKeys[e.NewEditIndex].Value);
Is it possible to get the whole object <CustomObject> that is being edited, using any of the ListView properties?
I just figured that out,
We can get the object being edited using following code-
protected void lstView1_ItemDataBound(object sender, ListViewItemEventArgs e)
{
ListViewDataItem objCurrentItem = (ListViewDataItem)e.Item;
**CustomObject obj = (CustomObject)objCurrentItem.DataItem;**
if (objCurrentItem.DisplayIndex == lstView1.EditIndex)
{
TextBox txtTitle = (TextBox)objCurrentItem.FindControl("txtTitle");
txtTitle.Text = obj.Title;
}
}
Here is the answer to your comment to my question:
Yes, the reason it is null in itemcommand and works fine in itemdatabound is that the location itemcommand is not correct for reading this value. You will always get the DataItem null in ItemCommand, no matter what you do. The reason lies in the control life cycle. The control gets initialized, created and then only does any other event related to the control can fire. During control creation the CreateControlHierarchy is called which then uses the DataBind event to create and databind the child controls. At that time the DataItem is live and is not null. Before that and after that it is always null, because its role lies only for that much time span.
By the way the DataItem you are looking at is the item from the related datasource that is being used to databind the listview. The datasource is used only during databinding, hence the DataItem is available only during Item Databound.
Hope this helps !
When you click on the edit for a given item in the listview the ItemCommand event gets fired. The arguments for that event tell that you can get the list item for which that event was fired. You will have to typecast that item properly to get the information you require. The itemcommand event looks like this
protected void ListView1_ItemCommand(object sender, ListViewCommandEventArgs e)
{
}
you have e.Item to use from the ListViewCommandEventArgs.
You item updating you don't have the object available for modifying. You only have the collection of the properties and their values in the new values and old values collections that you get from event arguments. I suppose you can edit the properties of the item over there. Its more or less similar to editing the object itself, because eventually these property values will get transferred to the object using reflection.
<asp:ListView runat="server" ID="list" OnItemCommand="listVideo_ItemCommand">
<ItemTemplate>
<asp:LinkButton ID="btDelVideo" runat="server" Text="Delete" OnClientClick="return confirm('Confirm delete ?');" CommandArgument='<%# Eval("KeyID") %>' CommandName="DELETE" />
<asp:LinkButton ID="btEditVideo" runat="server" Text="Edit" CommandArgument='<%# Eval("KeyID") %>' CommandName="EDIT" />
</ItemTemplate>
</asp:ListView>
protected void list_ItemCommand(object sender, ListViewCommandEventArgs e)
{
int videoId = (int)e.CommandArgument;
switch (e.CommandName)
{
case "DELETE":
//Implement Delete event
goto default;
case "EDIT":
//Implement Edit event
goto default;
default:
//Rebind listview
break;
}
}
}

ASP.NET DataGrid within a Repeater

I have a table that has two columns:
CommunityID
PersonID
And A "People" table that has (among other things):
FirstName
LastName
I would like to display a different DataGrid for each community, each datagrid having only the people that are part of that community. I would like to do this without using 4 seperate SqlDataSources.
A Repeater looks like a good way, with a DataGrid inside the ItemTemplate, but I can't seem to make heads or tails of getting that to work with the different values for each repetition.
If anyone has any suggestions on better ways to do this, I'd be very appreciative, as this is one of my first forays into the world for ASP.NET
Thanks,
Mike
Personally I wouldn't use a DataGrid control, since it restricts your control over your output and they've been replaced by the newer GridView & ListView controls (although DataGrid is not obsolete so feel free to use it if you want). You may want to consider using the alternatives but you aren't required to do so.
To do what you're looking for, you would have markup like the following:
<asp:Repeater runat="server" ID="myRepeater"
onitemdatabound="Repeater_ItemDataBound">
<ItemTemplate>
<asp:DataGrid runat="server" ID="myDataGrid">
</asp:DataGrid>
</ItemTemplate>
</asp:Repeater>
Then you'll wire up the markup with the following code-behind:
protected void Page_Load(object sender, EventArgs e)
{
myRepeater.DataSource = new Object[0];
myRepeater.DataBind();
}
protected void Repeater_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
DataGrid dg = (DataGrid)e.Item.FindControl("myDataGrid");
object o = e.Item.DataItem;// Cast the DataItem as whatever
// your Repeater's DataSource is
// ...
// Do whatever you need to get the
// data source for your DataGrid here
// ...
dg.DataSource = DataGridSourceObjectHere;
dg.DataBind();
}
The key is the Repeater's ItemDataBound event, which is the method called every time a repeater row is created. This is where you can data bind your DataGrid source. You can put any logic you need to within this method using the RepeaterItemEventArgs parameter to access the data item you bound to your Repeater.

Resources