So I have a ListView (assignmentsListView) in an UpdatePanel, being filtered by a DropDownList in the same UpdatePanel. The DropDownList has a list of persons in it and uses autopostback, and the ListView shows the tasks those persons are assigned to.
I am trying to use code similar to this:
protected void assignmentsListView_DataBound(object sender, EventArgs e)
{
string resFirstName = Utilities.getResourceFirstName(resDdl.SelectedValue);
if (assignmentsListView.Items.Count <= 0)
{
//Show error message
}
else
{
//Try to find the ImageButton in the ListView's header template.
ImageButton exportButton = (ImageButton)assignmentsListView.FindControl("ImageButton3");
//Register this button as a PostBack event in the Master Page
ScriptManager scriptManager = ScriptManager.GetCurrent(this.Page);
ScriptManager.RegisterPostBackControl(exportButton);
}
}
When I first load the page, the DropDownList shows the first person in the list, and the ListView correctly shows that persons tasks.
If I then select a person who I know has zero tasks, I get an error at the RegisterPostBackControl() method, saying the passed-in control cannot be null.
When debugging, at the RegisterPostBackControl method, it shows that the ListView Items collection has >0 elements in it (the number of elements matches the person selected before the current person).
What's going on? Any suggestions?
In Asp.Net Web Forms applications, the order of events are not always what you'd want. For your case, the new person selection is probably applied after this method is executed. The best thing you could do is force databindings in an earlier event (like Page_Init)
Related
I have an webforms ASPX page, which has a dynamically added user control. The user control has two DropDownLists, one is in an UpdatePanel as its items depend on the selection in the first DropDownList.
The problem is that if you do not change the value of the first DropDownList the value of the second DropDownList does not get saved. If you do change the value of the first DropDownList then it works fine.
This is how it works briefly...
The first time the page loads, the previous values are set. This happens in the main ASPX page Page_Load event where the user control is dynamically added and the initial values are set through a property of the user control. The property sets the selected value of the first DropDownList, triggers the SelectedIndexChanged event which populates items in the second DropDownList with choices based on the first DropDownList selection, and then selects the previous value of the second DropDownList.
Then in the main ASPX page, the user control is dynamically added again on post back in the PreLoad event.
Viewstate is fully enabled throughout.
I have debugged and on post back the second DropDownList has no Items. Even during the UpdatePanel partial post back the items collection is empty. However, if the first DropDownList is changed then the Items collection is correct - it's only if the second DropDownList is last populated on the initial load that the problem happens.
Here's the relevant parts of the code...
Any help greatly appreciated.
ASPX page:
protected void Page_PreLoad(object sender, EventArgs e)
{
if (Page.IsPostBack)
{
PopulateTemplateOptions();
}
}
protected void PopulateTemplateOptions(bool init = false)
{
//this is where the control is dynamically added
//If not post back then initial values are set by passing values to
//user control's TemplateOptions property
}
User control:
public string TemplateOptions
{
set
{
//this is where the initial values are set
}
}
protected void ddlEnquirySubjectDefault_SelectedIndexChanged(object sender, EventArgs e)
{
//this is where items are added to the second drop down list based on the selection in the first one.
}
(These are the only parts that are relevant, but there's another 7,000 lines of code I didn't include.)
Here are two of my ideas:
1) This line might be wrong in your code: if (Page.IsPostBack)
It must be if (!Page.IsPostBack), although I never used Page_PreLoad event so I am not sure if it is right.
2) Try to do what you want in Page_Init event instead. I use it on my projects, without problem.
My gridview is basically a hit list of results from which the user selects one, and I want to get a postback saying which one he has selected. There is no need for the grid contents to survive the postback round-trip because the hit list disappears as soon as he has selected an item.
I don't want to use viewstate because the hit list is likely to be large. I don't want to databind from the database in PageLoad to repopulate the grid because the search may take a while.
what I'm thinking at the moment is that I can put some javascript on the 'select' link to store the ID of the selected item in a hidden field and then call __doPostBack
this still seems a bit clunky. can you think of a cleaner way?
If the postback should be triggered when the user clicks the anywhere in the row, use the ItemDataBound event to attach a client onclick handler:
protected void GridView1_ItemDataBound(object sender, GridViewRowEventArgs e)
{
var dataItem = e.Item as GridViewRow;
if (dataItem != null)
{
dataItem.Attributes["onclick"] = string.Format("__doPostBack(this, '{0}')", e.Row.RowIndex);
}
}
Is the ID of the selected item something sensitive that should not be seen by the user?
If it is not, you could just make your select button a link to the next page, with the ID as a querystring variable. Clicking would just move to the next page, no postback required.
I have a doubt over the items available in a drop down list after postbacks.
I created a custom web server control deriving from DropDownList:
public class StateListControl : DropDownList
{
public StateListControl()
{
this.Items.Add(new ListItem("New York", "NY"));
this.Items.Add(new ListItem("Nebraska", "NE"));
this.Items.Add(new ListItem("Texas", "TX"));
}
}
I added the control to a page, the in the Page_Load event did the following:
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
StateListControl1.Items.Add(new ListItem("Michigan", "MI"));
}
}
Now this item will be added when there is no postback. I added button control with no attached event.
Now when I repeatedly click my button, the fourth entry is visible after every postback.
My doubt relates to where is the information saved that the drop down list has a fourth item, when that item is added only on first request and is still available after repeated postbacks?
On first page load, dropdownlist is populated with all four values and these are stored in the viewstate. When you post the page back, dropdownlist is initialized again and values are loaded from the viewstate. Since no changes are made in the server side load event to dropdownlist values, the values remain as the four entries in viewstate.
If you disable viewstate for the dropdown, you will see that only 3 entries are available when you postback, depending on whether the initialize code is run on every page load.
A control has a ControlState - similar to a ViewState - that retains the information during a post back.
ControlState vs. ViewState
When you add the new list item - it is added to the dropdownlist and saved in viewstate.
Given the following
public class MyControl : CompositeControl
{
private DropDownList myList;
protected override void CreateChildControls()
{
base.CreateChildControls();
myList = new DropDownList();
myList.AutoPostBack = true;
this.Controls.Add(myList);
if (!Page.IsPostBack)
{
myList.DataSource = MyBLL.SomeCollectionOfItems;
myList.DataBind();
}
}
}
I find that the items in the list persist properly, but when a different control is rendered and then this one is rendered again, the last selected item is not persisted. (The first item in the list is always selected instead)
Should the last selected item be persisted in ViewState automatically, or am I expecting too much?
I think this is a hidden ViewState issue. You create and bind a control in CreateChildControls. You should only create the control at this place. Move the binding code to the classes load event and use EnsureChildControls.
Here is the solution which is best recommended. It lies in understandng the Page Life Cycle correctly!! Postback Controls like Drop Down List restore their posted state (the selected item of a Drop Down List posted). It forgets its selected value because you are rebinding it in Page_Load event, which is after the Drop Down List has been loaded with posted value (because View State is loaded after Page_Init event and before Page_Load event). And in this rebinding in Page_Load event, the Drop Down List forgets its restored selected item. The best solution is to perform the Data Binding in the Page_Init event instead of Page_Load event.
Do something like the below...
Suppose Drop Down List name is lstStates.
protected void Page_Init(object sender, EventArgs e)
{
lstStates.DataSource = QueryDatabase(); //Just an example.
lstStates.DataTextField = "StateName";
lstStates.DataValueField = "StateCode";
lstStates.DataBind();
}
ASP.NET loads control's View State after Page_Init event and before Page_Load event, so Drop Down List's selectedIndex will not be affected, and you will get desired results magically!!
I have a GridView with dynamically created image buttons that should fire command events when clicked. The event handling basically works, except for the very first time a button is clicked. Then, the postback is processed, but the event is not fired.
I have tried to debug this, and it seems to me, that the code executed before and after the first click is exactly the same as for any other clicks. (With the exception that in the first click, the event handler is not called.)
There is some peculiarity in that: The buttons which fire the event are created dynamically through databinding, i.e. databinding must be carried out twice in the page lifecycle: Once on load, in order to make the buttons exist (otherwise, events could not be handled at all), and once before rendering in order to display the new data after the events have been processed.
I have read these posts but they wouldn't match my situation:
ASP.NET LinkButton OnClick Event Is Not Working On Home Page,
LinkButton not firing on production server,
ASP.NET Click() event doesn't fire on second postback
To the details:
The GridView contains image buttons in each row. The images of the buttons are databound. The rows are generated by GridView.DataBind(). To achieve this, I have used the TemplateField with a custom ItemTemplate implementation. The ItemTemplate's InstantiateIn method creates the ImageButton and assigns it the according event handler. Further, the image's DataBinding event is assigned a handler that retrieves the appropriate image based on the respective row's data.
The GridView is placed on a UserControl. The UserControl defines the event handlers for the GridView's events. The code roughly looks as follows:
private DataTable dataTable = new DataTable();
protected SPGridView grid;
protected override void OnLoad(EventArgs e)
{
DoDataBind(); // Creates the grid. This is essential in order for postback events to work.
}
protected override void Render(HtmlTextWriter writer)
{
DoDataBind();
base.Render(writer); // Renews the grid according to the latest changes
}
void ReadButton_Command(object sender, CommandEventArgs e)
{
ImageButton button = (ImageButton)sender;
GridViewRow viewRow = (GridViewRow)button.NamingContainer;
int rowIndex = viewRow.RowIndex;
// rowIndex is used to identify the row in which the button was clicked,
// since the control.ID is equal for all rows.
// [... some code to process the event ...]
}
private void DoDataBind()
{
// [... Some code to fill the dataTable ...]
grid.AutoGenerateColumns = false;
grid.Columns.Clear();
TemplateField templateField = new TemplateField();
templateField.HeaderText = "";
templateField.ItemTemplate = new MyItemTemplate(new CommandEventHandler(ReadButton_Command));
grid.Columns.Add(templateField);
grid.DataSource = this.dataTable.DefaultView;
grid.DataBind();
}
private class MyItemTemplate : ITemplate
{
private CommandEventHandler commandEventHandler;
public MyItemTemplate(CommandEventHandler commandEventHandler)
{
this.commandEventHandler = commandEventHandler;
}
public void InstantiateIn(Control container)
{
ImageButton imageButton = new ImageButton();
imageButton.ID = "btnRead";
imageButton.Command += commandEventHandler;
imageButton.DataBinding += new EventHandler(imageButton_DataBinding);
container.Controls.Add(imageButton);
}
void imageButton_DataBinding(object sender, EventArgs e)
{
// Code to get image URL
}
}
Just to repeat: At each lifecycle, first the OnLoad is executed, which generates the Grid with the ImageButtons. Then, the events are processed. Since the buttons are there, the events usually work. Afterwards, Render is called, which generates the Grid from scratch based upon the new data. This always works, except for the very first time the user clicks on an image button, although I have asserted that the grid and image buttons are also generated when the page is sent to the user for the first time.
Hope that someone can help me understand this or tell me a better solution for my situation.
A couple problems here. Number one, there is no IsPostBack check, which means you're databinding on every load... this is bound to cause some problems, including events not firing. Second, you are calling DoDataBind() twice on every load because you're calling it in OnLoad and Render. Why?
Bind the data ONCE... and then again in reaction to events (if needed).
Other issue... don't bind events to ImageButton in the template fields. This is generally not going to work. Use the ItemCommand event and CommandName/CommandArgument values.
Finally... one last question for you... have you done a comparison (windiff or other tool) on the HTML rendered by the entire page on the first load, and then subsequent loads? Are they EXACTLY the same? Or is there a slight difference... in a control name or PostBack reference?
Well I think the event dispatching happens after page load. In this case, its going to try to run against the controls created by your first data-binding attempt. This controls will have different IDs than when they are recreated later. I'd guess ASP.NET is trying to map the incoming events to a control, not finding a control, and then thats it.
I recommend taking captures of what is in the actual post.
ASP.NET is pretty crummy when it comes to event binding and dynamically created controls. Have fun.
Since in my opinion this is a partial answer, I re-post it this way:
If I use normal Buttons instead of ImageButtons (in the exact same place, i.e. still using MyItemTemplate but instantiating Button instead of ImageButton in "InstantiateIn", it works fine.
If I assert that DoDataBind() is always executed twice before sending the content to the client, it works fine with ImageButtons.
Still puzzled, but whatever...