Let’s add a little more complexity to our class. Because we’re storing birth dates to our custom objects, we can classify persons according to their age. Most basic developers will see an opportunity to use constants here. Instead of using constants to describe the various age groups, we’ll use an enumeration with the following group names:
Public Enum AgeGroup
Infant
Child
Teenager
Adult
Senior
Overaged
End Enum
Code language: VB.NET (vbnet)
These statements must appear outside any procedure in the class, and we usually place them at the beginning of the file, right after the declaration of the class. Public is an access modifier (we want to be able to access this enumeration from within the application that uses the class). Enum is a keyword: It specifies the beginning of the declaration of an enumeration and it’s followed by the enumeration’s name. The enumeration itself is a list of integer values, each one mapped to a name. In our example, the name Infant corresponds to 0, the name Child corresponds to 1, and so on. The list of the enumeration’s members ends with the End Enum keyword. You don’t really care about the actual values of the names because the very reason for using enumerations is to replace numeric constants with more-meaningful names. You’ll see shortly how enumerations are used both in the class and the calling application.
As you already know, the Framework uses enumerations extensively, and this is how you can add an enumeration to your custom class. You should provide an enumeration for any property with a relatively small number of predetermined settings. The property’s type should be the name of the enumeration, and the editor will open a drop-down box with the property’s settings as needed.
Now add to the class the GetAgeGroup method (Listing 6.12), which returns the name of the age group to which the person represented by an instance of the Minimal class belongs. The name of the group is a member of the AgeGroup enumeration.
Listing 6.12: Using an Enumeration
Public Function GetAgeGroup() As AgeGroup
Select Case m_Age
Case Is < 3 : Return (AgeGroup.Infant)
Case Is < 10 : Return (AgeGroup.Child)
Case Is < 21 : Return (AgeGroup.Teenager)
Case Is < 65 : Return (AgeGroup.Adult)
Case Is < 100 : Return (AgeGroup.Senior)
Case Else : Return (AgeGroup.Overaged)
End Select
End Function
Code language: VB.NET (vbnet)
The GetAgeGroup method returns a value of the AgeGroup type. Because the AgeGroup enumeration was declared as Public, it’s exposed to any application that uses the Minimal class. Let’s see how we can use the same enumeration in our application. Switch to the form’s code window, add a new button, and enter the statements from Listing 6.13 in its event handler.
Listing 6.13: Using the Enumeration Exposed by the Class
Protected Sub Button1_Click(...) _
Handles Button1.Click
Dim obj As Minimal
obj = New Minimal()
Try
obj.BDate = InputBox("Please Enter your birthdate")
Catch ex As ArgumentException
MsgBox(ex.Message)
Exit Sub
End Try
Debug.WriteLine(obj.Age)
Dim discount As Single
If obj.GetAgeGroup = Minimal.AgeGroup.Infant Or _
obj.GetAgeGroup = Minimal.AgeGroup.Child Then discount = 0.4
If obj.GetAgeGroup = Minimal.AgeGroup.Senior Then discount = 0.5
If obj.GetAgeGroup = Minimal.AgeGroup.Teenager Then discount = 0.25
MsgBox("You age is " & obj.Age.ToString & _
" and belong to the " & _
obj.GetAgeGroup.ToString & _
" group" & vbCrLf & "Your discount is " & _
Format(discount, "Percent"))
End Sub
Code language: VB.NET (vbnet)
This routine calculates discounts based on the person’s age. Notice that we don’t use numeric constants in our code, just descriptive names. Moreover, the possible values of the enumeration are displayed in a drop-down list by the IntelliSense feature of the IDE as needed (Figure 6.3), and you don’t have to memorize them or look them up as you would with constants. I’ve used an implementation with multiple If statements in this example, but you can perform the same comparisons by using a Select Case statement.
Figure 6.3 – The members of an enumeration are displayed automatically in the IDE as you type
You could also call the GetAgeGroup method once, store its result to a variable, and then use this variable in the comparisons. This approach is slightly more efficient, because you don’t have to call the member of the class repeatedly. The variable, as you can guess, should be of the AgeGroup type. Here’s an alternate code of the statements of Listing 6.13 using a temporary variable, the grp variable, and a Select Case statement:
Dim grp As AgeGroup = obj.GetAgeGroup
Select Case grp
Case Minimal.AgeGroup.Infant, Minimal.AgeGroup.Child ...
Case Minimal.AgeGroup.Teenager ...
Case Minimal.AgeGroup.Senior ...
Case Else
End Select
Code language: VB.NET (vbnet)
You’ve seen the basics of working with custom classes in a VB application. Let’s switch to a practical example that demonstrates not only the use of a real-world class, but also how classes can simplify the development of a project.
The Contacts Project Example
In Chapter, “Working with Forms,” I discussed briefly the Contacts application. This application uses a custom Structure to store the contacts and provides four navigational buttons to allow users to move to the first, last, previous, and next contact. Now that you have learned how to program the ListBox control and how to use custom classes in your code, we’ll revise the Contacts application. First, we’ll implement a class to represent each contact. The fields of each contact (company and contact names, addresses, and so on) will be implemented as properties and they will be displayed in the TextBox controls on the form.
We’ll also improve the user interface of the application. Instead of the rather simplistic navigational buttons, we’ll place all the company names in a sorted ListBox control. The user can easily locate the desired company and select it from the list to view the fields of the selected company. The editing buttons at the bottom of the form work as usual, but we no longer need the navigational buttons. Figure 6.4 shows the revised Contacts application.
Figure 6.4 – The interface of the Contacts application is based on the ListBox control.
Name the new class Contact and enter the code from Listing 6.14 into it. The names of the private members of the class are the same as the actual property names, and they begin with an underscore. (This is a good convention that lets you easily distinguish whether a variable is private, and the property value it stores.) The implementation of the properties is trivial, so I’m not showing the code for all of them.
Listing 6.14: The Contact Class
<Serializable()> Public Class Contact
Private _companyName As String
Private _contactName As String
Private _address1 As String
Private _address2 As String
Private _city As String
Private _state As String
Private _zip As String
Private _tel As String
Private _email As String
Private _URL As String
Property CompanyName() As String
Get
CompanyName = _companyName
End Get
Set(ByVal value As String)
If value Is Nothing Or value = "" Then
Throw New Exception("Company Name field can’t be empty")
Exit Property
End If
_companyName = value
End Set
End Property
Property ContactName() As String
Get
ContactName = _contactName
End Get
Set(ByVal value As String)
_contactName = value
End Set
End Property
Property Address1() As String
Get
Address1 = _address1
End Get
Set(ByVal value As String)
_address1 = value
End Set
End Property
Property Address2() As String
Get
Address2 = _address2
End Get
Set(ByVal value As String)
_address2 = value
End Set
End Property
Property City() As String
Get
City = _city
End Get
Set(ByVal value As String)
_city = value
End Set
End Property
Property State() As String
Get
State = _state
End Get
Set(ByVal value As String)
_state = value
End Set
End Property
Property ZIP() As String
Get
ZIP = _zip
End Get
Set(ByVal value As String)
_zip = value
End Set
End Property
Property tel() As String
Get
tel = _tel
End Get
Set(ByVal value As String)
_tel = value
End Set
End Property
Property EMail() As String
Get
EMail = _email
End Get
Set(ByVal value As String)
If value.Contains("@") Or value.Trim.Length = 0 Then
_email = Value
Else
Throw New Exception("Invalid e-mail address!")
End If
End Set
End Property
Property URL() As String
Get
URL = _URL
End Get
Set(ByVal value As String)
_URL = value
End Set
End Property
Overrides Function ToString() As String
If _contactName = "" Then
Return _companyName
Else
Return _companyName & vbTab & "(" & _contactName & ")"
End If
End Function
Public Sub New()
MyBase.New()
End Sub
Public Sub New(ByVal CompanyName As String, _
ByVal LastName As String, ByVal FirstName As String)
MyBase.New()
Me.ContactName = LastName & ", " & FirstName
Me.CompanyName = CompanyName
End Sub
Public Sub New(ByVal CompanyName As String)
MyBase.New()
Me.CompanyName = CompanyName
End Sub
End Class
Code language: VB.NET (vbnet)
The first thing you’ll notice is that the class’s definition is prefixed by the <Serializable()> keyword. The topic of serialization is discussed in Chapter, “XML and Object Serialization,” but for now all you need to know is that the .NET Framework can convert objects to a text or binary format and then store them in files. Surprisingly, this process is quite simple; as you will see, we’ll be able to dump an entire collection of Contact objects to a file with a single statement. The <Serializable()> keyword is an attribute of the class, and (as you will see later in this book) there are more attributes you can use with your classes — or even with your methods. The most prominent method attribute is the <WebMethod> attribute, which turns a regular function into a web method.
The various fields of the Contact structure are now properties of the Contact class. The implementation of the properties is trivial except for the CompanyName and EMail properties, which contain some validation code. The Contact class requires that the CompanyName property have a value; if it doesn’t, the class throws an exception. Likewise, the EMail property must contain the symbol @. Finally, the class provides its own ToString method, which returns the name of the company followed by the contact name in parentheses.
The ListBox control, in which we’ll store all contacts, displays the value returned by the object’s ToString method, which is why you have to provide your own implementation of the method to describe each contact. The company name should be adequate, but if there are two companies by the same name, you can use another field to differentiate them. I used the contact name, but you can use any of the other properties (the URL would be a good choice).
The ListBox displays a string, but it stores the object itself. In essence, it’s used not only as a navigational tool, but also as a storage mechanism for our contacts. Now, we must change the code of the main form a little. Start by removing the navigational buttons; we no longer need them. Their function will be replaced by a few lines of code in the ListBox control’s SelectedIndexChanged event. Every time the user selects another item on the list, the statements shown in Listing 6.15 display the contact’s properties in the various TextBox controls on the form.
Listing 6.15: Displaying the Fields of the Selected Contact Object
Private Sub ListBox1 SelectedIndexChanged(...) _
Handles ListBox1.SelectedIndexChanged
currentContact = ListBox1.SelectedIndex
ShowContact()
End Sub
Code language: VB.NET (vbnet)
The ShowContact() subroutine reads the object stored at the location specified by the currentContact variable and displays its properties in the various TextBox controls on the form. The TextBox controls are normally read-only, except when editing a contact. This action is signaled by clicking the Edit or the Add button on the form.
When a new contact is added, the code reads its fields from the controls on the form, creates a new Contact object, and adds it to the ListBox control. When a contact is edited, a new Contact object replaces the currently selected object on the control. The code is similar to the code of the Contacts application. I should mention that the ListBox control is locked while a contact is being added or edited, because it doesn’t make sense to select another contact at that time.
Adding, Editing, and Deleting Contacts
To delete a contact (Listing 6.16), we simply remove the currently selected object from the ListBox control. In addition, we must select the next contact on the list (or the first contact if the deleted one was last in the list).
Listing 6.16: Deleting an Object from the ListBox
Private Sub bttnDelete_Click(...) Handles bttnDelete.Click
If currentContact > -1 Then
ListBox1.Items.RemoveAt(currentContact)
currentContact = ListBox1.Items.Count - 1
If currentContact = -1 Then
ClearFields()
MsgBox("There are no more contacts")
Else
ShowContact()
End If
Else
MsgBox("No current contacts to delete")
End If
End Sub
Code language: VB.NET (vbnet)
When you add a new contact, the following code is executed in the Add button’s Click event handler:
Private Sub bttnAdd_Click(...) Handles bttnAdd.Click
adding = True
ClearFields()
HideButtons()
ListBox1.Enabled = False
End Sub
Code language: VB.NET (vbnet)
The controls are cleared in anticipation of the new contact’s fields, and the adding variable is set to True. The OK button is clicked to end either the addition of a new record or an edit operation. The code behind the OK button is shown in Listing 6.17.
Listing 6.17: Committing a New or Edited Record
Private Sub bttnOK_Click(...) Handles bttnOK.Click
If SaveContact() Then
ListBox1.Enabled = True
ShowButtons()
End If
End Sub
Code language: VB.NET (vbnet)
As you can see, the same subroutine handles both the insertion of a new record and the editing of an existing one. All the work is done by the SaveContact() subroutine, which is shown in Listing 6.18.
Listing 6.18: The SaveContact() Subroutine
Private Function SaveContact() As Boolean
Dim contact As New Contact
Try
contact.CompanyName = txtCompany.Text
contact.ContactName = txtContact.Text
contact.Address1 = txtAddress1.Text
contact.Address2 = txtAddress2.Text
contact.City = txtCity.Text
contact.State = txtState.Text
contact.ZIP = txtZIP.Text
contact.tel = txtTel.Text
contact.EMail = txtEMail.Text
contact.URL = txtURL.Text
Catch ex As Exception
MsgBox(ex.Message)
Return False
End Try
If adding Then
ListBox1.Items.Add(contact)
Else
ListBox1.Items(currentContact) = contact
End If
Return True
End Function
Code language: VB.NET (vbnet)
The SaveContact() function uses the adding variable to distinguish between an add and an edit operation, and either adds the new record to the ListBox control or replaces the current item in the ListBox with the values on the various controls. Because the ListBox is sorted, new contacts are automatically inserted in the correct order. If an error occurs during the operation, the SaveContact() function returns False to alert the calling code that the operation failed (most likely because one of the assignment operations caused a validation error in the class’s code).
The last operation of the application is the serialization and deserialization of the items in the ListBox control. Serialization is the process of converting an object to a stream of bytes for storing to a disk file, and deserialization is the opposite process. To serialize objects, we first store them into an ArrayList object, which is a dynamic array that stores objects and can be serialized as a whole. Likewise, the disk file is deserialized into an ArrayList to reload the persisted data back to the application; then each element of the ArrayList is moved to the Items collection of the ListBox control. ArrayLists and other Framework collections are discussed in Chapter, “Storing Data in Collections,” and object serialization is discussed in Chapter Serialization and XML. You can use these features to test the application and examine the corresponding code after you read about ArrayLists and serialization. I’ll discuss the code of the Load and Save operations of the Contacts sample project in Chapter “Serialization and XML”.
Making the Most of the ListBox Control
This section’s sample application demonstrates an interesting technique for handling a set of data at the client. We usually need an efficient mechanism to store data at the client, where all the processing takes place — even if the data comes from a database. In this example, we used the ListBox control, because each item of the control can be an arbitrary object. Because the control displays the string returned by the object’s ToString method, we’re able to customize the display by providing our own implementation of the ToString method. As a result, we’re able to use the ListBox control both as a data-storage mechanism and as a navigational tool. As long as the strings displayed on the control are meaningful descriptions of the corresponding objects and the control’s items are sorted, the ListBox control can be used as an effective navigational tool. If you have toomany items to display on the control, you should also provide a search tool to help users quickly locate an item in the list, without having to scroll up and down a long list of items. Review the ListBoxFind project of Chapter 6 for information on searching the contents of the ListBox control.
When data are being edited, you have to cope with another possible problem. The user may edit the data for hours and forget to save the edits every now and then. If the computer (or, even worse, the application) crashes, a lot of work will be wasted. Sure the application provides a Save command, but you should always try to protect users from their mistakes. It would be nice if you could save the data to a temporary file every time the user edits or adds an item to the list. This way, if the computer crashes, users won’t lose their edits. When the application starts, it should automatically detect the presence of the temporary file and reload it. Every time the user saves the data by using the application’s Save command, or terminates the application, the temporary file should be removed.