Part 1: Dynamic vs. Static
Part 2: Creating Dynamic Controls
Part 3: Adding Dynamic Controls to the Control Tree

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);
}