There is existing literature in MSDN which illustrates how to create
control
arrays at run time, but this may not be a suitable approach in all cases.
In particular, if the developer is able to specify the required controls at
design time, then the problem becomes of how to add the desired controls to
an array. The approach described in this article has the benefit of allowing
the developer to even specify different properties for the controls (eg. Background
color, Drop-down style, etc.) which something not supported by the MSDN approach.
The approach illustrated in this article is intended to provide the basic functionality
for anyone wanting to implement control arrays. It is then up to the developer
to try and improve and build upon the functionality as required.
Software
Architecture
The architecture adopted relies on the following:
1) A Windows Control library containing abstract base
classes for different control types
2) A Windows Application which contains a derived class
for each abstract base class
3) A Windows Form and a User Control which contain the
controls to be assigned to arrays
The sequence of steps to take at design-time and run-time is the following:
a) Add controls to the container control and adopt pre-specified
naming convention(s) that identifies controls to be added to each required
array
b) Add code to declare and instantiate collections in
client form, all within the constructor of the container control
c)
Add logic to implement the event handlers for each
array in the derived class
d) The arrays will always be created at run-time whenever
the Form or User Control is instantiated.
The
rest of the article will examine each of the above aspects in more detail. For simplicity, each
section will take as an example the implementation of TextBox control arrays.
The
Windows Control Library
The library is made up of five distinct abstract classes. Each class inherits
from System. Collections.CollectionBase
which is the namespace recommended by Microsoft for creating strongly typed custom collections. It is important to underline that each array is
a strongly typed collection by design because it will only contain elements
of the same type. In addition, all classes are structured similarly and could
also have a common interface. But to simplify the explanation a common interface
has not been defined.
The individual classes are responsible for assigning to arrays the most common
types of controls used as arrays Buttons, ComboBoxes, TextBoxes, RadioButtons
and CheckBoxes. The classes are defined as abstract because they implement
abstract members for a limited number of abstract methods that act as event
handlers for the array. For example, the TextBox array class [clsTextArray]
only contains abstract methods for the KeyPress and Validating events. The
classes though can easily be modified to contain additional abstract methods
for other events the developer may wish to add.
Each abstract class contains two constructors:
a) Constructor # 1 caters for controls located on Windows
Forms
b) Constructor # 2 caters for controls located on Windows
User Control
The advantage of having two different constructors is to encapsulate the inner
workings of the class, regardless of the hosting control.
In addition, both constructors contain two parameters to identify the array
items
c)
Control Prefix string [eg. strText]
d) Array identifier string [eg. strArr]
These two variables are concatenated together to make up a class member variable
[eg. mstrArr] which will be used in the
AddForm<ControlName> function. In order for the assignment
to arrays to work correctly, the developer must adopt a consistent naming
convention for the specific controls to include in an array [eg. strText =
TextBox1, strArr = _Arr]
Each constructor then contains a call to a common function AddForm<ControlName>
[eg AddFormTextBoxes] which has a System.Windows.Forms.Control.ControlCollection as it’s only input parameter. This approach is
valid regardless of whether the class is called from a Windows Form or a Windows
User Control. This is because the System.Windows.Forms.Form.ControlCollection
namespace inherits from the System.Windows.Forms.Control.ControlCollection
namespace so passing in the superclass will always be allowed.
The classes then contain a series of read-only properties which are used to
identify, count and define other properties of the array items. In the case
of the TextBox class these are listed below:
-
Item property [Default]:
returns the array item corresponding to a specified index
-
IndexOf property:
returns the index corresponding to a specified array item
-
Ubound property:
returns the Upper Bound of the array = Items.Count - 1
-
SourceField property:
custom property that returns the database source field for the item
The latter property illustrates well the flexibility of the approach, where
the user can assign custom properties as required to the array items. For
example, the SourceField property can be used to avoid having to hard-code
field names in SQL Insert and Update statements to modify Form or User Control
data.
The class then contains the declarations for the abstract event handler methods:
'Abstract event handler
Public MustOverride Sub
TxtKeyPressHandler(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyPressEventArgs)
Public MustOverride Sub
TxtValidatingHandler(ByVal sender As Object, ByVal e As System.ComponentModel.CancelEventArgs)
The purpose of the AddForm<ControlName> [e.g. AddFormTextBoxes] function
is to loop through all the controls in the ControlsCollection and identify
those that need to be added to the array. This is done within the following
For.Next loop.
'Loop through the control collection
For x = 0 To Count 'Count
= collCtls.Count - 1
ctl = collCtls.Item(x)
If TypeOf
(ctl) Is TextBox Then
If InStr(LCase(ctl.Name),
mstrArr, CompareMethod.Text) > 0 Then
AddTextBox(collCtls, collCtls.IndexOf(ctl))
End If
ElseIf TypeOf (ctl)
Is GroupBox Or TypeOf (ctl) Is Panel
Then
'Recursively call function,
passing in Controls collection
AddFormTextBoxes(ctl.Controls)
End If
Next
The logic is as follows:
a) First check if Item(x) of the collection is of the
correct type [eg. Textbox]
b) If it is, then compare the control name to the mstrArr
variable using the InStr function. If this returns a value > 0 then the
control can be added to the array by calling the Add<ControlType> function.
c)
If it’s not, but the item is a GroupBox or a Panel
control, then recursively call the function
to verify the existence of other controls of the specified
type.
Finally, the Add<ControlType> function [e.g. AddTextBox] function carries
out the following tasks:
a) Adds the control item to the collection. All the design-time
properties of the control are left unaltered, thus giving the developer flexibility
in the implementation of the UI [e.g. Name, BackColor, DropDownStyle, etc.]
b) Assigns any custom properties. In this case the value
to assign to the property is written to the TAG property of the control at
Design Time. Once the property is assigned, the TAG can be cleared and re-used
during run-time.
c)
Adds handlers for all of the required events, using
the defined abstract methods.
List.Add(collCtls.Item(iIndex))
'Add property to collection
mSourceField.Add(collCtls.Item(iIndex).Tag)
'Reset the TAG [OPTIONAL]
collCtls.Item(iIndex).Tag =
'Associate to common event handlers
AddHandler collCtls.Item(iIndex).KeyPress, _
AddressOf TxtKeyPressHandler
AddHandler collCtls.Item(iIndex).Validating, _
AddressOf TxtValidatingHandler
At this point the work of the abstract base class is complete. The important
point to stress about the abstract classes is that they are completely independent
of any application logic. The only constraint upon the client application
is a consistent control naming convention for the individual array items.
So let’s now look at the derived classes in the client code.