Chapter 5. Working with Data Structures

Provided by:   Thearon Willis, Bryan Newsome

Beginning Microsoft Visual Basic 2008

This topic contains the following sections.

  • Understanding Arrays
  • Understanding Enumerations
  • Understanding Constants
  • Structures
  • Working with ArrayLists
  • Working with Collections
  • Building Lookup Tables with Hashtable
  • Advanced Array Manipulation
  • Summary
  • Exercises

In the previous chapters, you worked with simple data types, namely Integer and String variables. Although these data types are useful in their own rights, more complex programs call for working with data structures; that is, groups of data elements that are organized in a single unit. In this chapter, you learn about the various data structures available in Visual Basic 2008. You also will see some ways in which you can work with complex sets of data. Finally, you learn how you can build powerful collection classes for working with, maintaining, and manipulating lists of complex data.

In this chapter, you learn about:

  • Arrays

  • Enumerations

  • Constants

  • Structures

Understanding Arrays

A fairly common requirement in writing software is the need to hold lists of similar or related data. You can provide this functionality by using an array. Arrays are just lists of data that have a single data type. For example, you might want to store a list of your friends’ ages in an integer array or their names in a string array.

In this section, you take a look at how to define, populate, and use arrays in your applications.

Defining and Using Arrays

When you define an array, you’re actually creating a variable that has more than one dimension. For example, if you define a variable as a string you can only hold a single string value in it:

Dim strName As String

However, with an array you create a kind of multiplier effect with a variable, so you can hold more than one value in a single variable. An array is defined by entering the size of the array after the variable name. So, if you wanted to define a string array with 10 elements, you’d do this:

Dim strName(9) As String

The reason you use (9) instead of (10) to get an array with 10 elements is explained in detail later. The basic explanation is simply that because numbering in an array starts at zero, the first element in an array is zero, the second element in an array is one, and so on.

When you have an array, you can access individual elements in it by providing an index value between 0 and a maximum possible value — this maximum possible value happens to be one less than the total size of the array.

So, to set the element with index 2 in the array, you’d do this:

strName(2) = “Katie”

To get that same element back again, you’d do this:

MessageBox.Show(strName(2))

What’s important is that other elements in the array are unaffected when you set their siblings. So, if you do this:

strName(3) = “Betty”

strName(2) remains set to “Katie”.

Perhaps the easiest way to understand what an array looks like and how it works is to write some code.

  1. In Visual Studio 2008, click the File menu and choose New Project. In the New Project dialog box, create a new Windows Forms Application called Array Demo.

  2. When the Designer for Form1 appears, add a ListBox control to the form. Using the Properties window set its Name property to lstFriends and its IntegralHeight property to False.

  3. Add a Button control to the form, set its Name property to btnArrayElement, and set its Text property to Array Element. Arrange your controls so that your form looks similar to Figure 5-1 as you’ll be adding more Button controls to this project later.

    Form1

    Figure 5-1

  4. Double-click the button and add the following highlighted code to its Click event handler. You’ll receive an error message that the ClearList procedure is not defined. You can ignore this error because you’ll be adding that procedure in the next step:

    Private Sub btnArrayElement_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles btnArrayElement.Click ‘Clear the list ClearList() ‘Declare an array Dim strFriends(4) As String ‘Populate the array strFriends(0) = “Wendy” strFriends(1) = “Harriet” strFriends(2) = “Jay” strFriends(3) = “Michelle” strFriends(4) = “Richard” ‘Add the first array item to the list lstFriends.Items.Add(strFriends(0)) End Sub

  5. Now create the following procedure:

        Private Sub ClearList()
            ‘Clear the list
            lstFriends.Items.Clear()
        End Sub
    
  6. Save your project by clicking the Save All button on the toolbar and then run it. When the form displays, click the Array Element button and the list box on your form will be populated with the name Wendy.

Note

First you clear the list box by calling the ClearList method. Although the list is empty at this point, you’ll be adding more buttons to this project in the following Try It Out exercises and may want to compare the results of the each of the buttons.

‘Clear the list ClearList()

When you define an array, you have to specify a data type and a size. In this case, you’re specifying an array of type String and also defining an array size of 5. The way the size is defined is a little quirky. You have to specify a number one less than the final size you want (you’ll learn why shortly). So here, you have used the line:

‘Declare an array Dim strFriends(4) As String

In this way, you end up with an array of size 5. Another way of expressing this is to say that you have an array consisting of 5 elements.

When done, you have your array, and you can access each item in the array by using an index. The index is given as a number in parentheses after the name of the array. Indexes start at zero and go up to one less than the number of items in the array. The following example sets all five possible items in the array to the names:

‘Populate the array strFriends(0) = “Wendy” strFriends(1) = “Harriet” strFriends(2) = “Jay” strFriends(3) = “Michelle” strFriends(4) = “Richard”

Just as you can use an index to set the items in an array, you can use an index to get items back out. In this case, you’re asking for the item at position 0, which returns the first item in the array, namely Wendy:

‘Add the first array item to the list lstFriends.Items.Add(strFriends(0))

The reason the indexes and sizes seem skewed is that the indexes are zero-based, whereas humans tend to number things beginning at 1. When putting items into or retrieving items from an array, you have to adjust the position you want down by one to get the actual index; for example, the fifth item is actually at position 4, the first item is at position 0, and so on. When you define an array, you do not actually specify the size of the array but rather the upper index bound — that is, the highest possible value of the index that the array will support.

Why should the indexes be zero-based? Remember that to the computer, a variable represents the address of a location in the computer’s memory. Given an array index, Visual Basic 2008 just multiplies the index by the size of one element and adds the product to the address of the array as a whole to get the address of the specified element. The starting address of the array as a whole is also the starting address of the first element in it. That is, the first element is zero times the size of an element away from the start of the whole array; the second element is 1 times the size of an element away from the start of the whole array; and so on.

The method you define contains only one line of code but its reuse becomes apparent in the next Try It Out. This method merely clears the Items collection of the list box.

Private Sub ClearList() ‘Clear the list lstFriends.Items.Clear() End Sub

Using For Each . . . Next

One common way to work with arrays is by using a For Each...Next loop. This loop is introduced in Chapter 4, when you used it with a string collection returned from the My.Computer.FileSystem.GetDirectories method. In the following Try It Out, you look at how you use For Each...Next with an array.

  1. Close your program if it is still running and open the Code Editor for Form1. Add the following highlighted variable declaration at the top of your form class:

    Public Class Form1 'Declare a form level array Private strFriends(4) As String

  2. In the Class Name combo box at the top left of your Code Editor, select (Form1 Events). In the Method Name combo box at the top right of your Code Editor, select the Load event. This causes the Form1_Load event handler to be inserted into your code. Add the following highlighted code to this procedure:

    Private Sub Form1_Load(ByVal sender As Object, _ ByVal e As System.EventArgs) Handles Me.Load 'Populate the array strFriends(0) = "Wendy" strFriends(1) = "Harriet" strFriends(2) = "Jay" strFriends(3) = "Michelle" strFriends(4) = "Richard" End Sub

  3. Switch to the Form Designer and add another Button control. Set its Name property to btnEnumerateArray and its Text property to Enumerate Array.

  4. Double-click this new button and add the following highlighted code to its Click event handler:

    Private Sub btnEnumerateArray_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles btnEnumerateArray.Click 'Clear the list ClearList() 'Enumerate the array For Each strName As String In strFriends 'Add the array item to the list lstFriends.Items.Add(strName) Next End Sub

  5. Run the project and click the button. You’ll see results like those in Figure 5–2.

Form1

Figure 5-2

Note

You start this exercise by declaring an array variable that is local to the form, meaning that the variable is available to all procedures in the form class. Whenever variables are declared outside a method in the form class, they are available to all methods in the form.

'Declare a form level array Private strFriends(4) As String

Next you added the Load event handler for the form and then added code to populate the array. This procedure will be called whenever the form loads, ensuring that your array always gets populated.

Private Sub Form1_Load(ByVal sender As Object, _ ByVal e As System.EventArgs) Handles Me.Load 'Populate the array strFriends(0) = "Wendy" strFriends(1) = "Harriet" strFriends(2) = "Jay" strFriends(3) = "Michelle" strFriends(4) = "Richard" End Sub

Chapter 4 shows the For Each.Next loop iterate through a string collection; in this example, it is used in an array. The principle is similar; you have to create a control variable that is of the same type as an element in the array and gives this to the loop when it starts. This has all been done in one line of code. The control variable, strName, is declared and used in the For Each statement by using the As String keyword.

The internals behind the loop move through the array starting at element 0 until it reaches the last element. For each iteration, you can examine the value of the control variable and do something with it; in this case, you add the name to the list.

'Enumerate the array For Each strName As String In strFriends 'Add the array item to the list lstFriends.Items.Add(strName) Next

Also, note that the items are added to the list in the same order that they appear in the array. That’s because For Each.Next proceeds from the first item to the last item as each item is defined.

Passing Arrays as Parameters

It’s extremely useful to be able to pass an array (which could be a list of values) to a function as a parameter. In the next Try It Out, you’ll look at how to do this.

  1. Return to the Forms Designer in the Array Demo project and add another Button control. Set its Name property to btnArraysAsParameters and its Text property to Arrays as Parameters.

  2. Double-click the button and add the following highlighted code to its Click event handler. You’ll receive an error message that the AddItemsToList procedure is not defined. You can ignore this error because you’ll be adding that procedure in the next step:

    Private Sub btnArraysAsParameters_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles btnArraysAsParameters.Click 'Clear the list ClearList() 'List your friends AddItemsToList(strFriends) End Sub

  3. Add the AddItemsToList procedure as follows:

    Private Sub AddItemsToList(ByVal arrayList() As String) 'Enumerate the array For Each strName As String In arrayList 'Add the array item to the list lstFriends.Items.Add(strName) Next End Sub

  4. Run the project and click the button. You’ll see the same results that were shown in Figure 5–2.

Note

The trick here is to tell the AddItemsToList method that the parameter it’s expecting is an array of type String. You do this by using empty parentheses, like this:

Sub AddItemsToList(ByVal arrayList() As String)

If you specify an array but don’t define a size (or upper-bound value), you’re telling Visual Basic 2008 that you don’t know or care how big the array is. That means that you can pass an array of any size through to AddItemsToList. In the btnArraysAsParameters_Click procedure, you’re sending your original array:

'List your friends AddItemsToList(strFriends)

But what happens if you define another array of a different size? In the next Try It Out, you’ll find out.

  1. Return to the Forms Designer of the Array Demo project. Add another Button control and set its Name property to btnMoreArrayParameters and its Text property to More Array Parameters.

  2. Double-click the button and add the following highlighted code to its Click event handler:

    Private Sub btnMoreArrayParameters_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles btnMoreArrayParameters.Click 'Clear the list ClearList() 'Declare an array Dim strMoreFriends(1) As String 'Populate the array strMoreFriends(0) = "Elaine" strMoreFriends(1) = "Debra" 'List your friends AddItemsToList(strFriends) AddItemsToList(strMoreFriends) End Sub

  3. Run the project and click the button. You will see the form shown in Figure 5–3.

Form1

Figure 5-3

Note

What you have done here is prove that the array you pass as a parameter does not have to be of a fixed size. You created a new array of size 2 and passed it through to the same AddItemsToList function.

As you’re writing code, you can tell whether a parameter is an array type by looking for empty parentheses in the IntelliSense pop-up box, as illustrated in Figure 5–4.

AddItemsToList(arrayList() As String)

Figure 5-4

Not only are you informed that arrayList is an array type, but you also see that the data type of the array is String.

Sorting Arrays

It is sometimes useful to be able to sort an array. In this Try It Out, you see how you can take an array and sort it alphabetically.

  1. Return to the Forms Designer in the Array Demo project and add another Button control. Set its Name property to btnSortingArrays and its Text property to Sorting Arrays.

  2. Double-click the button and add the following highlighted code to its Click event handler:

    Private Sub btnSortingArrays_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles btnSortingArrays.Click 'Clear the list ClearList() 'Sort the array Array.Sort(strFriends) 'List your friends AddItemsToList(strFriends) End Sub

  3. Run the project and click the button. You’ll see the list box on your form populated with the names from your array sorted alphabetically.

Note

All arrays are internally implemented in a class called System.Array. In this case, you use a method called Sort on that class. The Sort method takes a single parameter—namely, the array you want to sort. The method then does as its name suggests and sorts it for you into an order appropriate to the data type of the array elements. In this case you are using a string array, so you get an alphanumeric sort. If you were to attempt to use this technique on an array containing integer or floating-point values, the array would be sorted in numeric order.

'Sort the array Array.Sort(strFriends)

The ability to pass different parameter types in different calls to the same method name and to get behavior that is appropriate to the parameter types actually passed is called method overloading. Sort is referred to as an overloaded method.

Going Backwards

For Each...Next will go through an array in only one direction. It starts at position 0 and loops through to the end of the array. If you want to go through an array backwards (from the length –1 position to 0), you have two options.

First, you can step through the loop backwards by using a standard For...Next loop to start at the upper index bound of the first dimension in the array and work your way to 0 using the Step -1 keyword, as shown in the following example:

For intIndex As Integer = strFriends.GetUpperBound(0) To 0 Step -1 ‘Add the array item to the list lstFriends.Items.Add(strFriends(intIndex)) Next

You can also call the Reverse method on the Array class to reverse the order of the array and then use your For Each...Next loop, as shown in the next Try It Out.

  1. Return to the Forms Designer and add another Button control. Set its Name property to btnReversingAnArray and its Text property to Reversing an Array.

  2. Double-click the button and add the following highlighted code to its Click event handler:

    Private Sub btnReversingAnArray_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles btnReversingAnArray.Click 'Clear the list ClearList() 'Reverse the order—elements will be in descending order Array.Reverse(strFriends) 'List your friends AddItemsToList(strFriends) End Sub

  3. Run the project and click the button. You’ll see the friends listed in reverse order as shown in Figure 5–5.

Form1

Figure 5-5

Note

The Reverse method reverses the order of elements in a one-dimensional array, which is what you are working with here. By passing the strFriends array to the Reverse method, you are asking the Reverse method to re-sequence the array from bottom to top:

'Reverse the order—elements will be in descending order Array.Reverse(strFriends)

After the items in your array have been reversed, you simply call the AddItemsToList procedure to have the items listed:

'List your friends AddItemsToList(strFriends)

If you want to list your array in descending sorted order, you would call the Sort method on the Array class to have the items sorted in ascending order and then call the Reverse method to have the sorted array reversed, putting it into descending order.

Initializing Arrays with Values

It is possible to create an array in Visual Basic 2008 and populate it in one line of code, rather than having to write multiple lines of code to declare and populate the array as shown here:

‘Declare an array Dim strFriends(4) As String ‘Populate the array strFriends(0) = “Wendy” strFriends(1) = “Harriet” strFriends(2) = “Jay” strFriends(3) = “Michelle” strFriends(4) = “Richard”

You learn more about initializing arrays with values in the next Try It Out.

  1. Return to the Forms Designer in the Array Demo project and add one last Button control. Set its Name property to btnInitializingArraysWithValues and its Text property to Initializing Arrays with Values.

  2. Double-click the button and add the following highlighted code to its Click event handler:

    Private Sub btnInitializingArraysWithValues_Click( _ ByVal sender As System.Object, ByVal e As System.EventArgs) _ Handles btnInitializingArraysWithValues.Click 'Clear the list ClearList() 'Declare and populate an array Dim strMyFriends() As String = {"Elaine", "Richard", "Debra", _ "Wendy", "Harriet"} 'List your friends AddItemsToList(strMyFriends) End Sub

  3. Run the project and click the button. Your list box is populated with the friends listed in this array.

Note

The pair of braces {} allows you to set the values that should be held in an array directly. In this instance, you have five values to enter into the array, separated with commas. Note that when you do this, you don’t specify an upper bound for the array; instead, you use empty parentheses. Visual Basic 2008 prefers to calculate the upper bound for you based on the values you supply.

'Declare and populate an array Dim strMyFriends() As String = {"Elaine", "Richard", "Debra", _ "Wendy", "Harriet"}

This technique can be quite awkward to use when populating large arrays. If your program relies on populating large arrays, you might want to use the method illustrated earlier: specifying the positions and the values. This is especially true when populating an array with values that change at runtime.

Understanding Enumerations

So far, the variables you’ve seen had virtually no limitations on the kinds of data you can store in them. Technical limits notwithstanding, if you have a variable defined As Integer, you can put any number you like in it. The same holds true for String and Double. You have seen another variable type, however, that has only two possible values: Boolean variables can be either True or False and nothing else.

Often, when writing code, you want to limit the possible values that can be stored in a variable. For example, if you have a variable that stores the number of doors that a car has, do you really want to be able to store the value 163,234?

Using Enumerations

Enumerations allow you to build a new type of variable, based on one of these data types: Integer, Long, Short, or Byte. This variable can be set to one value of a set of possible values that you define, and ideally prevent someone from supplying invalid values. It is used to provide clarity in the code, as it can describe a particular value. In the following Try It Out, you’ll look at how to build an application that looks at the time of day and, based on that, can record a DayAction of one of these possible values:

  • Asleep

  • Getting ready for work

  • Traveling to work

  • At work

  • At lunch

  • Traveling from work

  • Relaxing with friends

  • Getting ready for bed

  1. Create a new Windows Forms Application in Visual Studio 2008 called Enum Demo.

  2. Set the Text property of Form1 to What’s Richard Doing?

  3. Now add a DateTimePicker control and set the following properties:

  • Set Name to dtpHour.

  • Set Format to Time.

  • Set ShowUpDown to True.

  • Set Value to 00:00 am.

  • Set Size to 90, 20.

  1. Add a Label control to the form, set its Name property to lblState, and set its Text property to State Not Initialized. Resize your form so it looks similar to Figure 5–6.

What's Richard Doing

Figure 5-6

  1. View the Code Editor for the form by right-clicking the form and choosing View Code from the context menu. At the top of the class add the following highlighted enumeration:

    Public Class Form1 'DayAction Enumeration Private Enum DayAction As Integer Asleep = 0 GettingReadyForWork = 1 TravelingToWork = 2 AtWork = 3 AtLunch = 4 TravelingFromWork = 5 RelaxingWithFriends = 6 GettingReadyForBed = 7 End Enum

  2. With an enumeration defined, you can create new member variables that use the enumeration as their data type. Add this member:

    'Declare variable Private CurrentState As DayAction

  3. Add the following code below the variable you just added:

    'Hour property Private Property Hour() As Integer Get 'Return the current hour displayed Return dtpHour.Value.Hour End Get Set(ByVal value As Integer) 'Set the date using the hour passed to this property dtpHour.Value = _ New Date(Now.Year, Now.Month, Now.Day, value, 0, 0) 'Set the display text lblState.Text = "At " & value & ":00, Richard is " End Set End Property

  4. In the Class Name combo box at the top of the Code Editor, select (Form1 Events), and in the Method Name combo box, select the Load event. Add the following highlighted code to the event handler:

    Private Sub Form1_Load(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MyBase.Load 'Set the Hour property to the current hour Me.Hour = Now.Hour End Sub

  5. In the Class Name combo box at the top of the Code Editor, select dtpHour, and in the Method Name combo box, select the ValueChanged event. Add the following highlighted code to the event handler:

    Private Sub dtpHour_ValueChanged(ByVal sender As Object, _ ByVal e As System.EventArgs) Handles dtpHour.ValueChanged 'Update the Hour property Me.Hour = dtpHour.Value.Hour End Sub

  6. Save your project and then run it. You will be able to click the up and down arrows in the date and time picker control and see the text updated to reflect the hour selected as shown in Figure 5–7.

What's Richard Doing

Figure 5-7

Note

In this application, the user will be able to use the date-time picker to choose the hour. You then look at the hour and determine which one of the eight states Richard is in at the given time. To achieve this, you have to keep the hour around somehow. To store the hour, you have created a property for the form in addition to the properties it already has, such as Name and Text. The new property is called Hour, and it is used to set the current hour in the DateTimePicker control and the label control. The property is defined with a Property.End Property statement:

Note the Get.End Get and Set.End Set blocks inside the Property.End Property statement. The Get block contains a Return statement and is called automatically to return the property value when the property name appears in an expression. The data type to be returned is not specified in the Get statement, because it was already declared As Integer in the Property statement. The Set block is called automatically when the value is set, such as by putting the property name to the left of an equals sign.

When the application starts, you set the Hour property to the current hour on your computer. You get this information from Now, a Date variable containing the current date and time:

Private Property Hour() As Integer Get 'Return the current hour displayed Return dtpHour.Value.Hour End Get Set(ByVal value As Integer) 'Set the date using the hour passed to this property dtpHour.Value = _ New Date(Now.Year, Now.Month, Now.Day, value, 0, 0) 'Set the display text lblState.Text = "At " & value & ":00, Richard is " End Set End Property ByVal e As System.EventArgs) Handles MyBase.Load 'Set the Hour property to the current hour Me.Hour = Now.Hour End Sub

You also set the Hour property when the Value property changes in the DateTimePicker control:

Private Sub dtpHour_ValueChanged(ByVal sender As Object, _ ByVal e As System.EventArgs) Handles dtpHour.ValueChanged 'Update the Hour property Me.Hour = dtpHour.Value.Hour End Sub

When the Hour property is set, you have to update the value of the DateTimePicker control to show the new hour value, and you have to update the label on the form as well. The code to perform these actions is put inside the Set block for the Hour property.

The first update that you perform is to update the Value property of the DateTimePicker control. The Value property of the date-time picker is a Date data type; thus, you cannot simply set the hour in this control, although you can retrieve just the hour from this property. To update this property, you must pass it a Date data type.

You do this by calling New (see Chapter 11) for the Date class, passing it the different date and time parts as shown in the code: year, month, day, hour, minute, second. You get the year, month, and day by extracting them from the Now variable. The hour is passed using the value parameter that was passed to this Hour property, and the minutes and seconds are passed as 0, since you do not want to update the specific minutes or seconds.

'Set the date using the hour passed to this property dtpHour.Value = _ New Date(Now.Year, Now.Month, Now.Day, value, 0, 0)

The second update performed by this Hour property is to update the label on the form using some static text and the hour that is being set in this property.

'Set the display text lblState.Text = "At " & value & ":00, Richard is "

You have not evaluated the Hour property to determine the state using the DayAction enumeration, but you do that next.

Determining the State

In the next Try It Out, you look at determining the state when the Hour property is set. You can take the hour returned by the DateTimePicker control and use it to determine which value in your enumeration it matches. This section demonstrates this and displays the value on your form.

  1. Open the Code Editor for Form1 and modify the Hour property as follows:

    Set(ByVal value As Integer) 'Set the date using the hour passed to this property dtpHour.Value = _ New Date(Now.Year, Now.Month, Now.Day, value, 0, 0) 'Determine the state If value >= 6 And value < 7 Then CurrentState = DayAction.GettingReadyForWork ElseIf value >= 7 And value < 8 Then CurrentState = DayAction.TravelingToWork ElseIf value >= 8 And value < 13 Then CurrentState = DayAction.AtWork ElseIf value >= 13 And value < 14 Then CurrentState = DayAction.AtLunch ElseIf value >= 14 And value < 17 Then CurrentState = DayAction.AtWork ElseIf value >= 17 And value < 18 Then CurrentState = DayAction.TravelingFromWork ElseIf value >= 18 And value < 22 Then CurrentState = DayAction.RelaxingWithFriends ElseIf value >= 22 And value < 23 Then CurrentState = DayAction.GettingReadyForBed Else CurrentState = DayAction.Asleep End If 'Set the display text lblState.Text = "At " & value & ":00, Richard is " & _ CurrentState End Set

  2. Run the project. You’ll see something like Figure 5–8.

What's Richard Doing

Figure 5-8

  1. Here’s the problem: The user doesn’t know what 2 means. Close the project and find the following section of code at the end of the Hour property:

    'Set the display text lblState.Text = "At " & value & ":00, Richard is " & _ CurrentState End Set

  2. Change the last line to read as follows:

    'Set the display text lblState.Text = "At " & value & ":00, Richard is " & _ CurrentState.ToString End Set

  3. Now run the project and you’ll see something like Figure 5–9.

What's Richard Doing

Figure 5-9

Note

As you typed the code, you might have noticed that whenever you tried to set a value against CurrentState, you were presented with an enumerated list of possibilities as shown in Figure 5–10.

DayAction.Asleep

Figure 5-10

Visual Studio 2008 knows that CurrentState is of type DayAction. It also knows that DayAction is an enumeration and that it defines eight possible values, each of which is displayed in the IntelliSense pop-up box. Clicking an item in the enumerated list causes a tooltip to be displayed with the actual value of the item; for example, clicking DayAction.RelaxingWithFriends will display a tooltip with a value of 6.

Fundamentally, however, because DayAction is based on an integer, CurrentState is an integer value. That’s why, the first time you ran the project with the state determination code in place, you saw an integer at the end of the status string. At 7 a.m., you know that Richard is traveling to work, or rather CurrentState equals DayAction.TravelingToWork. You defined this as 2, which is why 2 is displayed at the end of the string.

What you’ve done in this Try It Out is to tack a call to the ToString method onto the end of the CurrentState variable. This results in a string representation of DayAction being used, rather than the integer representation

Enumerations are incredibly useful when you want to store one of a possible set of values in a variable. As you start to drill into more complex objects in the Framework, you’ll find that they are used all over the place!

Setting Invalid Values

One of the limitations of enumerations is that it is possible to store a value that technically isn’t one of the possible defined values of the enumeration. For example, you can change the Hour property so that rather than setting CurrentState to Asleep, you can set it to 999:

ElseIf value >= 22 And value < 23 Then CurrentState = DayAction.GettingReadyForBed Else CurrentState = 999 End If

If you build the project, you’ll notice that Visual Basic 2008 doesn’t flag this as an error if you have the Option Strict option turned off. When you run the project, you’ll see that the value for CurrentState is shown on the form as 999.

So, you can see that you can set a variable that references an enumeration to a value that is not defined in that enumeration and the application will still “work” (as long as the value is of the same type as the enumeration). If you build classes that use enumerations, you have to rely on the consumer of that class being well behaved. One technique to solve this problem would be to disallow invalid values in any properties that used the enumeration as their data type.

Understanding Constants

Another good programming practice that you need to look at is the constant. Imagine you have these two methods, each of which does something with a given file on the computer’s disk. (Obviously, we’re omitting the code here that actually manipulates the file.)

Public Sub DoSomething() ‘What’s the filename? Dim strFileName As String = “c:\Temp\Demo.txt” ‘Open the file ... End Sub Public Sub DoSomethingElse() ‘What’s the filename? Dim strFileName As String = “c:\Temp\Demo.txt” ‘Do something with the file ... End Sub

Using Constants

The code defining a string literal gives the name of a file twice. This is poor programming practice because if both methods are supposed to access the same file, and if that file name changes, this change has to be made in two separate places.

In this instance, both methods are next to each other and the program itself is small. However, imagine you had a massive program in which a separate string literal pointing to the file is defined in 10, 50, or even 1,000 places. If you needed to change the file name, you’d have to change it many times. This is exactly the kind of thing that leads to serious problems for maintaining software code.

What you need to do instead is define the file name globally and then use that global symbol for the file name in the code, rather than using a string literal. This is what a constant is. It is, in effect, a special kind of variable that cannot be varied when the program is running. In the next Try It Out, you learn to use constants.

  1. Create a new Windows Forms Application in Visual Studio 2008 called Constants Demo.

  2. When the Forms Designer appears, add three Button controls. Set the Name property of the first button to btnOne, the second to btnTwo, and the third to btnThree. Change the Text property of each to One, Two, and Three, respectively. Arrange the controls on your form so it looks similiar Figure 5–11.

Form1

Figure 5-11

  1. View the Code Editor for the form by right-clicking the form and choosing View Code from the context menu. At the top of the class definition, add the following highlighted code:

    Public Class Form1 'File name constant Private Const strFileName As String = "C:\Temp\Hello.txt"

  2. In the Class Name combo box at the top of the editor, select btnOne, and in the Method Name combo box select the Click event. Add the following highlighted code to the Click event handler:

    Private Sub btnOne_Click(ByVal sender As Object, _ ByVal e As System.EventArgs) Handles btnOne.Click 'Using a constant MessageBox.Show("1: " & strFileName, "Constants Demo") End Sub

  3. Select btnTwo in the Class Name combo box and select its Click event in the Method Name combo box. Add the highlighted code:

    Private Sub btnTwo_Click(ByVal sender As Object, _ ByVal e As System.EventArgs) Handles btnTwo.Click 'Using the constant again MessageBox.Show("2: " & strFileName, "Constants Demo") End Sub

  4. Select btnThree in the Class Name combo box and the Click event in the Method Name combo box. Add this code to the Click event handler:

    Private Sub btnThree_Click(ByVal sender As Object, _ ByVal e As System.EventArgs) Handles btnThree.Click 'Reusing the constant one more time MessageBox.Show("3: " & strFileName, "Constants Demo") End Sub

  5. Save and run your project and then click button One. You’ll see the message box shown in Figure 5–12.

Constants Demo

Figure 5-12

Likewise, you’ll see the same file name when you click buttons Two and Three.

Note

A constant is actually a type of value that cannot be changed when the program is running. It is defined as a variable, but you add Const to the definition indicating that this variable is constant and cannot change.

'File name constant Private Const strFileName As String = "C:\Temp\Hello.txt"

You’ll notice that it has a data type, just like a variable, and you have to give it a value when it’s defined—which makes sense, because you can’t change it later.

When you want to use the constant, you refer to it just as you would refer to any variable:

Private Sub btnOne_Click(ByVal sender As Object, _ ByVal e As System.EventArgs) Handles btnOne.Click 'Using a constant MessageBox.Show("1: " & strFileName, "Constants Demo") End Sub

As mentioned before, the appeal of a constant is that it allows you to change a value that’s used throughout a piece of code by altering a single piece of code. However, note that you can change constants only at design time; you cannot change their values at runtime. Look at how this works.

Different Constant Types

In this section, you’ve seen how to use a string constant, but you can use other types of variables as constants. There are some rules: Basically, a constant must not be able to change, so you should not store an object data type (which we will discuss in Chapter 11) in a constant.

Integers are very common types of constants. They can be defined like this:

Public Const intHoursAsleepPerDay As Integer = 8

Also, it’s fairly common to see constants used with enumerations, like this:

Public Const intRichardsTypicalState As DayAction = DayAction.AtWork

Structures

Applications commonly need to store several pieces of information of different data types that all relate to one thing and must be kept together in a group, such as a customer’s name and address (strings) and balance (a number). Usually, an object of a class is used to hold such a group of variables, as you’ll discover in Chapter 11, but you can also use a structure. Structures are similar to class objects but are somewhat simpler, so they’re discussed here.

Later on, as you design applications, you need to be able to decide whether to use a structure or a class. As a rule of thumb, we suggest that if you end up putting a lot of methods on a structure, it should probably be a class. It’s also relatively tricky to convert from a structure to a class later on, because structures and objects are created using different syntax rules, and sometimes the same syntax produces different results between structures and objects. So choose once and choose wisely!

Building Structures

Take a look at how you can build a structure.

  1. Create a new Windows Forms Application in Visual Studio 2008 called Structure Demo.

  2. When the Forms Designer appears add four Label controls, four TextBox controls, and a Button control. Arrange your controls so that they look similar to Figure 5–13.

  3. Set the Name properties as follows:

  • ■Set Label1 to lblName.

  • ■Set TextBox1 to txtName.

  • ■Set Label2 to lblFirstName.

  • ■Set TextBox2 to txtFirstName.

  • ■Set Label3 to lblLastName.

  • ■Set TextBox3 to txtLastName.

  • ■Set Label4 to lblEmail.

  • ■Set TextBox4 to txtEmail.

  • ■Set Button1 to btnListCustomer.

  1. Set the Text properties of the following controls:
  • ■Set lblName to Name.

  • ■Set lblFirstName to First Name.

  • ■ Set lblLastName to Last Name.

  • ■Set lblEmail to E-mail.

  • ■Set btnListCustomer to List Customer.

Form1

Figure 5-13

  1. Right-click the project name in the Solution Explorer, choose the Add menu item from the context menu, and then choose the Class submenu item. In the Add New Item – Structure Demo dialog box, enter Customer in the Name field and then click the Add button to have this item added to your project.

  2. When the Code Editor appears, replace all existing code with the following code:

    Public Structure Customer 'Public members Public FirstName As String Public LastName As String Public Email As StringEnd Structure

Note that you must make sure to replace the Class definition with the Structure definition!

  1. View the Code Editor for the form and add this procedure:

    Public Class Form1 Public Sub DisplayCustomer(ByVal customer As Customer) 'Display the customer details on the form txtFirstName.Text = customer.FirstName txtLastName.Text = customer.LastName txtEmail.Text = customer.Email End Sub

  2. In the Class Name combo box at the top of the editor, select btnListCustomer, and in the Method Name combo box select the Click event. Add the following highlighted code to the Click event handler:

    Private Sub btnListCustomer_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles btnListCustomer.Click 'Create a new customer Dim objCustomer As Customer objCustomer.FirstName = "Michael" objCustomer.LastName = "Dell" objCustomer.Email = "mdell@somecompany.com" 'Display the customer DisplayCustomer(objCustomer) End Sub

  3. Save and run your project. When the form appears, click the List Customer button and you should see results similar to those shown in Figure 5–14.

Form1

Figure 5-14

Note

You define a structure using a Structure.End Structure statement. Inside this block, the variables that make up the structure are declared by name and type: These variables are called members of the structure.

Public Structure Customer 'Public members Public FirstName As String Public LastName As String Public Email As StringEnd Structure

Notice the keyword Public in front of each variable declaration as well as in front of the Structure statement. You have frequently seen Private used in similar positions. The Public keyword means that you can refer to the member (such as FirstName) outside of the definition of the Customer structure itself.

In the btnListCustomer_Click procedure, you define a variable of type Customer using the Dim statement. (If Customer were a class, you would also have to initialize the variable by setting objCustomer equal to New Customer, as will be discussed in Chapter 11.)

Private Sub btnListCustomer_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles btnListCustomer_Click.Click 'Create a new customer Dim objCustomer As Customer

Then you can access each of the member variables inside the Customer structure objCustomer by giving the name of the structure variable, followed by a dot, followed by the name of the member:

objCustomer.FirstName = "Michael" objCustomer.LastName = "Dell" objCustomer.Email = "mdell@somecompany.com" 'Display the customer DisplayCustomer(objCustomer) End Sub

The DisplayCustomer procedure simply accepts a Customer structure as its input parameter and then accesses the members of the structure to set the Text properties of the text boxes on the form:

Public Sub DisplayCustomer(ByVal customer As Customer) 'Display the customer details on the form txtFirstName.Text = customer.FirstName txtLastName.Text = customer.LastName txtEmail.Text = customer.Email End Sub

Adding Properties to Structures

You can add properties to a structure, just as you did to the form in the Enum Demo project earlier in the chapter. You learn how in the next Try It Out.

  1. Open the Code Editor for Customer and add this highlighted code to create a read-only property Name:

    'Public members Public FirstName As String Public LastName As String Public Email As String 'Name property Public ReadOnly Property Name() As String Get Return FirstName & " " & LastName End Get End Property

  2. Open the Code Editor for Form1. Modify the DisplayCustomer method with the highlighted code:

    Public Sub DisplayCustomer(ByVal customer As Customer) 'Display the customer details on the form txtName.Text = customer.Name txtFirstName.Text = customer.FirstName txtLastName.Text = customer.LastName txtEmail.Text = customer.Email End Sub

  3. Run the project and click the List Customer button. You’ll see that the Name text box, which was empty in Figure 5–14, is now populated with the customer’s first and last name.

Working with ArrayLists

Suppose you need to store a set of Customer structures. You could use an array, but in some cases the array might not be so easy to use.

  • If you need to add a new Customer to the array, you need to change the size of the array and insert the new item in the new last position in the array. (You’ll learn how to change the size of an array later in this chapter.)

  • If you need to remove a Customer from the array, you need to look at each item in the array in turn. When you find the one you want, you have to create another version of the array one element smaller than the original array and copy every item except the one you want to delete into the new array.

  • If you need to replace a Customer in the array with another customer, you need to look at each item in turn until you find the one you want and then replace it manually.

The ArrayList provides a way to create an array that can be easily manipulated as you run your program.

Using an ArrayList

Look at using an ArrayList in this next Try It Out.

  1. Return to the Structure Demo project in Visual Studio 2008. Make the form larger, move the existing controls down, and then add a new ListBox control as shown in Figure 5–15. Set the Name property of the list box to lstCustomers and its IntegralHeight property to False.

You can click the form and press Ctrl+A to select all of the controls and then drag them to their new location.

Form1

Figure 5-15

  1. Open the Code Editor for Form1 and add the member highlighted here to the top of the class definition:

    Public Class Form1 'Form level members Private objCustomers As New ArrayList

  2. Add this method to Form1 to create a new customer:

    Public Sub CreateCustomer(ByVal firstName As String, _ ByVal lastName As String, ByVal email As String) 'Declare a Customer object Dim objNewCustomer As Customer 'Create the new customer objNewCustomer.FirstName = firstName objNewCustomer.LastName = lastName objNewCustomer.Email = email 'Add the new customer to the list objCustomers.Add(objNewCustomer) 'Add the new customer to the ListBox control lstCustomers.Items.Add(objNewCustomer) End Sub

  3. Modify the btnListCustomer_Click method next, making these code changes:

    Private Sub btnListCustomer_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles btnListCustomer.Click 'Create some customers CreateCustomer("Darrel", "Hilton", "dhilton@somecompany.com") CreateCustomer("Frank", "Peoples", "fpeoples@somecompany.com") CreateCustomer("Bill", "Scott", "bscott@somecompany.com") End Sub

  4. Run the project and click the List Customer button. You’ll see results like those shown in Figure 5–16.

Form1

Figure 5-16

You are adding Customer structures to the list, but they are being displayed by the list as Structure_Demo.Customer; this is the full name of the structure. The ListBox control accepts string values, so, by specifying that you wanted to add the Customer structure to the list box, Visual Basic 2008 called the ToString method of the Customer structure. By default, the ToString method for a structure returns the structure name, not the contents that you wanted to see. So what you want to do is tweak the Customer structure so that it can display something more meaningful. When you do that in the next Try It Out, you’ll see how the ArrayList works.

  1. Return to the Structure Demo project and open the Code Editor for Customer and add the following method to the structure, ensuring that it is below the member declarations. Remember from Chapter 3 that to insert an XML Document Comment block, you type three apostrophes above the method name:

    ''' <summary> ''' Overrides the default ToString method ''' </summary> ''' <returns>String</returns> ''' <remarks>Returns the customer name and email address</remarks> Public Overrides Function ToString() As String Return Name & " (" & Email & ")" End FunctionEnd Structure

  2. Run the project and click the List Customer button. You’ll see the same results as shown in Figure 5–17.

Form1

Figure 5-17

Note

Whenever a Customer structure is added to the list, the list box calls the ToString method on the structure to get a string representation of that structure. With this code, you override the default functionality of the ToString method so that, rather than returning just the name of the structure, you get some useful text:

''' <summary> ''' Overrides the default ToString method ''' </summary> ''' <returns>String</returns> ''' <remarks>Returns the customer name and email address</remarks> Public Overrides Function ToString() As String Return Name & " (" & Email & ")" End Function

An ArrayList can be used to store a list of objects/structures of any type (in contrast to a regular array). In fact, you can mix the types within an ArrayList—a topic we’ll be talking about in a little while. In this example, you created a method called CreateCustomer that initializes a new Customer structure based on parameters that were passed to the method:

Public Sub CreateCustomer(ByVal firstName As String, _ ByVal lastName As String, ByVal email As String) 'Declare a Customer object Dim objNewCustomer As Customer 'Create the new customer objNewCustomer.FirstName = firstName objNewCustomer.LastName = lastName objNewCustomer.Email = email

After the structure has been initialized, you add it to the ArrayList stored in objCustomers:

'Add the new customer to the list objCustomers.Add(objNewCustomer)

You also add it to the list box itself, like this:

'Add the new customer to the ListBox control lstCustomers.Items.Add(objNewCustomer)

With CreateCustomer defined, you can call it to add new members to the ArrayList and to the ListBox control when the user clicks the List Customer button:

Private Sub btnListCustomer_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles btnListCustomer.Click 'Create some customers CreateCustomer("Darrel", "Hilton", "dhilton@somecompany.com") CreateCustomer("Frank", "Peoples", "fpeoples@somecompany.com") CreateCustomer("Bill", "Scott", "bscott@somecompany.com") End Sub

Deleting from an ArrayList

OK, so now you know the principle behind an ArrayList. You use it to do something that’s traditionally hard to do with arrays but is pretty easy to do with an ArrayList, such as dynamically adding new values. Now let’s look at how easy it is to delete items from an ArrayList.

  1. Return to the Code Editor in the Structure Demo project and add the SelectedCustomer property to the form as follows:

    Public ReadOnly Property SelectedCustomer() As Customer Get If lstCustomers.SelectedIndex <> -1 Then 'Return the selected customer Return CType(objCustomers(lstCustomers.SelectedIndex), Customer) End If End GetEnd Property

  2. Now switch to the Forms Designer and add a new Button control to the bottom of the form and set its Name property to btnDeleteCustomer and its Text property to Delete Customer.

  3. Double-click the button and add the highlighted code:

    Private Sub btnDeleteCustomer_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles btnDeleteCustomer.Click 'If no customer is selected in the ListBox then. If lstCustomers.SelectedIndex = -1 Then 'Display a message MessageBox.Show("You must select a customer to delete.", _ "Structure Demo") 'Exit this method Exit Sub End If 'Prompt the user to delete the selected customer If MessageBox.Show("Are you sure you want to delete " & _ SelectedCustomer.Name & "?", "Structure Demo", _ MessageBoxButtons.YesNo, MessageBoxIcon.Question) = _ DialogResult.Yes Then 'Get the customer to be deleted Dim objCustomerToDelete As Customer = SelectedCustomer 'Remove the customer from the ArrayList objCustomers.Remove(objCustomerToDelete) 'Remove the customer from the ListBox lstCustomers.Items.Remove(objCustomerToDelete) End If End Sub

  4. Run the project and click the List Customer button. Do not select a customer in the list box and then click the Delete Customer button. You’ll see a message box indicating that you must select a customer.

  5. Now select a customer and click Delete Customer. You’ll see a confirmation dialog box similar to the one shown in Figure 5–18.

  6. Click Yes, and the customer you selected will be removed from the list.

Structure Demo

Figure 5-18

Note

The trick here is to build a read-only property that returns the Customer structure that’s selected in the list box back to the caller on demand. The SelectedIndex property of the list box returns a value of -1 if no selection has been made. Otherwise it returns the zero-based index of the selected customer. Since the Items collection of the list box contains a collection of Object data types, you must convert the object returned to a Customer object, which you do by using the CType function.

Public ReadOnly Property SelectedCustomer() As Customer Get If lstCustomers.SelectedIndex <> -1 Then 'Return the selected customer Return CType(objCustomers(lstCustomers.SelectedIndex), Customer) End If End GetEnd Property

Like the Name property that you added to the Customer structure, this property is identified as read-only by the keyword ReadOnly. It contains a Get block but no Set block. The reason for making it read-only is that it constructs the value it returns from other information (the contents of the Customer structures in the list) that can be set and changed by other means.

Inside the Click event handler for the Delete Customer button, you first test to see whether a customer has been selected in the list box. If no customer has been selected, you display a message box indicating that a customer must be selected. Then you exit the method allowing the user to select a customer and try again:

'If no customer is selected in the ListBox then. If lstCustomers.SelectedIndex = -1 Then 'Display a message MessageBox.Show("You must select a customer to delete.", _ "Structure Demo") 'Exit this method Exit Sub End If

If a customer has been selected, you prompt the user to confirm the deletion:

'Prompt the user to delete the selected customer If MessageBox.Show("Are you sure you want to delete " & _ SelectedCustomer.Name & "?", "Structure Demo", _ MessageBoxButtons.YesNo, MessageBoxIcon.Question) = _ DialogResult.Yes Then

If the user does want to delete the customer, you get a return value from MessageBox.Show equal to DialogResult.Yes. Then you declare a customer structure to save the customer to be deleted and populate that structure with the selected customer:

'Get the customer to be deleted Dim objCustomerToDelete As Customer = SelectedCustomer

The Remove method of the ArrayList can then be used to remove the selected customer:

'Remove the customer from the ArrayList objCustomers.Remove(objCustomerToDelete)

You also use a similar technique to remove the customer from the list box:

'Remove the customer from the ListBox lstCustomers.Items.Remove(objCustomerToDelete

Showing Items in the ArrayList

For completeness, you’ll want to add one more piece of functionality to enhance the user interface of your application. In the next Try It Out, you add code in the SelectedIndexChanged event for the Customers list box. Every time you select a new customer, the customer’s details will be displayed in the text boxes on the form.

  1. Return to the Forms Designer in the Structure Demo project and double-click the list box. This creates a new SelectedIndexChanged event handler. Add the highlighted code:

    Private Sub lstCustomers_SelectedIndexChanged( _ ByVal sender As System.Object, ByVal e As System.EventArgs) _ Handles lstCustomers.SelectedIndexChanged 'Display the customer details DisplayCustomer(SelectedCustomer) End Sub

  2. Run the project and click the List Customer button to populate the list box. Now when you select a customer in the list box, that customer’s information will appear in the fields at the bottom of the form, as shown in Figure 5–19.

Form1

Figure 5-19

Working with Collections

The ArrayList is a kind of collection, which the .NET Framework uses extensively. A collection is a way of easily creating ad hoc groups of similar or related items. If you take a look back at your Structure Demo code and peek into the CreateCustomer method, you’ll notice that when adding items to the ArrayList and to the list box, you use a method called Add:

‘Add the new customer to the list objCustomers.Add(objNewCustomer) ‘Add the new customer to the ListBox control lstCustomers.Items.Add(objNewCustomer)

The code that deletes a customer uses a method called Remove on both objects:

‘Remove the customer from the ArrayList objCustomers.Remove(objCustomerToDelete) ‘Remove the customer from the ListBox lstCustomers.Items.Remove(objCustomerToDelete)

Microsoft is very keen to see developers use the collection paradigm whenever they need to work with a list of items. They are also keen to see collections work in the same way, irrespective of what they actually hold — which is why you use Add to add an item and Remove to remove an item, even though you’re using a System.Collections.ArrayList object in one case and a System.Windows.Forms.ListBox.ObjectCollection object in another. Microsoft has taken a great deal of care with this feature when building the .NET Framework.

Consistency is good — it allows developers to map an understanding of one thing and use that same understanding with a similar thing. When designing data structures for use in your application, you should take steps to follow the conventions that Microsoft has laid down. For example, if you have a collection class and want to create a method that removes an item, call it Remove, not Delete. Developers using your class will have an intuitive understanding of what Remove does because they’re familiar with it. On the other hand, developers would do a double-take on seeing Delete, because that term has a different connotation.

One of the problems with using an ArrayList is that the developer who has an array list cannot guarantee that every item in the list is of the same type. For this reason, each time an item is extracted from the ArrayList, the type should be checked to avoid causing an error.

The solution is to create a strongly typed collection, which contains only elements of a particular type. Strongly typed collections are very easy to create. According to .NET best programming practices as defined by Microsoft, the best way to create one is to derive a new class from System.Collections.CollectionBase (discussed in the How It Works for the next Try It Out) and add two methods (Add and Remove) and one property (Item):

  • Add adds a new item to the collection.

  • Remove removes an item from the collection.

  • Item returns the item at the given index in the collection.

Creating CustomerCollection

In this Try It Out, you create a CustomerCollection designed to hold a collection of Customer structures.

  1. Return to the Structure Demo project in Visual Studio 2008 and in the Solution Explorer, right-click the project and choose Add from the context menu and then choose the Class submenu item. In the Add New Item – Structure Demo dialog box, enter CustomerCollection in the Name field and then click the Add button to have the class added to your project.

  2. Add the following highlighted line in the Code Editor:

    Public Class CustomerCollection Inherits CollectionBaseEnd Class

  3. You’ll need to add an Add method to add a customer to the collection. Add the following code:

    'Add a customer to the collection Public Sub Add(ByVal newCustomer As Customer) Me.List.Add(newCustomer) End Sub

  4. Next, you need to add a Remove method to remove a customer from the collection, so add this method:

    'Remove a customer from the collection Public Sub Remove(ByVal oldCustomer As Customer) Me.List.Remove(oldCustomer) End Sub

  5. Open the Code Editor for the form and find the definition for the objCustomers member. Change its type from ArrayList to CustomerCollection as highlighted here:

    Public Class Form1 'Form level members Private objCustomers As New CustomerCollection

  6. Finally, run the project. You’ll notice that the application works as before.

Note

Your CustomerCollection class is the first occasion for you to create a class explicitly (although you have been using them implicitly from the beginning). Classes and objects are discussed in Chapter 11 and later chapters. For now, note that, like a structure, a class represents a data type that groups one or more members that can be of different data types, and it can have properties and methods associated with it. Unlike a structure, a class can be derived from another class, in which case it inherits the members, properties, and methods of that other class (which is known as the base class) and can have further members, properties, and methods of its own.

Your CustomerCollection class inherits from the System.Collections.CollectionBase class, which contains a basic implementation of a collection that can hold any object. In that respect it’s very similar to an ArrayList. The advantage comes when you add your own methods to this class.

Since you provided a version of the Add method that has a parameter type of Customer, it can accept and add only a Customer structure. Therefore, it’s impossible to put anything into the collection that isn’t a Customer. You can see there that IntelliSense is telling you that the only thing you can pass through to Add is a Structure_Demo.Customer structure.

Internally, CollectionBase provides you with a property called List, which in turn has Add and Remove methods that you can use to store items. That’s precisely what you use when you need to add or remove items from the list:

'Add a customer to the collection Public Sub Add(ByVal newCustomer As Customer) Me.List.Add(newCustomer) End Sub 'Remove a customer from the collection Public Sub Remove(ByVal oldCustomer As Customer) Me.List.Remove(oldCustomer) End Sub

Building collections this way is a .NET best practice. As a newcomer to .NET programming, you may not appreciate just how useful this is, but trust us—it is. Whenever you need to use a collection of classes, this technique is the right way to go and one that you’ll be familiar with.

Adding an Item Property

At the beginning of this section, you read that you were supposed to add two methods and one property. You’ve seen the methods but not the property, so take a look at it in the next Try It Out.

  1. Return to Visual Studio 2008, open the Code Editor for the CustomerCollection class, and add this code:

    'Item property to read or update a customer at a given position 'in the list Default Public Property Item(ByVal index As Integer) As Customer Get Return CType(Me.List.Item(index), Customer) End Get Set(ByVal value As Customer) Me.List.Item(index) = value End Set End Property

  2. To verify that this works, open the Code Editor for Form1. Modify the SelectedCustomer property with this code:

    Public ReadOnly Property SelectedCustomer() As Customer Get If lstCustomers.SelectedIndex <> -1 Then 'Return the selected customer Return objCustomers(lstCustomers.SelectedIndex) End If End Get End Property

  3. Run the project. Click the Test button and note that when you select items in the list, the details are shown in the fields as they were before.

Note

The Item property is actually very important; it gives the developer direct access to the data stored in the list but maintains the strongly typed nature of the collection.

If you look at the code again for SelectedCustomer, you’ll notice that when you wanted to return the given item from within objCustomers, you didn’t have to provide the property name of Item. Instead, objCustomers behaved as if it were an array:

If lstCustomers.SelectedIndex <> -1 Then 'Return the selected customer Return objCustomers(lstCustomers.SelectedIndex) End If

IntelliSense tells you to enter the index of the item that you require and that you should expect to get a Customer structure in return.

The reason you don’t have to specify the property name of Item is that you marked the property as the default by using the Default keyword:

'Item property to read or update a customer at a given position 'in the list Default Public Property Item(ByVal index As Integer) As Customer Get Return CType(Me.List.Item(index), Customer) End Get Set(ByVal value As Customer) Me.List.Item(index) = value End Set End Property

A given class can have only a single default property, and that property must take a parameter of some kind. This parameter must be an index or search term of some description. The one used here provides an index to an element in a collection list. You can have multiple overloaded versions of the same property so that, for example, you could provide an e-mail address rather than an index. This provides a great deal of flexibility to enhance your class further.

What you have at this point is the following:

  • A way of storing a list of Customer structures, and just Customer structures

  • A way of adding new items to the collection on demand

  • A way of removing existing items from the collection on demand

  • A way of accessing members in the collection as if it were an ArrayList

Building Lookup Tables with Hashtable

So far, whenever you want to find something in an array or in a collection, you have to provide an integer index representing the position of the item. It’s common to end up needing a way of being able to look up an item in a collection when you have something other than an index. For example, you might want to find a customer when you provide an e-mail address.

In this section you’ll take a look at the Hashtable. This is a special kind of collection that works on a key-value principle.

Using Hashtables

A Hashtable is a collection in which each item is given a key. This key can be used at a later time to unlock the value. So, if you add Darrel’s Customer structure to the Hashtable, you’ll be given a key that matches his e-mail address of dhilton@somecompany.com. If at a later time you come along with that key, you’ll be able to find his record quickly.

Whenever you add an object to the Hashtable, it calls a method System.Object.GetHashCode, which provides a unique integer value for that object that is the same every time it is called, and uses this integer ID as the key. Likewise, whenever you want to retrieve an object from the Hashtable, it calls GetHashCode on the object to get a lookup key and matches that key against the ones it has in the list. When it finds it, it returns the related value to you.

Lookups from a Hashtable are very, very fast. Irrespective of the object you pass in, you’re only matching on a relatively small integer ID. You learn to use a Hashtable in the following Try It Out.

An integer ID takes up 4 bytes of memory, so if you pass in a 100-character string (which is 200 bytes long), the lookup code only needs to compare 4 bytes, which makes everything run really quickly.

  1. Return to Visual Studio 2008 and open the Code Editor for the CustomerCollection class. Add the highlighted member to the top of the class definition:

    Public Class CustomerCollection Inherits CollectionBase 'Private member Private objEmailHashtable As New Hashtable

  2. Next, add this read-only property to the class:

    'EmailHashtable property to return the Email Hashtable Public ReadOnly Property EmailHashtable() As Hashtable Get Return objEmailHashtable End Get End Property

  3. Now, make this change to the Add method:

    'Add a customer to the collection Public Sub Add(ByVal newCustomer As Customer) Me.List.Add(newCustomer) 'Add the email address to the Hashtable EmailHashtable.Add(newCustomer.Email, newCustomer) End Sub

  4. Next, add this overloaded version of the Item property that allows you to find a customer by e-mail address:

    'Overload Item property to find a customer by email address Default Public ReadOnly Property Item(ByVal email As String) As Customer Get Return CType(EmailHashtable.Item(email), Customer) End Get End Property

  5. Open the Forms Designer for Form1, resize the controls on your form, and add a new Button control next to the E-mail text box as shown in Figure 5–20. Set the Name property of the button to btnLookup and the Text property to Lookup.

Form1

Figure 5-20

  1. Double-click the Lookup button and add the following highlighted code to its Click event handler:

    Private Sub btnLookup_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles btnLookup.Click 'Declare a customer object and set it to the customer 'with the email address to be found Dim objFoundCustomer As Customer = objCustomers(txtEmail.Text) If Not IsNothing(objFoundCustomer.Email) Then 'Display the customers name MessageBox.Show("The customers name is: " & _ objFoundCustomer.Name, "Structure Demo") Else 'Display an error MessageBox.Show("There is no customer with the e-mail" & _ " address " & txtEmail.Text & ".", "Structure Demo") End If End Sub

  2. Run the project and click the List Customer button to populate the list of customers. If you enter an e-mail address that does not exist into the E-mail text box and click the Lookup button, you’ll see a message box similar to the one shown in Figure 5–21.

Structure Demo

Figure 5-21

If you enter an e-mail address that does exist, for example, dhilton@somecompany.com, the name of the customer is shown in the message box.

Note

You’ve added a new member to the CustomerCollection class that can be used to hold a Hashtable:

'Private member Private objEmailHashtable As New Hashtable

Whenever you add a new Customer to the collection, you also add it to the Hashtable:

'Add a customer to the collection Public Sub Add(ByVal newCustomer As Customer) Me.List.Add(newCustomer) 'Add the email address to the Hashtable EmailHashtable.Add(newCustomer.Email, newCustomer) End Sub

However, unlike the kinds of Add methods that you saw earlier, the EmailHashtable.Add method takes two parameters. The first is the key, and you’re using the e-mail address as the key. The key can be any object you like, but it must be unique. You cannot supply the same key twice. (If you try to, an exception will be thrown.) The second parameter is the value that you want to link the key to, so whenever you give that key to the Hashtable, you get that object back.

The next trick is to create an overloaded version of the default Item property. This one, however, takes a string as its only parameter. IntelliSense displays the overloaded method as items 1 and 2 when you access it from your code.

This time you can provide either an index or an e-mail address. If you use an e-mail address, you end up using the overloaded version of the Item property, and this defers to the Item property of the Hashtable object. This takes a key and returns the related item, provided that the key can be found:

'Overload Item property to find a customer by email addressDefault Public ReadOnly Property Item(ByVal email As String) As Customer Get Return EmailHashtable.Item(email) End GetEnd Property

At this point, you have a collection class that not only enables you to look up items by index but also allows you to look up customers by e-mail address.

Cleaning Up: Remove, RemoveAt, and Clear

It isn’t possible to use the same key twice in a Hashtable. Therefore, you have to take steps to ensure that what’s in the Hashtable matches whatever is in the list itself.

Although you implemented the Remove method in your CustomerCollection class, the CollectionBase class also provides the RemoveAt and Clear methods. Whereas Remove takes an object, RemoveAt takes an index. In the next Try It Out, you need to provide new implementations of these methods to adjust the Hashtable.

  1. Return to Visual Studio 2008 and open the Code Editor for Form1. Locate the btnListCustomer_Click method and add the highlighted code to clear the two lists:

    Private Sub btnListCustomer_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles btnListCustomer.Click 'Clear the lists objCustomers.Clear() lstCustomers.Items.Clear() 'Create some customers CreateCustomer("Darrel", "Hilton", "dhilton@somecompany.com") CreateCustomer("Frank", "Peoples", "fpeoples@somecompany.com") CreateCustomer("Bill", "Scott", "bscott@somecompany.com") End Sub

  2. To demonstrate how a Hashtable cannot use the same key twice, run your project and click the List Customer button to have the customer list loaded. Now click the List Customer button again and you’ll see the error message shown in Figure 5–22:

ArguementException was unhandled

Figure 5-22

  1. Click the Stop Debugging button on the toolbar in Visual Studio 2008 to stop the program.

  2. Add the following method to the CustomerCollection class:

    'Provide a new implementation of the Clear method Public Shadows Sub Clear() 'Clear the CollectionBase MyBase.Clear() 'Clear your hashtable EmailHashtable.Clear() End Sub

  3. Modify the Remove method as follows:

    'Remove a customer from the collection Public Sub Remove(ByVal oldCustomer As Customer) Me.List.Remove(oldCustomer) 'Remove customer from the Hashtable EmailHashtable.Remove(oldCustomer.Email.ToLower) End Sub

  4. Add the RemoveAt method to override the default method defined in the CollectionBase class:

    'Provide a new implementation of the RemoveAt method Public Shadows Sub RemoveAt(ByVal index As Integer) Remove(Item(index)) End Sub

  5. Run the project and click the List Customer button to load the customers. Click the List Customer button again to have the existing list of customers cleared before the customers are added again. Note that this time no exception has been thrown.

Note

The exception isn’t thrown the second time around, because you are now making sure that the Hashtable and the internal list maintained by CollectionBase are properly synchronized. Specifically, whenever your CustomerCollection list is cleared using the Clear method, you make sure that the Hashtable is also cleared.

To clear the internal list maintained by CollectionBase, you ask the base class to use its own Clear implementation rather than try to provide your own implementation. You do this by calling MyBase.Clear(). Right after that, you call Clear on the Hashtable:

'Provide a new implementation of the Clear method Public Shadows Sub Clear() 'Clear the CollectionBase MyBase.Clear() 'Clear your hashtable EmailHashtable.Clear() End Sub

You’ll also find that when you delete items from the collection by using Remove, the corresponding entry is also removed from the Hashtable, because of this method that you added:

'Provide a new implementation of the RemoveAt method Public Shadows Sub RemoveAt(ByVal index As Integer) Remove(Item(index)) End Sub

The Shadows keyword indicates that this Clear procedure and RemoveAt procedure should be used instead of the Clear procedure and RemoveAt procedure in the base class. The arguments and the return type do not have to match those in the base class procedure, even though they do here.

You don’t need to worry too much about the details of Shadows and Overrides at this point, as they are discussed in detail in Chapter 11.

Case Sensitivity

It’s about this time that case sensitivity rears its ugly head again. If you run your project and click the List Customer button and then enter a valid e-mail address in all uppercase letters, you’ll see a message box indicating that there is no customer with that e-mail address.

You need to get the collection to ignore case sensitivity on the key. In the next Try It Out, you do this by making sure that whenever you save a key, you transform the e-mail address into all lowercase characters. Whenever you look up based on a key, you transform whatever you search for into lowercase characters too.

  1. Return to Visual Studio 2008, open the Code Editor for the CustomerCollection class, and make the highlighted change to the Add method:

    'Add a customer to the collection Public Sub Add(ByVal newCustomer As Customer) Me.List.Add(newCustomer) 'Add the email address to the Hashtable EmailHashtable.Add(newCustomer.Email.ToLower, newCustomer) End Sub

  2. Find the overloaded Item property that takes an e-mail address and modify the code as shown here:

    'Overload Item property to find a customer by email address Default Public ReadOnly Property Item(ByVal email As String) As Customer Get Return CType(EmailHashtable.Item(email.ToLower), Customer) End Get End Property

  3. Find the Remove method and modify the code as shown here:

    'Remove a customer from the collection Public Sub Remove(ByVal oldCustomer As Customer) Me.List.Remove(oldCustomer) 'Remove customer from the Hashtable EmailHashtable.Remove(oldCustomer.Email.ToLower) End Sub

  4. Run the project and click the List Customer button. Now if you enter a valid e-mail address in all uppercase, the lookup will still work.

Note

In Chapter 4 you saw how you could perform case-insensitive string comparisons using the String.Compare method. You can’t use this technique here because the Hashtable is handling the comparison and, ideally, you don’t want to produce your own version of the comparison code that the Hashtable uses just to do a case-insensitive match.

You can use the ToLower method available on strings. This creates a new string in which all of the characters are transformed into the lowercase equivalent, so whether you pass DHILTON@SOMECOMPANY.COM or DHilton@SomeCompany.com in, you always get dhilton@somecompany.com out.

When you add an item to the collection, you can get ToLower to convert the e-mail address stored in the Customer structure so that it is always in lowercase:

'Add the email address to the Hashtable EmailHashtable.Add(newCustomer.Email.ToLower, newCustomer)

Likewise, when you actually do the lookup, you also turn whatever value is passed in as a parameter into all lowercase characters:

Return CType(EmailHashtable.Item(email.ToLower), Customer)

When you’re consistent with it, this action makes uppercase characters “go away”—in other words, you’ll never end up with uppercase characters being stored in the key or being checked against the key.

This technique for removing the problem of uppercase characters can be used for normal string comparisons, but String.Compare is more efficient.

Advanced Array Manipulation

Being able to manipulate the size of an array from code, and being able to store complex sets of data in an array is important, but with .NET it’s far easier to achieve both of these using the collection functionality that the majority of this chapter has discussed. The following two sections are included for completeness and so that you can make the comparisons between the two for yourself.

Dynamic Arrays

When using an array, if you want to change its size in order to add items, or clean up space when you remove items, you need to use the ReDim keyword to make it a dynamic array. This is a short form of, not surprisingly, redimension. In the next Try It Out, you’ll reuse the Array Demo project you created at the start of the chapter and tweak it so that you can add new friends to the array after the initial array has been created.

  1. Find and open the Array Demo project in Visual Studio 2008. Open the Code Editor for Form1 and modify the code in the AddItemsToList method so that it looks like this:

    Private Sub AddItemsToList(ByVal arrayList() As String) 'Enumerate the array For Each strName As String In arrayList 'Add the array item to the list lstFriends.Items.Add("[" & strName & "]") Next End Sub

  2. Run the project and click the Initializing Arrays with Values button. Your form should look like Figure 5–23; note the square brackets around each name.

Form1

Figure 5-23

  1. Stop the project and make the highlighted change to the btnInitializingArraysWithValues_Click method:

    Private Sub btnInitializingArraysWithValues_Click( _ ByVal sender As System.Object, ByVal e As System.EventArgs) _ Handles btnInitializingArraysWithValues.Click 'Clear the list ClearList() 'Declare and populate an array Dim strMyFriends() As String = {"Elaine", "Richard", "Debra", _ "Wendy", "Harriet"} 'Make the strMyFriends array larger ReDim strMyFriends(6) strMyFriends(5) = "Lane" strMyFriends(6) = "Joel" 'List your friends AddItemsToList(strMyFriends) End Sub

  2. Run the project again and click the Initializing Arrays with Values button. Your form should look like the one shown in Figure 5–24.

Form1

Figure 5-24

Note

After defining an array of five items, you use the ReDim keyword to redimension the array to have an upper boundary of 6, which, as you know, gives it a size of 7. After you do that, you have two new items in the array to play with—items 5 and 6:

'Make the strMyFriends array larger ReDim strMyFriends(6) strMyFriends(5) = "Lane" strMyFriends(6) = "Joel"

Then, you can pass the resized array through to AddItemsToList:

'List your friends AddItemsToList(strMyFriends)

But, as you can see from the results, the values for the first five items have been lost. (This is why you wrapped brackets around the results—if the name stored in the array is blank, you still see something appear in the list.) ReDim does indeed resize the array, but when an array is redimensioned, by default all of the values in the array are cleared, losing the values you defined when you initialized the array in the first place.

You can solve this problem by using the Preserve keyword.

Using Preserve

By including the Preserve keyword with the ReDim keyword, you can instruct Visual Basic 2008 to not clear the existing items. One thing to remember is that if you make an array smaller than it originally was, data will be lost from the eliminated elements even if you use Preserve. In the next Try It Out, you use Preserve.

  1. Return to Visual Studio 2008, open the Code Editor for Form1, and modify the btnInitializingArraysWithValues_Click method as follows:

    'Make the strMyFriends array larger ReDim Preserve strMyFriends(6) strMyFriends(5) = "Lane" strMyFriends(6) = "Joel"

  2. Run the project again and click the Initializing Arrays with Values button. You should now find that the existing items in the array are preserved, as shown in Figure 5–25.

Form1

Figure 5-25

Summary

In this chapter, you saw some ways in which you could manage complex groups of data. You started by looking at the concept of an array, or rather, defining a special type of variable that’s configured to hold a one-dimensional list of similar items rather than a single item.

You then looked at the concepts behind enumerations and constants. Both of these can be used to great effect in making more readable and manageable code. An enumeration lets you define human-readable, common-sense titles for basic variable types. So rather than saying “CurrentState = 2”, you can say “CurrentState = DayAction.TravelingToWork”. Constants allow you to define literal values globally and use them elsewhere in your code.

You then looked at structures. These are similar to classes and are well suited for storing groups of items of information that all pertain to a particular thing or person. After looking at these, you moved on to look at various types of collections, including the basic ArrayList and then saw how you could build your own powerful collection classes inherited from CollectionBase. Finally, you looked at the Hashtable class and covered some of the less commonly used array functionality.

To summarize, you should know how to:

  • Define and redimension fixed and dynamic string arrays

  • Enumerate through arrays and find their upper dimension

  • Define an enumeration of values using the Enum class

  • Create and use structures to manipulate sets of related data

  • Use an ArrayList to hold any type of object

  • Use collections to manage sets of related data

Exercises

  1. Create a Windows Forms Application that contains three buttons. Add an enumeration of three names to your code. For the Click event for each button, display a message box containing a member name and value from the enumeration.

  2. Create a Windows Forms Application that contains a TextBox control and a Button control. At the form level, create a names array initialized with a single name. In the Click event for the button control, add the code to redimension the array by one element while preserving the existing data, add the new name from the text box to the array, and display the last name added to the array in a message box.Hint: To determine the upper boundary of the array, use the GetUpperBound(0) method.

Beginning Microsoft® Visual Basic® 2008, Copyright © 2008 by Wiley Publishing, Inc., ISBN: 978-0-470-19134-7, Published by Wiley Publishing, Inc., All Rights Reserved. Wrox, the Wrox logo, Wrox Programmer to Programmer, and related trade dress are trademarks or registered trademarks of John Wiley & Sons, Inc and/or its affiliates.