TRULY Understanding Dynamic Controls (Part 3)

Part 1: Dynamic vs. Static
Part 2: Creating Dynamic Controls
Part 3: Adding Dynamic Controls to the Control Tree
Part 4: Because you don't know what to render at design time

PART 3


Adding Dynamic Controls to the Control Tree

Creating a control obviously isn't enough to get it involved in the page life cycle. A control is just an instance of a class like any other class. You have to put it somewhere so the framework knows it exists. You also probably want to initialize it into some state that suits your needs. You may even want to add even more dynamic controls to it.

When should dynamic controls be added? OnInit? OnLoad? DataBind? What if the controls I create depend on ViewState? Should I initialize the controls before or after I add them? How and when do I databind a dynamic dropdown list? These are just some of the questions you may have (or should have) asked yourself if you have used dynamic controls.

All these questions combine with your particular situation to create a mind boggling amount of permutations. I can't cover them all without writing whole book on it, so it will be up to you to try and adapt this knowledge to your own needs. But I will cover many scenarios that should be similar to your own.

Simply having the knowledge of how to use dynamic controls doesn't mean you should! In a future episode I will cover some of these scenarios and how you can simplify your life by avoiding them altogether. But for the purposes of this section, I will be using examples that you wouldn't do in the "real world". For example, there's no good reason why you should create a label dynamically when a static one would suffice. So don't take these examples literally.

 

The Control Tree

The Control Tree is a data structure. Strictly speaking, it is an implementation of an ordered tree where the type of each node is System.Web.UI.Control, and the root node's type is System.Web.UI.Page. The actual types may be derived from those types, of course. The root node isn't really a special case since Page derives from Control. It's an ordered tree because the order of the child nodes matters. That is to say, the fact that the "First Name" label control occurs before the TextBox, for example, is important (it is important but not necessarily for the reason you think... more on that later).

From a particular node in the tree (a control), you can get its child nodes (child controls) through the Controls property, which returns type System.Web.UI.ControlCollection. On the surface, this collection is not unlike any other collection. It has methods like Add, AddAt, Remove, RemoveAt, Clear, etc. You can also get to a parent node through the Parent property, which returns type Control. The root node (the page) has no parent just as described in the definition of an ordered tree, and thusly returns null.

Manipulating the Tree

Thinking about ASP.NET's control tree from the perspective of a strict ordered tree is a good thing. If you take a look at the Wikipedia definition again, it defines the following typical operations for manipulating this data structure. Adjacent to each operation I have included sample code that demonstrates it:

  • Enumerating all the items:
    private void DepthFirstEnumeration(Control root) {
        // deal with this control
     
        // visit each child
        foreach(Control control in root.Controls) {
            this.DepthFirstEnumeration(control);
        }
    }
  • Searching for an item:
    // read about the control ID syntax later
    this.FindControl("namingContainer$txtFirstName");
  • Adding a new item at a certain position on the tree:
    Control myControl = this.LoadControl("~/MyControl.ascx");
    someParent.Controls.Add(myControl);
  • Deleting an item:
    someParent.Controls.Remove(myControl);
  • Removing a whole section of a tree (called pruning):
    // build a mini-tree
    Control myControl = this.LoadControl("~/MyControl.ascx");
    myControl.Controls.Add(new LiteralControl("First name:"));
    myControl.Controls.Add(new TextBox());
    someParent.Controls.Add(myControl);
     
    // removes the entire mini-tree we just built
    someParent.Controls.Remove(myControl);
  • Adding a whole section to a tree (called grafting):
    // build a mini-tree
    Control myControl = this.LoadControl("~/MyControl.ascx");
    myControl.Controls.Add(new LiteralControl("First name:"));
    myControl.Controls.Add(new TextBox());
    // adds the entire mini-tree just built
    someParent.Controls.Add(myControl);
  • Finding the root for any node:
    // get a control's parent
    Control parent = someControl.Parent;
     
    // or... get the root to the tree given any node in it
    while(someControl.Parent != null)
        someControl = someControl.Parent;

Note that consistent with the definition of a tree, a node may only have one parent at a time. So lets say you tried to be sneaky by adding the same control to two places in the tree:

Control foo = this.LoadControl("~/foo.ascx");
this.Controls.Add(foo);
Control bar = this.LoadControl("~/bar.ascx");

this
.Controls.Add(bar);
foo.Controls.Add(bar);

No. This isn't going to throw an error or anything though. When you add a control to another control, and that control already had a parent, it is first removed from the old parent. In other words, this code is essentially the same thing as doing this:

Control foo = this.LoadControl("~/foo.ascx");
this.Controls.Add(foo);
Control bar = this.LoadControl("~/bar.ascx");
this.Controls.Add(bar);

// remove it

this
.Controls.Remove(bar);
// then add it to a different parent
foo.Controls.Add(bar);

There may be times where you would legitimately move a control from one parent to another, but it should be extremely rare.

Controls are notified when their child control collection is modified. As a control or page developer you can override these methods. This only applies to immediate children though.

protected override void AddedControl(Control control, int index) {
    // someone added a control to this control's collection
    base.AddedControl(control, index);
}
 
protected override void RemovedControl(Control control) {
    // someone removed a control from this control's collection
    base.RemovedControl(control);
}

 

Be Responsible for the Control Tree

Building the tree is your responsibility. Whether you do it dynamically or statically, it doesn't matter. One way or another, the controls of the tree must be completely reconstructed upon each and every request to the page. A common mistake made by developers is to add a dynamic control to the tree only in response to some event. For example, lets say you have a button on the form that reads, "Click here to enter your name", and when clicked you want to dynamically add a TextBox for them to enter it into:

private void cmdEnterName_Click(object sender, EventArgs e) {
    TextBox txtFirstName = new TextBox();
    this.Controls.Add(txtFirstName);
}

If you try it, you'll see the TextBox like you expect. But on the next postback, unless the user clicks this very same button a second time, the TextBox will cease to exist! Many developers who run into issues like this believe it to be due to broken ViewState. They scour the internet with queries like "how to enable ViewState for dynamic controls". It has nothing to do with ViewState. ViewState is responsible for maintaining the state of the controls in the control tree -- not the state of the control tree itself! That responsibility lies wholly on your shoulders. Dynamically created controls will not exist on the next postback unless you add them to the control tree once again. Once a control is a member of the tree, the framework takes over to maintain the control's ViewState. Of course, there are some gotchas with that, which will cover in more detail.

Okay. How then can we solve the problem stated in the example? As I've mentioned already -- and you will hear more about it later on -- when it comes to dynamic controls, just because you can doesn't mean you should! It's always better to statically declare controls whenever it is humanly possible. You will save yourself a lot of work. So the solution here is to avoid using dynamic controls in the first place.

Simply declare that TextBox on the form, but mark it as Visible="false". Then when you need it, just make it Visible. When you don't need it any more, make it not visible again. Simple as can be.

By the way, some of you may be aware of a little control out there called the DynamicPlaceHolder. I am reluctant to even mention it, because if you haven't heard of it you might go looking for it and then you will be tainted. Please, don't use it!!! I'm not saying there aren't times where a control like that could be useful -- but 99.999% of the time it is the wrong way go about solving a dynamic control problem. There are all sorts of disadvantages to using it, all of which can be avoided by solving the problem "correctly" to begin with. I believe this so firmly that if you find yourself using it, I will personally work with you via email to find a better way.

 

Revisiting the Repeater

This is somewhat off-topic but it's the perfect time to point this out, and it drives home the "responsibility" point. Remember in Part 2 we discussed how a Repeater uses it's ItemTemplate to repeatedly create controls dynamically? (If you skipped part 2 I highly recommend you go back and read it before continuing). If you stop and think about it for a moment, you might realize that the Repeater (or any other databound control) has a problem to deal with. When you call DataBind(), it creates the template one time for every data item. How is the Repeater going to follow the guideline that all controls must be recreated on every request? On a postback, you aren't databinding the repeater again (because you usually only do it when !IsPostback). So how can the repeater possibly rebuild the control tree, when it doesn't have any data to work with?

Simple. All it has to do is remember how many data items there were. When you call DataBind(), not only does it use the template for each data item, but it takes the number of data items there were and stores it in a ViewState field. Then on a postback, it waits for LoadViewState to occur, gets the count value back out of ViewState, and then uses the template count times.

Volia! As it creates each data item by using the template, the controls contained within it become "rooted" in the tree. Thanks to the framework, the instant they are rooted, their ViewState is loaded, and are restored to the state they were in when you first called DataBind() many requests ago. It's really a brilliant process. The repeater itself doesn't have to remember any state information about the controls it contains. All it has to remember is the number of items, and let the framework do the rest.

If you've ever dealt with the ItemCreated and ItemDataBound events on the Repeater (or again, any other databound control that has them), now you can appreciate the distinction, and why the DataItem is not always available.

 

The state of the Control Tree

We now have all the building blocks. We know how to create various types of dynamic controls. We know how they differ (or don't differ) from static controls. We know how to add these controls into the control tree. And we understand that it is up to us to rebuild that tree every request.

But the control tree isn't just a data structure. It's an evolving data structure. Throughout the page event sequence, the state of the tree and all the controls within it change. That has implications for dynamic controls, because your dynamic controls are going to be late-commers in the game. For example, if you add a control during the Load event, your control hasn't even had its Init event fired, but its older siblings will have already gone through Init, LoadViewState, and LoadPostData. If you are the youngest one in your family, you know how mean older siblings can be. Obviously, the framework has a way to regulate this situation (and it doesn't involve turning the car around).

Basically, dynamically added controls play "catch up" with the state of the tree. The instant a control becomes "rooted" in the control tree, the framework fast-forwards through the events in that control along with any children it contains until it has caught up with the page's current state. By "rooted" I mean that you can get to the root node of the tree by following the Parent properties. Here's an example:

PlaceHolder ph = new PlaceHolder();
ph.Controls.Add(new TextBox());
// at this point, neither the PlaceHolder or TextBox are "rooted"
 
this.Controls.Add(ph); // moment of rooting!
// now the PlaceHolder _and_ the TextBox are "rooted"

This example assumes that the control represented by "this" is already rooted in the tree. If the control that contains this code isn't rooted when this code runs, then those controls aren't rooted yet either.

So Controls.Add() isn't an innocent addition to a simple collection. It immediately has an impact on the control being added, as it "grows up" at an accelerated pace. But the steps through which the control is fast forwarded don't completely correspond to all of the events available on the page. The only events eligible for this process are (in order): Init, LoadViewState, Load, PreRender.

You must memorize that list...

Part 4, soon to come, will explore the different stages of the page event sequence in which you may be adding dynamic controls to the tree, and the scenarios they are meant for. Originally I planned for that to be part of this part, but it seems I'm so long winded I have reached Windows Live Writer's maximum length for a blog post!

104 Comments

  • I'd love to read your posts, they look great, but the CSS makes it unbearable to my eyes. The code samples give me a headace (okay, usually it's because I have the IQ of a small badger, but this time it's because of the design of this page).

  • You wrote: ".Thanks to the framework, the instant they are rooted, their ViewState is loaded, and are restored to the state they were in when you first called DataBind() many requests ago."
    But for load ViewState it must be saved? Who saves ViewState for Repeater's items? Repeater itself holds ViewState of all its items?

  • RussianGeek -- each control in the control tree is responsible for its own viewstate. The repeater does not need to do anything special to maintain the state of its child controls, it happens automatically.

    "Who saves ViewState for Repeater's items"? The framework does.

    You could test this for yourself by dynamically creating a label in your OnInit. Put some code in a button handler that modifies the text of that label. Then do a regular postback. The label's text remains what it was changed to, and you didn't have to do anything special to enable that. The control just needs to be a member of the control tree to participate in state management.

  • Plip -- I'm sorry it doesn't suit you. But its hard to decide if I should change my theme when I get just as many compliments about it as I do complaints. I wish I could provide a way for each user to choose their own theme, but I don't have time to write my own blogging engine.

  • I don't much like reading in this kind color scheme.
    It looks pretty, but to be able to read it, I do a CTRL+A to select the whole page. This puts most of the content as dark on white.
    Pasting the whole thing into MS Word seems to produce a very readalbe output.

  • RussianGeek -- in order for the server to know about changes on the client, the data has to be posted by the browser when there's a form submission. Labels are just spans on the client, so there's no way that data is going to be saved. The example I stated was for server-side updates to the label.

    To accomplish that you'd have to create a new server control that mimics a label, except that it stores its value in a hidden form field just prior to form submission -- and then you'd need code to handle loading that value on the server side (by implementing IPostBackDataHandler). It's a bit outside the scope of this article, and too much to explain in a comment.

    Unless you need this kind of functionality in lots of places, it might better if you just manually maintain the value in a static hidden form field, and then handle its value in the page itself. Or you can use Atlas (err, MS ASP.NET Ajax).

  • No URL hacking allowed, no sir. Good thing CS knows it's not published, because there's a draft in the works and that is indeed the url to it :) You wouldn't like it in its current form though...

    The team has been pretty busy with the latest Microsoft AJAX library these past few weeks. Hopefully sometime soon I will get a chance to post some updates here. Thanks for reading :)

  • Ch -- UserControls basically nothing more than Server Controls whose control tree is built automatically. So yes, they absolutely make things easier for you. Server Controls are more complex to write but have the advantage of being easily portable. Whatever works best for you, you should do that.

    Server Controls are best suited for highly redistributable controls that may be useful in many different scenarios or may be used in many different applications.

    By the way, there's a way to convert a UserControl into a Server Control (afterall that is basically what asp.net itself does when it generates the dynamic class based on your markup). You lose the ASCX of course, so maintaining the control still needs to be done in the original ASCX. Sorry I don't have a link for you but you should be able to find something about.

  • Dave - thanks for the guidance..
    Just curious to know If atlas ajax framework does offer full support in creating any type of Atlas(&Toolkit) controls(e.g-july CTP) in 100% dynamic fashion in a Custom Server control? If so, do you have any recommendation to watch out for any pitfalls in this approach...
    Your reply would really help me to decide If I should go down the path of creating Custom Server Control(DLL) or User Control(ascx) especially in context of Integrating Atlas Ajax feature..
    Thank for your time & help!
    Ch.

  • Ch - dynamic scenarios are definitely feasible with the Ajax framework. For example, recently support was added for dynamic update panels.

    Still, as with any controls, its generally easier to work with user controls. Personally I favor server controls because I usually make my controls useful on their own. But when developing a specific use application and you are just developing pages, user controls are often the way to go for the added agility.

  • i just cant wait for your next article .. Every day i check for your new articles .. You really hit the problem at the root..
    Thanks for this great effort..  

  • How do you call a method of a dynamically loaded user control ?. I have a public method in the userconrol (ascx) which I wish to call from the parent ASPX page.
    Thanks

  • You need to cast the control to the type of your user control, then you can use whatever properties or methods on it you want.

    The trick is figuring out how to do the cast -- it depends on how your project is setup. If you have a plain jane asp.net website project, then you will have to add a reference to the user control from the control or page you need it from (there's a directive you can place).

    If you are using a Web Application Project type (this was an out of band project template release that makes the compilation model of asp.net 2 more similar to the compiliation mode of asp.net 1), then the type of the user control's code behind should already be available to you with no special work.

  • Great article. Looking forward to part 4.

  • HI

    How would you handle the situation where clicking a button adds a control to the page? Say I want to keep using this dynamically created control on subsequent postbacks, however, using other buttons to perform the postbac.. I now have to remember that I have created that control. Is this the best way to handle this?

    (ASP 2.0)
    --- ASPX ---







    --- Code behind ---
    public partial class Test : System.Web.UI.Page
    {
    protected void Page_Load(object sender, EventArgs e)
    {
    if (ViewState["CreateTXT"] != null && ViewState["CreateTXT"].ToString() == "yes")
    CreateTXT();
    }

    protected void butCreate_Click(object sender, EventArgs e)
    {
    CreateTXT();
    ViewState.Add("CreateTXT", "yes");
    }

    private void CreateTXT()
    {
    TextBox txt = new TextBox();
    txt.ID = "txt1";
    txt.Text = "Default";
    this.form1.Controls.Add(txt);
    }

    }

  • Valimai -- that is basically what you have to do, except you should do the if(ViewState["CreateTXT"]) check in LoadViewState (after calling base.LoadViewState) instead of Page Load.

    Or like I said in the article -- avoid doing it dynamically in the first place. If it must be dynamic, then if its possible to just load it every request do that -- then you just have to make it visible when you need it. And if that isn't possible... the solution you pasted is pretty much it.

  • hi, great article. I've got a problem with dynamic controls and ajax update panel. I have Update panel on my page and PlaceHolder in it. Than i put dynamic created  User controls inside, after one of my buttons is clicked. I really need to do it this way as I have to create 'no-reload' page. The problem is: When I click one of the buttons inside user control, the content of this control disappears. I can't figure it out.  Could you help me? thx in advance

  • p --

    Controls must be re-created on every request. If you are only creating the dynamic control in response to a button click, then it isn't going to be recreated again on the next posback (unless it was via the same button).

    It would be easier on you if you didn't do it dynamically -- just declare the control as invisible, then make it visible when you need it and vise-versa. If that doesn't suit your needs you will have to be sure to load the control dynamically on other postbacks. You can do that by setting a ViewState variable in your button click event, then override the LoadViewState method, and if the variable exists (after calling base.LoadViewState), load the control. In the button click you will have to Controls.Clear the placeholder, since the control may already be loaded.

    I hope that makes sense... feel free to send me some code examples via the contact form and we can work on it privately.

  • RE: OnInit vs OnLoad

    Scenario: I have a drop-down list of two "form" choices and a placeholder for the "form" to be loaded. I want to display the selected form when the SelectedIndexChanged event is fired. Therefore, the LoadControl( formX.ascx ) will occur after the OnInit (and even after OnLoad) event(s). The form selected is saved in a ViewState entry to be reloaded during the postback.

    Problem: To see what's wrong with this, I might as well quote the MSDN docs:
    "When you load a control into a container control, the container raises all of the added control's events until it has caught up to the current event. However, [... f]or an added control to participate in postback data processing, including validation, the control must be added in the Init event rather..."

    So while the form loads properly when added in the SelectedIndexChanged event or in the OnLoad (i.e., when reloaded in a postback from the ViewState-saved form name), it is a non-functional form since postback processing does not occur for controls loaded after OnInit. Since ViewState is not available in the OnInit, using the ViewState method only works if we load the control from the OnLoad event!

    Solution?: OK, so maybe I can give up the nifty SelectedIndexChanged event and use of ViewState. I could just use the drop-down's value from the Request[] variable from within the OnInit. This does work. However, this little example is a prototype for a more complicated real-world example where I have a menu (which changes based on who logs into the system) and potentially 100s of forms. Plus, I think using ViewState like your examples make a lot of sense.

    Is there a way out of this circular problem of OnInit form-loading vs OnLoad/ViewState form-loading? If not in this version, in a future version of ASP.NET?

    Thanks!

    P.S. For bonus points, how do I clear the ViewState and postback values that share the same textbox IDs when switching from one form to another?

  • Jonathan -- whew, okay. Here we go.

    First of all, dynamically loaded controls that are loaded during OnLoad _do_ go through postback processing, but not until after OnLoad. This behavior usually allows these controls to act normally, but it can cause you trouble if you depend on the state of those controls from OnLoad, since they won't be updated yet.

    But... what you should do is override the LoadViewState method. Call base, and then immediately after, reload the user control. That way you are loading the control before the first pass to process postback data. That is basically what databound controls do -- as soon as viewstate is loaded they repopulate their dynamically generated UI, and then all the controls that were created live happilly ever after.

    Bonus points... I love bonus points... what can I use them for?

    You should make sure each potential form gets an ID that is both unique to it and won't ever change while someone sits on the page. The easiest way to do that would be to create an ID based on the data element's ID, like form1, form2, form3, etc. Be sure to assign that ID after loading the control in the indexchanged event as well as in LoadViewState. It is crucial that you give the form an actual ID in this scenario -- because you will be loading it at different stages of the lifecycle. First it is loaded after OnLoad (in the event) and then later it is loaded before OnLoad (in LoadViewState). Depending on the form, if you allow the control to assume an automatically generated ID, the ID it is assigned could be different on a postback, and you will likely lose state.

    That being said, even without this recommendation, I'm not sure I see how you could get "crossover" of viewstate or form values. Imagine you are on form1, and the user has just selected form2 and a postback is occurring. First, LoadViewState is called, and you load form1 in order to restore the form to its known state. form1 now loads its ViewState. Then SelectedIndexChanged fires. You remove form1 from the page (ala placeholder.controls.clear() or something), and then add form2. Finally, form2 saves its viewstate.

    If you are seeing some kind of crossover I'm interested in hearing exactly what the setup is.

    _Happy_ coding!

  • Jonathan -- sorry for the delay, community server classified your comment as 'spam'. Hmmm... :)

    j/k

    When I said set the ID of the form I didn't literally mean the FORM tag. I mean the user control that you are loading. You seemed to refer to them as 'dynamic forms' that you are loading, hence the confusion.

    As for the docs, notice it also says 'participate in validation'. I havent looked into that but there could be truth to that statement. Not sure. Either way... as with any system, the docs are usually just a starting point. Truly understanding the system takes experience.

  • Hey There,
    Great articles! I'm still kindof working towards my ahem moment, but thought I would ask you a question anyways.
    You mentioned,
    "If you have ever dealt with dynamic controls before, you know that they really are different. It's true that your experience with them may differ from that of static controls, but it's important to understand exactly why there's a difference. The difference has to do with when the control enters the control tree. When the framework adds controls, it does it extremely early in the event sequence. The only thing that happens first is the control or page constructor. That's why in OnInit or OnPreInit, the controls already exist and are ready for use (well, master pages can 'mess' with that process, but this is still a true statement)."
    can you elaborate on what 'mess' maters pages can cause on this topic?
    Thanks and happy New Year.

  • Dave -- thanks :)

    The controls on the page are moved after they are added, when there is a master page. So by 'mess with', I just mean they may be shuffled around. If you override OnPreInit you may need to call base.OnPreInit before the control instances are assigned to their respective declared variables. I'm not 100% sure about that, its been a while since it affected me.

  • Keith -- Assuming you are only databinding it on the initial page load, you will have to enumerate over the items and update them. But you need to do it after they've been restored and loaded postback values. You also want to do it after ViewState has been saved so that your changes don't dirty viewstate. The change doesn't need to be persisted into viewstate because you're going to update it every request.

    The only place you can do that is ... Render.

    Override render on your page (or control, whichever you are adding it to), and do something like this:

    foreach(RepeaterItem item in repeater.Items) {
    Control chk = item.FindControl("chk");
    Control ddl = item.FindControl("ddl");
    ddl.Visible = chk.Visible;
    }

    The ID passed to FindControl should match the declared ID of the checkbox and dropdownlist.

  • GREAT article!

    From one of the comments above, I got the impression that controls such as the asp.net repeater build their dynamic controls when the LoadViewState is called i.e.

    protected override void LoadViewState(object savedState)
    {
    base.LoadViewState(object savedState);
    this.EnsureChildControls();
    }


    protected override void CreateChildControls()
    {
    //Construct the controls here
    }

    I have built a control that has a dynamic number of updatepanels (each updatepanel represents a tab). I plan on storing the number of tabs and the selected tab in the control state.

    So my question is can I do/should I do the following instead:

    protected override void LoadControlState(object savedState)
    {
    //load the controlstate data here
    this.EnsureChildControls();
    }


    protected override void CreateChildControls()
    {
    //Construct the controls here
    }

    It strikes me that the num of tabs and the selected tab should be stored in the controlstate rather than the viewstate?? Thoughts??

  • Rick -- I think I'd stick with ViewState in this case. You should be very careful when using ControlState, because users of your control will have no way to disable it. Perhaps the user of your control wants to disable ViewState, and then redatabind it every request? If you use ViewState you are giving them that option.

  • In this case, I actually dont want them to be able to disable this functionality! Many thanks for your help!

  • Hi again. LoadViewState doesnt always seem to be called. Is it only called when there is available viewstate?

  • I am also creating a tab control.
    Should my tab control remember the controls added to it?
    should the developer be forced to manually add each control to the tab control on each postback, or should the tab control rebuild them automatically?
    Your opinion would be appreciated!

  • Rick -- The way Repeater and DataGrid do it is by placing control over their structure in the hands of the user of the control.

    You can't repeater.Add(item) to grow a repeater by one. To do it, you have to change the underlying datasource, reassign the datasource to the repeater, and re-databind it.

    If you want to be consistent with DataBound controls, you should work on a similar model. Your datasource is the list of tabs (maybe just a number, for how many). The user of your control must assign that list (or number) then call DataBind. I understand you are trying to increase perf by growing 10 at a time... its an interesting idea, but it does really complicate things. I think you'll find that rebuilding the tabs doesn't really take too much time.

    If you'd rather stick to your current model, you'll have to work around issues like this creatively and in your own way. Your queueing idea is one way to go.

  • Tom -- Well, if you design it correcty, you can have it both ways. A databound control can work with ViewState disabled if it is databound every request. Or, it can work with ViewState enabled if it is only databound once. The beauty is that it's up to the user of your control, giving them more flexibility. And it's consistent with the existing controls, so they should already know how to use it.

    So... expose a DataSource property, which is appropriate for whatever source you need. Then override DataBind and build the tabs there. Remember how many there are in ViewState, and just after LoadViewState, rebuild that many tabs (thats the basic scenario -- you may need to use Templates if you plan on the user having content declared inside the tabs). In your DataBind method be sure to clear your control tree first, since you may be databound after already rebuilding the tabs.

  • Rick -- as you figured out, LoadViewState doesn't occur on the first request. It should always be called subsequently.

  • When ever I create a WebControl, I build its child controls in the CreateChildControls method.

    I then use the EnsureChildControls throughout my control i.e on Controls collection accessor.

    When building usercontrols that require controlstate to determine which controls need to be built, I also build my controls in CreateChildControls.

    As a rule of thumb should I try to always create dynamic child controls in the CreateChildControls method?

    How do you approach this problem?



  • Rick -- CreateChildControls is the right place to setup your control tree when you're creating a custom control. But not for UserControls, because their control tree is parsed and created long before that.

    You need to keep in mind that you shouldn't depend on state information to determine what controls to create. The reason is simple: You don't know which will come first, the state or the controls. If you depend on a property, someone may change the value after you create the controls. If you depend on ViewState or ControlState, what if EnsureChildControls is called before it has been loaded?

    Instead, you need to create every possible control that you might need, and make sure they are all added to your control collection. Then, you can control the visibility of those controls through state. For example if you're depending on a property value, and it gets set to ABC, which means control X should not be visible but control Y should be, then you can set their visible properties as such right away, within the property setter.

    If this scheme doesn't work because you have a variable number of controls that could be created based on the data, then what you really need to create is a DataBound and/or Templated control.

  • Hi Dave,
    Nice article. I am trying to add controls dynamically to updatepanel (the ajax one, of course) and somehow they disappear on postback.. be it ajax postback or a normal one.  
    TextBox t = new TextBox();
    UpdatePanel2.ContentTemplateContainer.Controls.Add(t);
    This control loads the first time, but if any control, like say a button, outside the update panel, causes a postback, this text box does not load again. This could be expected to happen, but what is the solution to this?

  • Ameya -- where is this code exactly? OnLoad, OnInit, etc? Is it occuring every request?

  • In order to access view state and control state, I build my controls in the CreateChildControls method of my bespoke control to ensure that LoadViewstate and LoadControlState will already have been called.

    This is fine, I can access controlstate and viewstate.

    However, when I build a textbox in the createchildcontrols method, it doesn't remember its typed in value.

    i.e.

    protected override CreateChildControls()
    {
    Textbox tbx = new TextBox();
    tbx.id="test";
    this.controls.add(tbx);
    }

    It does remember its values, however, if I build it during the oninit method.

    Why doesn't it remember the values when its built during the CreateChildControls phase? Is this a TrackViewState issue???

    Everytime I think I understand this stuff, I realise I dont at all!

    Very, very frustrating!

  • Rick -- it may be ok in your specific scenario, but keep in mind that there is no contract anywhere that says CreateChildControls runs after LoadViewState. It can be called at ANY time. If someone calls FindControl on your control, that calls it. If someone derives from your control, they might call it earlier than you expect. You really should try hard not to depend on state information from CreateChildControls. Usually there's no need for it, because there are other ways to handle it, such as by creating all the controls you might need, then showing/hiding the appropriate one once LoadViewState occurs. Or if that isn't enough, you can create the controls directly from LoadViewState (this is basically what databound controls do).

    But anyway, moving on :)

    The latest CreateChildControls can be called in during the PreRender phase. At that point, its too late for controls to load their posted values. You might think thats the problem... however, when a control has a posted value in the post data, the framework tries to 'find' the control by calling FindControl. FindControl ends up calling EnsureChildControls, which calls CreateChildControls, so your textbox should be created just in time. If that is not happening it could be because the framework can't find that control, or that the textbox is not posting anything (it could be outside of a form tag, or it could be 'disabled' in html). Try one easy thing for me... actually two. First, see what happens if you remove setting the ID of that TextBox. So remove the id ="test" line. Whether that works or not, put it back, and make your control implement INamingContainer.

  • I've got the same problem when trying to add a control dynamically in an update panel. Basically it dosen't seem to invoke anything when you try a postback!!! In fact it actually disappears. If I put the control into the update panel normally (i.e register it at top of page and insert the server tags) then it works fine. Something that looks like it needs to be ironed out for another ajax release.

  • darren -- can you post some code? It definitely shouldn't disappear, so there may be a something else going on.

  • David -- what is this Initialize() function? Whomever is calling it, do they call it every request or only on non-postbacks? During which page event do they do it?

    The fact you aren't calling EnsureChildControls until after building the checkbox list's data means the checkbox list will not be "rooted" in the control tree when this is done -- unless someone else is calling EnsureChildControls from somewhere else -- which means it will not store its data in viewstate, because it wasn't tracking yet.

    If you intend for the call to Initialize to 'stick' in viewstate so that it doesn't need to be called on postbacks, you should call EnsureChildControls at the TOP of Initialize not the bottom.

    Essentially you've created a DataBound control. You should follow the pattern that other databound controls use. They should call DataBind, not Initialize. The data they pass in through Initialize should be properties instead. This makes your control not only look and behave like other databound controls, but it makes it possible to use completely declaratively. You cant call a method declaratively, but you can set properties. Know what I mean?

  • Just moving the EnsureChildControls() call helped a LOT. I just started creating custom controls this week, so I guess the Initialize sub was my way of allowing the user to set some values, save those in a ViewState, and then load the databound CheckBoxList.
    They would be calling Initialize in the Page_load on non-post-backs. Moving EnsureChildControls() allowed that to work, but now that you pointed out that this is really just a Databound control, I'm intrigued on how I can make it more like other databound controls.
    Can you give me an example of how they would do this with Databind? Do I override the Databind function, get the properties they set when the control was created, and then load my checkbox list within that? When does the user call Databind()?
    Thanks for your help!

  • David -- you've got it. You override DataBind and just refer to the properties they have set. It is at that point when you commit things "to memory", viewstate.

    When does the user call it? Whenever they want to! They may want to disable viewstate can databind you every request. Or they may do the usual only on non-postbacks. It shouldn't really matter to you. Just be sure and 'clear out' any existing controls or data, as they could possibly call databind on you more than once in a request.

  • Realy useful articlefor the Developers as well as learners, if possible please post an example for dynamically addition of controls to a aspx page and the sequential methods we have to follow for that. Those controls values should be editable and should be saved in database during postback. Thanx for this and the all the best for the next...

  • Where's part 4 of the series??

  • I create a bunch of dynamic controls (including AjaxControlToolkit controls) in OnInit() using ParseControl() from a string that I build up by parsing a custom XML file. I then add the resulting control tree into a placeholder control that I have on the form. This was all working fine.

    Then, in an attempt to reduce the overhead of parsing the XML and calling ParseControl() every time, I store the resulting control tree in the Session object. So OnInit() first looks for the control tree in the Session object. If it doesn't find it, it build it from XML & ParseControl() and adds it to the Session object. Subsequent requests (e.g. post backs) now find the control tree in the Session object and add it directly to the placeholder control without reparsing.

    For some reason, there are problems with some postbacks (specifically those where I need to modify properties of some of the dynamic controls). When I attempt to do this using the control tree retrieved from the Session object the AtlasControlToolkit throws the following exception: "Extender controls may not be registered after PreRender."

    Is there some reason that using the control tree from the Session object would screw up the page lifecycle in some way that it would cause this problem? If I skip the Session usage and reparse the XML and recreate the control tree for each request, the problem goes away.

  • Yes that is a problem. You cannot "cache" controls. They go through the page lifecycle and data internal to them changes. Controls are meant to be thrown away after they are used, they cannot be used to serve multiple requests. There are all sorts of reasons why it won't work.

    What you should do instead -- don't use parse control in the first place. I don't know what your xml file looks like, but you should be able to design this such that you parse the xml file into meta information about the controls (control type, properties, etc). You cache THAT. And then you use that meta information tree to create the controls fresh every request. You could also potentially go straight from the xml file into control instances, then there's really nothing to cache except the xml itself. Basically -- cut out the middle man -- result of parsing xml is controls, not a string which is parsed into controls.

    Again I don't know what your xml file looks like, but have you considered using templates in ASCX files? You can use LoadTemplate to load an ASCX file, and then call InstantiateIn(placeHolder) to instantiate the tree. This would have all sorts of advantages -- the format of the file isn't some custom xml format, its actual asp.net markup, the control is parsed and compiled just like regular user controls.

    You could also dynamically write the ASCX file as a result of parsing your xml file, then call LoadTemplate on that.

    Lots of options.... none of which involve ParseControl.

  • Hi, i have have a need to create a grid with nested grids for each data item. I am using one grid (the parent) and a template column with a placeholder (you said to avoid). If the record has details i add a subgrid in the position of the place holder, the result is an n level nested grid. I am creating this structure on the oninit stage.

    As you can imagine it has become tricky. Do you have a better approach that will achive the same result?

    Jackietrillo@bellsouth.net

  • You can put the nested grid directly within the template column. You don't need a placeholder. You can assign the inner grid's DataSource property the same way you can assign other properties via databinding syntax...

    <asp:DataGrid ... DataSource=''>

  • First of all, your article is extremely helpful. I've read and re-read it over and over (the concepts can be quite confusing!). However, it seems like I'm following all the "rules" but still I'm losing viewstate in my dynamically added user controls upon postback. I have a button on my aspx page that when clicked, adds to an array stored in session state, clears the page of any user controls and then calls Page_Init, which then loops through the session state array and loads the controls back to the page. Everything is working perfectly, except that the controls lose their viewstate values. For example, when the aspx page first loads, it contains one user control, which has a number of checkboxes. I select a few checkboxes and then click 'Add another user', which adds another user control, but none of the checkboxes in the first user control retain their values. Here's some code for clarification:
    'Init Method
    Private Sub Page_Init(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Init
    InitializeComponent()
    'Clear the controls from the place holder

    If IsPostBack Then
    For i As Integer = 0 To Me.UserControlIDList.Count - 1
    'LoadViewState(ViewState)
    'Load the controls again
    LoadUserControl(i)
    Next
    Else
    Dim sControlID As String
    sControlID = "User" & Me.UserControlIDList.Count + 1
    Me.UserControlIDList.Add(sControlID)
    LoadUserControl(0)
    End If
    End Sub

    'Load Control Method
    Private Sub LoadUserControl(ByVal index As Integer)
    Dim newUserControl As New User
    newUserControl = CType(Page.LoadControl("User.ascx"), User)
    newUserControl.ID = Me.UserControlIDList(index)
    newUserControl.EnableViewState = True
    Me.FindControl("ContentPlaceHolder").Controls.Add(newUserControl)

    End Sub

    'Add another user control
    Private Sub lnkAnotherUser_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkAnotherUser.Click
    Dim sControlID As String
    sControlID = "User" & Me.UserControlIDList.Count + 1
    Me.UserControlIDList.Add(sControlID)
    Me.ContentPlaceHolder.Controls.Clear()
    Page_Init(sender, e)
    End Sub

    ---------------------------------------------------------------------------------
    Any help you can provide would be greatly appreciated, as I've spent several days and have made very little progress. Thanks!

  • Wade -- If you remove a control after its loaded its viewstate, you are throwing away the viewstate, too. Not just the viewstate, everything about it. Adding back another control of the same type doesn't make that control assume the same viewstate as the previous one. When you add a new control you should just append to the child controls rather than clear the entire list and rebuild it. Your Page_Init should still rebuild all of the controls, since that will happen on postbacks to get the state up to the previous state. The button handler then will have a populated list to add the single control to.

  • Hi, thanks alot for your articles - it helped me alot, but I'm facing a problem with dynamically creating rows of custom table server control.

    I want to do something like form designer and have the following problem:

    I have a server control PropertiesTable, which ihnerits from Table. It is declaratively added to the ASPX page. Then, when I click on some control on page, I populate the table with public method ShowProperties, where I loop through some properties of control and for each property I create a row in the table dynamically. The row contains 2 cells, first cell with the name of property and second cell with dropdownlist with possible values, or textbox with the value of property. That all works fine. For each row, which I add to the table, I save the info about it (like ID, type etc. ) into viewstate (also for all child controls of row - like cells, dropdownlist, textbox). On each postback I repopulate saved controls in override method LoadViewState, so the rows don't dissapear. That all works fine, but at first postback, all values of textboxes or dropdownlists are lost! In DropDownLists is selected always the first value(range of values are persisted) and TextBoxes has empty value or some strange value. What is really missunderstanding, that all other postbacks (except the first one) works fine! I really don't know, where can be a problem - I tried to debug it hours and hours, but dont know, what I do wrong.

    Many Thanks for your help in advance. I'm really desperate...

    Postol

  • D King -- sorry for the extremely delayed reply. I hope you are monitoring this comment feed. One thing you said has me worried -- you shouldn't need to totally rebuild all of the controls just because you are adding 1. You should just add the one. Separately from LoadViewState (assuming you are using viewstate to remember what items there are) you build the previous list. The event handler (or whatever adds a new one) then just appends to that built up list. If that doesn't help shoot me a private message and we'll get through it.

  • Hi I've developed a Composite control where a GridView is placed inside a UpdatePanel. This was done so that later the gridView Event could be performed asynchronously. Now the problem is the control disappears in postback. [The composite control is placed design time and all its child control is created in createchildcontrols() method]

    Can u help me?

  • Great articles. Like Karissa I find myself rereading many times. I believe that I have a similar situation to a few of last posts when there is a complicated grid involved.

    I built my grid up with some of my own classes that inherit the HtmlTableCell and HtmlTableRow. I did this because I wanted more control over the HTML produced to make some JavaScript easier to write.

    Anyway, one of the HtmlTableCell classes contains a TextBox. On the main page, I have a button which fires an event to create a new column. The column is created, but when I add a value to the text box and click save, the value disappears. If I add the value again and save, it works.

    Close inspection of the HTML reveals that the input tag in the added column has a different Id in each of the two cases. So I say, "ah hah, I'm responsible for creating the control tree in the right order." But I just can't seem to do it.

    I suspect that the id is different because the first time in the total column is created and my new column is inserted before the total column throwing off the order, but even removing the total column and adding it back in the event gives the same result. Since I'm inheriting the HTMLTableRow, I just call the row.Cells.Add method to add my column.

    Any ideas?

    Lash

  • You don't have to tell me a thousand times...

    I read the articles again (again), and kept seeing that I should create the controls earlier. So I did that. I created the new column with the rest of the them and made it invisible, only to make it visible if someone pushed the button to call the event. Works great now.

    As an aside, I find the styles of the this blog hard to read also, but I just open it in Firefox and go to the View | Page Styles menu, select No Style. That might help someone.

    Thanks again for the fabulous blog. I'll check in from time to time.

    Lash

  • And what about the continuation?

  • I dare you to post Part 4 before Oct. 16, 2007

  • So, what about the 5-th part?

    P. S. I have written you email about the 4th part some days ago. Please read it.

  • This really is a great article.
    Keep up the great work since you cover a very important area with these articles.

    Unfortunately, I think my current problem really is a bug in the repeater control.

    I fill several usercontrols (that all contain 1 repeater) by a master-detail scenario: when clicking a button in a repeaterrow the data is sent to another usercontrol that shows results based on the selected row by using hiddenfields in the ItemCommand event. All usercontrols are added as webparts in a webpart zone. I use the standard .NET interfaces for providing the connections and they work fine.

    For some reason, the '4th generation' usercontrols don't fire the ItemCommand event! I absolutely have no idea why.
    Scenario (uc = usercontrol):
    select row in uc1 -> uc2 is filled -> select row in uc2 (based on uc1 rowdata) -> uc3 is filled -> select row in uc3 (based on uc2 rowdata) -> uc4 is filled -> select row in uc4 (based on uc3 rowdata) -> NO ItemCommand EVENT.

    Can you please help me out?

  • Dave -- you're on. Oct 16th. Hopefully, it's way before that...

    Alex -- there is no 4th part yet, so there certainly isn't a 5th part yet :) Did I answer your question, I don't recall getting an email from you. If I did, sorry I forgot... if I didn't, can you resend it?

    Trifonious -- You'll have to send me some code. You can use the 'contact' link at the top to send me an email. When and where do you databind each repeater?

  • I just sent you a small part of my code.
    Thanks a lot for helping me out, it would be great when I understand this thing.

    I do all the databinding at the Render stage of the usercontrols.

  • First of all, I need to say that your articles are absolutely fantastic pieces of knowledge. I haven't met anything like this before.

    But I still haven't find the answer for one of questions:
    What bad thing will happen if I create child controls inside overloaded OnInit method instead of CreateChildControls?

  • Alexander -- thank you :)

    Bad thing? Nothing bad... thats often where and when I create them. CreateChildControls plays more of a performance kind of role than anything else... I'll cover this in part 4, which yes, I am working on actively...

  • Jon -- sorry for the delayed response to your question.

    I'm not sure what is going on without seeing some code. But by the sound of it, I'm not sure it would work no matter what you did. If you only create certain controls in response to clicking on a node in the treeview, you're going to have a problem with other postbacks, because the controls will cease to exist. You'd also have to store via ViewState some key that let you know which node they last clicked on, so you could recreate the controls (from LoadViewState) even if they don't click on the node again.

  • Shira -- You'll have to post some code.

    One thing you said though -- you store the controls in private fields. You have to understand that every request to a page is a whole new life cycle. Everything is reinstantiated. Its a fresh start every time.

  • I stumbled on your articles today and they really helped me a lot.
    I am trying to port to asp.net a win32 application which build ui on the fly reading form definition from a database. Since I am an asp.net newbie it took me days before figure out at least a structure for this task. I studied the formview control and (thanks to your article) how to dynamically implement his template. Now I wonder if in my case formview control is useful. What if I simply create textbox and manually set / get text to / from the data source ? Lot of doubts here ...
    Thanks again for your articles
    Bye
    Nicola

  • Brilliant information about dynamic control and ViewState, thanks!

    I ran into a situation that I need to dynamically add a new row (more accurately RepeaterItem) to a repeater. The RepeaterItem contains several child controls including ddl and textbox, and checkbox. And the RepeaterItem is statically defined inside the repeater in .aspx file. My C# code looks like this:

    RepeaterItem newRow = new RepeaterItem(2, ListItemType.Item); // No sure if I should use a different constructor.
    Repeater1.ItemTemplate.InstantiateIn(newRow);
    Repeater1.Controls.Add(newRow);

    The code runs, and the new created row does contain all child controls. But it seems that all controls in the new row are simply the root controls as control ID and name do not have any parent control information.

    Any idea about this? Thanks in advance...

  • Danie -- I've never seen anyone try to dynamically create a RepeaterItem like that. Very creative :) But I don't think its designed to work that way.

    Can you just re-bind the repeater? Depending on your exact scenario that may work or may not -- but if not, that may be a sign that there's a better way to design your workflow.

  • Nik -- welcome to the world of asp.net. Its a powerful framework. There are lots of different ways to approach any problem. What would work best for you, I don't know without a lot more details. But you certainly don't need to use one particular control or another to be able to dynamically create the controls based on a database. As long as you create them all every request and in the right way...

  • For dynamically added controls when does the Load event of the control actually fire. I have some 3rd party user controls I load & they throw an exception in the Page_Load & I cant figure out when in the lifecyle they get to that point to be able to catch the exception. My control hiearchy looks like this:

    1. ASPX -> Init loads UserControl1 into Panel
    2. UserControl1 -> CreateChildControls loads UC2 into Panel
    3. UC2 -> Init loads UC3 into Panel
    4. UC3 (3rd Party) -> Throws Exception

    On the first load I can catch the Exception from UserControl1 but on PostBacks it is very random when the Page_Load is executed for UC3. I load all controls on every postback btw. Is there a proper event that I can handle to catch this exception. Reading the StackTrace I still couldnt tell when this happens.

  • Arif -- controls added dynamically late in the game play 'catch up' as soon as they are added to the control tree. So for example say you add a control from PreRender... as soon as you call Controls.Add, it's going to zip through Init, LoadViewState, and Load. PreRender and SaveViewState will then occur normally after you're done with your own PreRender.

    You're confusion probably mostly comes from the fact that CreateChildControls doesn't occur at a predefined time. It can be as late as PreRender, but it can occur any time before that. On postbacks is can occur pretty early if there's a control within it that has postback data (like a textbox), but the framework calls EnsureChildControls to make sure the control exists to load its data.

    FYI you can catch any exception that occurs by any control during page processing by hooking the Page.Error event.


  • Hi I've developed a Composite control where a GridView is placed inside a UpdatePanel. This was done so that later the gridView Event could be performed asynchronously. Now the problem is the control disappears in postback. [The composite control is placed design time and all its child control is created in createchildcontrols() method]

    Can u help me?

  • Viji -- sounds ok, so there must be something going on you're not telling me :) Send me some code.. blog@infinity88.com

  • AWESOME thank you so much this knowledge is priceless. I cant tell you how long i have been battling with this and just like that you sort it out! Thanks again!

  • hi,

    "If you try it, you'll see the TextBox like you expect. But on the next postback, unless the user clicks this very same button a second time, the TextBox will cease to exist!"

    I tried the button and the textbox example and was just wondering if i did it correctly. when i clicked the button it showed the textbox as stated. i entered some information in the textbox and hit the button a 2nd time. the value in the textbox disappeared but the textbox was still there. was the textbox suppose to be there too?

    thanks,
    rodchar

  • Rodney -- the 2nd button you click is supposed to be a different button. The idea is to illustrate that the TextBox won't exist unless it is added. If you're clicking the same button you're adding it a 2nd time, which is why it exists. Click a different button that doesn't add it, and it won't exist.

  • may I add my praise to these articles. Much help indeed.

    this may be a little off-topic, but here's the idea:
    1. I store data in a session and a TabPanel has tab-controls cleared and the data added in OnInit using LoadControl from an ascx (and the .asx populated with the data)
    2. User clicks - "Add Tab" and the data is added to the Session and the entire panel rebuil as per OnInit
    3. User clicks - "remove Tab" (an event on the .ascx is raised) and the relevant data is removed via a method on the page and panel rebuilt as in OnInit

    My question : is this the correct way to go about it? I get the feeling i could just add the Tab in the Add and just remove it in the remove

  • Glenn -- I believe the tabs are similar to a dropdown's items collection. They aren't controls themselves, and they are saved in viewstate. So you would be able to just add and remove them. But to save viewstate size you might be better off rebuilding them every time anyway, especially if the data is in session which is cheap to read. Be sure to disable viewstate though.

  • Amazinlgy fast reply. Thanks!

    It would then appear that I am doing something else wrong, and I suspect the TabPanel possibly in conjunction with the UpdatePanel that is causing me headaches as the "remove panel" click, if clicking the last one, causes them all not be rendered.

    I am almost desperate enough to send you this code because as soon as it seems to work, it stops for no reason. I am feeling pretty retarded and tidying things up to make 100% certain I am doing it right.

  • I assume you are referring to the TabPanel control in the toolkit? I'm afraid I don't know much at all about the controls in the toolkit. It could be that it simply isn't compatible with update panels. There's a pretty good forum on the toolkit at asp.net, that may be your best resource.

  • Thanks once again - ViewState off, rebuilding in OnInit et al works a charm.

    My specific problem stemmed from a TabPanel "feature" that has some flawed logic in determining shown tabs when the last one is deleted.

    Dave - you have helped me incredibly much. Nice to know that I finally understand this stuff!

    Thanks.

  • Extender controls may not be registered after PreRender.

    this was fixed for me by simply changing the base class of my server control to DynamicPopulateExtenderControlBase

    e.g.

    [ToolboxData("")]
    [Designer(typeof(CollapsiblePanelDesigner))]
    [ParseChildren(false)]
    public class CollapsiblePanel : DynamicPopulateExtenderControlBase
    {
    }


    rich rendle code master

  • great article.... finally i understand!

  • I don't think that I still understand. I have two update panels on a form. One Contains a menu with many options one of which contains a table with 10 rows and 10 buttons one on each row.
    When I select the menu option it loads the ascx file containing the rows and buttons into the second update panel. Clicking on a button within one of these rows removes the contents of the second update table. The reason for using a dynamic load is quite simple: I want the page to perform like a desktop application where there is no refresh of the entire page and the update panel would be able to hold the different controls on call.

  • Kokenm -- you said it. You are loading a control in response to clicking on a button. But the next time there is a postback, that control disappears. That's because you didn't load it again. You must load dynamic controls every request.

    You can make it behave like a winforms app, but it ISN'T one -- this page is completely torn down and reconstructed every request, that isn't true of winforms.

    You need to have a mechanism as described in this series of articles that reloads the necessary control even when the button event that originally loaded it doesn't fire.

  • You told that Repeater is good, but DynamicPlaceHolder is evil.
    AFAIK, DynamicPlaceHolder does similar thing: it stores info about controls into ViewState. Then on a postback, it waits for LoadViewState to occur, gets this info and adds controls to control tree.
    Of course, DynamicPlaceHolder has been created by one person and less reliable than standard MS .Net Framework Repeater. And I agree, that Repeater and DataList is usually a better choice, than custom LoadControl.
    But will DynamicPlaceHolder do the same job, that your code in the example in part 4?

  • Michael -- dynamic placeholder and repeater do NOT do similar things :) Repeater doesn't store data about the controls, at all. All it stores is how many items were databound to it. Then all it has to do is instantiate the ItemTemplate that many times each postback. After that, the controls it created in the process, using nothing but data available on the server, can restore their own individual states as usual (using ViewState, post data, and declared values).

    DynamicPlaceHolder stores information about what the controls were as well as how many there were, and not just that, it has to store everything that would normally be declared. It needs to do that because there's no declaration like an ItemTemplate from which to figure out that data. It's a lot more information that you can totally build your app without. I'm not saying you should never use it, but you should never use it out of laziness just because you dont want to be bothered with recreating the controls every request. There's a reason the framework doesn't do it automatically.

  • You rock :)
    I had the disappearing controls problem after dynamically adding a 'random' number of controls to a page. You had the answer! Thanks!

    And to the people who "can't stand the colors" - get over yourself. It might not be the colors you prefer, but good programmers use black screens, and our eyes last a lot longer because of it. I find it hard to believe that it "hurts your eyes" and if it does, you are weak. I look at code in all sorts of colors all day long, and none of it hurts my eyes. I get less eye strain with black backgrounds, and you would too, but please... stop being overly dramatic.

    Hurts my eyes... jeez...

  • This was a great blog... first time I've come across this site. I wonder if anybody can help me...

    Does anybody have any idea how to request the form value of a textbox that's been dynamically/programmatically created within a dynamically/programmatically registered server control within a content place holder, within a master page? FindControl(X) isn't working for me!

    Thanks in advance,
    Chris

  • Chris -- the same you get the value of a declared textbox, read it's Text property. If you dont have an instance of it, then sure, FindControl is how you do it -- but you have to make sure you are calling FindControl on the correct control and with the correct ID. Make sure you are using the TextBox's UniqueID if you are calling Page.FindControl. If you can call FindControl on the TextBox's first naming container, which is probably the content place holder but could be something else, then all you need is the ID. If that doesn't work it may be because the TextBox hasnt been dynamically created for the current request yet, which means you aren't using CreateChildControls in the server control, which is called whenever FindControl is used to ensure children can be found with it.

  • Great article!

    I have a situation i am looking into. This is a search mechanism where I want the user to search for a date. When the page is loaded I dynamicaly create a TableRow in a Table with among other things a dropdownlist where the user can choose to add another identical TableRow. I am trying the approach where I am using a dynamicaly added eventHandler to my dropDownList an from the selectedIndex create another TableRow.

    The problem is of course that I do not know in advance how many TableRows the user want to add. Therfor it is difficult to recreate all of the generated conrols in the overriden OnInit function.

    Is this a scenario you have experienced or do you have a suggestion for how/where to load the dynamic controls so that the evnetHandler added on each new TableRow will be reached on the next postback?

  • geirp -- sounds like you need to take a close look at this:

    http://weblogs.asp.net/infinitiesloop/archive/2008/04/23/truly-understanding-dynamic-controls-by-example.aspx

    I think that pretty much covers your scenario in principle.

  • I've spent over a week trying to fix the issue I'm facing on my own; this is a great series of blogs, but I'm not the sharpest knife in the drawer... so I am asking for help and would even talk about contracting you to fix this issue or just teaching me what I'm missing so this works.

    I've got a 'wizard' user control where I look up a db record to see what user control I should add to a content panel on each step of the wizard. From now on in this post when I say user control I am refering to the user control added to the content panel. The wizard itself has a save button, forward and back buttons.

    I'm adding the user control to the content panel on Page Init, on every postback. The user control I'm adding has some normal aspnet textboxes, listboxes, and a checkbox. When you go to the wizard the first time, the first user control is loaded. You can enter text in the textbox, etc. and when you press save, that triggers an event that I pick up in the user control and I can write out the values to make sure they are what they should be (eventually to a db).

    When the wizard initially loads and you enter text, etc. and press save, the page posts back and I can see the data in the fields is retained, and the label I post that info to is correct after the postback. I can do this over and over again.

    However if I press the next button, the second control loads, I enter text, press the checkbox, hit the Save button, the page posts back and all the values are blank plus the label gives blank values for the fields wehre I entered data. The cotnrol is there, it got rebuilt but doesn't have the user-entered values. BUT, if I enter values again and press save again, without doing anything else, the values are retained on the second postback, and every postback after that. My label shows the values entered.

    Now if I go back to step 1, enter values, press Save, those values are now lost as well. I enter text and check the checkbox, select a dropdown list item, press save again, and on this second postback values are retained and the label shows the correct values.

    Why is it only on the seocnd postback and subsequent postbakcs on a particular wizard step that the viewstate is retained. Yes, the control is recreated in the page init event the same way each time. But only the second time is the viewstate restored. What on earth can I do to fix this issue? I'm not doing anything to load values on any of the postbacks, I'm just letting the framework do it.

    Ideally I'd rather add the controls on form load but then they never retain viewstate.

  • Rick -- it sounds like the "shifting IDs" problem. Thats where a control has a particular ID on one request, but is recreated with a different ID on a postback. Controls need to have the same ID for postback and viewstate to target them, since they had that ID when they were posted. You can avoid that by giving the user control a specific ID that is always the same, but unique to each particular step or control (e.g. step1, step2, etc). All the IDs of controls inside the user control will then also remain stable because user control is a naming container.

    How exactly are you loading and adding the user control to the wizard? Do you do it for every step in the wizard or only the active step? is there any thing else on the page dynamicaly creating controls?

  • I posted a replay the same day you answered my initial request but I don't know why it didn't post. Sorry about that.

    Adding a unique ID was the solution to the viewstate issue and I thought I was up and running but the wizard has other issues. First of all, I though the user controls I was loading (ascx pages) would act normally, going through their event cycles. This is true on the first step of the wizard, but when I click next step and put a break point on the Page Load event of the ascx control hosted in the second step, the 'if not page.is posback' code never runs because the wizard is posting back on every step except the first step. This means I can't run code 'only once' in the second and sebseuqent user controls.

    The second issue is that the page localization does not happen until the hosted for is posted back a second time. I steped through the logic and the localize code is being called, but it's returning nothing for the getstring method until the loaded control is posted back.

    Is there any solution to these issues?

    I'm loading the user controls on teh wizard's page init event; however using a button event of the wizard to load the second and subsequent steps. In the hosted form a declare a wizard object 'with events' and get a reference to the parent wizard using this code: hostWizard = DirectCast(Page.FindControl(Me.NamingContainer.UniqueID), StepWizardEvents)

    then I can hook into the events of the wizard; on the Next button click I set a public property int the wizard to True, and doing that causes the wizard to load the next step. The result is that on page init the origina control is reloaded, and then the second control is is loaded (the panel the control is loaded to is cleared of controls on every load).

    So...
    Step 1... user control loaded from ref in db; all events are normal in the hosted control (I have the not postback to work with)

    Step 2: Page Init of Wizard reloads step 1 since that's all it knows about at this point; then the property I set to true is read and since it is true, the code LoadMyControl code is called and loads the next step, loading User Control 2. At this stage, user control 2 detects it is in postback and won't run the not-postback code; and also, the strings on the cotnrol are not localized. I have a linkbutton on control 2 and click that, causing a postback, and now the strings are localized.

    Can you help? We'd be happy to pay a fee to get this working correctly.

  • Rick -- this is a classic example of why it is not good practice for controls to "drive themselves". When they do, they don't necessarily fit in very well with how the page that is using them wants them to fit in.

    In this case, what you need to do is put whatever code is in your !IsPostBack block and put it into an override of the DataBind method. Then, the hosting page just calls DataBind on each control the first time it loads it, but not subsequently.

    As for your localization, I dont know how you're doing it.

  • A really helpful article ! I was stuck for one whole day puzzling about what was going wrong with the custom web part I am building. I was suspecting something wrong with the viewstate. This article really nicely explained the concept.

  • I have a problem with a databound Accordion control from the Ajax Control Toolkit. I implemented simple data paging on my accordion in which I load the next dataset in response to a "next" button click. However, the controls inside the accordion panes do not function as expected after loading the next dataset. For instance, after paging, clicking a button inside an accordion pane results in a postback, but the button's onclick event handler is not executed and I suspect that it is due to some fault in the dynamic recreation of the templated accordion panes. It appears to fix itself after the first postback, though, because subsequent clicks work properly. I probably didn't explain my problem sufficiently, but I have a working demo (along with downloadable code) here. I would really appreciate some help figuring out how to fix this.

  • Sneak -- sounds like a shifting-ID problem. When you change to page 2, you are clearing out old controls and putting in new ones, but their automatically generated IDs are still additive. Thus the next postback they dont have the same IDs as they did before, since this time there was no page 1 to remove. You will need to make sure the top level naming containers of the page all have a stable ID that is set explicitly, so they arent automatically generated. How that specifically translates to your scenario I dunno, I'd have to see some code.

  • Sneak -- it is the shifting-ID problem, though it isn't your fault. Take a look at the ID of the header label for example. The 11th item after switching to page 2 is something like "ctrl32_lblHeader". After clicking review (and it not working), its ID is now something like "ctrl1_lblHeader". When databinding the accordian, it is not clearing the control tree in a manner which resets the automatic id counter, which is being used to give your template controls uniqueIDs (they each live within an item container, which is a naming container). You could try doing accordian.Controls.Clear() right before the call to DataBind() on it.

  • Unfortunately, clearing the accordion's controls does not reset the counter. The accordion does not implement INamingContainer. Accordion has a Panes collection. Each Pane contains two AccordionContentPanels (one as the header and one for content). It is these AccordionContentPanels that implement INamingContainer. I tried iterating through all the panes in the collection and clearing both the header controls and content controls, but still the counter is not reset. How can I force the counter to reset if clearing the controls does not do it?

  • Thanks to your help explaining the situation to me I was able to find a patch for the Accordion control.

    For anyone else having this problem, the attachment entitled "08-05-03.12-36.PatchFor33002.zip" located on this work item contains the fix: http://www.codeplex.com/AjaxControlToolkit/WorkItem/View.aspx?WorkItemId=11055

    Thanks for the great articles and for your help!

  • Am I missing something?

    When my app starts it dynamically creates the controls
    ....on each postback it also creates the controls

    The problem is the controls get created before any of the previously initiated events are fired, once they are I haev toe recreate the custom controls a 2nd time

    This seems a little screwy to me.

    How do you format it so that the events of the preceeding postback call will fire before the dynamic controls are recreated?

    Or is this impossible? It seems like bad design to form dynamic controls to be completly recreated from scratch twice per button click - that instantly makes my app go through 2x as many database transactions than if I hardcoded all my controls.

    Can someone please help?

Comments have been disabled for this content.