Today's lesson will cover a very important aspect of programminghandling runtime errors. Although you should always work to make sure your program can anticipate any problems that might occur while a user is running your software, you can't account for every possibility. That's why every good program should have a solid error handling system.
Today you will learn just what an error handler is and why error handlers are so important. You'll also learn about some of the inner workings of Visual Basic and how that affects error handling.
You'll learn about the difference between local error handling methods and global error handling methods. You'll also learn the advantages and disadvantages of each method. You'll see the various types of errors your program is likely to encounter and some guidelines on how to handle each type of error.
You'll also learn how to create error logs to keep track of errors that occur in your program. You'll learn how to create a trace log to analyze your programs. And you'll learn how you can write your programs to turn these features on or off without having to rewrite program code.
Finally, you'll build another library module todaythe LIBERROR.BAS module. This module will contain all the functions and subroutines you'll need to set up error handlers in all the programs you write throughout this book and for any programs you write in the future.
Error handling is an essential part of any program. No program is complete unless it has good error handling. It is important to write your programs in a way that reduces the chances that errors will occur, but you won't be able to think of everything. Errors do happen! Well-designed programs don't necessarily have fewer errors; they just handle them better.
Writing error handlers is not difficult. In fact, you can add consistent error handling to your program by adding only a few lines of code to each module. The difficult part of writing good error handlers is knowing what to expect and how to handle the unexpected. You'll learn how to do both in today's lesson.
Adding error handling to your program will make your program seem much more polished and friendly to your users. Nothing is more annoyingor frighteningto a user than to see the screen freeze up, hear a startling beep, or watch the program (and any file your user had been working on) suddenly disappear from the screen entirely. This only needs to happen a few times before the user will vow never to use your program again.
Polite error messages, recovery routines that allow users to fix their own mistakes or correct hardware problems, and opportunities for the user to save any open files before the program halts due to errors are all essential parts of a good error handling strategy.
Writing Error Handlers in Visual Basic is a bit trickier than in most PC languages. There are several reasons for this. First, Visual Basic is an event-driven language model, rather than procedure-driven like most PC languages. Second, Visual Basic uses a call stack method that isolates local variables. This means that when you exit the routine, you can lose track of the values of internal variables, which can make resuming execution after error handling difficult. Thirdly, in Visual Basic, all errors are local. If an error occurs, it's best to handle it within the routine in which the error occurred, which means you'll have to write a short error handler for each routine in your Visual Basic program.
NOTE: Technically, Visual Basic does allow the use of a global error handler. However, after Visual Basic travels up the procedure stack to locate the error handler, it can't travel back down the stack to resume execution after the error has been corrected. (This is typical of most object-oriented languages.) For this reason, we highly recommend using local error handlers in your Visual Basic programs.
Error handlers in Visual Basic have three main parts:
The On Error Goto statement appears at the beginning of the Sub or Function. This is the line that tells Visual Basic what to do when an error occurs, as in the following example:
On Error Goto ErrorHandler
In the preceding code line, every time an error occurs in this Sub or Function, the program immediately jumps to the ErrorHandler label in the routine and execute the error handler code.
The error handler code can be as simple or as complex as needed to handle the error. A very simple error handler would just report the error number and error message, like this:
ErrorHandler: msgbox Str(Err)+" - "+Error$
In the preceding code example, as soon as the error occurs, Visual Basic reports the error number (Err) and the error message (Error) in a message box.
The third, and final, part of a Visual Basic error handler is the exit statement. This is the line that tells Visual Basic where to go after the error handler is done doing its work. There are four different ways to exit an error handler routine:
Which exit method you use depends on the type of error that occurred and the error handling strategy you employ throughout your program. Error types and error handling strategies are covered later in this chapter.
Now that you have the basics of error handling covered, you can write some error handling routines.
To start, write a simple error handling routine to illustrate how Visual Basic behaves when errors occur. Start up Visual Basic and begin a new project. Add a single command button to the default form. Add the code in Listing 14.1 behind the command button.
Listing 14.1. Writing a simple error handler.
Sub Command1_Click () On Error GoTo Command1ClickErr ' turn on error handling ' Dim x As Integer ' declare integer Dim cMsg as String ' declares string x = 10000000 ' create overflow error GoTo Command1ClickExit ' exit if no error ' ' local error handler Command1ClickErr: cMsg = Str(Err) + " - " + Error$ ' make message MsgBox cMsg, 0, "Command1Click" ' show message Resume Next ' continue on ' ' routine exit Command1ClickExit: ' End Sub
Save the form as ERROR01.FRM, and save the project as ERROR01.VBP. Then execute the program and click on the command button. You'll see the error message displayed on the screen (see Figure 14.1).
The results of a simple error handler.
The example in Listing 14.1 has all the basic parts of a good error handler. First, the first line in the routine tells Visual Basic what to do in case of an error. Notice the naming convention used for the error handler: the name of the Sub or Function plus the letters Err. Next, you declare an integer and then purposely stuff it with an illegal value. This causes the error routine to kick in.
The error routine is very simple. It is a message that contains the error number and the associated text messages. You then display that message along with the warning symbol and the name of the routine that is reporting the error. The next line tells Visual Basic what to do after the error is handled. In this case, you tell Visual Basic to resume execution with the line of program code that immediately follows the line that caused the error (Resume at the Next line).
When Visual Basic resumes execution, the routine hits the line that tells Visual Basic to go to the exit routine (Goto Command1ClickExit). Notice again the naming convention for the exit routine. Use the name of the Sub or Function plus the word Exit.
What happens if you get an error within your error routine? Although it isn't fun to think about, it can happen. When an error occurs inside the error handling routine, Visual Basic looks for the next declared error routine. This would be an error routine started in the previous calling routine using the On Error Goto Label statement. If no error routine is available, Visual Basic halts the program with a fatal error.
As an example, let's modify the ERROR01.VBP project from Listing 14.1 to create a cascading error. First, create a new Sub procedure called NewSub. Then copy all the code from Command1_Click to NewSub. Change the names of the labels from Command1Click to NewSub. Finally, add a new error in the NewSubErr routine in order to force an error cascade in the program. Refer to the code in Listing 14.2 when creating the NewSub routine.
Listing 14.2. Creating cascading errors.
Sub NewSub () On Error GoTo NewSubErr ' turn on error handling ' Dim x As Integer ' declare integer Dim cMsg as String ' declare string x = 10000000 ' create overflow error GoTo NewSubExit ' exit if no error ' ' local error handler NewSubErr: cMsg = Str(Err) + " - " + Error$ ' make message MsgBox cMsg, 0, "NewSub" ' show message Open "junk.txt" For Input As 1 ' create a new error Resume Next ' continue on ' ' routine exit NewSubExit: ' End Sub
Notice the new error that is introduced by attempting to open a nonexistent file from with the error handling routine of NewSub.
Before you save and run the project, you must first change the Command1_Click routine to call NewSub. Bring up Command1_Click and remove the line that declares the integer variable x and the line that sets the value of x. Your Command1_Click routine should look like the one in Listing 14.3.
Listing 14.3. Calling a subroutine to create cascading error.
Sub Command1_Click () On Error GoTo Command1ClickErr ' turn on error handling ' Dim cMsg as String ' declare string NewSub ' call new routine GoTo Command1ClickExit ' exit if no error ' ' local error handler Command1ClickErr: cMsg = Str(Err) + " - " + Error$ ' make message MsgBox cMsg, 0, "Command1Click" ' show message Resume Next ' continue on ' ' routine exit Command1ClickExit: ' End Sub
Save the program and run it to see the results. When you first click the command button, you see the error message that announces the overflow error. Notice that the title of the message box indicates that the error is being reported by the NewSub module. (See Figure 14.2.)
The error message for NewSub.
When you click the OK button in the message box, you'll see another error message. This one reports an Error 53 File not Found message, which occurred when NewSub tried to open the nonexistent file (see Figure 14.3).
The error message that appears when a file is not found.
Here's the important point. Notice that the second error message box tells you that the error is being reported from the Command1Click routineeven though the error occurred in the NewSub routine! The error that occurred in NewSub could not be handled locally and Visual Basic searched upward in the call stack to find the next available error handler to invoke. This action by Visual Basic can be a blessing and a curse. It's good to know that Visual Basic will use the next available error handling routine when things like this happen. But it's also likely to cause confusion for you and your users if you are not careful. For all you can tell in this example, an error occurred in Command1Click. You must keep this in mind when you are debugging Visual Basic error reports.
The simplest method for exiting an error handler is the Resume method. When you exit an error handler with the Resume keyword, Visual Basic returns to the line of code that caused the error and attempts to run that line again. The Resume keyword is useful when you encounter an error that the user can easily correct, such as attempting to read a disk drive when the user forgot to insert a diskette or close the drive door. You can use the Resume keyword whenever you are confident that the situation that caused the error has been remedied, and you want to retry the action that caused the error.
Let's modify the ERROR01 project to add an error handler that uses the Resume keyword. Add an additional button to the form. Set the caption property to Resume. Set the Name property to cmdResume. Now add the code in Listing 14.4 behind the new button.
Listing 14.4. Using the Resume keyword.
Private Sub cmdResume_Click() On Error GoTo cmdResumeErr ' Dim x As Integer Dim cMsg As String Dim nReturn As Integer ' x = InputBox("Enter an Integer Value") GoTo cmdResumeExit ' cmdResumeErr: cMsg = Str(Err) + " - " + Error$ nReturn = MsgBox(cMsg, vbCritical, "cmdResume") Resume ' cmdResumeExit: ' End Sub
Your new form should look something like the one in Figure 14.4.
Adding the Resume button to the ERROR01 project.
Save and run the project. When you press the Resume button, you are prompted to enter an integer value (see Figure 14.5).
The prompt for integer input.
If you enter a value that is greater than 32,767, you invoke the error handler and receive an error message from Visual Basic (see figure 14.6).
The error message for an invalid integer.
When you click the OK button, Visual Basic redisplays the input prompt and waits for your reply. If you enter another invalid value, you see the error message, and then you see the prompt again. This is the Resume exit method in action. You can't get beyond this screen until you enter a valid value.
If you try to press the Cancel button on the input screen, you still see an error message because selecting Cancel did not set the variable to a valid integer value. This can be very frustrating for your users. What if they don't know what value to enter here? Are they stuck in this terrible error handler forever? Whenever you use the Resume keyword, you should give your users an option to ignore the error and move on or cancel the action completely. Those options are covered next.
Using the Resume Next method to exit an error handler allows your user to get past a problem spot in the program as if no error had occurred. This is useful when you use code within the error handler to fix the problem, or when you think the program can go on even though an error has been reported.
Deciding whether to continue the program even though an error has been reported is sometimes a tough call. It is usually not a good idea to assume that your program will work fine even though an error is reported, especially if the error that occurs is one related to physical devices (missing diskette, lost communications connection, and so on) or file errors (missing, corrupted, or locked data files, and so on). The Resume Next keywords are usually used in error handlers that fix any reported error before continuing.
Modify your current project to contain an error handler that uses Resume Next. Add a new command button to the project. Set its Caption property to &Next and its Name property to cmdNext. Place the button anywhere on the form and enter the code in Listing 14.5 behind the button's cmdNext_Click event.
Listing 14.5. Using the Resume Next keywords.
Private Sub cmdNext_Click() On Error GoTo cmdNextErr ' Dim x As Integer Dim cMsg as String Dim nReturn as Integer ' x = InputBox("Enter a valid Integer") MsgBox "X has been set to " + Str(x) GoTo cmdNextExit ' cmdNextErr: If Err = 6 Then cMsg = "You have entered an invalid Integer Value." + Chr(13) cMsg = cMsg + "The program will now set X = 0 for you." + Chr(13) cMsg = cMsg + "Select YES to set X = 0 and continue." + Chr(13) cMsg = cMsg + "Select NO to return to enter a new value." ' nReturn = MsgBox(cMsg, vbCritical + vbYesNo, "cmdNext") If nReturn = vbYes Then x = 0 Resume Next Else Resume End If Else cMsg = Str(Err) + " - " + Error$ MsgBox cMsg, vbCritical, "cmdNext" Resume End If ' cmdNextExit: ' End Sub
In Listing 14.5, you added a section of code to the error handler that tests for the anticipated overflow error. You explain the options to the user and then give the user a choice of how to proceed. This is a good general model for error handling that involves user interaction. Tell the user the problem, explain the options, and let the user decide how to go forward.
Notice, also, that you continued to include a general error trap for those cases when the error is not caused by an integer overflow. Even in cases when you think you have covered all the possible error conditions, you should always included a general error trap.
Save and run this project. When you press the Next command button and enter an invalid value (that is, any number greater than 32,767), you'll see the error message that explains your options (see Figure 14.7).
An error message asking for user input.
There are times when you need your program to return to another spot within the routine in order to fix an error that occurs. For example, if you ask the user to enter two numbers that you will use to perform a division operation, and it results in a divide by zero error, you will want to ask the user to enter both numbers again. You might not be able to simply use the Resume statement after you trap for the error.
When you need to force the program to return to a specific point in the routine, you can use the Resume label exit method. The Resume label method enables you to return to any place within the current procedure. You can't use Resume label to jump to another Sub or Function within the project.
Modify the ERROR01 project to include an example of Resume label. Add a new command button to the project. Set its Caption property to Label and its Name property to cmdLabel. Place the code in Listing 14.6 behind the cmdLabel_Click event.
Listing 14.6. Using the Resume label keywords.
Private Sub cmdLabel_Click() On Error GoTo cmdLabelErr ' Dim x As Integer Dim y As Integer Dim z As Integer ' cmdLabelInput: x = InputBox("Enter a Divisor:", "Input Box #1") y = InputBox("Enter a Dividend:", "Input Box #2") z = x / y MsgBox "The Quotient is: " + Str(z), vbInformation, "Results" GoTo cmdLabelExit ' cmdLabelErr: If Err = 11 Then ' divide by zero error MsgBox Str(Err) + " - " + Error$, vbCritical, "cmdLabel" Resume cmdLabelInput Else MsgBox Str(Err) + " -" + Error$, vbCritical, "cmdLabel" Resume Next End If ' cmdLabelExit: ' End Sub
Save and run the project. Enter 13 at the first input box and 0 at the second input box. This causes a Divide by Zero error, and the error handler takes over from there. You'll see the error message shown in Figure 14.8 and then be returned to the line that starts the input process.
Using the Resume label exit method.
There are times when an error occurs and there is no good way to return to the programfor example, when you attempt to open files on a network file server and the user has forgotten to log onto the server. In this case, you need to either exit the routine and return to the calling procedure, or exit the program completely. Exiting to a calling routine can work if you have written your program to anticipate these critical errors. Usually it's difficult to do that. Most of the time, critical errors of this type mean you should end the program and let the user fix the problem before restarting the program.
Let's add one more button to ERROR01. Set its Caption property to &End and its Name property to cmdEnd. Enter the code in Listing 14.7 behind the cmdEnd_Click event.
Listing 14.7. Using the End keyword.
Private Sub cmdEnd_Click() On Error GoTo cmdEndErr ' Dim cMsg as String Open "junk.txt" For Input As 1 GoTo cmdEndExit ' cmdEndErr: If Err = 53 Then cMsg = "Unable to open JUNK.TXT" + Chr(13) cMsg = cMsg + "Exit the program and check your INI file" + Chr(13) cMsg = cMsg + "to make sure the JUNKFILE setting is correct." MsgBox cMsg, vbCritical, "cmdEnd" Unload Me End Else MsgBox Str(Err) + " - " + Error$, vbCritical, "cmdEnd" Resume Next End If ' cmdEndExit: ' End Sub
In Listing 14.7, you add a check in the error handler for the anticipated File not Found error. You give the user some helpful information and then tell him you are closing down the program. It's always a good idea to tell the user when you are about to exit the program. Notice that you did not use the Visual Basic End keyword; you used Unload Me. Remember that End stops all program execution immediately. Using Unload Me causes Visual Basic to execute any code placed in the Unload event of the form. This event should contain any file closing routine needed to safely exit the program.
Save and run the project. When you click the End button, you see a message box explaining the problem and suggesting a solution (see Figure 14.9). When you click the OK button, Visual Basic ends the program.
The error message that exits the program.
So far, you have seen how to build a simple error handler and the different ways to exit error handlers. Now you need to learn about the different types of errors that you will encounter in your Visual Basic programs and how to plan for them in advance.
In order to make writing error handlers easier and more efficient, you can group errors into typical types. These error types can usually be handled in a similar manner. When you get an idea of the types of errors you can encounter, you can begin to write error handlers that take care of more than one error. You can write handlers that take care of error types.
There are four types of Visual Basic errors:
Each of these types of errors need to be handled differently within your Visual Basic programs. You'll learn general rules for handling these errors in the following sections.
These are errors that occur due to invalid data file information such as a bad filename, data path, or device name. Usually these error can be fixed by the user and the program can continue from the point of failure. General file errors should be handled by an error handler that reports the problem to the user and asks for additional information to complete or retry the operation. If the retries fail, the program should allow the user to exit the program safely and give the user hints on how to fix the problem (refer to documentation, or other advice).
In Listing 14.8, the error handler is called when the program attempts to open a control file called CONTROL.TXT. The error handler then prompts the user for the proper file location and continues processing. Modify ERROR01.VBP by adding a new command button. Set its Caption property to Control and its Name property to cmdControl. Also, add a CommonDialog control to the project. Enter the code in Listing 14.8 into the cmdControl_Click event.
Listing 14.8. Adding code to the cmdControl_Click event.
Private Sub cmdControl_Click() On Error GoTo cmdControlErr ' Dim cFile As String Dim cMsg As String Dim nReturn As Integer ' cFile = "control.txt" ' Open cFile For Input As 1 MsgBox "Control File Opened" GoTo cmdControlExit ' cmdControlErr: If Err = 53 Then cMsg = "Unable to Open CONTROL.TXT" + Chr(13) cMsg = cMsg + "Select OK to locate CONTROL.TXT" + Chr(13) cMsg = cMsg + "Select CANCEL to exit program." nReturn = MsgBox(cMsg, vbCritical + vbOKCancel, "cmdControl") ' If nReturn = vbOK Then CommonDialog1.filename = cFile CommonDialog1.DefaultExt = ".txt" CommonDialog1.ShowOpen Resume Else Unload Me End If Else MsgBox Str(Err) + " - " + Error$ Resume Next End If ' cmdControlExit: ' End Sub
Save and run this project. When you click on the Control button, the program tries to open the CONTROL.TXT file. If it can't be found, you see the error message (see Figure 14.10).
The File Not Found error message.
If the user selects OK, the program calls the CommonDialog control and prompts the user to locate the CONTROL.TXT file. It can be found in the \TYSDBVB\CHAP14 directory (see Figure 14.11).
Using the common dialog control to locate CONTROL.TXT.
Notice the use of the CommonDialog control to open the file. Whenever you need to prompt users for file-related action (open, create, save), you should use the CommonDialog control. This is a familiar dialog for your users, and it handles all of the dirty work of scrolling, searching, and so on. Also, if the error here was caused by a bad value in the registry or .INI file, this routine should write the corrected value back to the registry for future reference.
Table 14.1 lists errors that are similar to the File Not Found error illustrated in Listing 14.8. Errors of this type usually involve giving the user a chance to re-enter the filename or reset some value. Most of the time, you can write an error trap that anticipates these errors, prompts the user to supply the corrected information, and then retries the operation that caused the error.
Table 14.1. Common general file errors.
Error Code | Error Message |
---|---|
52 | Bad filename or number |
53 | File not found |
54 | Bad file mode |
55 | File already open |
58 | File already exists |
59 | Bad record length |
61 | Disk full |
62 | Input past end of file |
63 | Bad record number |
64 | Bad filename |
67 | Too many files |
74 | Can't rename with different drive |
75 | Path/File access error |
76 | Path not found |
In cases when it is not practical to prompt a user for additional information (such as during initial startup of the program), it is usually best to report the error in a message box, give the user some ideas about how to fix the problem, and then exit the program safely.
A very common type of error that occurs in database applications is the data-related error. These errors include those that deal with data type or field size problems, table access restrictions including read-only access, locked tables due to other users, and so on. Database errors fall into two groups. Those caused by attempting to read or write invalid data to or from tables, including data integrity errors, make up the most common group. The second group are those errors caused by locked tables, restricted access, or multiuser conflicts.
In most cases, all you need to do is trap for the error, report it to the user, and allow the user to return to the data entry screen to fix the problem. If you use the Visual Basic data control in your data forms, you can take advantage of the automatic database error reporting built into the data control. As an example, let's put together a simple data entry form to illustrate some of the common data entry-oriented database errors.
Let's modify ERROR01.VBP to illustrate common database errors. Add a new command button to the form. Set its Caption property to Data and its Name property to cmdData. In the cmdData_Click event, add the following code:
Private Sub cmdData_Click() On Error Resume Next frmData.Show End Sub
This code piece calls the new data entry form that you are about to create. To do that, you need to add a new form to the project and add a data control, two bound input controls, and two label controls. Use Table 14.2 as a reference for adding the controls to the form. Refer to Figure 14.12 as a guide for placing the controls. Notice that you also added a line that tells Visual Basic to ignore any error reported in this routine. This stops any cascading errors that might occur on the next form.
Table 14.2. Controls for the frmData form.
Control | Property | Setting |
---|---|---|
Form | Name Caption Height Left Top Width | frmData Data Entry Form 1860 3015 2490 4170 |
Command Button | Name Caption Height Left Top Width | cmdAdd &Add 300 2760 1020 1200 |
Data Control | Name Caption Connect DatabaseName Height RecordSource Top Width | Data1 Data1 Access ERRORDB.MDB 300 Table1 1020 2595 |
Text Box | Name DataField DataSource Height Left Top Width | Text1 Name Data1 300 1440 600 2475 |
Text Box | Name DataField DataSource Height Left Top Width | Text2 KeyField Data1 300 1440 120 1200 |
Label | Name Caption Height Left Top Width | Label1 Name: 300 120 600 1200 |
Label | Name Caption Height Left Top Width | Label2 Key Field: 300 120 120 1200 |
The layout of the frmData form.
The only code you need to add to this form is a single line behind the Add button. Place the following code behind the cmdAdd_Click event.
Private Sub cmdAdd_Click() On Error Goto cmdAddClickErr Data1.Recordset.AddNew goto cmdAddClickExit cmdAddClickErr: MsgBox Str(Err) + " - " + Error$ Resume Next cmdAddClickExit: End Sub
Now save the new form as ERROR02.FRM and run the project. At the first screen, press the Data button to bring up the data entry form. To test the error trapping, edit the KeyField in the first record to create a duplicate primary key. Enter KF109 in the KeyField input box, and then press one of the arrows on the data control to force it to save the record. You should see a database error message that looks like the one in Figure 14.13.
A sample database error message.
Are you surprised? You didn't add an error trap to the data entry form, but you still got a complete database error message! The Visual Basic data control is kind enough to provide complete database error reporting even if you have no error-coded error traps in place. In fact, it is not a good idea to attempt to override this facility with your own database errors. As long as you use the Visual Basic data control, you do not need to add database error trapping routines to your data entry forms.
If you do not use the Visual Basic data control, you need to add error handling routines to your project. For example, if you want to create a Dynaset using Visual Basic code, you need to trap for any error that might occur along the way. Let's modify the ERROR01.VBP project to create a Dynaset within the data entry form.
Add the code in Listing 14.9 to the Form_Load event of frmData. This code opens the database and creates a Dynaset to stuff into the data control that already exists on the form.
Listing 14.9. Adding code to the Form_Load event.
Private Sub Form_Load() On Error GoTo FormLoadErr ' Dim db As DATABASE Dim ds As Dynaset Dim cSelect As String ' Set db = OpenDatabase(App.Path + "errordb.mdb") cSelect = "SELECT * FROM Table2" Set ds = db.CreateDynaset(cSelect) GoTo FormLoadExit ' FormLoadErr: MsgBox Str(Err) + " - " + Error$ Unload Me ' FormLoadExit: ' End Sub
The code in Listing 14.9 establishes some variables and then opens the database and creates a new Dynaset from a data table called Table2. Because there is no Table2 in ERRORDB.MDB, you get a database error. The error message is displayed and then the form is unloaded completely (see Figure 14.14).
The database error message from the Form_Load event.
It is a good idea to open any data tables or files that you'll need for a data entry form during the Form_Load event. That way, if there are problems, you can catch them before data entry begins.
Another group of common errors is caused by problems with physical media. Unresponsive printers, disk drives that do not contain diskettes, and downed communications ports are the most common examples of physical media errors. These errors might, or might not, be easily fixed by your user. Usually, you can report the error, wait for the user to fix the problem, and then continue with the process. For example, if the printer is jammed with paper, all you need to do is report the error to the user, and then wait for the OK to continue.
Let's add another button to the ERROR01.VBP project to display an example of physical media error handling. Add a new command button to the project. Set its Caption property to &Media and its Name property to cmdMedia. Enter the code in Listing 14.10 into the cmdMedia_Click event.
Listing 14.10. Trapping media errors.
Private Sub cmdMedia_Click() On Error GoTo cmdMediaErr Dim cMsg As String Dim nReturn As Integer ' ' open a file on the a drive ' an error will occur if there ' is no diskette in the drive ' Open "a:\junk.txt" For Input As 1 Close #1 GoTo cmdMediaExit ' cmdMediaErr: If Err = 71 Then cMsg = "The disk drive is not ready." + Chr(13) cMsg = cMsg + "Please make sure there is a diskette" + Chr(13) cMsg = cMsg + "in the drive and the drive door is closed." ' nReturn = MsgBox(cMsg, vbCritical + vbRetryCancel, "cmdMedia") ' If nReturn = vbRetry Then Resume Else Resume Next End If Else MsgBox Str(Err) + " - " + Error$ Resume Next End If ' cmdMediaExit: ' End Sub
In Listing 14.10, you attempt to open a file on a disk drive that contains no diskette (or has an open drive door). The error handler prompts the user to correct the problem and allows the user to try the operation again. If all goes well the second time, the program continues. The user also has an option to cancel the operation.
Save and run the project. When you click on the Media button, you should get results that look like those in Figure 14.15.
The results of a physical media error.
The final type of common errors are program code errors. These are errors that occur as part of the Visual Basic code. Errors of this type cannot be fixed by users and are usually due to unanticipated conditions within the code itself. Error messages such as Variable Not Found, Invalid Object, and so on, will be a mystery to most of your users. The best way to handle errors of this type is to tell the user to report the message to the programmer and close the program safely.
In the previous sections, you created several error handlers, each tuned to handle a special set of problems. Although this approach works for small projects, it can be tedious and burdensome if you have to put together a large application. Also, after you've written an error handler that works well for one type of error, you can use that error handler in every other program that might have the same error. Why write it more than once?
Even though Visual Basic requires error traps to be set for each Sub or Function, you can still create a generic approach to error handling that takes advantage of code you have already written. In this section, you'll write a set of routines that you can install in all your Visual Basic programsthe Error Handling Library.
You'll create this library routine as a Visual Basic .BAS file. To do this, add a module to the ERROR01.VBP project by selecting Insert | Module from the main Visual Basic menu. This opens the code window for your .BAS module.
First, add some global variables that you will need for all error handling operations. Your routine will have options to simply report the errors and act as the programmer plans, or give users options to ignore or retry the operation. You need constants that reflect all those options. Add the following code to the declaration section of the module.
Option Explicit ' ' error handler constants ' Global Const errExit = 0 Global Const errResume = 1 Global Const errNext = 2 Global Const errSelect = 3
These constants will be available to all Subs and Functions in the project. They will be used to control possible error exit options for the main error handler.
Now let's write the main error handler. You have been creating a very simple message box for all the error handling routines so far. Now you can use that basic message box as the heart of your generic error handler. This box will have option buttons on it, based on the value sent to the routine. Use the Insert | Procedure menu option to create a new Function called errHandler, which accepts three parameters and return a single integer. The declaration line should look like this:
errHandler(nErrNumber as integer, cErrText as string, nErrOption as integer) as integer
Add the code in Listing 14.11 inside the new function.
Listing 14.11. Coding the errHandler function.
Function errHandler(nErrNumber, cErrText, nErrOption) As Integer Dim cMsg As String Dim nReturn As Integer ' ' build message cMsg = "" cMsg = cMsg + "Err:" + Chr(9) + Str(nErrNumber) + Chr(13) cMsg = cMsg + "Text:" + Chr(9) + cErrText + Chr(13) ' ' handle option Select Case nErrOption Case Is = errExit MsgBox cMsg, vbCritical, "Exiting Program" GoTo errHandlerEnd Case Is = errResume MsgBox cMsg, vbCritical, "Error" errHandler = errResume Case Is = errNext MsgBox cMsg, vbCritical, "Error" errHandler = errNext Case Is = errSelect nReturn = MsgBox(cMsg, vbCritical + vbAbortRetryIgnore, "Error") Select Case nReturn Case Is = vbAbort GoTo errHandlerEnd Case Is = vbRetry errHandler = errResume Case Is = vbIgnore errHandler = errNext End Select End Select Exit Function ' errHandlerEnd: MsgBox "Exiting Program" End End Function
The Visual Basic function in Listing 14.11 first declares some local variables for internal use. Then, it builds the basic error message. This message will contain both the Visual Basic error number and the Visual Basic error message. Then the main Select[el]Case structure is invoked. This set of code reads the nErrOptions parameter and decides what kind of error message is displayed. If the nErrOptions parameter is set to errSelect, the message box contains command buttons that allow the user to decide what action to take at the exit of the error handler.
There is a section at the end of the error handler that exits the program if needed. You will add to this section a little later. For now, it simply ends the program.
After entering the code in Listing 14.11, save the module as LIBERROR.BAS. Because it is now part of the ERROR01.VBP project, you can use it. Modify the command1_click routine and the NewSub routines to call to errHandler. To do this, call each routine up and replace the error message lines with the call to errHandler. The following code examples show how this is done in command1_click.
Before the change:
Command1ClickErr: cMsg = Str(Err) + " - " + Error$ ' make message MsgBox cMsg, 0, "Command1Click" ' show message Resume Next ' continue on
After the change:
Command1ClickErr: nReturn = errHandler(Err, Error$, errNext) Resume Next ' continue on
Notice that you removed the two lines that created and displayed the message, and replaced them with the single line that calls errHandler. Notice also that you told errHandler that the only exit option available is Resume Next.
Make changes to the NewSub error handler so that is looks like the one in the following code section:
NewSubErr: nReturn = errHandler(Err, Error$, errNext) Open "junk.txt" For Input As 1 ' create a new error Resume Next ' continue on
Now save and run the project. When you click on the Command1 button, you'll see the new error messages (see Figure 14.16).
An error message from errHandler.
Now let's add an option that will create an error report file whenever the error handler is activated.
When errors occur, users often do not remember details that appear in the error messages. It's much more useful to create an error log on disk whenever errors occur. This enables programmers or system administrators to review the logs and see the error messages without having to be right next to the user when the error occurs.
Listing 14.12 shows a new routine that writes the error log to the disk file. This routine creates a text file that contains the application name, error number and error message along with a date and time the error occurred. The target directory for these error files is set with a module-level variable. This could be altered to fit your future projects. Add this routine to the library module.
Listing 14.12. Coding the errWriteogFile routine.
Public Sub errWriteLogFile(cLogMsg) On Error GoTo errWriteLogFileErr ' ' write error message to log file ' Dim cFile As String Dim nFile As Integer ' nFile = FreeFile() ' get first available file handle cFile = errDir + Format(Now, "mmddhhss") + ".err" ' Open cFile For Output As nFile Print #nFile, "*** ERROR REPORT - [" + App.EXEName + "] ***" Print #nFile, "" Print #nFile, "DATE: " + Format(Now, "General Date") Print #nFile, "" Print #nFile, cLogMsg Print #nFile, "" Print #nFile, "*** eof ***" Close nFile GoTo errWriteLogFileExit ' errWriteLogFileErr: MsgBox Str(Err) + " - " + Error$, vbCritical, "Unable to Write Error Log" Exit Sub ' errWriteLogFileExit: ' End Sub
Notice that you create a filename that contains the month, day, hour, and second the error was created. This is a quick and simple way to create unique filenames. Notice also that you added an error handler in this routine. Because you are about to perform disk operations, you need to be ready for errors here, too!
TIP: The Visual Basic FreeFile() function is used to return a number that represents the first available file channel Visual Basic uses to open the data file. Using FreeFile() guarantees that you do not select a file channel that Visual Basic is already using for another file.
After adding the new routine to the library module, edit the errHandler function to add a line to call the errWriteLogFile routine, as shown in Listing 14.13. The only line you need to add is the errWriteLogFile cMsg line. This forces the log file to be created each time the error handler is invoked.
Listing 14.13. Adding errWriteLogFile to the errHandler function.
Function errHandler(nErrNumber, cErrText, nErrOption) As Integer Dim cMsg As String Dim nReturn As Integer ' ' build message cMsg = "" cMsg = cMsg + "Err:" + Chr(9) + Str(nErrNumber) + Chr(13) + Chr(10) cMsg = cMsg + "Text:" + Chr(9) + cErrText ' errWriteLogFile cMsg ' write log file ' ' handle option Select Case nErrOption Case Is = errExit MsgBox cMsg, vbCritical, "Exiting Program" GoTo errHandlerEnd Case Is = errResume MsgBox cMsg, vbCritical, "Error" errHandler = errResume Case Is = errNext MsgBox cMsg, vbCritical, "Error" errHandler = errNext Case Is = errSelect nReturn = MsgBox(cMsg, vbCritical + vbAbortRetryIgnore, "Error") Select Case nReturn Case Is = vbAbort GoTo errHandlerEnd Case Is = vbRetry errHandler = errResume Case Is = vbIgnore errHandler = errNext End Select End Select Exit Function ' errHandlerEnd: MsgBox "Exiting Program" End End Function
Now save and run the project. When you click the Command1 button, the system creates a log file for each error message displayed on the screen. You can use Notepad (or any other ASCII editor) to view the resulting log file. Look for a file with the .ERR extension. An example error report is shown in the following lines.
*** ERROR REPORT - [ERROR01] *** DATE: 7/25/95 5:49:07 PM TIME: 5:49:07 PM Err: 6 Text: Overflow
You can add a toggle variable to turn the error reporting on or off. This toggle could be set by a value in an .INI file or registry entry. Listing 14.14 contains the added code for the toggle switch.
Add a global variable for the toggle switch by adding it to the declarations section of the library module.
Listing 14.14. Declaring global variables for the error handler.
Option Explicit ' ' error handler constants ' Global Const errExit = 0 Global Const errResume = 1 Global Const errSelect = 2 Global Const errNext = 3 ' ' module level stuff for log files Const errDir = "\abc\examples\" Global errLogFile As Integer
Now set the toggle value at the start of the project. In the first loaded form, add the following code to the Form_Load event.
Private Sub Form_Load() errLogFile = True ' set the err log file on End Sub
Now alter the errHandler function to query the toggle switch before calling the log report routine.
If errLogFile = True Then errWriteLogFile cMsg ' write log file End If
All you did here is wrap the existing line that calls the error log report in an If...Then statement. Now save and run the project and check your results. Because you set the toggle to True at the start of the project, you should see two more error reports when you click the Command1 button.
The final touch to add to your error handler library is the option to keep track of and print a module trace. A module trace keeps track of all the modules that have been called and the order in which they were invoked. This can be very valuable when you're debugging programs. Often, a routine works just fine when it is called from one module, but reports errors if called from another module. When errors occur, it's handy to have a module trace to look through to help find the source of your problems.
In order to keep track of the modules that have been called, you need a routine that maintains an array of all the modules currently running in your application. You also have to add a few lines of code to each module that update that array as your program runs.
First add some variables (in Listing 14.15) in the declaration area of LIBERROR.BAS.
Listing 14.15. Adding variables for the Module Trace option.
Option Explicit ' ' error handler constants ' Global Const errExit = 0 Global Const errResume = 1 Global Const errSelect = 2 Global Const errNext = 3 ' ' module level stuff for log files Const errDir = "\abc\examples\" Global errLogFile As Integer ' ' global stack constants Global errStackFlag As Integer Global Const errPush = 0 Global Const errPop = 1 Global Const errList = 2 Global Const errFile = 3 Global gblNProc As Integer ' stack pointer Global gblAProc() As String ' array of routines
You have added four constants that control how the stack routine behaves. You can add values to the stack (errPush), remove values from the stack (errPop), list the stack to the screen (errList), or send the list to a file (errFile). You also declared a pointer to the stack and an array that holds the names of all the routines called in your Visual Basic program. Finally, you added a toggle switch (errStackFlag) that lets you turn the stack operations on or off.
Now add the routine that handles all the stack processingerrProcStack. Create a new Sub that accepts two parameters called nStackAction and cProcName. Then enter the code in Listing 14.16.
Listing 14.16 Coding the errProcStack routine.
Sub errProcStack(nStackAction, cProcName) On Error GoTo errProcStackErr ' Dim cMsg As String Dim x As Integer Dim nFile As Integer Dim cFile As String ' ' skip it if toggle is off If errStackFlag = False Then GoTo errProcStackExit End If ' ' handle stack action Select Case nStackAction Case Is = errPush ' add new procedure to stack gblNProc = gblNProc + 1 ReDim Preserve gblAProc(gblNProc) gblAProc(gblNProc) = UCase$(cProcName) Case Is = errPop ' remove procedure from stack gblNProc = gblNProc - 1 ReDim Preserve gblAProc(gblNProc) Case Is = errList ' list stack to screen cMsg = "" For x = gblNProc To 1 Step -1 cMsg = cMsg + Trim(Str$(x)) + " - " + gblAProc(x) + Chr(13) + Chr(10) Next x MsgBox cMsg, vbInformation, "Stack Dump [" + App.EXEName + "]" Case Is = errFile ' list stack to file nFile = FreeFile cFile = Format(Now, "mmddhhss") + ".stk" Open cFile For Output As nFile Print #nFile, "*** PROCEDURE STACK DUMP [" + App.EXEName + "] ***" Print #nFile, "DATE: " + Format(Now, "General Date") Print #nFile, "" ' Print #nFile, String(40, "-") For x = gblNProc To 1 Step -1 Print #nFile, Chr(9) + Trim(Str(x)) + " - " + gblAProc(x) Next x Print #nFile, String(40, "-") Print #nFile, "" Print #nFile, "*** eof ***" Close #nFile End Select GoTo errProcStackExit ' errProcStackErr: ' ' unexpected error MsgBox Str(Err) + " - " + Error$, vbCritical, "Unable to Process Stack Request" Exit Sub ' errProcStackExit: ' End Sub
This routine handles all the operations needed to keep track of all routines running in your program. It can also send the list of routines to the screen or printer. Before this can happen, however, you have to add some code to each module. At the start of each module, you have to add code that tells the system what new routine is running. That way, if an error occurs, the system knows what routine caused it. Also, at the end of the routine, you need to add code that removes the routine's name from the active list.
Let's use the Command1_Click routine as an example. Modify the Command1_Click routine to match Listing 14.17.
Listing 14.17. Adding errProcStack to Command1_Click.
Private Sub Command1_Click() On Error GoTo Command1ClickErr ' turn on error handling ' errProcStack errPush, "Command1" ' add routine to list ' NewSub ' call new routine GoTo Command1ClickExit ' exit if no error ' ' local error handler Command1ClickErr: nReturn = errHandler(Err, Error$, errResume) Resume Next ' continue on ' ' routine exit Command1ClickExit: ProcStack errPop, "" ' remove routine from list ' End Sub
Notice that you added a line at the start of the routine and a line at the end of the routine. This is all you need to do in order to update the procedure stack for the program. But, for this to be really valuable, you have to do this for each routine that you want to track. For now, let's add the same code to the NewSub routine. The modified code is shown in Listing 14.18.
Listing 14.18. Adding errProcStack to NewSub.
Private Sub NewSub() On Error GoTo NewSubErr ' turn on error handling ' errProcStack errPush, "NewSub" ' add to stack Dim x As Integer ' declare integer x = 10000000 ' create overflow error GoTo NewSubExit ' exit if no error ' ' local error handler NewSubErr: nReturn = errHandler(Err, Error$, errNext) Open "junk.txt" For Input As 1 ' create a new error Resume Next ' continue on ' ' routine exit NewSubExit: errProcStack errPop, "" ' remove from stack ' End Sub
You only added the two lines that add and remove the routine from the stack. Now, add a bit of code (Listing 14.19) to the errHandler routine that forces the program to display the error stack on-screen each time the error handler is activated.
Listing 14.19. Modifying errHandler to call errProcStack.
Function errHandler(nErrNumber, cErrText, nErrOption) As Integer Dim cMsg As String Dim nReturn As Integer ' ' build message cMsg = "" cMsg = cMsg + "Err:" + Chr(9) + Str(nErrNumber) + Chr(13) + Chr(10) cMsg = cMsg + "Text:" + Chr(9) + cErrText ' If errLogFile = True Then errWriteLogFile cMsg ' write log file End If ' ' handle option Select Case nErrOption Case Is = errExit MsgBox cMsg, vbCritical, "Exiting Program" GoTo errHandlerEnd Case Is = errResume MsgBox cMsg, vbCritical, "Error" errHandler = errResume Case Is = errNext MsgBox cMsg, vbCritical, "Error" errHandler = errNext Case Is = errSelect nReturn = MsgBox(cMsg, vbCritical + vbAbortRetryIgnore, "Error") Select Case nReturn Case Is = vbAbort GoTo errHandlerEnd Case Is = vbRetry errHandler = errResume Case Is = vbIgnore errHandler = errNext End Select End Select ' errProcStack errList, "" ' force list to screen Exit Function ' errHandlerEnd: MsgBox "Exiting Program" End End Function
The only line you added here is the call to errProcStack with the parameter that forces the list to the screen.
Finally, add the code at the Form_Load event of the first form which turns on the stack processor.
Private Sub Form_Load() errLogFile = True ' set the err log file on errStackFlag = True ' set stack flag on End Sub
Now save and run the project. When you click on the Command1 button, you'll see the usual error messages and a new message box that shows the modules that are currently running. Running this routine brings out a very handy aspect of the stack routine. Look carefully at the second stack message box (see Figure 14.17).
The second stack message box.
Notice that the second stack message tells you that two routines are active in the program. The last routine (#2) to run was the NewSub routine. That is the current routine. Remember who reported this second error? It was the Command1 routine's error handler that caught the error due to an error cascade. Now, even under cascade circumstances, the errProcStack routine gives you accurate information on the routine that really caused the error.
In a real application environment, you wouldn't want to show the procedure stack each time an error is reported. The best place for a stack dump is at exit time due to a fatal error. You should probably use the errFile option to write the stack to disk instead of displaying it to the user.
Now that you have the basics of error handling under your belt, you can continue to add features to the generic error handler. As you add these features, your programs take on a more professional look and feel. Also, using options, such as error report logs and procedure stack logs, makes it easier to debug and maintain your applications.
Additional features that you can add to your error handler include the following:
Today's lesson covered all the basics of creating your own error handling routines for Visual Basic applications. You learned that an error handler has three basic parts:
You learned that an error handler has four possible exits:
You learned about the major types of errors that you are likely to encounter in your program:
You also learned that you can declare a global error handler or a local error handler. The advantage of the global error handler is that it allows you to create a single module that handles all expected errors. The disadvantage is that, due to the way Visual Basic keeps track of running routines, you are not able to resume processing at the point the error occurs once you arrive at the global error handler. The advantage of the local error handler is that you are always able to use Resume, Resume Next, or Resume label to continue processing at the point the error occurs. The disadvantage of the local error handler is that you need to add error handling code to every routine in your program.
Finally, you learned how you can create an error handler that combines local error trapping with global error messages and responses. This combined error handler was developed as part of the LIBERROR.BAS library module you built in this lesson. The LIBERROR.BAS library also contains modules to keep track of the procedures currently running at the time of the error, a process for printing procedure stack dumps to the screen and to a file, and a process that creates an error log on file for later review.
Run this program and elect to find the file. Cancel out of any common dialogs that appear. After this, create the file using Notepad and run the process again. Finally, move the file to a location other than the C drive and run the program. Use the common dialog to search for and select the file.