Life Without Control Arrays in Visual Basic .NET
InStep Technologies, Inc
July 11, 2003
Summary: Control arrays were the best way to manage the controls on your Visual Basic forms, but there are no control arrays in Visual Basic .NET. Deborah Kurata describes how to use new Visual Basic .NET features to obtain control array functionality without the need for a control array. (8 printed pages)
Microsoft Visual Basic® .NET 2003
This is the first in a series of articles that describe the fundamental changes in Microsoft Visual Basic® and how to do today with Visual Basic .NET what you used to do with earlier versions of Visual Basic.
The original versions of Visual Basic provided control arrays for managing the controls on your forms. Control arrays had several benefits. They allowed you to share event procedures for a set of controls. For example, one GotFocus event procedure could handle the focus event for all of the text boxes on your forms. Control arrays provided a mechanism for iterating through a set of controls and for adding controls at runtime. And if you were concerned about system resources, using a control array counted as one control, regardless of the number of controls in the control array.
There were limits to control arrays as well. You could only put controls of the same type in a control array. If one of your input boxes was a masked edit control, it could not be in the same control array as your text boxes.
Control arrays were not consistent in the language. They were not quite collections and not quite arrays. This is why they are not provided in Visual Basic .NET. Instead, Visual Basic .NET has a rich set of features that provide all of the benefits of control arrays without the limitations.
Note Actually, Visual Basic .NET does support control arrays through the Microsoft Visual Basic .NET Compatibility library. This library allows you to retain some of the Visual Basic 6.0 features in Visual Basic .NET to simplify the migration process. Features of this library should be used only for migration.
Sharing Event Procedures
Control arrays in prior versions of Visual Basic allowed you to define one set of event procedures for all of the controls in the control array. For example, you could use a control array for all of the text boxes on your form. In the single GotFocus event procedure for the control array, you could change the background color of the text box. When any of the text boxes in the control array got focus, the background color would change. This minimized the amount of code you needed to write and ensured that the controls in your control array had consistent behavior.
How do you get that same functionality in Visual Basic .NET? The answer is in the new .NET event procedures, which are now called event handlers.
The event handler syntax in Visual Basic .NET makes it easier for a set of controls to share event handlers without the need for control arrays. An event handler looks like the following:
Private Sub txtName_Enter(ByVal sender As Object, _ ByVal e As System.EventArgs) _ Handles txtName.Enter End Sub
This event handler manages the Enter event for the text box named txtName. Notice that the generated name of the event handler is similar to the name given to event procedures in Visual Basic 6.0. However, in Visual Basic 6.0, the name of the event procedure defined the control and event that the procedure handled. In .NET, the event handler name has no intrinsic meaning. The above event handler could be rewritten as:
Private Sub ProcessEnter(ByVal sender As Object, _ ByVal e As System.EventArgs) _ Handles txtName.Enter End Sub
The .NET event handler uses the Handles keyword to define which event the handler will manage. The ProcessEnter event handler will automatically handle the Enter event for the textbox named txtName.
You can share an event handler by simply adding another event to the Handles clause like this:
Private Sub ProcessEnter(ByVal sender As Object, _ ByVal e As System.EventArgs) _ Handles txtName.Enter, txtAddress.Enter End Sub
The ProcessEnter event handler now handles the Enter events for both the txtName and txtAddress textboxes.
With the Handles clause, .NET takes event sharing to a new level because it allows sharing of event handlers for controls of different types and for different events (as long as the event handler signature is the same). This allows you to use one event handler for all of your text boxes, masked input controls, and any other .NET or third-party control.
Let’s take a quick look at the other parameters of the event handler. The first parameter defines the object that generated the event:
ByVal sender As Object
This returns a generic sender object so that the same event handler can be used for many different types of controls. To access the properties or methods of the sender object, you need to cast (or convert) the sender variable from the generic object type to a specific control type. You can do this with the DirectCast function:
Private Sub ProcessEnter(ByVal sender As Object, _ ByVal e As System.EventArgs) _ Handles txtName.Enter, txtAddress.Enter DirectCast(sender, TextBox).BackColor = Color.Wheat End Sub
If your code needs to perform differently based on the control that generated the event, you can look at the Name property of the sender object. For example:
Private Sub ProcessEnter(ByVal sender As Object, _ ByVal e As System.EventArgs) _ Handles txtName.Enter, txtAddress.Enter If DirectCast(sender, TextBox).Name = "txtName" Then ' Use one color for required fields DirectCast(sender, TextBox).BackColor = Color.Wheat Else ' Use a different color for optional fields DirectCast(sender, TextBox).BackColor = Color.LightYellow End If End Sub
The second parameter of the event handler is the set of event arguments:
ByVal e As System.EventArgs
This parameter is used by events that allow you to access event arguments. For example, the event arguments for the Closing event allow you to cancel the event and stop the closing. This is useful if you want to prevent the user from exiting a form without saving.
Iterating Through Controls
Control arrays also made it easy to perform operations on a set of controls. For example, you could clear the text for all of the controls in a control array by simply looping through the control array.
In .NET, controls are automatically added to a Controls collection. However, this collection is hierarchical, which makes it a little more difficult to work with. The first level of the Controls collection hierarchy includes only the controls directly on the form. The second level of the hierarchy includes the controls that are contained in any of the controls that are directly on the form.
This is easier to understand with an example. Say you have a form with two panels as shown in Figure 1.
Figure 1. Visual Basic .NET form with two panels
In pnlSelection you have a combo box that allows the user to select an entry. In pnlInformation you have a tab control. The tab control contains two tab pages, and on each tab page you have sets of controls. If you looked at the Controls collection for the form you would see that the collection only contains two controls:
These are the only two controls in this example that are directly on the form.
To access the controls contained in pnlSelection, you have to access the Controls collection for pnlSelection. To access the controls on the tab control on pnlInformation, you have to access the Controls collection for pnlInformation (which contains the tab control), the Controls collection for the tab control (which contains the tab pages), and then the Controls collection of each of the tab pages (which contains the text boxes).
To write a function that accesses all of the controls on the form in a generic fashion, you need to write a function that is recursive, meaning that it calls itself. The function iterates through the Controls collection and, if it finds a control in the Controls collection that contains other controls (referred to as child controls), the function will call itself to iterate through the control’s Controls collection.
Private Sub ClearForm(ByVal ctrlParent As Control) Dim ctrl As Control For Each ctrl In ctrlParent.Controls If TypeOf ctrl Is TextBox Then ctrl.Text = "" End If ' If the control has children, ' recursively call this function If ctrl.HasChildren Then ClearForm(ctrl) End If Next End Sub
This routine clears all of the text box fields of the form, even if the text box is contained within another control.
Adding Controls at Runtime
Control arrays provided an easy way to add controls at runtime. You could create the control array at design time and then add controls of the same type to the array at runtime. Again, this was limited to adding controls of the same type.
In Visual Basic .NET, you can use the Controls collection to add controls of any type at runtime. You could, for example, add a text box and a label to the Contact Information tab of the form as shown in Figure 2.
Figure 2. Adding an Email label and associated control at runtime
The following routine adds the Email label and associated text box at run time.
Private Sub AddEmailAddress() Dim txtEmail As New TextBox Dim lblEmail As New Label ' Set the desired properties txtEmail.Top = txtAddress.Top + txtAddress.Height + 10 txtEmail.Left = txtAddress.Left lblEmail.Text = "Email" lblEmail.Location = New Point(lblAddress.Location.X, _ txtEmail.Location.Y) ' Add to the collection tpAddress.Controls.Add(txtEmail) tpAddress.Controls.Add(lblEmail) End Sub
This code first creates instances of the TextBox and Label controls by using the New keyword on the Dim statement. Any desired properties of the controls are then set. Notice that the location of a control can be set using the Top and Left properties of the control, as with the txtEmail TextBox in the example code. This is similar to setting the location of controls in Visual Basic 6.0. Alternatively, you can set the location of the control by creating a new point on the form and assigning that point to the Location property of the control. This is shown in the example code with the lblEmail label control.
Finally, the new controls are added to the Controls collection for the tab page. This is needed to ensure the controls appear on the tab and not directly on the form. If the controls do not need to be on a tab, panel, or other container, you can use the Controls collection of the form instead.
Adding Event Handlers at Runtime
If you added a control to a control array at runtime, the control’s events were automatically handled by the events for the control array. With Visual Basic .NET, you need to define the event handler for the control.
You can define the event handler for a control at runtime by using the AddHandler statement.
' Add the event handler AddHandler txtEmail.Enter, AddressOf ProcessEnter
The first parameter of the AddHandler statement defines the event to be handled; in this case the Enter event of txtEmail. The second parameter defines the address of the event handler that will handle the event. In this case, the ProcessEnter event handler described earlier in this article will be reused.
The AddHandler statement can be used to wire up event handlers for controls added to the form at design time or at runtime. You can later remove event handlers for a particular control event using the RemoveHandler statement.
You can use AddHandler to write generic code that automatically connects up events for your controls. For example:
Private Sub AddEvents(ByVal ctrlParent As Control) Dim ctrl As Control For Each ctrl In ctrlParent.Controls If TypeOf ctrl Is TextBox Then AddHandler ctrl.Leave, AddressOf ProcessLeave End If ' If the control has children, ' recursively call this function If ctrl.HasChildren Then AddEvents(ctrl) End If Next End Sub
This example is similar to the code that cleared the form, but this code adds an event handler for every TextBox on the form. The ProcessLeave event handler referenced in the above example is as follows:
Private Sub ProcessLeave(ByVal sender As Object, _ ByVal e As System.EventArgs) DirectCast(sender, TextBox).BackColor = _ Color.FromKnownColor(KnownColor.Window) End Sub
This event handler has no Handles clause because the events are wired up using the AddHandler statement. The AddHandler statement defines that this ProcessLeave event handler should be used whenever the Leave event is generated for a TextBox on the form.
The result is that when the user enters a text box, the background color is changed to a wheat color (light tan) allowing the user to easily see which control has focus. When the user leaves the text box, the background color is changed back to the user’s default window color.
Using the AddHandler statement in generic code such as this example simplifies maintenance and improves your productivity because, as text boxes are added to the form over time, they will automatically have all of the appropriate event behavior.
The world of Visual Basic development has changed, but it has only gotten better. The hard part is learning all of the differences and how best to take advantage of the many new features.
Control arrays are gone, but with the new event handlers and controls collection, we can achieve the same functionality with less code, easier maintenance, and no limitations.