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:
|
Prerequisites:
|
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(); |
VB
Dim NewButton As New
Button |
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(); |
VB
SuspendLayout() |
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); |
C# Version 2
NewButton.Click +=
this.Button_OnClick; |
VB (Handles Syntax)
' This can only be
done if a variable is pre-declared with the WithEvents keyword. End Sub |
VB (AddHandler Syntax)
' This syntax is more
flexible but not as easy in that the event wiring is not automatic. |
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){ |
VB
Private Sub
NewButton_OnClick(sender As Object, e As EventArgs) |
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; |
VB
Dim chkOptionBoxes As
CheckBox() |
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(){ |
VB
Private Sub
DeleteOptionCheckBoxes() |
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 |
VB 8
' An ArrayList can be
used in VB7 |
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); |
C# Version 2
NewButton.Click -=
this.Button_OnClick; |
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