# VBForums CodeBank > CodeBank - Visual Basic .NET >  Control Arrays in VB.NET

## jmcilhinney

If you're like me and you started coding in VB.NET (or C#, or probably any other programming language) without ever having coded in VB6 then you've probably never felt the need for a control array, at least in the VB6 sense.  To hear many migrating VB6 developers tell it though, VB6 control arrays are the greatest programming invention ever and living without them in VB.NET is nigh on unbearable.

The thing is, design-time support for control arrays was added to VB6 for a reason: to enable the same event to be handled for multiple controls with a single method.  In VB.NET, you can do that simply by adding multiple controls to the Handles clause of a method.  As such, design-time support for control arrays is not needed in VB.NET, so it was never added.  Take away the need for handling events for multiple controls and control arrays are exactly like any other arrays, which is exactly how they are treated in VB.NET and all other .NET languages.

VB6 developers often contend that creating a control array in the designer is sooooo much more convenient than doing so in code.  I don't necessarily disagree that it could be more convenient but I probably do disagree with how much more.  With Intellisense you can generally create an array of controls in code in less than a minute, if not in seconds.  If the array should contain a very large number of controls then you can always use a loop and get each control from the form's Controls collection by name.  It's really not a big deal.

Anyway, rather than fight it any longer I decided to create something that would help these poor souls.  I know that various people have posted various such things around the place.  I haven't checked any of those out so I don't know they compare to this current implementation.  I've also never used VB6 so I don't know exactly how this compares to how control arrays work in VB6.   What I do know is that this current implementation allows you to set the Index property of a control in the designer to add it to a collection that you can access in code, either using a For Each loop or accessing a specific control by index.

So, the first order of business is design-time support.  In order to be able to add something to the Toolbox in VS, it must implement the IComponent interface.  You can implement IComponent yourself, but the more usual way is to inherit the Component class.  As such, that was the first thing to do here:
vb.net Code:
Public Class ControlArray
    Inherits System.ComponentModel.Component
End Class
The next thing to do is to provide the Index property on each control.  If you've ever used a ToolTip, you know that this can be done, although you may not know how.  The secret is the IExtenderProvider interface, along with a few associated tricks.  To implement the interface, you must define the CanExtend method.  This method takes an object and returns a Boolean that indicates whether the object can be extended or not:
vb.net Code:
Public Class ControlArray
    Inherits System.ComponentModel.Component
    Implements System.ComponentModel.IExtenderProvider
     Public Function CanExtend(ByVal extendee As Object) As Boolean Implements System.ComponentModel.IExtenderProvider.CanExtend
        Return TypeOf extendee Is Control
    End Function
 End Class
That's not enough on its own though.  There's two more elements required to make this class useful.  First, we need to specify exactly what property eligible controls will extended with.  After that, we need to actually provide an implementation for that property.  A property is just a convenient way to package together two methods: one that gets a value and one that sets a value.  In this case, we implement those as just regular methods:
vb.net Code:
<System.ComponentModel.ProvideProperty("Index", GetType(Control))>
Public Class ControlArray
    Inherits System.ComponentModel.Component
    Implements System.ComponentModel.IExtenderProvider
     Private controls As New List(Of Control)
     Public Function CanExtend(ByVal extendee As Object) As Boolean Implements System.ComponentModel.IExtenderProvider.CanExtend
        Return TypeOf extendee Is Control
    End Function
     Public Function GetIndex(ByVal control As System.Windows.Forms.Control) As Integer
        Return Me.controls.IndexOf(control)
    End Function
     Public Sub SetIndex(ByVal control As Control, ByVal index As Integer)
        Me.controls.Insert(index, control)
    End Sub
 End Class
The final step is to expose the internal collection so that it can be used in code:
vb.net Code:
<System.ComponentModel.ProvideProperty("Index", GetType(Control))>
Public Class ControlArray
    Inherits System.ComponentModel.Component
    Implements System.ComponentModel.IExtenderProvider
     Private _controls As New List(Of Control)
     Public ReadOnly Property Controls As List(Of Control)
        Get
            Return Me._controls
        End Get
    End Property
     Public Function CanExtend(ByVal extendee As Object) As Boolean Implements System.ComponentModel.IExtenderProvider.CanExtend
        Return TypeOf extendee Is Control
    End Function
     Public Function GetIndex(ByVal control As System.Windows.Forms.Control) As Integer
        Return Me.controls.IndexOf(control)
    End Function
     Public Sub SetIndex(ByVal control As Control, ByVal index As Integer)
        Me.controls.Insert(index, control)
    End Sub
 End Class
That's all the basics in place, but that implementation won't do it.  The attached solution includes a much more rigorous implementation that addresses these issues.  The ControlArray class included in that solution is actually a base class only, with various derived classes provided for various specific types of controls.  The sample application includes a form that contains a ButtonArray and a TextBoxArray.  When you run the project, you'll be shown each item in the collection.

In the designer, you can select a Button or a TextBox and you'll find an extra Index property at the bottom of the Properties window.  Clearing the property will remove the control from the collection and setting it will add the control to the collection.  Note that adding and removing controls will automatically adjust the Indexes of all other controls in the collection.  Note also that you can set the Index of a control to a value beyond the size of the collection and it will automatically adjust to add the control to the end of the collection.  A very interesting feature of this auto-adjusting behaviour is that you can select multiple controls in the designer and then set the index property to a single value for all of them and they will all be added to the collection with distinct Index values.

A further feature of the attached sample is an extended derived class for the RadioButton control.  Any and all methods of the base class that add or remove items in the collection have been declared Overridable.  This means that you can override all those members and provide extra processing when items are added and removed.  The RadioButtonArray class provided adds and removes a handler on the CheckedChanged event of each item in the collection that will uncheck every other item when one RadioButton is checked.  This may sound redundant as it's the default behaviour of RadioButton's anyway, but in this case the behaviour is independent of container.  This means that RadioButtons in different containers can all act as a single group.  The sample contains three groups of three RadioButtons contained in different Panels to demonstrate this.

Finally, the attached solution was created in VS 2010 so will only be able to be opened in VS 2010 or VB Express 2010.  You can add the ControlArray.vb code file to any project in VB 2005 or later though.  The one change that might be required is the addition of a few line continuation characters.

----------


## NickThissen

Looks good, although I don't have any need for it like yourself. 

I do remember that in VB6 you would create control arrays by giving multiple controls the same name. They would then become a control array automatically if I remember correctly. That also means that copy/pasting a control in the designer would give the copy the same name, but naturally a different index. Since in .NET you can't have multiple controls with the same name I don't think implementing this feature is possible though. Perhaps with an extension for Visual Studio itself, but that would involve an effort much greater than just learning to live without control arrays  :Stick Out Tongue:

----------


## Max Peck

I missed control arrays for about 30 seconds after going into VB.Net (and/or C#).  When I discovered that you can use a method for multiple controls that problem vanished.

-Max  :Big Grin:

----------


## Exem

Hi

Many thanks for your great app! I was just what I was looking for when I started a new project recently. The forms have multiple sets of checkboxes and radiobuttons and your class has simplified the design considerably.

However, I have had a problem with the sequence of the controls not following the index defined at the design stage. It appears to be related to the way the controls are defined in Designer.vb. 

I solved the problem in my design by manually changing the designer code which, of course, is not ideal.

I can post a simple app showing the problem if required.

Also, is there a simple way at design time of removing the index from a group of selected controls?

----------


## jmcilhinney

> I can post a simple app showing the problem if required.


That would be a good idea.  I had a quick look at the code and didn;t see anything that would explain that.


> Also, is there a simple way at design time of removing the index from a group of selected controls?


Not as it stands.  Because all controls must have different indexes, if you select multiple controls then the Index property will display blank.  If you try to set a single value for multiple controls, which you could then clear, it will not work because the actual indexes used will be determined by the number of elements in the array.  You might be able to change the implementation but you would lose something else as a result.  You could add a designer function to the array itself to clear it.  Maybe I'll try that some time as it's something I've never done before.

----------


## Exem

Thanks for your speedy reply.

You will see I have attached two zips - a 'Good' and a 'Bad'.

In both apps the form displays eight radiobuttons with only the first four having an index set. In the 'Good' app they sequence properly but not in the 'Bad' app where the sequence is 1, 4, 2, 3.

You can apparently change the index in the designer but the sequence is still wrong. If you close the solution and re-open the design then shows the wrong sequence.

Hope this is clear.

----------


## techgnome

question... what do you mean by "Sequence" ? I'm asking because it sounds like you might be mistaking the Index value for the TabOrder value... but I haven't looked at your code yet either.

-tg

----------


## Exem

What I mean by sequence is the order the controls are returned by the code:



```
For Each rb In Me.RadioButtons1
      rb.Select()
      MessageBox.Show(rb.Name, String.Format("RadioButtons({0})", Me.RadioButtons1.IndexOf(rb)))
Next
```

I'm not confusing TabIndex but the values are set in order too.

----------


## techgnome

fair enough... just wanted to eliminate the possibilities. I've seen some confuse one for the other.

-tg

----------


## Exem

I decided to try to fix the issue myself and found that the order of the controls in the list did not agree with the sequence set in the designer.

Here is my modification:


```
    
Public Overridable Sub SetIndex(ByVal control As TControl, ByVal index As Integer?)
        If Not index.Equals(Me.GetIndex(control)) Then
            'The control is either being moved to a different index or  removed altogether, so remove it from its current index.
            Me.Remove(control)
        End If

        If index.HasValue Then
            'Insert the control at its new index, or to the end of the list if the index is invalid.
            'was - Me.Insert(Math.Min(index.Value, Me.Count), control)
            If index.Value > Me.Count - 1 Then
                For i = Me.Count To index.Value
                    Me.Add(control)
                Next
            End If
            Me.RemoveAt(index.Value)
            Me.Insert(index.Value, control)
        End If
    End Sub
```

There may be a more elegant way of fixing the problem but this does appear to work for now.

----------


## a1024

*Really you a professional.

Thanks...*

----------


## a1024

*Hi...

The ProvideProperty name became "*Index on RadioButtons*".
How can I get the name be "*Index*" without "*on RadioButtons*"?

**Thanks...*

*-----------------------------------------------------------------------
-----------------------------------------------------------------------
*



> Please don't ask the same question in two different places.  I've already answered this question in your own thread.


*Sorry ..

Will not be repeated again.

Thanks..*

----------


## jmcilhinney

> *Hi...
> 
> The ProvideProperty name became "*Index on RadioButtons*".
> How can I get the name be "*Index*" without "*on RadioButtons*"?
> 
> **Thanks...*


Please don't ask the same question in two different places.  I've already answered this question in your own thread.

----------


## Absolute_Zero

How to share one procedure for all control in an array? 
I know i can do with


```
        For Each b In Buttons
            AddHandler b.Click, AddressOf Buttons_Click
        Next
```

Is it possible in code editor if i select Buttons from controls dropdown list, all events related to Button control appear in events dropdown list, selecting one will auto generate a procedure (Buttons_Click, Buttons_MouseMove, Buttons_KeyDown etc...) that will used by all buttons in Buttons array.

----------


## jmcilhinney

> How to share one procedure for all control in an array? 
> I know i can do with
> 
> 
> ```
>         For Each b In Buttons
>             AddHandler b.Click, AddressOf Buttons_Click
>         Next
> ```
> ...


If you want to create a handler for the same event for multiple controls then simply select those multiple controls and then use the Properties window to create or select an event handler.  This functionality has been available for over a decade and possibly back to the original VS.NET.  The fact that it exists is one of the primary reasons that you don't need control arrays in VB.NET in the first place.

If you wanted this control array class to do it for you then you'd have to add a corresponding event to the array class and then use AddHandler as a control was added to the array and RemoveHandler as one was removed.

----------


## Absolute_Zero

> If you wanted this control array class to do it for you then you'd have to add a corresponding event to the array class and then use AddHandler as a control was added to the array and RemoveHandler as one was removed.


I do it like this, first added declaration for events i want


```
    Public Event Click As EventHandler
    Private Sub Click_internal(sender As Object, e As EventArgs)
        RaiseEvent Click(sender, e)
    End Sub

    Public Event MouseMove As MouseEventHandler
    Private Sub MouseMove_internal(sender As Object, e As MouseEventArgs)
        RaiseEvent MouseMove(sender, e)
    End Sub

    Public Event KeyDown As KeyEventHandler
    Private Sub KeyDown_internal(sender As Object, e As KeyEventArgs)
        RaiseEvent KeyDown(sender, e)
    End Sub
```

second added two methods to add/remove handlers


```
    Private Sub AddEventHandelers(control As TControl)
        AddHandler control.Click, AddressOf Click_internal
        AddHandler control.MouseMove, AddressOf MouseMove_internal
        AddHandler control.KeyDown, AddressOf KeyDown_internal
    End Sub

    Private Sub RemoveEventHandelers(control As TControl)
        RemoveHandler control.Click, AddressOf Click_internal
        RemoveHandler control.MouseMove, AddressOf MouseMove_internal
        RemoveHandler control.KeyDown, AddressOf KeyDown_internal
    End Sub
```

third call *AddEventHandelers* & *RemoveEventHandelers* from Add and SetIndex methods




```
    Public Overridable Sub Add(ByVal item As TControl) Implements ICollection(Of TControl).Add
        Me.items.Add(item)
        AddEventHandelers(item)
    End Sub



    Public Overridable Sub SetIndex(ByVal control As TControl, ByVal index As Integer?)
        If Not index.Equals(Me.GetIndex(control)) Then
            'The control is either being moved to a different index or  removed altogether, so remove it from its current index.
            RemoveEventHandelers(control)
            Me.Remove(control)
        End If

        If index.HasValue Then
            'Insert the control at its new index, or to the end of the list if the index is invalid.
            Me.Insert(Math.Min(index.Value, Me.Count), control)
            AddEventHandelers(control)
        End If
    End Sub
```

It works well, but i wonder is this the correct way to do it?
You are so professional and your advise is appreciated  :Smilie:

----------


## jmcilhinney

> I do it like this, first added declaration for events i want
> 
> 
> ```
>     Public Event Click As EventHandler
>     Private Sub Click_internal(sender As Object, e As EventArgs)
>         RaiseEvent Click(sender, e)
>     End Sub
> 
> ...


That's basically it but there are a couple of things to note there.

1. There are more locations than you have shown that you need to use AddHandler and RemoveHandler.  Check out the RadioButtonControlArray class in the provided solution to see all those locations.
2. It might work for you to simply pass through the 'sender' and 'e' arguments from one event handler to another but you're actually breaking the usual convention by doing so.  The 'sender' should be the object that raised the event so you should always pass Me to RaiseEvent.  That would mean creating a new type for 'e' that had a property for the control that raised the original event.  That's going to be more trouble but is more correct in that it follows the standard conventions for events.

----------


## Absolute_Zero

> 2. It might work for you to simply pass through the 'sender' and 'e' arguments from one event handler to another but you're actually breaking the usual convention by doing so.  The 'sender' should be the object that raised the event so you should always pass Me to RaiseEvent.  That would mean creating a new type for 'e' that had a property for the control that raised the original event.  That's going to be more trouble but is more correct in that it follows the standard conventions for events.


Have to do just to follow _the standard conventions for events_ or there is performance issue?

----------


## jmcilhinney

Performance would not be an issue.

----------


## Absolute_Zero

> Performance would not be an issue.


Then let the standard conventions go to hell for now.   :Big Grin:

----------


## Absolute_Zero

What is the meaning of question mark used in *GetIndex* and *SetIndex* methods?


```
    Public Function GetIndex(ByVal control As TControl) As Integer?
        Return If(Me.Contains(control), Me.IndexOf(control), DirectCast(Nothing, Integer?))
    End Function
```

----------


## jmcilhinney

> What is the meaning of question mark used in *GetIndex* and *SetIndex* methods?
> 
> 
> ```
>     Public Function GetIndex(ByVal control As TControl) As Integer?
>         Return If(Me.Contains(control), Me.IndexOf(control), DirectCast(Nothing, Integer?))
>     End Function
> ```


Appending a question mark to a value type denotes that it is nullable.  'Integer?' is shorthand for 'Nullable(Of Integer)'.

----------


## Absolute_Zero

Thanks!

----------

