I am trying to build a server control that, depending on a "QuestionTypeId" display either a text box, date picker or Yes-No radio buttons.
I have my control displaying how I want it to, but when the submit button is pressed on the form, the text box, date picker or radio buttons that were generated in the RenderContents method are all null.
I have attempted to store the generated controls in view state, that stopped them being null, but the user inputs were not being stored.
I will post code if it is needed. Just ask.
I think you need to create (and add) the controls in CreateChildControls. This will mean you'll need to store the value of the QuestionTypeId in either Viewstate or ControlState (I'd argue that ControlState is applicable in this case, as your control can't work without this value).
When you add controls dynamically, you need to make sure they are recreated before the viewstate is restored.
I haven't done this in a while, but from memory I think you should recreate your controls in the OnInit method. This happens before postback data has been loaded and before the controls have their values set from viewstate.
It may be worth doing some reading on the asp.net page lifecycle:
http://msdn.microsoft.com/en-us/library/ms178472.aspx
You can create a user control and use server controls for textbox, datepicker, radiobuttons.
If you create a cusom server control then you have to add the posted data to your control properties. You can do this at your control OnInit event:
MyProperty = Request.Form("myControl");
A simpler method would be to create all the controls at design time and make the controls invisible based on you requirements.
Example code:
protected void Page_Load(object sender, EventArgs e)
{
txtBox.Visible = QuestionTypeID == 1;
chkBox.Visible = QuestionTypeID == 2;
}
If you do use dynamic controls you should do as David pointed out, save the value QuestionTypeID in ViewState or ControlState and then create the control you want based on that value.
(the control needs to be created every time the page loads even on a post back and they cannot be created later in the page life cycle then the Page_Load method if you want their ViewState persisted and recovered)
Example code:
protected void Page_Load(object sender, EventArgs e)
{
var questionId = ViewState["QuestionTypeID"];
if(questionId == /* Value to create TextBox */) {
var txt = new TextBox { ID = "txt" };
placeHolder.Controls.Add(txt);
} else if(questionId == /* Value to create Calender */) {
var cal = new Calender { ID = "cal" };
placeHolder.Controls.Add(cal);
}
/* Once the controls are added they will be populated with the posted values */
}
P.S.
It's always a good idea with dynamic controls to specify the ID.
You can save the added controls to member variables and use them elsewhere (after they are assigned)
You can subscribe to their events and if the user posted a new value your method will be called
I have followed your advice and done the following:
1) Question Type is stored in view state in my server control.
2) on CreateChildControls now creates a new instance of my control and adds it to a place holder on the page.
My problem now is that things seem to fire in bit of an odd order:
1)On initial load of page, create child controls is fired and the RenderContents method of my server control fires.
2)A button is clicked to load a new contact, this triggers create child controls and RenderContents is fired.
3)details are entered and save is pressed, this triggers Create Child Controls but RenderContents is not triggered and a NullReferenceException is generated by trying to access my control to get the value out. (If i skip the code that access my controls, RenderContents is called and renders.
Seconly, another issue is that when I try an set a value (onDataBind) I try to access the Text box that has been generated in my server control and get another NullReferanceExeption
Thoughts?
Related
I have a set of programmatically added user controls on the page (they are created and added during the Init event of the main page). Each of these control programmatically add buttons during his own Init event.
(So it's "two levels" of dynamically created controls. Don't know if it matters.)
But I need to access the viewstate while creating those buttons (I use the viewstate to remember user data).
protected void Page_Init(object sender, EventArgs e)
{
// I need my ViewState right now !
Button myButton = new Button();
myButton.Text = "Click me";
myButton.Command += new CommandEventHandler(myCommandHandler);
myButton.CommandName = "Delete";
myButton.ID = "myButtonID";
myPlaceholder.Controls.Add(myButton);
}
I understood that the viewstate isn't available during the Init event. But buttons don't fire their events (OnClick, etc.) if they are created after the Init.
How to create dynamic controls and access the viewstate at the same time ?
I don't think you can. Not ideal but I would put the user data in the Session in this instance. Dynamic controls are difficult to work with and I think this is one of the trade offs. ViewState loads after Page_Init - this forum post confirms
Another option might be to store the information in your own hidden fields. These will become available in the Request.Form collection during postback and will be available. Of course if you make them runat="server" (which you will probably have to) the framework will append all the naming container ids to the name so you will have to be a bit clever when digging them out. It's going to get a bit hacky though so I would be tempted to stick to the Session
Someone else might know different of course.
I add a custom user control to my page, with textboxes and radioboxes, but after each postback, the contents are removed and I don't know how to read the values that were entered in it.
From what I've learned you have to add the user controls on each refresh, but that does not restore their state. I want the values to be stored in the ViewState but that does not happen for some reason.
Note: I need to add multiple user controls on one page, so I need to identify each user control, either trough an ID or a linq expression that selects that user control from my page.
Im programming with C# 4.0
How I add my user control:
private void AddInstrumentDetailToPage()
{
RMAItem lItem = (RMAItem)Page.LoadControl("/Controls/RMAItem.ascx");
InstrumentDetail.Controls.Add(lItem);
}
I save the amount of controls I have in the ViewState, and call AddInstrumentDetail() that amount of times, but they appear to be empty. I've read somewhere that I have to add it in Page_Init because view state is not initialised yet, but that doesn't help either.
You need to recreate dynamically added (User-)Controls at latest in Page_Load to maintain ViewState. So you should store the number of already added controls in a ViewState-Property and according to that reload them in Page_Load. You have to sum up this variable with 1 in AddInstrumentDetailToPage.
MSDN: Dynamically Adding User Controls
If you store the number of added controls in a Viewstate variable, you cannot recreate controls in Page's Init-Event because the ViewState-Variable would yet not been reloaded there.
You won't have any ControlState unless you make a PostBack. Refreshing your browser/requesting the same link will just be a fresh start.
By default, any control added to your page are data stored in ViewState and ControlState for your UserControl.
If you are willing to save your contents, I would suggest you save it on Request.Session.Add("yourKey", "yourValue").
You can access your session with var yourValue = Request.Session["yourKey"]; on your protected void Page_Load(object sender, EventArgs e) method.
Specify an ID for your RMAItem instance, otherwise storing data on ControlState might not work!
private void AddInstrumentDetailToPage()
{
RMAItem lItem = (RMAItem)Page.LoadControl("/Controls/RMAItem.ascx");
lItem.ID = "rmaItem1"; //<-- This is important
InstrumentDetail.Controls.Add(lItem);
}
Hope it helps!
There are plenty of similar questions on SO (1, 2, 3, 4 etc) and on the web (1, 2 etc) but none of the corresponding answers clarifies my situation.
I have a simple custom user control consisting of two drop-down lists. Selection of a value in one drop-down list should lead to the populating of the other one. As long as I declare the user control in the .aspx code everything works as expected.
Now I want to add the user control on the page programmatically (on button click). Although the control is being added, the selection in one drop-down list causes only a postback but no action of the other drop-down list.
While debugging I've found out that not only OnSelectedIndexChanged does not fire, but also OnLoad and all the other events.
Common reasons for such a behaviour regarding all the discussions I've looked through are the following:
AutoPostBack of the DropDownList is not set to true or the databound DropDownList is being rebound on each postback which causes the losing of events. //not a case here, more likely refers to dynamically added drop-down lists
ID is assigned to the dynamically added control automatically on each postback (and every time a different one so that ViewState is not persisted correctly and events do not know that they should fire). // ok I've checked it and now assign the ID manually
The control is added only once (as opposed to the addition on every postback which is necessary because in the ViewState only the state (values) of server controls are stored, but not the controls themselves) and/or
the control is added on each postback but too late in the Page Life Cycle. //ok I'm adding my control in OnInit event handler now
In order to let the page know that the control was added (and how many of them) I use Session. Below a bit of code and then finally the question :)
.aspx:
<asp:Content ID="Content3" ContentPlaceHolderID="MainContent" runat="server">
<asp:PlaceHolder ID="PlaceHolder1" runat="server"></asp:PlaceHolder>
<asp:Button ID="Button1" runat="server" Text="Button" onclick="Button1_Click" />
</asp:Content>
and the code behind:
protected void Page_Load(object sender, EventArgs e)
{
if(!this.IsPostBack)
{
Session.Remove("Childcontrols");
}
}
private void AddTransitControl()
{
List<Control> controls = (List<Control>)Session["Childcontrols"];
AddTransitPoint atp = (AddTransitPoint)LoadControl("~/UserControls/AddTransitPoint.ascx");
string id = this.ID + "_eb" + (controls.Count).ToString();
atp.ID = id;
controls.Add(atp);
PlaceHolder1.Controls.Add(atp);
}
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
if (Page.IsPostBack)
{
if (Session["Childcontrols"] == null)
{
Session["Childcontrols"] = new List<Control>();
}
List<Control> controls = (List<Control>)Session["Childcontrols"];
int count = 0;
foreach (Control c in controls)
{
// AddTransitPoint atp = (AddTransitPoint) c; //mystically not working (fires no events)
AddTransitPoint atp = (AddTransitPoint)LoadControl("~/UserControls/AddTransitPoint.ascx"); //it is working!
string id = this.ID + "_eb" + count;
count++;
atp.ID = id;
PlaceHolder1.Controls.Add(atp);
}
}
}
protected void Button1_Click(object sender, EventArgs e)
{
AddTransitControl();
}
(I'm pretty sure that the code of the user control itself is not really relevant for the case but on request I can add it later).
So now the question: Through trial and error I've found out that if I store the newly added control in the collection in Session and OnInit just take the control out of this collection and add again to the control collection of my placeholder, no events of this control fire on the next postback (independent of the way the postback is called). Other way if I create OnInit a new control for each stored in Session and add to the placeholder control collection this newly created control - everything works! So what is wrong with stored in the Session controls and why do they lose their events?
And one more small question here. What is the best practice for creating IDs for such controls? I use a string of particular format along with a counter, but I doubt it is the best way to do it. For example if I added different types of controls I would face trouble with this method.
Thank you everybody for reading such a long question and for your valuable input!
My understanding is this: events must be re-established on every postback. You get this for free if the event is defined in the the ASPX file, because those attributes (OnClick, OnSelectionChanged, etc.) are re-processed when the page object is instantiated.
You do not get this for free with dynamically-built controls, or controls which are defined in the ASPX file but have their events wired-up in the codebehind. For those controls, the events are not known at instantiation time, thus the page can not automatically re-establish them for you. For those controls, you must re-establish your events on every postback.
My understanding of session-stored data is: when you put an object in the session, that object itself is not kept alive in memory. Instead, it is serialized, and the serialized data is stored in the session. When you get the object back out of the session, it is deserialized.
Objects' events do not survive the serialization/deserialization round-trip because they are logically function pointers - so when the object is removed from memory and later re-built, the entire memory "landscape" has changed, so those events/pointers would no longer be valid.
I haven't done a lot of work with dynamically-generated controls, so I can't tell you the most effective pattern to managing them and their events. But I know that storing them in the session isn't really buying you any benefit over re-creating them on each postback; and it is bloating up the session memory unnecessarily. Hopefully some other SO'ers on this thread can point you to the best-practices way of managing them.
The controls events don't work when stored in the session because they are now referencing an instance of the page which no longer exists.
Let me rephrase what I think you are saying (after a brief look at the code):
When I add a user control (ASCX) to a page dynamically, the controls on the user control do not fire.
If so, the code for the user control is relevant, because it should be handling the events fired from its controls.
If not, then you wire up the server controls (Microsoft created or you created) with delegates to handle the event, as you are missing the "I'll do the magic for you" step when dragging and dropping a control on a page.
Am I at least somewhere close to the target?
You will have to recreate the viewstate within the control to solve the problem
I've created 2 controls to manipulate a data object: 1 for viewing and another for editing.
On one page I load the "view" UserControl and pass data to it this way:
ViewControl control = (ViewControl)LoadControl("ViewControl.ascx");
control.dataToView = dataObject;
this.container.Controls.Add(control);
That's all fine and inside the control I can grab that data and display it.
Now I'm trying to follow a similar approach for editing. I have a different User Control for this (with some textboxes for editing) to which I pass the original data the same way I did for the view:
EditControl control = (EditControl)LoadControl("EditControl.ascx");
control.dataToEdit = dataObject;
this.container.Controls.Add(control);
Which also works fine.
The problem now is getting to this data. When the user clicks a button I need to fetch the data that was edited and do stuff with it.
What's happening is that because the controls are being added programmatically, the data that the user changed doesn't seem to be accessible anywhere.
Is there a solution for this? Or is this way of trying to keep things separated and possibly re-usable not possible?
Thanks in advance.
Just keep a reference to the control as a variable in the page's code-behind. I.e.
private EditControl control;
protected void Page_Init(object sender, EventArgs e)
{
control = (EditControl)LoadControl("EditControl.ascx");
control.dataToEdit = dataObject;
this.container.Controls.Add(control);
}
protected void Button_Click(object sender, EventArgs e)
{
var dataToEdit = control.dataToEdit;
}
As long as you're creating the control in the right part of the page's lifecycle (Initialization) then ViewState will be maintained. I'm assuming dataToEdit is just a TextBox or something?
Controls created dynamically must be added on Init or PreInit event on the page.
You have to do it here because controls state is not yet loaded. If you add the the control AFTER you'll have them empty since the page already filled control page values early in the page life cycle.
More info about page life cycle here
Here's my solution:
In the page, I keep a reference to the control that is loaded programmatically.
In the control I created a GetData() method that returns an object with the data entered by the user.
When the user submits the form, the page calls the GetData() method on the control to access the data the user entered.
I have an ASP.NET web form which I am adding a variable number User Controls to. I have two problems:
The User Controls are added to a PlaceHolder on the form in the first PageLoad event (I only add them when "(!this.IsPostback)", but then when the form is posted back, the controls are gone. Is this normal? Since other controls on the form keep their state, I would expect these dynamically added ones to stay on the form as well. Do I have to add them for every postback?
I also have a button and an event handler for the button click event, but this event handler is never called when I click on the button. Is there something special I have to do to catch events on dynamically added controls?
Yes, you need to add them in every postback.
Yes... the control needs to be in the control hierarchy before asp.net dispatches the event (i.e. create the dynamic controls as early in the page lifecycle as possible).
1) You should add the controls on the Pre-init (Page life cycle)
2) You have to attach the event handler to the event of the created button.(events might occur much later in the page life cycle than the same events for controls created declaratively)
To achieve this, add your controls at page init instead of page load. (re-add at postback)
You'll need to know the id of the buttons added to bind them to the event.
I ran into a similar problem. I had a page that displayed a collection of custom web controls. My solution was to add an additional invisible web control so that when I clicked a button to add another control that I would just use the invisible one. Then on post back my load function would add another invisible control to the collection.
I figured out yesterday that you can actually make your app work like normal by loading the control tree right after the loadviewstateevent is fired. if you override the loadviewstate event, call mybase.loadviewstate and then put your own code to regenerate the controls right after it, the values for those controls will be available on page load. In one of my apps I use a viewstate field to hold the ID or the array info that can be used to recreate those controls.
Protected Overrides Sub LoadViewState(ByVal savedState As Object)
MyBase.LoadViewState(savedState)
If IsPostBack Then
CreateMyControls()
End If
End Sub
I ran into the exact same problem and struggled through like 5-6 hours.
I'm posting this maybe someone like me could get help.
1) You should initialize your controls at Page.PreInit event. (In my case I had to add my controls to a place holder so I extended PreInit to load those controls before but you don't need to do that. It depends on your scenario.)
2) You should bind those exact methods to your controls after you initialize them in your Page.PreInit event.
Here is my sample code:
protected override void OnPreInit(EventArgs e)
{
// Loading controls...
this.PrepareChildControlsDuringPreInit();
// Getting ddl container from session and creating them...
if (GetDDLSession().Count != 0)
{
foreach (DropDownList ddl in GetDDLSession())
{
ddl.SelectedIndexChanged += SelectedIndexChanged;
phDropDowns.Controls.Add(ddl);
}
}
base.OnPreInit(e);
}
public static void PrepareChildControlsDuringPreInit(this Page page)
{
// Walk up the master page chain and tickle the getter on each one
MasterPage master = page.Master;
while (master != null) master = master.Master;
}