þControls On The Fly

A question that pops up once in a while is "How do I create dynamic controls?" From time to time, this sort of functionality is essential. How do we make the right amount of textboxes and labels for a user to enter data when the number of questions depends on a database table? How do we make enough checkboxes when we don't know until runtime what options there might be?

Applies to:
  • Visual Basic 7
  • Visual Basic 8
  • C# 1.0
  • C# 1.1
  • C# 2.0
Prerequisites:
  • Object Instantiation
  • Casting
  • Windows Forms
  • Generics (recommended)

When creating controls on the fly, there are a number of issues to address that are not quite common sense. Usually the designer takes care of everything for you, so when you are left to do it on your own you run into a few expected bumps. The questions that I will address are the following:

 

 

Creating The Controls

This is actually probably the simplest step. You use normal object creation syntax to create controls.

C#

TextBox NewTextBox = new TextBox();

VB

Dim NewTextBox As New TextBox

As you can see, though, you provide absolutely no information to the control with the constructor. How does it know what form to be on? What location to be at? What text to display? Without the designer to help us, we have to answer the question:

 

How Do I Initialize The Controls?

If you look at the designer generated code (it may be hidden from you if you are using VB8) you will see code very similar to what we will make. The exact code for initialization depends on what control you are using and why you are using it, but for the sake of simplicity, let's say that we are creating a button at a specific position with specific text and other specific things. The code would be exactly as you expect.

C#

Button NewButton = new Button();
NewButton.Text = "Click Me!";
NewButton.Location = new Point(56, 32);
NewButton.Size = new Size(64, 21);

VB

Dim NewButton As New Button
NewButton.Text = "Click Me!"
NewButton.Location = New Point(56, 32)
NewButton.Size = New Size(64, 21)

Now we need to add the controls to their container. This is also easy enough. If we are writing the code inside a form, you can use the unqualified Controls property. If you are writing code outside the Form's code, you simply qualify the Controls property with a form, i.e. MainForm.Controls instead of Controls or this.Controls.

C#

Controls.Add(NewButton);

VB

Controls.Add(NewButton)

And if you are adding multiple controls, you should use the SuspendLayout and ResumeLayout methods, similar to how you would use BeginUpdate and EndUpdate when adding or removing from a ListView, for example:

C#

SuspendLayout();
Controls.Add(NewButton1);
Controls.Add(NewButton2);
ResumeLayout();

VB

SuspendLayout()
Controls.Add(NewButton1)
Controls.Add(NewButton2)
ResumeLayout
()

 

How Do I Handle Events For The Controls?

Obviously, your controls will need to do more than sit there and look pretty. Fortunately, both VB and C# have the capability to attach event handlers at run-time. Exactly how depends on which version of what language you are using, and preference of style may come into play. Let's add a handler for our button's click event, which will use our hypothetical Button_OnClick function.

C# Version 1/2

NewButton.Click += New EventHandler(this.Button_OnClick);
// This instantiates a delegate and assigns the delegate to the event.

C# Version 2

NewButton.Click += this.Button_OnClick;
// This is simply an abbreviated syntax

VB (Handles Syntax)

' This can only be done if a variable is pre-declared with the WithEvents keyword.
' This can not be used for arrays of controls. This code goes in the form's declaration.
' This variable can not be declared in a function, which limits its usefulness.

WithEvents NewButton As Button = Nothing

' When a control is assigned to the NewButton variable, any functions with a matching 
' Handles clause will be wired automatically.

Private Sub NewButton_OnClick(sender As Object, e As EventArgs) Handles NewButton.Click

End Sub 

VB (AddHandler Syntax)

' This syntax is more flexible but not as easy in that the event wiring is not automatic.
' This is my preferred method because it is more universal.

AddHandler NewButton.Click, AddressOf Me.NewButton_OnClick

At this point, your controls will work just like the designer generated controls, but

 

How Do I Access The Controls Once I Have Created Them?

Handling events is nice and everything, but how do I change the control's text? Or see if it is checked? Well, there are several ways to access a dynamically created control. The easiest method can be used in event handlers and is great when you use the same handlers for multiple controls. The control is actually passed as the sender parameter, and simply needs to be cast to a Control or a more specific class to have its properties accessed.

C#

private void NewButton_OnClick(Object sender, EventArgs e){
    Button EventControl = (Button)sender;
 
  // Once we have a strongly typed reference, we can access
    // the control's properties.

 
    EventControl.Enabled = false;
    EventControl.Text = "Goodness! I've been clicked!";
}

VB

Private Sub NewButton_OnClick(sender As Object, e As EventArgs)
    Dim EventControl As Button = DirectCast(sender, Button);
    ' Once we have a strongly typed reference, we can access
    ' the control's properties.

 
    EventControl.Enabled = False
    EventControl.Text = "Goodness! I've been clicked!"
End Sub

What if you want to access the control outside of an event handler? You simple need to store a reference to the control. Depending on your needs, the reference can be stored in a variable, in an array, or in even in a class or structure. An array (or similar collection object) would probably be the most common method. For instance, suppose you load a list of options from an external source (file, database, website, etc.) and want to show a CheckBox for each option. Assuming that the options are stored in a string array, we simply create a CheckBox array and store a reference to each checkbox in the array. The array can be stored in a class level variable, in this case, chkOptionBoxes. No events will be wired for the controls. Nothing exciting will happen when you click them (although in the real world, you might need to save the options when "OK" is clicked). They will appear in a column 20 pixels over from the left edge of the form.

C#

CheckBox[] chkOptionBoxes = null;

private void CreateCheckBoxes(string[] options){
    chkOptionBoxes = new CheckBox[options.Length];
   
    // This variable stores the Y location
    // of the checkboxes and adds 20 pixels
    // for each one.

    int yPosition;

    SuspendLayout();


    foreach(string Option in options){
        CheckBox newbox = new CheckBox();
        newbox.Left = 20;
        newbox.Top = yPosition;
        yPosition += 20;
        newbox.Size = new Size(100, 20);
        newbox.Text = Option;

        this.Controls.Add(newbox);

    } 

    ResumeLayout();
}

VB

Dim chkOptionBoxes As CheckBox()

Private Sub CreateCheckBoxes(options As String())
    chkOptionBoxes = New CheckBox(options.Length - 1) {}
   
    ' This variable stores the Y location
    ' of the checkboxes and adds 20 pixels
    ' for each one.

    Dim yPosition As Integer

    SuspendLayout()

    For Each Option As String In Options
        Dim NewBox As New CheckBox
        NewBox.Left = 20
        NewBox.Top = yPosition
        yPosition += 20
        NewBox.Size = New Size(100, 20)
        NewBox.Text = Option

        this.Controls.Add(NewBox)
    Next
 

    ResumeLayout
End Sub

Now, at any point, you can access any of the CheckBoxes by it's index. If you prefer, you could store it in a Dictionary object and access the CheckBoxes by name.

 

What Happens To The Controls When I Am Done?

The answer to this question depends on when, how, and why you are using your nift-o-tastic dynamic controls. If the controls are created once each time a form is created then the controls will be disposed automatically when the form is closed. On the other hand, if controls will be created and destroyed continuously throughout the life of the form there are a few options.

The simplest method is to remove the controls from the form that contains them and Dispose the controls immediately.

C#

private void DeleteOptionCheckBoxes(){
    SuspendLayout();
    foreach(CheckBox c In chkOptionBoxes){
        Controls.Remove(c);
        c.Dispose();
    }
    ResumeLayout();

    // Forget about CheckBoxes
    chlOptionBoxes = null;
}

VB

Private Sub DeleteOptionCheckBoxes()
    SuspendLayout()
    For Each c As CheckBox In chkOptionBoxes
        Controls.Remove(c)
        c.Dispose()
    }
    ResumeLayout()

    ' Forget about CheckBoxes
    chlOptionBoxes = Nothing
End Sub

In a situation where the number of controls changes frequently, you might want to consider recycling controls. This may take some load off the garbage collector if the container of the controls has a long life-span (or might waste memory if the container has a short life-span, although this topic is worthy of, and discussed in, articles of its own), and saves the time and lag of allocating/deallocating large numbers of controls. The exact method depends on the exact situation, but could be something similar to the following:

C# 2.0

// An ArrayList can be used in C# 1.0
List<CheckBox> UnusedCheckBoxes = new List<CheckBox>();
CheckBox[] chkOptionBoxes = null;

void DeleteCheckBox(CheckBox c){
    // Save the checkbox for later use.
    UnusedCheckBoxes.Add(c);
    c.Hide();
   
// Depending on circumstances, removing the checkbox from the
    // form instead of hiding them may or may not be a better idea.
    // The checkbox should NOT be disposed.

}

// The checkboxes will automatically be hidden when "deleted", but
// not shown when "allocated" because they must first be initialized.

CheckBox GetCheckBox(){
    if(UnusedCheckBoxes.Count == 0) {
        // If we are out of recyclable checkboxes, create a new one.
        CheckBox c = new CheckBox();
        c.Visible = false;
// Shouldn't be shown before it's initialized.
        Controls.Add(c);
        return c;
    } else {
        // If we can, grab an old unused checkbox
        CheckBox c = UnusedCheckBoxes[0];
        UnusedCheckBoxes.RemoveAt(0);
        return c;
    }
}

VB 8

' An ArrayList can be used in VB7
Dim UnusedCheckBoxes As New List(Of CheckBox);
Dim chkOptionBoxes As CheckBox() = null;

Sub DeleteCheckBox(c As CheckBox){
    ' Save the checkbox for later use.
    UnusedCheckBoxes.Add(c)
    c.Hide()
   
' Depending on circumstances, removing the checkbox from the
    ' form instead of hiding them may or may not be a better idea.
    ' The checkbox should NOT be disposed.

End

' The checkboxes will automatically be hidden when "deleted", but
' not shown when "allocated" because they must first be initialized.

Function GetCheckBox() As CheckBox
    If UnusedCheckBoxes.Count = 0 Then
        ' If we are out of recyclable checkboxes, create a new one.
        CheckBox c = new CheckBox
        c.Visible = False
' Shouldn't be shown before it's initialized.
        Controls.Add(c)
        Return c
    Else
        ' If we can, grab an old unused checkbox
        Dim c As CheckBox = UnusedCheckBoxes(0)
        UnusedCheckBoxes.RemoveAt(0)
        Return c
    End If
End Function

The biggest problem with this method is that every property that might be set to a non-default value must be re-initialized every time the control is recycled. Otherwise you might use a textbox, set it to multi-line, and recycle it later, expecting a single-line textbox. Obviously, this can cause all kinds of problems. You will probably need to unwire events, too. The last thing you want would be the wrong handlers attached to your control's events. In C# this is done with the -= operator. In VB this is done automatically with the Handles syntax and explicitly with the RemoveHandler syntax.

C# Version 1/2

NewButton.Click -= New EventHandler(this.Button_OnClick);
// This instantiates a delegate and removes the event handler.

C# Version 2

NewButton.Click -= this.Button_OnClick;
// This is simply an abbreviated syntax

VB (Handles Syntax)

' Automatic

VB (AddHandler Syntax)

RemoveHandler NewButton.Click, AddressOf Me.NewButton_OnClick

 

All Good And Well, But...

At this point, there is a good chance that you are saying, "This is all good and well, but I'm having a hard time putting all these bits and pieces of code together and making anything happen. Don't you have some sort of working example?" If I were standing next to you and I heard you saying that to the computer, I would probably make some mean comments about talking to inanimate objects and then ask you why you don't click the links right below the text you're reading.

CS_ControlMagic.zip (67 kB)
C# Dynamic control demo

VB_ControlMagic.zip (73 kB)
VB Dynamic control demo

Both demos contain equivalent code for dynamically creating controls. The demo includes a form that creates and destroys a PictureBox control, a form that creates controls and tracks them in a List object, and a form that recycles controls.

 

 

Thomas Hudson
4/3/2006
snarfblam@gmail.com