Simple Steps in VB.NET. Part 4 - Finishing Touches - The Ugly Duckling's Striking Back
Article source code: ssteps4_addressbook.zip
Before our address book becomes a useful application, it has to undergo some fine-tuning. Fine-tuning is not merely changing the appearance. It is also adjusting, and hopefully improving, the functionality of the application.
Less is often more, and our program is no exception. The simpler our program is, the less it is prone to errors. The less it is prone to errors, the less you have to work on it. So let's think how we can simplify our program, or make things redundant.
One thing that really blows my fuse is the Load button. To me it seems as necessary as headaches. Why can't the program simply load the database at startup? There's good news: it can. Double-click the form and edit the code so that it looks like code1. You can also copy and paste the code from the Load button, as I did. The code continues with the tool tips, but I won't display them here because it would take up more space without any need.
code1:
Private Sub DataForm1_Load(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles MyBase.Load
'[load the database]
Try
'Attempt to load the dataset.
Me.LoadDataSet()
Catch eLoad As System.Exception
'Add your error handling code here.
'Display error message, if any.
System.Windows.Forms.MessageBox.Show(eLoad.Message)
End Try
Me.objMyAddresses_PositionChanged()
'[/load the database]
Our Load button has become redundant, and should be treated as such. Double-click the Load button and delete code2:
code2:
Private Sub btnLoad_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnLoad.Click
Try
'Attempt to load the dataset.
Me.LoadDataSet()
Catch eLoad As System.Exception
'Add your error handling code here.
'Display error message, if any.
System.Windows.Forms.MessageBox.Show(eLoad.Message)
End Try
Me.objMyAddresses_PositionChanged()
End Sub
Now, we still have a useless Load button that we should delete. In design view, click it once and press the Delete button on your keyboard.
The Load button was, up to now, the only thing that I found little useful. You might have other ideas, and if you do, let me know.
The Tough Part of Programming
The way I introduced you to error handling in part 3 was meant to give you an idea about what it is and how it can work. We did that in a very light way, just for fun. Now, the truth is, error handling is anything but fun. Some programmers, however, think it is the most interesting and rewarding part of all. It is probably the toughest part, depending on how many and which errors you want/have to eliminate. Many programmers can tell you tales of eliminating one problem which produced another one, and with this I want to welcome you to the club. When you finish today, you will know what I mean. Yes, it can drive you crazy, and it can take much longer than making the program. Handling errors needs a lot of logical thinking and testing, although sometimes you might believe that there was nothing logical about the error. Okay, some good books on the subject would also come in handy. But still, it is almost impossible to produce a real-world program which is totally error-free.
Our program will not be totally error-free, I know that. What we are going to do today will merely give you a glimpse at what the upcoming programmer has to deal with. And our error correction is likely to produce new errors, I know that, too. So, why the heck do it at all? The common user doesn't know how your program works and doesn't know how to deal with errors. You can avoid some common problems, but the common user can't. They just complain when the error occurs. Not to talk about the experience it gives you, and the pride of being the winner of this challenge. How you eliminate the errors is simply your problem; it's the result that counts, not how you got there.
This is also a good point to write some words to more advanced programmers because I know that some of them read newbie-articles. I'm sure that when some more advanced programmers sees this article, they might think "Well, that can be done easier" or "Man, is that guy messy!" Guess what! I know that, but let's stay at our level. You advanced guys are very much invited to teach us better, so don't grumble and put pen to paper. After all, my idea is to do something for those who most need it: beginners. I'm a beginner myself without access to books on the topic since I'm living in Brazil. So what you see here is what I taught myself since March this year by trial and error, reading tutorials, forums, and asking fellow programmers. And before March this year, I didn't even have the slightest idea on programming. But I'm a determined person; the project I start will be finished - at all costs. So let's carry on.
From now on, not only do we have to think as common users, but we also have to think for them. It doesn't bring anything to think "But that won't happen." If the possibility is there, then it is going to happen in the long run.
When the program opens, what could a user do? The user could type in data without having clicked the Add button. He/she might then update and close the program without having actually saved the data. We can avoid this problem by making all textboxes read-only. You can do so by setting the read-only property of each textbox accordingly in the properties window. You can also do that faster when you double-click the form and place code3 between the load instructions and the tool tips. The tool tips are not shown here to save some space.
code3:
Private Sub DataForm1_Load(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles MyBase.Load
'[load the database]
Try
'Attempt to load the dataset.
Me.LoadDataSet()
Catch eLoad As System.Exception
'Add your error handling code here.
'Display error message, if any.
System.Windows.Forms.MessageBox.Show(eLoad.Message)
End Try
Me.objMyAddresses_PositionChanged()
'[/load the database]
'[make the textboxes read-only]
editName.ReadOnly = True
editSurname.ReadOnly = True
editStreetAddress.ReadOnly = True
editZipCode.ReadOnly = True
editState.ReadOnly = True
editPhoneNumber.ReadOnly = True
editEmail.ReadOnly = True
'[/make the textboxes read-only]
Go into design view and set the back color property for all textboxes to highlight text to have all textboxes appearing white, or live with the different color in read-only mode.
When you now run the program, the user can't type anymore. We could now hope that he/she clicks the Add button, but hoping is nothing for programmers. Insert a label above the textboxes and rename it to lblReadOnlyMsg
. In the text property, type this sentence: "Read-only protection is ON", and resize the label accordingly. Run the program and check that everything is as intended.
This little error correction caused another problem. When the user clicks the Add button, he/she can't write because everything is read-only. We have to add code for the Add button, so that it cancels the read-only protection when a user clicks the button. The click must also change the text of our label to tell the user that the read-only protection is canceled. The first textbox in which a user is supposed to type should be focused so that the user doesn't have to click the textbox or use the tab-key. To do all this, edit the code for the Add button so that it looks like code4:
code4:
Private Sub btnAdd_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnAdd.Click
'[cancel the read-only protection]
editName.ReadOnly = False
editSurname.ReadOnly = False
editStreetAddress.ReadOnly = False
editZipCode.ReadOnly = False
editState.ReadOnly = False
editPhoneNumber.ReadOnly = False
editEmail.ReadOnly = False
'[/cancel the read-only protection]
'[change the text of our label]
lblReadOnlyMsg.Text = "Read-only protection is OFF."
'[/change the text of our label]
'[focus the textbox Name]
editName.Focus()
'[/focus the textbox Name]
Try
'Clear out the current edits
Me.BindingContext(objMyAddresses, "AddressBooktb").EndCurrentEdit()
Me.BindingContext(objMyAddresses, "AddressBooktb").AddNew()
Catch eEndEdit As System.Exception
System.Windows.Forms.MessageBox.Show(eEndEdit.Message)
End Try
Me.objMyAddresses_PositionChanged()
End Sub
A user could now type an address and save it. If the user forgets an item, we have to remind him/her. code5 does exactly this and more. You can edit the code now and read detailed explanations in the following. Just be careful that you type one instruction as one line. The instruction for a message box, for example, can easily count several lines on the page you're viewing, but one instruction should be one line. If you get into trouble with this, download the updated sample and have a look at that.
code5:
Private Sub btnUpdate_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnUpdate.Click
'[declare the variable for the person's first name]
Dim n As String
n = editName.Text
'[/declare the variable for the person's first name]
'[declare the variable for the person's surname]
Dim sn As String
sn = editSurname.Text
'[/declare the variable for the person's surname]
'[set the conditions and make decisions]
If Me.editName.Text = "" Then
MessageBox.Show("Please enter a name.", "My Address Book", _
MessageBoxButtons.OK, MessageBoxIcon.Information)
editName.Focus()
Exit Sub
ElseIf editSurname.Text = "" Then
MessageBox.Show("Please enter " & n & "'s surname.", _
"My Address Book", MessageBoxButtons.OK, _
MessageBoxIcon.Information)
editSurname.Focus()
Exit Sub
ElseIf editStreetAddress.Text = "" Then
MessageBox.Show( _
"Please enter the street address for " & n & " " & sn & ".", _
"My Address Book", MessageBoxButtons.OK, _
MessageBoxIcon.Information)
editStreetAddress.Focus()
Exit Sub
ElseIf editZipCode.Text = "" Then
MessageBox.Show( _
"Please enter the zip code for " & n & " " & sn & ".", _
"My Address Book", MessageBoxButtons.OK, _
MessageBoxIcon.Information)
editZipCode.Focus()
Exit Sub
ElseIf editState.Text = "" Then
MessageBox.Show( _
"Please enter the state for " & n & " " & sn & ".", _
"My Address Book", MessageBoxButtons.OK, _
MessageBoxIcon.Information)
editState.Focus()
Exit Sub
ElseIf editPhoneNumber.Text = "" Then
MessageBox.Show( _
"Please enter the phone number for " & n & " " & sn & ".", _
"My Address Book", MessageBoxButtons.OK, _
MessageBoxIcon.Information)
editPhoneNumber.Focus()
Exit Sub
ElseIf editEmail.Text = "" Then
MessageBox.Show( _
"Please enter the e-mail address for " & n & " " & sn & ".", _
"My Address Book", MessageBoxButtons.OK, _
MessageBoxIcon.Information)
editEmail.Focus()
Exit Sub
End If
'[/set the conditions and make decisions]
Try
'Attempt to update the datasource.
Me.UpdateDataSet()
Catch eUpdate As System.Exception
'Add your error handling code here.
'Display error message, if any.
System.Windows.Forms.MessageBox.Show(eUpdate.Message)
End Try
Me.objMyAddresses_PositionChanged()
'[display the person's full name in the messagebox]
MessageBox.Show( _
n & " " & sn & " has been added to your database." & _
Microsoft.VisualBasic.ControlChars.CrLf & "The read-only " _
& "protection will now be reactivated.", "My Address Book", _
MessageBoxButtons.OK, MessageBoxIcon.Information)
'[/display the person's full name in the messagebox]
'[reactivate the read-only mode]
editName.ReadOnly = True
editSurname.ReadOnly = True
editStreetAddress.ReadOnly = True
editZipCode.ReadOnly = True
editState.ReadOnly = True
editPhoneNumber.ReadOnly = True
editEmail.ReadOnly = True
'[/reactivate the read-only mode]
'[Change the text for our label]
lblReadOnlyMsg.Text = "Read-only protection is ON."
'[/Change the text for our label]
End Sub
Understanding the Instructions
'[declare the variable for the person's first name]
Dim n As String
n = editName.Text
'[/declare the variable for the person's first name]
'[declare the variable for the person's surname]
Dim sn As String
sn = editSurname.Text
'[/declare the variable for the person's surname]
The line Dim n As String
instructs the program to take the text from "n". The second line n = editName.Text
tells the program that "n" is the text from the textbox for the person's name. The same happens for the person's surname. Since it is a different location it must be declared as such. The 'n' or 'sn' could be replaced with anything you like provided you tell the program in the second line what it is. As an example, you could type the following:
Dim BrittneySpears As String
BrittneySpears = editName.Text
No, no, it won't bring the lady onto your screen; it merely tells the program that our Brittney Spears is nothing more and nothing less than the text in the textbox for the name. Are you disappointed now? Ah, yes, and it doesn't work with men either, my ladies, so let's go ahead.
If Me.editName.Text = "" Then
MessageBox.Show("Please enter a name.", "My Address Book", _
MessageBoxButtons.OK, MessageBoxIcon.Information)
editName.Focus()
Exit Sub
The line If Me.editName.Text = "" Then
is very close to spoken English and means: "If a user clicks the Update button and the textbox for the name is empty, then..."
MessageBox.Show("Please enter a name.", "My Address Book", _
MessageBoxButtons.OK, MessageBoxIcon.Information)
"...show him/her a message box with the text 'Please enter a name', the title is 'My Address Book', there is only an OK button and an information icon (question mark).
editName.Focus()
"When the user clicks the OK button, focus the textbox for the name."
Exit Sub
"Get out o' here!"
ElseIf
It is practically the continuation of "if". In a later article, I will shed a light on this and other forms of setting conditions and making decisions.
MessageBox.Show("Please enter " & n & "'s surname.")
"Show a message box with the text 'Please enter + the name of the person +'s surname." This could look like 'Please enter Peter's surname.'
MessageBox.Show( _
"Please enter the street address for " & n & " " & sn & ".")
"Show a message box with the text 'Please enter the street address for + the person's name + the person's surname+.'"
This could look like 'Please enter the street address for Peter Pan.'
When you type the code, pay close attention to empty spaces. Otherwise you might find a message like "Please enterPeter's surname." or Please enter the street address forPeterPan." Neither would look very nice.
End If
"Stop the job."
I'll skip the explanation for the code which updates our database since it is an automatically generated code. We should inform the user, with a message box, what kind of data has been added to the database. Otherwise the user might click the Update button 154,768 times to make sure it's updated. Let's take advantage of the situation and tell the user that we will reactivate the read-only mode to avoid trouble similar to what we talked about a while ago. And, of course, we have to change the text of our label because it should show that we are in read-only mode again.
'[display the person's full name in the messagebox]
MessageBox.Show( _
n & " " & sn & " has been added to your database." & _
Microsoft.VisualBasic.ControlChars.CrLf & "The read-only " _
& "protection will now be reactivated.", "My Address Book", _
MessageBoxButtons.OK, MessageBoxIcon.Information)
'[/display the person's full name in the messagebox]
'[reactivate the read-only mode]
editName.ReadOnly = True
editSurname.ReadOnly = True
editStreetAddress.ReadOnly = True
editZipCode.ReadOnly = True
editState.ReadOnly = True
editPhoneNumber.ReadOnly = True
editEmail.ReadOnly = True
'[/reactivate the read-only mode]
'[Change the text for our label]
lblReadOnlyMsg.Text = "Read-only protection is ON."
'[/Change the text for our label]
End Sub
The next two buttons (Cancel and Cancel All) are doing a fine job. The user should have a message box to confirm or cancel the action. Edit the codes for the Cancel button (code6) and the Cancel All button (code7).
code6:
Private Sub btnCancel_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnCancel.Click
If MessageBox.Show("Do you want to cancel the current entry?", _
"My Address Book", MessageBoxButtons.YesNo, _
MessageBoxIcon.Question) = DialogResult.Yes Then
Me.BindingContext(objMyAddresses, _
"AddressBooktb").CancelCurrentEdit()
Me.objMyAddresses_PositionChanged()
End If
End Sub
code7:
Private Sub btnCancelAll_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnCancelAll.Click
If MessageBox.Show("Do you want to cancel all changes?", _
"My Address Book", MessageBoxButtons.YesNo, _
MessageBoxIcon.Question) = DialogResult.Yes Then
Me.objMyAddresses.RejectChanges()
End If
End Sub
The Delete button is also fine. However, we should tell the person what data is being deleted, and the user should have a chance to cancel the action, using code8.
code8:
Private Sub btnDelete_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnDelete.Click
'[declare the variable for the person's first name]
Dim n As String
n = editName.Text
'[/declare the variable for the person's first name]
'[declare the variable for the person's surname]
Dim sn As String
sn = editSurname.Text
'[/declare the variable for the person's surname]
If MessageBox.Show( _
"This will delete the entry for " & n & " " & sn & "." & _
Microsoft.VisualBasic.ControlChars.CrLf & "Do you want to " _
& "continue?", "My Address Book", MessageBoxButtons.YesNo, _
MessageBoxIcon.Question) = DialogResult.Yes Then
If (Me.BindingContext(objMyAddresses, _
"AddressBooktb").Count > 0) Then
Me.BindingContext(objMyAddresses, _
"AddressBooktb").RemoveAt(Me.BindingContext( _
objMyAddresses, "AddressBooktb").Position)
Me.objMyAddresses_PositionChanged()
End If
End If
End Sub
Let's come to our Close button. When a user clicks this button, he/she should be informed about what is going to happen. Still, some happy clickers don't care and just click. So we should save the data before we close the program, even if the data is not complete. Later on, the user might decide to complete or delete the entry. code9 does this.
code9:
Private Sub btnClose_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnClose.Click
If MessageBox.Show( _
"This will save any unsaved data and exit." & _
Microsoft.VisualBasic.ControlChars.CrLf & "Do want to continue?", _
"My Address Book", MessageBoxButtons.YesNo, _
MessageBoxIcon.Question) = DialogResult.Yes Then
Try
Me.UpdateDataSet()
Catch eUpdate As System.Exception
System.Windows.Forms.MessageBox.Show(eUpdate.Message)
End Try
Me.objMyAddresses_PositionChanged()
End
End If
End Sub
Let's run the program and test it. Insert two or three addresses, save the thing and exit. Run it again and check that everything's there. Now try to edit existing data. Oops! We can't. How now, brown cow?
Take it easy. It's quite some work, but we'll get that straight. Insert a checkbox and change the text property to "Read-only protection for existing data is ON." Double-click the checkbox and edit code10. The code will cancel the read-only protection when checked, and it will reactivate the read-only protection when unchecked. It will also change the text of our label and the checkbox depending on whether the read-only protection is on or off.
code10:
Private Sub CheckBox1_CheckedChanged(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles CheckBox1.CheckedChanged
'[cancel the read-only protection]
If CheckBox1.Checked = True Then
editName.ReadOnly = False
editSurname.ReadOnly = False
editStreetAddress.ReadOnly = False
editZipCode.ReadOnly = False
editState.ReadOnly = False
editPhoneNumber.ReadOnly = False
editEmail.ReadOnly = False
'[/cancel the read-only protection]
'[display the corresponding text]
CheckBox1.Text = "Read-only protection for existing data is OFF."
lblReadOnlyMsg.Text = "Read-only protection is OFF."
'[/display the corresponding text]
'[make the textboxes read-only]
ElseIf CheckBox1.Checked = False Then
editName.ReadOnly = True
editSurname.ReadOnly = True
editStreetAddress.ReadOnly = True
editZipCode.ReadOnly = True
editState.ReadOnly = True
editPhoneNumber.ReadOnly = True
editEmail.ReadOnly = True
'[/make the textboxes read-only]
'[display the corresponding text]
CheckBox1.Text = "Read-only protection for existing data is ON."
lblReadOnlyMsg.Text = "Read-only protection is ON."
'[/display the corresponding text]
End If
End Sub
Let's run the program again and check that we have what we intended. Edit an address and then update it. You will see that our checkbox doesn't agree with our label, so let's convince it. In design view, double click the update button and put code11 between the code for our label text and End Sub, then check the result.
code11:
'[Change the text for our label]
lblReadOnlyMsg.Text = "Read-only protection is ON."
'[/Change the text for our label]
'[change the text and state of our checkbox]
CheckBox1.Checked = False
CheckBox1.Text = "Read-only protection for existing data is ON."
'[/change the text and state of our checkbox]
End Sub
Let's take the Add button and check it. When you click Add, our checkbox doesn't agree with our label again. So let's change that with code12 between the code to focus the textbox and the code that clears the textboxes.
code12:
'[focus the textbox Name]
editName.Focus()
'[/focus the textbox Name]
'[change the settings of our checkbox]
CheckBox1.Checked = False
CheckBox1.Text = "Read-only protection for existing data is OFF."
'[/change the settings of our checkbox]
Try
'Clear out the current edits
Me.BindingContext(objMyAddresses, "AddressBooktb").EndCurrentEdit()
Me.BindingContext(objMyAddresses, "AddressBooktb").AddNew()
Catch eEndEdit As System.Exception
System.Windows.Forms.MessageBox.Show(eEndEdit.Message)
End Try
Me.objMyAddresses_PositionChanged()
End Sub
Right now, we have solved the problem with our Add button and with our Update button. Let's test the Cancel button to see if everything will be all right. When you add a new address and then cancel the entry, the last entry will be displayed and could accidentally be edited. That's why we have to make everything read-only after an entry was canceled. Double-click the Cancel button and edit code13. Then check the result.
code13:
Private Sub btnCancel_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnCancel.Click
If MessageBox.Show("Do you want to cancel the current entry?", _
"My Address Book", MessageBoxButtons.YesNo, _
MessageBoxIcon.Question) = DialogResult.Yes Then
Me.BindingContext(objMyAddresses, _
"AddressBooktb").CancelCurrentEdit()
Me.objMyAddresses_PositionChanged()
'[make the textboxes read-only]
editName.ReadOnly = True
editSurname.ReadOnly = True
editStreetAddress.ReadOnly = True
editZipCode.ReadOnly = True
editState.ReadOnly = True
editPhoneNumber.ReadOnly = True
editEmail.ReadOnly = True
'[/make the textboxes read-only]
End If
End Sub
We should do the same thing with the Cancel All button. If you have added more than one entry, this button will normally not display the last entry. However, I tested with clicking the Cancel button and Cancel All button alternatively, and I eventually came to an address. To play it save, put it there.
Now, such an address book is a fine thing. But why do we put the e-mail address there if we have to type it all over again to send an e-mail? There must be a way to send an e-mail right from the program. That's quite simple.
Add a button and rename it to btnmail or something like that. Change the text to something appropriate, like "E - mail this person". Double-click the button and edit the code so that it looks like code14. By the way, if you prefer, you can use a link label. This even works with a normal label.
Instead of an address, we're using the textbox where we should have an e-mail address. We just have to click the button, and an empty e-mail opens with the person's address.
code14:
Private Sub btnMail_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnmail.Click
'[e-mail the person displayed]
System.Diagnostics.Process.Start("mailto:" & editEmail.Text)
'[/e-mail the person displayed]
End Sub
Our project is almost coming to an end. I said almost because we still need an installer, which we will do in the next part.
Related devCity.NET articles: