Results 1 to 23 of 23

Thread: Control Arrays in VB.NET

  1. #1

    Thread Starter
    Super Moderator jmcilhinney's Avatar
    Join Date
    May 2005
    Location
    Sydney, Australia
    Posts
    110,302

    Control Arrays in VB.NET

    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:
    1. Public Class ControlArray
    2.     Inherits System.ComponentModel.Component
    3. 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:
    1. Public Class ControlArray
    2.     Inherits System.ComponentModel.Component
    3.     Implements System.ComponentModel.IExtenderProvider
    4.  
    5.     Public Function CanExtend(ByVal extendee As Object) As Boolean Implements System.ComponentModel.IExtenderProvider.CanExtend
    6.         Return TypeOf extendee Is Control
    7.     End Function
    8.  
    9. 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:
    1. <System.ComponentModel.ProvideProperty("Index", GetType(Control))>
    2. Public Class ControlArray
    3.     Inherits System.ComponentModel.Component
    4.     Implements System.ComponentModel.IExtenderProvider
    5.  
    6.     Private controls As New List(Of Control)
    7.  
    8.     Public Function CanExtend(ByVal extendee As Object) As Boolean Implements System.ComponentModel.IExtenderProvider.CanExtend
    9.         Return TypeOf extendee Is Control
    10.     End Function
    11.  
    12.     Public Function GetIndex(ByVal control As System.Windows.Forms.Control) As Integer
    13.         Return Me.controls.IndexOf(control)
    14.     End Function
    15.  
    16.     Public Sub SetIndex(ByVal control As Control, ByVal index As Integer)
    17.         Me.controls.Insert(index, control)
    18.     End Sub
    19.  
    20. End Class
    The final step is to expose the internal collection so that it can be used in code:
    vb.net Code:
    1. <System.ComponentModel.ProvideProperty("Index", GetType(Control))>
    2. Public Class ControlArray
    3.     Inherits System.ComponentModel.Component
    4.     Implements System.ComponentModel.IExtenderProvider
    5.  
    6.     Private _controls As New List(Of Control)
    7.  
    8.     Public ReadOnly Property Controls As List(Of Control)
    9.         Get
    10.             Return Me._controls
    11.         End Get
    12.     End Property
    13.  
    14.     Public Function CanExtend(ByVal extendee As Object) As Boolean Implements System.ComponentModel.IExtenderProvider.CanExtend
    15.         Return TypeOf extendee Is Control
    16.     End Function
    17.  
    18.     Public Function GetIndex(ByVal control As System.Windows.Forms.Control) As Integer
    19.         Return Me.controls.IndexOf(control)
    20.     End Function
    21.  
    22.     Public Sub SetIndex(ByVal control As Control, ByVal index As Integer)
    23.         Me.controls.Insert(index, control)
    24.     End Sub
    25.  
    26. 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.
    Attached Files Attached Files

  2. #2
    PowerPoster
    Join Date
    Apr 2007
    Location
    The Netherlands
    Posts
    5,070

    Re: Control Arrays in VB.NET

    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

  3. #3
    Hyperactive Member Max Peck's Avatar
    Join Date
    Oct 2007
    Posts
    384

    Re: Control Arrays in VB.NET

    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
    The name's "Peck" .... "Max Peck"

    "If you think it's expensive to hire a professional to do the job, wait until you hire an amateur." - Red Adair

  4. #4
    New Member
    Join Date
    Aug 2011
    Posts
    4

    Re: Control Arrays in VB.NET

    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?

  5. #5

    Thread Starter
    Super Moderator jmcilhinney's Avatar
    Join Date
    May 2005
    Location
    Sydney, Australia
    Posts
    110,302

    Re: Control Arrays in VB.NET

    Quote Originally Posted by Exem View Post
    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.
    Quote Originally Posted by Exem View Post
    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.

  6. #6
    New Member
    Join Date
    Aug 2011
    Posts
    4

    Re: Control Arrays in VB.NET

    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.
    Attached Files Attached Files

  7. #7
    Smooth Moperator techgnome's Avatar
    Join Date
    May 2002
    Posts
    34,532

    Re: Control Arrays in VB.NET

    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
    * I don't respond to private (PM) requests for help. It's not conducive to the general learning of others.*
    * I also don't respond to friend requests. Save a few bits and don't bother. I'll just end up rejecting anyways.*
    * How to get EFFECTIVE help: The Hitchhiker's Guide to Getting Help at VBF - Removing eels from your hovercraft *
    * How to Use Parameters * Create Disconnected ADO Recordset Clones * Set your VB6 ActiveX Compatibility * Get rid of those pesky VB Line Numbers * I swear I saved my data, where'd it run off to??? *

  8. #8
    New Member
    Join Date
    Aug 2011
    Posts
    4

    Re: Control Arrays in VB.NET

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

    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.

  9. #9
    Smooth Moperator techgnome's Avatar
    Join Date
    May 2002
    Posts
    34,532

    Re: Control Arrays in VB.NET

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

    -tg
    * I don't respond to private (PM) requests for help. It's not conducive to the general learning of others.*
    * I also don't respond to friend requests. Save a few bits and don't bother. I'll just end up rejecting anyways.*
    * How to get EFFECTIVE help: The Hitchhiker's Guide to Getting Help at VBF - Removing eels from your hovercraft *
    * How to Use Parameters * Create Disconnected ADO Recordset Clones * Set your VB6 ActiveX Compatibility * Get rid of those pesky VB Line Numbers * I swear I saved my data, where'd it run off to??? *

  10. #10
    New Member
    Join Date
    Aug 2011
    Posts
    4

    Re: Control Arrays in VB.NET

    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:
    Code:
        
    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.

  11. #11
    Member
    Join Date
    Feb 2013
    Posts
    32

    Re: Control Arrays in VB.NET

    Really you a professional.

    Thanks...

  12. #12
    Member
    Join Date
    Feb 2013
    Posts
    32

    Re: Control Arrays in VB.NET

    Hi...

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

    Thanks...


    -----------------------------------------------------------------------
    -----------------------------------------------------------------------

    Quote Originally Posted by jmcilhinney View Post
    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..
    Last edited by a1024; Feb 11th, 2013 at 12:23 AM.

  13. #13

    Thread Starter
    Super Moderator jmcilhinney's Avatar
    Join Date
    May 2005
    Location
    Sydney, Australia
    Posts
    110,302

    Re: Control Arrays in VB.NET

    Quote Originally Posted by a1024 View Post
    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.

  14. #14
    Addicted Member
    Join Date
    Dec 2011
    Posts
    194

    Re: Control Arrays in VB.NET

    How to share one procedure for all control in an array?
    I know i can do with
    Code:
            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.
    On Error GoTo Hell

  15. #15

    Thread Starter
    Super Moderator jmcilhinney's Avatar
    Join Date
    May 2005
    Location
    Sydney, Australia
    Posts
    110,302

    Re: Control Arrays in VB.NET

    Quote Originally Posted by Absolute_Zero View Post
    How to share one procedure for all control in an array?
    I know i can do with
    Code:
            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.
    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.

  16. #16
    Addicted Member
    Join Date
    Dec 2011
    Posts
    194

    Re: Control Arrays in VB.NET

    Quote Originally Posted by jmcilhinney View Post
    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
    Code:
        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
    Code:
        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


    Code:
        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
    On Error GoTo Hell

  17. #17

    Thread Starter
    Super Moderator jmcilhinney's Avatar
    Join Date
    May 2005
    Location
    Sydney, Australia
    Posts
    110,302

    Re: Control Arrays in VB.NET

    Quote Originally Posted by Absolute_Zero View Post
    I do it like this, first added declaration for events i want
    Code:
        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
    Code:
        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


    Code:
        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
    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.

  18. #18
    Addicted Member
    Join Date
    Dec 2011
    Posts
    194

    Re: Control Arrays in VB.NET

    Quote Originally Posted by jmcilhinney View Post
    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?
    On Error GoTo Hell

  19. #19

    Thread Starter
    Super Moderator jmcilhinney's Avatar
    Join Date
    May 2005
    Location
    Sydney, Australia
    Posts
    110,302

    Re: Control Arrays in VB.NET

    Performance would not be an issue.

  20. #20
    Addicted Member
    Join Date
    Dec 2011
    Posts
    194

    Re: Control Arrays in VB.NET

    Quote Originally Posted by jmcilhinney View Post
    Performance would not be an issue.
    Then let the standard conventions go to hell for now.
    On Error GoTo Hell

  21. #21
    Addicted Member
    Join Date
    Dec 2011
    Posts
    194

    Re: Control Arrays in VB.NET

    What is the meaning of question mark used in GetIndex and SetIndex methods?
    Code:
        Public Function GetIndex(ByVal control As TControl) As Integer?
            Return If(Me.Contains(control), Me.IndexOf(control), DirectCast(Nothing, Integer?))
        End Function
    On Error GoTo Hell

  22. #22

    Thread Starter
    Super Moderator jmcilhinney's Avatar
    Join Date
    May 2005
    Location
    Sydney, Australia
    Posts
    110,302

    Re: Control Arrays in VB.NET

    Quote Originally Posted by Absolute_Zero View Post
    What is the meaning of question mark used in GetIndex and SetIndex methods?
    Code:
        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)'.

  23. #23
    Addicted Member
    Join Date
    Dec 2011
    Posts
    194

    Re: Control Arrays in VB.NET

    Thanks!
    On Error GoTo Hell

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width