Monday, May 18, 2020

VB.NET What Happened to Control Arrays

The omission of control arrays from VB.NET is a challenge for those teaching about arrays. No longer is it possible to simply copy a control, such as a textbox, and then paste it (once or several times) to create a control array.The VB.NET code for creating a structure similar to a control array has been, in all the books on VB.NET that I have bought and online, much longer and much more complex. It lacks the simplicity of coding a control array that is found in VB6. If you reference the VB6 compatibility library, there are objects in there that act pretty much like control arrays. To see what I mean, simply use the VB.NET upgrade wizard with a program that contains a control array. The code is ugly again, but it works. The bad news is that Microsoft will not guarantee that the compatibility components will continue to be supported, and youre not supposed to use them. The VB.NET code to create and use control arrays is much longer and much more complex. According to Microsoft, to do something even close to what you can do in VB 6 requires the creation a simple component that duplicates control array functionality. You need both a new class and a hosting form to illustrate this.  The class actually creates and destroys new labels. The complete class code is as follows: Public Class LabelArray      Inherits System.Collections.CollectionBase      Private ReadOnly HostForm As _  Ã‚     System.Windows.Forms.Form      Public Function AddNewLabel() _  Ã‚     As System.Windows.Forms.Label            Create a new instance of the Label class.            Dim aLabel As New System.Windows.Forms.Label            Add the Label to the collections  Ã‚      internal list.            Me.List.Add(aLabel)            Add the Label to the Controls collection   Ã‚  Ã‚            of the Form referenced by the HostForm field.            HostForm.Controls.Add(aLabel)            Set intial properties for the Label object.            aLabel.Top Count * 25            aLabel.Width 50            aLabel.Left 140            aLabel.Tag Me.Count            aLabel.Text Label Me.Count.ToString            Return aLabel      End Function       Public Sub New( _  Ã‚     ByVal host As System.Windows.Forms.Form)            HostForm host            Me.AddNewLabel()      End Sub      Default Public ReadOnly Property _            Item(ByVal Index As Integer) As _            System.Windows.Forms.Label            Get                  Return CType(Me.List.Item(Index), _  Ã‚     Ã‚  Ã‚     System.Windows.Forms.Label)            End Get      End Property      Public Sub Remove()            Check to be sure there is a Label to remove.            If Me.Count 0 Then                  Remove the last Label added to the array  Ã‚                  from the host form controls collection.  Ã‚  Ã‚            Note the use of the default property in  Ã‚                  accessing the array.                  HostForm.Controls.Remove(Me(Me.Count - 1))                  Me.List.RemoveAt(Me.Count - 1)            End If      End SubEnd Class To illustrate how this class code would be used, you could create a Form that calls it. You would have to use the code shown below in the form: Public Class Form1 Inherits System.Windows.Forms.Form #Region Windows Form Designer generated code Also you must add the statement: MyControlArray New LabelArray(Me) after the InitializeComponent() call in the hidden Region code. Declare a new ButtonArray object. Dim MyControlArray As LabelArray Private Sub btnLabelAdd_Click( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles btnLabelAdd.Click Call the AddNewLabel method of MyControlArray. MyControlArray.AddNewLabel() Change the BackColor property of the Button 0. MyControlArray(0).BackColor _ System.Drawing.Color.Red End Sub Private Sub btnLabelRemove_Click( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles btnLabelRemove.Click Call the Remove method of MyControlArray. MyControlArray.Remove() End Sub End Class First, this doesnt even do the job at Design Time like we used to do it in VB 6! And second, they arent in an array, they are in a VB.NET Collection - a much different thing than an array. The reason VB.NET doesnt support the VB 6 control array is that there is no such thing as a control array (note the change of quotation marks). VB 6 creates a collection behind-the-scenes and makes it appear as an array to the developer. But its not an array and you have little control over it beyond the functions provided through the IDE. VB.NET, on the other hand, calls it what it is: a collection of objects. And they hand the keys to the kingdom to the developer by creating the whole thing right out in the open. As an example of the kind of advantages this gives the developer, in VB 6 the controls had to be of the same type, and they had to have the same name. Since these are just objects in VB.NET, you can make them different types and give them different names and still manage them in the same collection of objects. In this example, the same Click event handles two buttons and a checkbox and displays which one was clicked. Do that in one line of code with VB 6! Private Sub MixedControls_Click( _      ByVal sender As System.Object, _      ByVal e As System.EventArgs) _      Handles Button1.Click, _                  Button2.Click, _                  CheckBox1.Click      The statement below has to be one long statement!      Its on four lines here to keep it narrow      enough to fit on a web page      Label2.Text   Ã‚      Microsoft.VisualBasic.Right(sender.GetType.ToString,  Ã‚      Len(sender.GetType.ToString) -  Ã‚      (InStr(sender.GetType.ToString, Forms) 5))End Sub The substring calculation is kind of complex, but it isnt really what were talking about here. You could do anything in the Click event. You could, for example, use the Type of the control in an If statement to do different things for different controls. Franks Computing Studies Group Feedback on Arrays Franks Study Group provided an example with a form that has 4 labels and 2 buttons. Button 1 clears the labels and Button 2 fills them. Its a good idea to read Franks original question again and notice that the example he used was a loop that is used to clear the Caption property of an array of Label components. Heres the VB.NET equivalent of that VB 6 code. This code does what Frank originally asked for! Public Class Form1 Inherits System.Windows.Forms.Form #Region Windows Form Designer generated code Dim LabelArray(4) As Label declare an array of labels Private Sub Form1_Load( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles MyBase.Load SetControlArray() End Sub Sub SetControlArray() LabelArray(1) Label1 LabelArray(2) Label2 LabelArray(3) Label3 LabelArray(4) Label4 End Sub Private Sub Button1_Click( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles Button1.Click Button 1 Clear Array Dim a As Integer For a 1 To 4 LabelArray(a).Text Next End Sub Private Sub Button2_Click( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles Button2.Click Button 2 Fill Array Dim a As Integer For a 1 To 4 LabelArray(a).Text _ Control Array CStr(a) Next End Sub End Class If you experiment with this code, you will discover that in addition to setting properties of the Labels, you can also call methods. So why did I (and Microsoft) go to all the trouble to build the Ugly code in Part I of the article? I have to disagree that its really a Control Array in the classic VB sense. The VB 6 Control Array is a supported part of the VB 6 syntax, not just a technique. In fact, maybe the way to describe this example is that it is an array of controls, not a Control Array. In Part I, I complained that the Microsoft example ONLY worked at run time and not design time. You can add and delete controls from a form dynamically, but the whole thing has to be implemented in code. You cant drag and drop controls to create them like you can in VB 6. This example works mainly at design time and not at run time. You cant add and delete controls dynamically at run time. In a way, its the complete opposite of the Part I example. The classic VB 6 control array example is the same one that is implemented in the VB .NET code. Here in VB 6 code (this is taken from Mezick Hillier, Visual Basic 6 Certification Exam Guide, p 206 - slightly modified, since the example in the book results in controls that cant be seen): Dim MyTextBox as VB.TextBox Static intNumber as Integer intNumber intNumber 1 Set MyTextBox _ Me.Controls.Add(VB.TextBox, _ Text intNumber) MyTextBox.Text MyTextBox.Name MyTextBox.Visible True MyTextBox.Left _ (intNumber - 1) * 1200 But as Microsoft (and I) agree, VB 6 control arrays arent possible in VB.NET. So the best you can do is duplicate the functionality. My article duplicated the functionality found in the Mezick Hillier example. The Study Group code duplicates the functionality of being able to set properties and call methods. So the bottom line is that it really depends on what you want to do. VB.NET doesnt have the whole thing wrapped up as part of the language -- Yet -- but ultimately its far more flexible. John Fannons Take on Control Arrays John wrote:   I needed control arrays because I wanted to put a simple table of numbers on a form at run time. I didnt want the nausea of placing them all individually and I wanted to use VB.NET.  Microsoft offers a very detailed solution to a simple problem, but its a very large sledgehammer to crack a very small nut. After some experimentation, I eventually hit upon a solution. Heres how I did it. The About Visual Basic example above shows how you can create a TextBox on a Form by creating an instance of the object, setting properties, and adding it to the Controls collection that is part of the Form object. Dim txtDataShow As New TextBoxtxtDataShow.Height 19txtDataShow.Width 80txtDataShow.Location New Point(X, Y)Me.Controls.Add(txtDataShow)Although the Microsoft solution creates a Class, I reasoned that it would be possible to wrap all this in a subroutine instead. Every time you call this subroutine you create a new instance of the textbox on the form. Heres the complete code: Public Class Form1      Inherits System.Windows.Forms.Form #Region Windows Form Designer generated code       Private Sub BtnStart_Click( _            ByVal sender As System.Object, _            ByVal e As System.EventArgs) _            Handles btnStart.Click             Dim I As Integer            Dim sData As String            For I 1 To 5                  sData CStr(I)                  Call AddDataShow(sData, I)            Next      End Sub      Sub AddDataShow( _            ByVal sText As String, _            ByVal I As Integer)             Dim txtDataShow As New TextBox            Dim UserLft, UserTop As Integer            Dim X, Y As Integer            UserLft 20            UserTop 20            txtDataShow.Height 19            txtDataShow.Width 25            txtDataShow.TextAlign _                  HorizontalAlignment.Center            txtDataShow.BorderStyle _                  BorderStyle.FixedSingle            txtDataShow.Text sText            X UserLft            Y UserTop (I - 1) * txtDataShow.Height            txtDataShow.Location New Point(X, Y)            Me.Controls.Add(txtDataShow)      End SubEnd ClassVery good point, John. This is certainly a lot more simple than the Microsoft code ... so I wonder why they insisted on doing it that way? To begin our investigation, lets try changing one of the property assignments in the code. Lets change txtDataShow.Height 19to txtDataShow.Height 100just to make sure that there is a noticeable difference. When we run the code again, we get ... Whaaaat ... the same thing. No change at all. In fact, you can display the value with a statement like MsgBox (txtDataShow.Height) and you still get 20 as the value of the property no matter what you assign to it. Why does that happen? The answer is that were not deriving our own Class to create the objects, were just adding things to another Class so we have to follow the rules of the other class. And those rules state that you cant change the Height property. (Wellllll ... you can. If you change the Multiline property to True, then you can change the Height.) Why VB.NET goes ahead and executes the code without even a whimper that there might be something wrong when, in fact, it totally disregards your statement is a whole nother gripe. I might suggest at least a warning in the compile, however. (Hint! Hint! Hint! Is Microsoft listening?) The example from Part I inherits from another Class, and this makes the properties available to the code in the inheriting Class. Changing the Height property to 100 in this example gives us the expected results. (Again ... one disclaimer: When a new instance of a large Label component is created, it covers up the old one. To actually see the new Label components, you have to add the method call aLabel.BringToFront().) This simple example shows that, although we CAN simply add objects to another Class (and sometimes this is the right thing to do), programming control over the objects requires that we derive them in a Class and the most organized way (dare I say, the .NET way ) is to create properties and methods in the new derived Class to change things. John remained unconvinced at first. He said that his new approach suits his purpose even though there are limitations from not being COO (Correctly Object Oriented). More recently, however, John wrote, ... after writing a set of 5 textboxes at runtime, I wanted to update the data in a subsequent part of the program - but nothing changed - the original data was still there. I found that I could get round the problem by writing code to take off the old boxes and putting them back again with new data. A better way to do it would be to use Me.Refresh. But this problem has drawn my attention for the need to supply a method to subtract the textboxes as well as add them. Johns code used a global variable to keep track of how many controls had been added to the form so a method ... Private Sub Form1_Load( _     ByVal sender As System.Object, _     ByVal e As System.EventArgs) _     Handles MyBase.Load     CntlCnt0 Me.Controls.CountEnd Sub Then the last control could be removed ... N Me.Controls.Count - 1Me.Controls.RemoveAt(N)John noted that, maybe this is a bit clumsy. Its the way Microsoft keeps track of objects in COM AND in their ugly example code above. Ive now returned to the problem of dynamically creating controls on a form at run time and I have been looking again at the What Happened to Control Arrays articles. I have created the classes and can now place the controls onto the form in the way I want them to be. John demonstrated how to control the placement of controls in a group box using the new classes he has started using. Maybe Microsoft had it right in their ugly solution after all!

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.