On an ASP.NET page, I have a GridView populated with the results of a LINQ query. I'm setting the DataSource in code, then calling DataBind on it. In the GridView's RowDataBound event, I'm selectively hiding links in some GridView fields based on the query results. (For instance, I hide the "Show Parent" link of the row in question has no parent row.)
This works fine initially. But on postback (when I don't call DataBind, but the GridView stays populated through ViewState), the data displays, but the RowDataBound event (obviously) doesn't fire, and my links don't get hidden.
What's the best way to get the links to be hidden after a postback?
The RowDataBound event only fires when the GridView's data changes during the postback. The event is short-circuited for speed so it's not re-generating the exact same data unnecessarily. Use the RowCreated event to manipulate the HTML instead - it fires on every postback regardless of whether the data has changed.
Here's how I ended up solving this:
I created a serializable class with readonly properties: PK of a row, and a boolean for each link indicating whether it's enabled or not. We'll call it LinkVisibility.
I created a serializable class inheriting from KeyedCollection to hold instances of the class above.
I created a ViewState-backed property holding an instance of that collection.
In my Search procedure (populating the GridView), I clear the collection.
In RowDataBound, which initially shows/hides the links, I add a LinkVisibility instance to the collection for each row.
In Page.Load, when IsPostBack is true, I loop through the GridView rows. I look up the LinkVisibility for each one by PK in the collection (DataKeyNames is set in the GridView), and I set the links accordingly.
I don't know that this is the best way to do this, but it certainly does work, which is more than I can say for anything else I've tried.
1) You could have a Method - ProcessDataRows() that would get called once on grid_DataBound(...). And then when you need it after PostBack.
And that way you process all rows when you want.
2) You could have methods like ShowParentLink(). That are then bound to the LinkButton in the grid (if you're using an ItemTemplate) and the link would have
Visible='<%#ShowParentLink()%>'
I would have expected the viewstate to also reflect the fact that you have removed some of the links (assuming that they were removed before viewstate was saved).
Maybe thats the question you need to ask 'why do the removed links still appear in viewstate?'.
Another solution is to put the logic in the LINQ query, so that you end up with a boolean LINQ field like "ShowParentLink". Then you can just bind the Visible property of the HyperLink field to that value - no RowDataBound required.
protected void btnHazardRating_Click(object sender, EventArgs e)
{
gvPanelRole.RowDataBound += new GridViewRowEventHandler(gvPanelRole_RowDataBound);
gvPanelRole.DataSource = dtGo;
gvPanelRole.DataBind();
ModalPopup.Show();
}
void Process Rows()
{
... do something
... process complete
datagrid.DataBind();
}
A page cannot process postback events unless it is rebuilt exactly as it was before (the postback). If you re-hide your links during the page-init, then your click events and such should fire. Unfortunately, without seeing some sample code I can't get more specific.
Also the data RowDataBound does not fire because you are not data binding. You are rebuilding the page from the viewstate- "viewstate binding" for lack of a better word.
Related
Using the code posted as answer at this question, it is possible to find the control that caused the postback action.
However, it is possible to check which event was called by this control? For example, identify an editing event fired by a GridView before the equivalent method (editing) be executed.
Thanks in advance.
It depends if the client code is providing this information. The standard GridView, for example, sets both __EVENTTARGET and __EVENTARGUMENT when a sort header link is clicked:
Request.Form["_EVENTTARGET"] contains "ctl00$MainContent$GridView1"
Request.Form["_EVENTARGUMENT"] contains "Sort$id"
where the __EVENTARGUMENT contains information that this is a Sort operation, and the column to be sorted on, separated by $.
For paging the __EVENTARGUMENT value looks similar:
Request.Form["__EVENTARGUMENT"] contains "Page$2"
Note though that not all controls populate __EVENTARGUMENT on postback, so you'll have to test.
I have a gridview with checkbox control as a template field and one bounded field..
The grid is binded on every postback through a function and the grid contents remain the same on every post back..now when i check one of the checkboxes and then click the button at the end of the page, I need to store that particular row information..but I'm not able to retreive that information because when I check and then click button..the page loads and then the grid again populates and then checkboxes become uncheck and no CheckedChanged event fires..Help me with this
I need to persist the state of checkbox on every postback even when it is checked..how to do this??
In the page_load event function, please use the following code for your persistent data
if (!IsPostBack)
{
//your static data
}
This particular problem is fairly common. I haven't seen any "simple" solution yet, but here are 3 separate methods I have used. Each was used because of a limitation in the system.
Solution 1
Use AJAX. By putting your controls within an update panel, you can persist the changes by making them "real-time" in the database. This is not really a "simple" solution, but in my opinion it is one of the easiest to implement. Since the change is psuedo-immediate, there is no real need to worry about post-backs and persistence.
Solution 2
Use a "change management" control of sorts. You can apply a hidden control whose value is used to keep track of any changes made in relevant controls. You would need to devise a coherent data structure that provide at least a control ID and the new value (possibly the old value if you need some kind of "roll-back" feature). This would need to be coded in JavaScript so that any changes to the hidden control's value were structured and not duplicated. Then on your postback you would need to read this control's value, make any pertinent changes, and then rebind your data as appropriate. This can be fairly cumbersome, and it would need to be well-documented in the event that you pass this application on to a successor.
Solution 3
Use the PostBack for CheckChanged events and keep all data managed in the view state. During the RowItemCreated event of the GridView you can find the checkbox control in the relevant cell and manually add the delegate handler to that control to handle the postback in the event of a CheckChanged event firing. You can then have the change immediate. The drawback to this is that PostBack events become frequent and heavy. If you're storing large amounts of data in the ViewState this also causes page load to be slow and unresponsive, so whatever structure you choose for the ViewState you'll want to keep it small.
This is possible if you are using asp.net 4.0 using
<asp:GridView id="GridView2" runat="server" EnablePersistedSelection="true">
</asp:GridView>
If you are using 3.5, you will have to retain checkbox info in viewstate. I hope this will be helpful.
http://www.codeproject.com/Articles/202938/How-to-select-multiple-records-from-the-GridView-a
Another Option:
This is how msdn have described a hotmail type gridview.. may be this can help.. this will require you to extend existing GridView Control.
http://msdn.microsoft.com/en-us/magazine/cc163612.aspx
Regards.
I have a Repeater nested inside of a GridView. On the RowDataBound event for the GridView, I set the DataSource (based on one of the row's columns) and then bind the Repeater. This works fine for the initial load, however I need to be able to add new items to the Repeater dynamically.
I append an item to the DataSource, save it to the ViewState, and where I would normally bind using a method call, I bind to the object saved to the ViewState instead. The DataSouce reflects the change, however the page does not.
What am I missing? I have the exact same setup on another page without the nesting and it works perfectly.
if (ViewState["RepeaterObj"]!=null)
{
rpt.DataSource=(IList<DataTransferObject>)ViewState["RepeaterObj"];
}
else
{
rpt.DataSource = controller.GetObj(param);
rpt.DataBind();
}
I ended up resolving the question by cutting out use of the ViewState entirely, though I thought my temporary DataSource would be lost across the postback it wasn't. I ended up going with a class-level variable which works perfectly. It seems I didn't properly understand what happens during a postback.
First of all, you shouldn't be storing a datasource in ViewState. That's pretty much the worst place you could put it.
As for your problem, I would suggest either rebinding the GridView when new items are added to the repeater, or find the repeater in the event that saves the new record and rebind it there.
I think the problem is you are not rebinding the the Repeater. You say you change how you bind to look at the ViewState object but are you actually triggering the Bind to occur? It sounds like you are not and the page is just reloading with the current data stored with the control's ViewState.
Make sure you are calling your Repeaters bind event explicitly to sure it is getting rebound.
EDIT: I suspect it might have something to do where you might need to rebind your GridView and not just the Repeater.
I'm trying to bind my GridView at runtime, but I'm also trying to avoid running all the binding events twice.
I have a GridView that gets populated from a function that returns a DataTable. I'm not using ViewState in the grid for a couple of reasons. I seem to have a Catch-22 situation here:
If I don't bind the grid by Page_Load at the latest, the RowCommand and other grid events won't fire.
If I DO bind the grid in Page_Load, but I'm on a PostBack from a pager link, sort link, or search button, those event handlers will change the data and need to rebind it, running all the binding code again.
The grid triggers DataBound, RowDataBound, and RowCreated events, which could be performing expensive operations. I really hate to call them all in Page_Load, and then wipe out the data and call them all again if the data changes. But I can't seem to avoid this double duty, because in Page_Load I don't know if it was a grid event that will change the data, or a grid event that doesn't.
Any ideas?
Try the command arguments. If a button in the gridview was clicked, that event will be fired and you can handle it appropriately. Your question is not clear enough i'm afraid. Could you be more specific?
Check if request is a postback. Bind the datatable to the grid like so:
If(!ispostback)...
That way you wont be binding the table to the grid on each request.
I'm trying to set the selected value of a ddl during page load ie. before databind.
This causes "selected value does not exist" errors. So I force a databind, and add a new element if it does not exist in the data source.
However it looks like when the databind is performed later in the page lifecycle that my added element(s) are removed/overwritten.
Am I setting the values in the wrong part of the life cycle?
what I'm doing seems rather hackish and I think im going about this the wrong way... is there a better way to do this?
Dont do it on PageLoad do it on the DataBound event of the ddl
Did you consider the OnPreRender event of the DDL... I think you will have everything to set the selected value there
However it looks like when the
databind is performed later in the
page lifecycle that my added
element(s) are removed/overwritten.
That is to be expected, databinding clears out the items and rebinds them again. You should look at what points in the page lifecycle you are calling DataBind and also attempting to set the Selected Value.
Have you considered Page_PreRender to set the SelectedValue? This fires after all the initialisation is done, last thing before the page is rendered to the browser. (Hopefully you won't be doing any databinding in Page_PreRender ;))
But it does not seem very logical to be setting the SelectedValue in one place only for it to be overwritten again, you should only be setting the SelectedValue once - after the final .DataBind()
However it looks like when the
databind is performed later in the
page lifecycle that my added
element(s) are removed/overwritten.
As bgs264 says, that's the behaviour of databinding by design. However, if you set the AppendDataBoundItems attribute to true for your DropDownList, this won't happen, your manually added item will remain in place.
<asp:DropDownList runat="server" id="MyDropDownList" AppendDataBoundItems="true" />
My work around to this solution is as follows:
In Page Load:
Page_Load(..)
{
if(...)
{
hidCGroup.value = objCG.CName;
}
}
In DataBound:
ddlContGroup_DataBound(..)
{
ddlContGroup.Items.Insert(0, "--Select--");
ddlContGroup.SelectedIndex = ddlContGroup.Items.IndexOf(ddlContGroup.Items.FindByText(hidCGroup.Value));
}
Now there are two things to take care of. When you are using FindByText and FindByValue always take special care of the value which you are selecting from the ddl.
Sometimes, we use a numeric item as the DataValue and a text item as the DataText, when that happens, you need to interchange FindByText and FindByValue so that the proper selection is made.
Hope this helps.
ddlExample.SelectedIndex=ddlExample.Items.IndexOf(ddlExample.Items.FindByValue(ExampleID.ToString()));
or
ddlExample.SelectedIndex=ddlExample.Items.IndexOf(ddlExample.Items.FindByText(ExampleText.ToString()));