For Loops and Arrays
A very common use for any loop structure is to process data arrays. Create a new project and call it PhoneNumbers. Add control objects to the form as shown in Figure 12.4.
Figure 12.4. Component objects for PhoneNumbers project.
There are two labels, three text boxes, and three buttons . These objects should all be pretty familiar to you now. Note that txtResult has the Multiline property set to True and the Scrollbars property is set to Vertical . Everything else is old hat by now. Some of the code, however, is not.
Structure Data Types
As you can probably tell from the form in Figure 12.4, our program is going to collect a set of names and phone numbers and display them in a text box. Now, we could stuff the names into one array, and then stuff the phone numbers into another array and sort of co-process the two arrays in parallel. It would work, and we'd probably do it that way if we didn't have better alternatives. After all, if the only tool you have is a hammer , pretty soon all your problems look like a nail. Not so with Visual Basic .NET. We have all kinds of tools available to us. Let's look at one that is especially suited to the task at hand. It's called the Structure .
The general form for a Structure declaration is
[ AccessSpecifier ] Structure StructureTag StructureMemberList End Structure
The keyword Structure begins the structure declaration. It can be preceded by an access specifier such as Public , Private , or Friend . For the moment, however, we're ignoring the optional access specifier until we're ready to discuss it in Chapter 15, "Encapsulation."
The Structure keyword is followed by the StructureTag , or name , you want to give to the structure. Next comes the declarations for the members that you want to have in the structure. The End Structure keywords mark the end of the list of Structure members. The statements between the StructureTag and the End Structure keywords are the Structure statement block.
In our program, we declare a Structure named PhoneList as shown in the following code fragment:
Structure PhoneList Dim Name As String Dim Phone As String End Structure
Here we have declared a Structure with the structure tag name of PhoneList . The Structure has two members, Name and Phone , both of which are String data types. Notice how the structure members use the Dim statement you've seen so many times before. The end of the structure member list and the Structure declaration itself are marked by the End Structure keywords.
Cool! Now we have a data structure named PhoneList that we can shove names and phone numbers into. WRONG!
Structures Are Cookie Cutters, Not Variables
Notice how careful we were to use the word declaration in the preceding paragraphs. Remember our discussion from Chapter 4 when we talked about defining variables? I stressed that the key element in defining a variable is that the operating system must hand back a valid lvalue for the data item to Visual Basic .NET and record it in the symbol table. Use your highlighter pen and highlight the last sentence . It's very important.
In the Structure code fragment shown earlier, the End Structure keywords tell Visual Basic .NET, "Okay, you now know all the members that belong to the PhoneList Structure . Make a cookie cutter that looks like this and keep it in the symbol table, but don't whack out any cookies with it yet."
The purpose of the Structure - End Structure keywords is to declare a list of member variables that can exist in the program. No storage has been allocated for the PhoneList Structure . Therefore, there are no PhoneList variables yet. Obviously, this also means there are no lvalues yet, either.
To get a PhoneList variable we can use in our program, we need to define one first. We can define a PhoneList variable as follows :
Dim MyList() As PhoneList
This statement defines a dynamic array of type PhoneList named Mylist() . Obviously, we'll set the array size later in the program. The key, however, is that we now have a variable named MyList() that exists in the symbol table and has an lvalue . (Actually, the lvalue of a pointer variable is defined at this point. However, the essential point is that an lvalue does now exist.)
Structures may not have local scope. Therefore, we need to place our structure declaration outside of any procedure definition. You'll usually give them module or namespace scope.
The concept of a Structure enables us to group related data together and treat it as a single data item. Plain old arrays require that the data in the array be homogeneous. That is, all of the data in the array must be of the same data type. No so for structures. The Structure members can be whatever data type we want them to be. Yet, the really neat thing is that we can reference this group of related (albeit dissimilar) data by a single variable name. Kim Brand once called structures "arrays for adults" because the array can have complex data members. It's a fitting description.
The btnAdd Click Event
It's pretty obvious that we want the user to type in a name and phone number and then click the Add button to add it to our list. Listing 12.4 shows the code for the btnAdd Click event.
Listing 12.4 Code for the btnAdd Click Event
Private Sub btnAdd_Click(ByVal sender As System.Object, ByVal e As _ System.EventArgs) Handles btnAdd.Click Static Count As Integer = 0 If txtname.Text <> "" And txtPhone.Text <> "" Then Count += 1 ReDim Preserve MyList(Count) MyList(Count - 1).Name = txtName.Text MyList(Count - 1).Phone = txtPhone.Text txtName.Text = "" ' Clear for next entry txtPhone.Text = "" txtName.Focus() Else Beep() MessageBox.Show("Must enter both a name and a number") End If End Sub
First, we define a variable named Count with the Static storage class. Why use the Static storage class? The reason is because Static variables can retain their values after program control leaves the procedure in which they're defined. In other words, Static variables have a lifetime that equals that of the program itself. Non- Static local variables do not. Because we want to maintain a count of the number of names and phone numbers that are entered, Count must be defined as a Static .
Although the initialization of Count to 0 is not necessary (Visual Basic .NET does this automatically), I still prefer to initialize it to 0 as a form of documentation of intent. That is, it tells everyone that we intend to initialize Count to 0 even if Visual Basic .NET ever decides not to. (Things could change, right?)
Next, the code uses an If statement to make sure that the user entered both a name and a phone number. A logical And operator is used for this check. Notice that if either text box is empty, an error message is displayed.
If all goes well, we enter the Then statement block. First, we increment Count by 1. We then dynamically allocate enough memory for Count elements of the MyList() array. We use the Preserve keyword because the second (and subsequent ) time we enter this procedure, we want to preserve all the previously entered names and phone numbers in the array. This is exactly what the Preserve keyword does for us. (If this is fuzzy to you, review Chapter 7, "Arrays.")
The next two statements takes the string data stored in the two text boxes and assigns them into the appropriate members of the PhoneList structure. Why is the array subscripted MyList(Count - 1) ?
MyList(Count - 1).Name = txtName.Text
The reason is because arrays start with element 0, but Count has already been increment to 1. Therefore, even though we might be entering the first person in the list (that is, Count = 1 ), that first person is actually stored in element 0 in the MyList() array. Think about it.
Once the data is stored in the array, we clear out the text boxes, and move the cursor back to the txtName text box to get ready for another name. The Focus() method of a text box is used to set the focus to the associated text box. Therefore, the Focus() method moves the cursor into the text box.
The btnDone Click Event
When the user has finished entering the names and phone numbers, she clicks the Done button. This causes the list of names and phone numbers to be displayed in the txtList text box. The code for the btnDone Click event is shown in Listing 12.5.
Listing 12.5 Code for the btnDone Click Event
Private Sub btnDone_Click(ByVal sender As System.Object, ByVal e As _ System.EventArgs) Handles btnDone.Click Dim i As Integer Dim NewLine As String NewLine = Chr(13) & Chr(10) txtList.Text = "" For i = 0 To UBound(MyList) txtList.Text += MyList(i).Name & " " & MyList(i).Phone & NewLine Next End Sub
The code defines several working variables and the NewLine string. The txtList text box is then cleared and we start executing the For loop. Because UBound() returns the highest valid index of an array, we can use it to set the terminating value of i in the For loop.
The loop counter i is initialized to because that's the starting element of the MyList() array. The statement block is a single statement that displays the name and phone number for each person entered by the user. The NewLine string simply causes each entry to start on a new line in the text box.
Notice how we use the loop counter variable i to index into the MyList() array. For loops are perfect for sequential processing of arrays. By the way, could we also use the code shown in the following code fragment?
For i = UBound(MyList) To 0 Step -1 txtList.Text += MyList(i).Name & " " & MyList(i).Phone & NewLine Next
Yes, it will work, but it also shows a small bug in our program. Because we ReDim for one more index than we actually use, the program never uses the last element in the MyList() array. When you display the list in normal order, this empty array is actually displayed, but we don't notice it because all the MyList() members are unused (empty). However, because the For loop in the preceding code fragment displays the list in reverse order, we can see that the first element displayed (which is the last one in the array) is actually empty. This must also mean that although UBound() is doing its thing correctly, the count of the number of names and phone numbers is actually one less than UBound() tells us. (How would you fix this? Remember: Never use an H-bomb to kill an ant.)