LoadViewState not fired on my user control - asp.net

I have a user control nested in a repeater.
Inside my user control I have another repeater and in that I have a panel.
I am trying to override the LoadViewState event of my user control and dynamically add controls to the panel. I want to do it in the LoadViewState so that the dynamic controls get added before the viewstate gets loaded, so they retain their values after post backs.
For some reason the LoadViewState event on the user control (ascx) is not firing. Is there some way to force it to fire, or is there another method I could use? I have ruled out the user controls repeater databind event, because I need it to work even if data binding isn't happening and I can't do it on the repeaters item created event either because the child panel and inner html doesn't exist yet.

LoadViewState isn't the appropriate place for adding child controls. For dynamically adding controls within a user control, you'll want to look at the CreateChildControls method.
It's not firing a LoadViewState event because you need to save at least one value in the ViewState to have the event fire.

I think I had a similar problem with some dynamically created children user controls. LoadViewState wasn't called in postbacks even if I was able to access their ViewState when creating them first. SaveViewState seemed to be also called correctly. It ended that the children ViewState was not really usable (without this resulting in an exception) in the page Init event before they were fully initializated, and this happened only when the controls were added to the parent. After having ensured this, the children ViewState was correctly persisted across postbacks.
// Belongs to a Page. If you create the children control in the
// Load event in you can also access the page ViewState
protected void Page_Init(object sender, EventArgs e)
{
if (!IsPostBack)
{
for (int it = 0; it < 5; it++)
{
ChildControl child = LoadControl("ChildControl.ascx")
as ChildControl;
child.ParentPage = this;
TabPanel tab = tabContainer.FindControl("TabPanel" + it)
as TabPanel;
// Ensure to add the child control to its parent before
// accessing its ViewState!
tab.Controls.Add(child); // <---
string caption = "Ciao" + it;
child.Caption = caption; // Here we access the ViewState
tab.HeaderText = caption;
tab.Visible = true;
_Children.Add(child);
}
}
[...]
}
// Belongs to ChildControl
public string Caption
{
get { return ViewState["Caption"] as string; }
internal set { this.ViewState["Caption"] = value; }
}

Related

Property null after postback - Dynamically loaded control

I'm aware this question has been asked many times before but I suspect I have a unique scenario.
I'm loading a Child Control (ASCX) and setting a Property on that Control. This works perfectly fine until postback where the property is null.
Herewith the First Class which loads the ChildControl :
protected override void CreateChildControls()
{
MyUserControl control = (MyUserControl)Page.LoadControl(_ascxPath);
control.MyProperty = base.MyProperty
Controls.Add(control);
}
Then, on my Child Control I've got the following code:
public partial class MyUserControl : UserControl
{
public MyType MyProperty { get; set; }
protected void Page_Load(object sender, EventArgs e)
{
//Exception on next line because Property is null (only on postback)
var somevalue = MyProperty.SubProperty;
Ok. Let me try to explain it.
1. Once page is created, you get full page lifecycle
2. You click on some control to create user control, and you get it
3. Now you are entering value to this control, and getting postback
4. On server side postback is handled, but as you can see viewstate actions appear as soon as page is loaded.
One of main purposes of viewstate is handling control events, to see if they are changed, or save their states or something else.
5. If on the moment, when viewstate is loaded you control is still not constructed, then all it's events and values would be ignored.
Solution either make it static control and just hide it, either create it before viewstate actions started.
You need to add the control and set properties in the Page_Init event, other wise you will lose the properties value.
In Microsoft explanations about ASP.NET page life cycle, it is written that dynamically created controls must be created in PreInit.
It worked for me.
Here is my main page :
protected global::System.Web.UI.HtmlControls.HtmlGenericControl FiltersZone;
(. . .)
protected override void OnPreInit(EventArgs e)
{
base.OnPreInit(e);
FiltersZone.Controls.Add(new PlanningFiltersSurgeonWeb());
}
This dynamically created ".ascx" control contains an hidden field :
<input id="hidTxtPaint" type="hidden" name="hidTxtPaint" runat="server" />
I am now able to retrieve its value from within dynamically created ASCX control Page_Load event, after a "submit" or a "__dopostback('hidTxtPaint')" initiated from JavaScript.
On the other hand, the hidden field's value is always empty after a POST if its parent ".ascx" control is added in main page's Page_Load event.

How to dynamically add user controls within a user control

I want to create a user control that will again contain a user control that will be dynamically generated and can be repeated N times with respect to data present in data table.
How can i do this.
You want to use TemplateControl.LoadControl (which both Page, UserControl and MasterPage inherit from). You need to do this during the Init event for postback stuff to work.
void Page_Init(Object Sender, EventArgs e) {
var control = LoadControl("Awesome.ascx");
// ... Magic!
}
Place a placeholder control on the parent user control:
<asp:PlaceHolder runat="server" id="phMainHolder" />
In the Page_Load or Page_Init event you simply instantiate the child user controls in a for(each) loop and add them to the placeholder like for example:
for(int i = 0; i < 10; i++) {
ChildUC child = new ChildIC();
phMainHolder.Controls.Add(child);
}
This is the bare minimum to do it. You can opt to set properties or whatever on your child controls of course.
The class ChildUC is a fictive class name for this example. Use the class, and if needed use the namespace as extra in which it is defined, of the user control you want to use.

Should a DropDownList within a CompositeControl remember selected item?

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!!

ASP.NET: Postback processed without events being fired

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...

Set Web User Control Property to GridView Selected Row

I have a web user control (ascx) that exposes an "ID" property. What I want to do is set this property when the SelectedIndexChanged event fires in a gridview in the containing page. However, I can't seem to do it.... Here's my code:
protected void grdPhysicians_SelectedIndexChanged(object sender, EventArgs e)
{
physicians_certif1.mdID = grdPhysicians.SelectedDataKey.ToString();
mvData.SetActiveView(viewEdit);
panAdditional.Visible = true;
}
Physicians_certif1 is the user control. It seems the user control is loading before the SelectedIndexChanged event has a chance to set it's property.
Any ideas folks?
ASP.Net page lifecycles can be hard to understand especially with ascx user controls which also have their own lifecycle. If you are setting the mdID property in Page_Load of either the page or the ASCX control or have hardcoded a default value into it in the XHTML, it is probably being reset after SelectedIndexChanged fires.
Set a breakpoint in grdPhysicians_SelectedIndexChanged, set a watch on physicians_certif1.mdID and step through the code using the debugger.
Yes, that is exactly what is happening. You should look at (and be familiar with) the following resource:
ASP.Net Page Life Cycle
The page will load, then the control will load, then your events will begin to fire. If you have configuration needs based on event triggers, it is best either to place those configurations in the Page_LoadComplete or Page_PreRender events of the user control in question or apply "Rebinding" instructions in the Set method of your property:
public MyValue MyProperty()
{
get
{
return _myProperty;
}
set
{
RebindMyControls();
_myProperty = value;
}
}

Resources