Day 14

Error Handling in Visual Basic 5

Today's lesson covers a very important aspect of programming--handling 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 learn just what an error handler is and why error handlers are so important. You also learn about some of the inner workings of Visual Basic and how they affect error handling.

You learn about the difference between local error-handling methods and global error-handling methods. You also learn the advantages and disadvantages of each method. You see the various types of errors your program is likely to encounter and get some guidelines on how to handle each type of error.

You also learn about the Err object and the Error collection and how to use these objects to improve the accuracy of error reporting within your application. You also learn how to use the Raise method of the Err object to flag errors from within custom controls or OLE Server objects.

You also learn how to create error logs to keep track of errors that occur in your program. You learn how to create a trace log to analyze your programs. And you learn how you can write your programs to turn these features on or off without having to rewrite program code.

Finally, you build an OLE Server DLL that contains an improved error handler, an error logging facility, and a module trace routine. You can use this new OLE Server in all your future VBA-compliant programming projects.

Error Handling in General

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 chance that errors 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 learn how to do both in today's lesson.

Adding error handling to your program makes your program seem much more polished and friendly to your users. Nothing is more annoying--or frightening--to a user than seeing the screen freeze up, hearing a startling beep, or watching the program (and any file the user had been working on) suddenly disappear from the screen. This only needs to happen a few times before the user vows 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 because of errors are all essential parts of a good error-handling strategy.

Error Handling in Visual Basic

Writing error handlers in Visual Basic is a bit trickier than in most PC languages. There are several reasons for this. First, Visual Basic follows an event-driven language model, rather than a procedure-driven model 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. Third, 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 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.

The Built-In Visual Basic Error Objects

Visual Basic 5 has two built-in objects that can be used to track and report errors at runtime. The Err object is a built-in object that exists in all Visual Basic programs. This object contains several properties and two methods. Each time an error occurs in the program, the Err object properties are filled with information you can use within your program.

The second built-in object that helps in tracking errors is the Error object, and its associated Errors collection. These are available to any Visual Basic 5 program that has loaded one of the Microsoft data access object libraries. The Error object is a child object of the DBEngine. You can use the Error object to get additional details on the nature of the database errors that occur in a program.


WARNING: The Error object is only available if you have loaded a Microsoft data-access object library. If you attempt to access the Error object from a Visual Basic program that does not have a Microsoft data access object library loaded, you receive an error.

The advantage of the Error object over the Err object is that the Error object contains more information about the database-related errors than the Err object. In some cases, back-end database servers return several error messages to your Visual Basic application. The Err object only reports the last error received from the back-end server. However, the Errors collection can report all of the errors received. For this reason, it is always a good idea to use the Error object when you are working with Visual Basic database applications.

Working with the Err Object

Visual Basic 5 has a built-in object called the Err object. This object holds all the information about the most recent error that occurred within the running application space.


WARNING: There is a bit of confusion regarding the Err keyword in Visual Basic. Visual Basic 5 still supports the outdated Err and Error functions, but we do not advise you to use them in your programs. In some rare cases, the values reported by the Err and Error functions are not the same as those reported by the Err object. Throughout this book, when we mention "Err" we are referring to the Err object, not the Err function.

The Err object has several important properties. Table 14.1 shows these properties and explains their use.

Table 14.1. The properties of the Err object.
Property Type Value
Number Long The actual internal error number returned by Visual Basic.
Source String Name of the current Visual Basic file in which the error occurred. This could be an EXE, DLL, or OCX file.
Description String A string corresponding to the internal error number returned in the Number property, if this string exists. If the string doesn't exist, Description contains "Application-defined or object-defined error."
HelpFile String The fully qualified drive, path, and filename of the Help file. This help file can be called to support the reported errors.
HelpContext Long The Help file context (topic) ID in the help file indicated by the HelpFile property.
LastDLLError Long The error code for the last call to a dynamic-link library (DLL). This is available only on 32-bit Microsoft platforms.


When an error occurs in your Visual Basic program, the Err object properties are populated with the details of the error. You can inspect these values during your program execution and, if possible, use Visual Basic code to correct the error and continue the program.

For example, once an error occurs, you can inspect the properties of the object using the following code:

Msgbox "<" & CStr(Err.Number) & "> " & Err.Description & "[" & Err.Source & "]"

Once the error occurs and the Err object properties are updated, the Err object values do not change until another error is reported or the error-handling system is re-initialized.


NOTE: The error handling system is re-initialized each time a procedure exit or end occurs, or when the special error-handling keywords Resume or On Error are executed. You learn more about these keywords later in this chapter.

If the error that was reported has an associated help file and help context ID, these properties are also filled in. You can use the HelpFile and HelpContext properties of the Err object to display an online help topic to explain the error condition to the user.


TIP: Although help file composition and the construction of context-sensitive help is beyond the scope of a database book, Appendix B at the back of this book shows you how to create help files and how to link them to your Visual Basic programs.

If your application has called a dynamic-link library (DLL), you may be able to use the LastDLLError property of the Err object to get additional information about an error that occurred in a DLL. This property is only available on the 32-bit platform and may not be supported by the DLL you are calling.

Working with the Error Object and the Errors Collection

In addition to the built-in Err object, Visual Basic 5 also has a built-in Error object for database errors. This object is a child object of the DBEngine object. For this reason, you can only access the Error object if you have loaded a Microsoft data access object library (select Project | References from the main menu).

The primary advantage of the Error object is that it can report additional error information not included in the standard Err object mentioned earlier in this chapter. Many times, your database application needs to depend on external processes such as ODBC data connections or OLE Server modules. When an error occurs in these external processes, they may report more than one error code back to your Visual Basic application.

The Err object is only able to remember the most recent error reported. However, the Error object (and its associated Errors collection) can remember all of the errors reported by external processes. That is why it is a good idea to use the Error object for reporting errors in all your Visual Basic database programs.

The properties of the Microsoft data access Error object are almost identical to the properties of the Visual Basic Err object (see the discussion earlier in this chapter). The only difference is that the Error object does not have the optional LastDLLError property. Therefore, the calling convention for the Error object is basically the same as that for the Err object:

Msgbox "<" & CStr(Error.Number) & "> " & Error.Description & "[" & Error.Source Â& "]"

While the Error and Err objects are quite similar, there is one major difference worth noting. While the Err object stands alone, the Microsoft data access Error object belongs to the Errors collection. This is very important when dealing with back-end database servers, especially when your Visual Basic program is connected to databases through the Open Database Connectivity (ODBC) interface. When an error occurs during an ODBC transaction, the Err object always returns the same error message, ODBC failed. However, the Errors collection often contains more than one error message, which can tell you a great deal more about the nature of the problem. You can retrieve all the error information by enumerating all the Error objects in the Errors collection. The code in Listing 14.1 shows how that can be done.

Listing 14.1. Enumerating the Errors collection.

Dim objTempErr as Object
Dim strMsg as String

For Each objTempErr In Errors
   StrMsg = "<" & CStr(objTempErr.Number) & "> "
   StrMsg = strMsg & objTempErr.Description
   StrMsg = strMsg & " in [" & objTempErr.Source & "]" & vbCrLf
Next
Msgbox strMsg 


The code in Listing 14.1 creates a single line of text (strMsg) that contains all the error messages reported by the back-end database server. You learn more about using both the Err and Error objects in the next section of the chapter.

Creating Your Own Error Handlers

Before getting into the details of using the Err and Error objects in your Visual Basic programs, let's take a look at a basic error handler in Visual Basic. 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 LocalErrHandler

In the preceding code line, every time an error occurs in this Sub or Function, the program immediately jumps to the LocalErrHandler label in the routine and executes 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:

LocalErrHandler:
   MsgBox CStr(Err.Number) & " - " & Err.Description

In the preceding code example, as soon as the error occurs, Visual Basic reports the error number (Err.Number) and the error message (Err.Description) 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 with 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, you can write some error-handling routines.

Creating a Simple Error Handler

To start, let's write a simple error-handling routine to illustrate how Visual Basic behaves when errors occur. Start up a new Standard EXE project in Visual Basic 5. Add a single command button to the default form. Set its Name property to cmdSimpleErr and its Caption Property to Simple. Now add the code in Listing 14.2 to support the command button.

Listing 14.2. Writing a simple error handler.

Private Sub cmdSimpleErr_Click()
    `
    ` a simple error handler
    `
    On Error GoTo LocalErr  ` turn on error handling
    `
    Dim intValue As Integer ` declare integer
    Dim strMsg As String    ` declare string
    intValue = 10000000     ` create overflow error
    GoTo LocalExit          ` exit if no error
    `
    ` local error handler
LocalErr:
   strMsg = CStr(Err.Number) & " - " & Err.Description ` make message
   MsgBox strMsg, vbCritical, "cmdSimpleErr_Click"  ` show message
   Resume Next  ` continue on
   `
   ` routine exit
LocalExit:
   `
End Sub 


Save the form as BASICERR.FRM, and save the project as BASICERR.VBP. Then execute the program and click the command button. You see the error message displayed on the screen (see Figure 14.1).

The example in Listing 14.2 exhibits all the parts of a good error handler. The first line in the routine tells Visual Basic what to do in case of an error. Notice that the name for the error-handling code is given as LocalErr. (Every local error handler written in this book is called LocalErr.) Next, the routine declares an integer variable and then purposely loads that variable 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 message. The routine then displays that message along with the warning symbol and the name of the routine that is reporting the error.

Figure 14.1. Displaying the results of a simple error handler.

The next line tells Visual Basic what to do after the error is handled. In this case, Visual Basic resumes 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 LocalExit). Notice again the naming convention for the exit routine. All exit jump labels in this book are called LocalExit.

Handling Cascading Errors

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 add a new button to the BASICERR project and create a cascading error condition. Set the button's Name property to cmdCascadeErr and its Caption property to Cascade. First, create a new Sub procedure called CreateErr. Then enter the code from List-ing 14.3.

Listing 14.3. Coding the CreateErr routine.

Public Sub CreateErr()
    `
    ` create an internal error
    `
    On Error GoTo LocalErr
    `
    Dim strMsg As String
    Dim intValue As Integer
    `
    intValue = 900000 ` create an error
    GoTo LocalExit ` all done
    `
LocalErr:
    strMsg = CStr(Err.Number) & " - " & Err.Description
    MsgBox strMsg, vbCritical, "CreateErr"
    `
    Open "junk.txt" For Input As 1 ` create another error
    Resume Next
    `
LocalExit:
    `
End Sub 


Notice that this routine is quite similar to the code from Listing 14.2. The biggest difference is in the lines of code in the error-handling portion of the subroutine. Notice that Visual Basic attempts to open a text file for input. Since this file does not currently exist, this action causes an error.

Now add the code from Listing 14.4 to the cmdCascadeErr_Click event. This is the code that calls the CreateErr routine.

Listing 14.4. Coding the cmdCascadeErr routine.

Private Sub cmdCascadeErr_Click()
    `
    ` create an error cascade
    `
    On Error GoTo LocalErr
    `
    Dim strMsg As String
    `
    CreateErr ` call another routine
    GoTo LocalExit ` all done
    `
LocalErr:
    strMsg = CStr(Err.Number) & " - " & Err.Description
    MsgBox strMsg, vbCritical, "cmdCascadeErr"
    Resume Next
    `
LocalExit:
    `
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 CreateErr routine (see Figure 14.2).

Figure 14.2. Reporting the error from CreateErr.

When you click the OK button in the message box, you see another error message. This one reports an Error 53-File not found message, which occurred when CreateErr tried to open the nonexistent file (see Figure 14.3).

Figure 14.3. Reporting the File not found error.

Here's the important point. Notice that the second error message box tells you that the error is being reported from the cmdCascadeErr routine--even though the error occurred in the CreateErr routine! The error that occurred in the CreateErr error-handling routine 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 uses 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 cmdCascadeErr. You must keep this in mind when you are debugging Visual Basic error reports.

Using Resume to Exit the Error Handler

The simplest way to exit an error handler is to use 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 BASICERR project by adding a new button to the project. Set its Name property to cmdResumeErr and its Caption property to Resume. Now add the Visual Basic code in Listing 14.5 to support the new button's Click event.

Listing 14.5. Using the Resume keyword.

Private Sub cmdResumeErr_Click()
    `
    ` show resume keyword
    `
    On Error GoTo LocalErr
    `
    Dim intValue As Integer
    Dim strMsg As String
    `
    intValue = InputBox("Enter an integer:")
    GoTo LocalExit
    `
LocalErr:
    strMsg = CStr(Err.Number) & " - " & Err.Description
    MsgBox strMsg, vbCritical, "cmdResumeErr"
    Resume ` try it again
    `
LocalExit:
    `
End Sub 


Save and run the project. When you press the Resume button, you are prompted to enter an integer value. If you press the Cancel button or the OK button without entering data (or 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.4).

Figure 14.4. Reporting an error message from the input box.

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.

This can be very frustrating for 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 Resume Next to Exit the Error Handler

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 has 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. This is especially true 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-handling routines that fix any reported error before continuing.

To illustrate the use of Resume Next, add a new command button to the project. Set its Name property to cmdResumeNext and its Caption property to Next. Now enter the code in Listing 14.6 behind the button's Click event.

Listing 14.6. Using the Resume Next keywords.

Private Sub cmdResumeNextErr_Click()
    `
    ` show use of resume next
    `
    On Error GoTo LocalErr
    `
    Dim intValue As Integer
    Dim strMsg As String
    Dim lngReturn As Long
    `
    intValue = InputBox("Enter a valid Integer")
    MsgBox "intValue has been set to " + CStr(intValue)
    GoTo LocalExit
    `
LocalErr:
    If Err.Number = 6 Then ` was it an overflow error?
        strMsg = "You have entered an invalid integer value." & vbCrLf
        strMsg = strMsg & "The program will now set the value to 0 for you." & ÂvbCrLf
        strMsg = strMsg & "Select YES to set the value to 0 and continue." & ÂvbCrLf
        strMsg = strMsg & "Select NO to return to enter a new value."
        `
        lngReturn = MsgBox(strMsg, vbCritical + vbYesNo, "cmdResumeNextErr")
        If lngReturn = vbYes Then
            intValue = 0
            Resume Next
        Else
            Resume
        End If
    Else ` must have been some other error(!)
        strMsg = CStr(Err.Number) & " - " & Err.Description
        MsgBox strMsg, vbCritical, "cmdResumeNext"
        Resume
    End If
    `
LocalExit:
    `
End Sub 


In Listing 14.6, 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 this routine includes a general error trap for those cases when the error is not caused by an integer overflow. Even when you think you have covered all the possible error conditions, you should always include 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 see the error message that explains your options (see Figure 14.5).

Using Resume label to Exit an Error Handler

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 use to perform a division operation, and it results in a divide-by-zero error, you want to ask the user to enter both numbers again. You might not be able to just use the Resume statement after you handle the error.

Figure 14.5. An error message that asks for user input.

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.

Now let's modify the BASICERR project to include an example of Resume label. Add a new command button to the project. Set its Name property to cmdResumeLabelErr and its Caption property to Resume Label. Now, place the code in Listing 14.7 behind the Click event.

Listing 14.7. Using the Resume label keywords.

Private Sub cmdResumeLabelErr_Click()
    `
    ` show resume label version
    `
    On Error GoTo LocalErr
    `
    Dim intX As Integer
    Dim intY As Integer
    Dim intZ As Integer
    `
cmdLabelInput:
    intX = InputBox("Enter a Divisor:", "Input Box #1")
    intY = InputBox("Enter a Dividend:", "Input Box #2")
    intZ = intX / intY
    MsgBox "The Quotient is: " + Str(intZ), vbInformation, "Results"
    GoTo LocalExit
    `
LocalErr:
    If Err = 11 Then     ` divide by zero error
        MsgBox CStr(Err.Number) & " - " & Err.Description, vbCritical, Â"cmdResumeLabelErr"
        Resume cmdLabelInput ` back for more
    Else
        MsgBox CStr(Err) & " -" & Error$, vbCritical, "cmdLabel"
        Resume Next
    End If
    `
LocalExit:
    `
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 see the error message shown in Figure 14.6 and then return to the line that starts the input process.

Figure 14.6. Displaying the Divide by Zero error message.


Using the Exit or End Method to Exit an Error Handler

There are times when an error occurs and there is no good way to return to the program. A good example of this type of error can occur when the program attempts 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 the BASICERR project. Set its Caption property to End and its Name property to cmdEndErr. Enter the code in Listing 14.8 to support the cmdEnd_Click event.

Listing 14.8. Using the End keyword.

Private Sub cmdEndErr_Click()
    `
    ` use End to exit handler
    `
    On Error GoTo LocalErr
    `
    Dim strMsg As String
    Open "junk.txt" For Input As 1
    GoTo cmdEndExit
    `
LocalErr:
   If Err.Number = 53 Then
      strMsg = "Unable to open JUNK.TXT" & vbCrLf
      strMsg = strMsg & "Exit the program and check your INI file" & vbCrLf
      strMsg = strMsg & "to make sure the JUNKFILE setting is correct."
      MsgBox strMsg, vbCritical, "cmdEnd"
      Unload Me
   Else
      MsgBox Str(Err) + " - " + Error$, vbCritical, "cmdEnd"
      Resume Next
   End If
   `
LocalExit:
   `
End Sub 


In Listing 14.8, 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.7). When you click the OK button, Visual Basic ends the program.

Figure 14.7. Showing the error message before exiting the program.


Using the Err.Raise Method to Create Your Own Error Conditions

Many times it is not practical, or desirable, to display an error message when an error occurs. Other times, you may want to use the error-handling capabilities of Visual Basic 5 to your advantage by creating your own error codes and messages. You can do this using the Raise method of the Err object. Using the Raise method allows you to alert users (or other calling applications) that an error has occurred, but gives both you and the user additional flexibility on how the error is handled.

The Raise method takes up to five parameters:

When you raise your own errors, you are required to report an error number. This number can be any unique value. If you use a number already defined as a Visual Basic error, you automatically get the ErrorDescription and any associated HelpFile and HelpContextID information as well. If you generate your own unique number, you can fill in the other parameters yourself. It is recommended that you use the vbObjectError constant as a base number for your own error codes. This guarantees that your error number does not conflict with any Visual Basic errors.

Here is a typical call to use the Err.Raise method:

`
LocalErr:
    ` trouble with file stuff!
    Err.Raise vbObjectError + 1, "errHandler.LogError", "Can't write log file [" Â& errLogFileName & "]"
    `
End Sub

It is especially important to use this method for marking errors when you are coding ActiveX DLL servers. Since servers may run at a remote location on the network, you cannot be sure that users ever see any error dialog you display. Also, remember that, even if the DLL is running on the local PC, error dialog boxes are application-modal. No other processing occurs until the dialog box is dismissed. You learn to use the Err.Raise method when you create your errHandler object library later in this chapter.

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 may encounter in your Visual Basic programs and how to plan for them in advance.

Types of Errors

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 may 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 needs to be handled differently within your Visual Basic programs. You learn general rules for handling these errors in the following sections.

General File Errors

General file errors occur because of invalid data file information such as a bad filename, data path, or device name. Usually the user can fix these errors, and the program can continue from the point of failure. The basic approach to handling general file errors is to create an error handler that reports the problem to the user and asks for additional information to complete or retry the operation.

In Listing 14.9, 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. Start a new Standard EXE project (ERRTYPES.VBP) and add a command button to the form. 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.9 into the cmdControl_Click event.

Listing 14.9. Adding code to the cmdControl_Click event.

Private Sub cmdControl_Click()
    `
    ` show general file errors
    `
    On Error GoTo LocalErr
   `
   Dim strFile As String
   Dim strMsg As String
   Dim lngReturn As Long
   `
   strFile = "\control.txt"
   `
   Open strFile For Input As 1
   MsgBox "Control File Opened"
   GoTo LocalExit
   `
LocalErr:
   If Err.Number = 53 Then ` file not found?
      strMsg = "Unable to Open CONTROL.TXT" & vbCrLf
      strMsg = strMsg & "Select OK to locate CONTROL.TXT" & vbCrLf
      strMsg = strMsg & "Select CANCEL to exit program."
      `
      lngReturn = MsgBox(strMsg, vbCritical + vbOKCancel, "cmdControl")
      `
      If lngReturn = vbOK Then
         CommonDialog1.filename = strFile
         CommonDialog1.DefaultExt = ".txt"
         CommonDialog1.ShowOpen
         Resume
      Else
         Unload Me
      End If
   Else
      MsgBox CStr(Err.Number) & " - " + Err.Description
      Resume Next
   End If
   `
LocalExit:
   `
End Sub


Save the form as FRMERRTYPES.FRM and the project as PRJERRTYPES.VBP. Now 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.8).

Figure 14.8. Displaying the File not Found error.

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 \TYSDBVB5\SOURCE\CHAP14 directory (see Figure 14.9).

Figure 14.9. Attempting to locate the CONTROL.TXT file.



TIP: 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.

Table 14.2 lists errors that are similar to the File not found error illustrated in Listing 14.9. 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.2. 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. Then give the user some ideas about how to fix the problem before you exit the program safely.

Physical Media Errors

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 PRJERRTYPES.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()
    `
    ` show handling of media errors
    `
    On Error GoTo LocalErr
    Dim strMsg As String
    Dim lngReturn As Long
   `
   ` 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 LocalExit
   `
LocalErr:
   If Err.Number = 71 Then
      strMsg = "The disk drive is not ready." & vbCrLf
      strMsg = strMsg + "Please make sure there is a diskette" & vbCrLf
      strMsg = strMsg + "in the drive and the drive door is closed."
      `
      lngReturn = MsgBox(strMsg, vbCritical + vbRetryCancel, "cmdMedia")
      `
      If lngReturn = vbRetry Then
         Resume
      Else
         Resume Next
      End If
   Else
      MsgBox Str(Err.Number) & " - " & Err.Description
      Resume Next
   End If
   `
LocalExit:
   `
End Sub 


In Listing 14.10, you attempt to open a file on a disk drive that has no disk (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.10.

Figure 14.10. The results of a physical media error.


Program Code Errors

Another common type of error is the program code error. These errors 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, create a mystery for 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.

Database Errors with the Data Control

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 includes those errors caused by locked tables, restricted access, or multiuser conflicts.


NOTE: Errors concerning table locks, restricted access, and multiuser issues are covered in depth in the last week of the book. In this chapter, you focus on the more common group of errors.

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.

Start a new Visual Basic Standard EXE project to illustrate common database errors. Add a data control, two bound input controls, and two label controls. Use Table 14.3 as a reference for adding the controls to the form. Refer to Figure 14.11 as a guide for placing the controls.

Figure 14.11. Laying out the DataErr form.


Table 14.3. Controls for the frmDataErr form.
Control Property Setting
VB.Form Name frmDataErr
Caption "Data Error Demo"
ClientHeight 1335
ClientLeft 60
ClientTop 345
ClientWidth 4665
StartUpPosition 3 `Windows Default
VB.CommandButton Name cmdAdd
Caption "&Add"
Height 375
Left 3300
Top 60
Width 1215
VB.TextBox Name txtName
DataField "Name"
DataSource "Data1"
Height 315
Left 1500
Top 540
Width 3015
VB.TextBox Name txtKeyField
DataField "KeyField"
DataSource "Data1"
Height 375
Left 1500
Top 60
Width 1515
VB.Data Name Data1
Align 2 `Align Bottom
Caption "Data1"
Connect "Access"
DatabaseName C:\TYSDBVB5\SOURCE\DATA\ERRORS\
ERRORDB.MDB
Height 360
RecordSource "Table1"
Top 975
Width 4665
VB.Label Name lblName
Caption "Name"
Height 255
Left 120
Top 540
Width 1215
VB.Label Name lblKeyField
Caption "Key Field"
Height 255
Left 120
Top 120
Width 1215


The only code you need to add to this form is a single line to support the Add button. Place the following code behind the cmdAdd_Click event.

Private Sub cmdAdd_Click()
   Data1.Recordset.AddNew
End Sub

Now save the new form as DATAERR.FRM and the project as DATAERR.VBP. When you run the project, you can test the built-in error trapping for Microsoft data controls by adding a new, duplicate record to the table. Press the Add button, then enter KF109 in the KeyField input box and 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.12.

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 handlers in your Visual Basic program. Along with the automatic errors, the data control also has the Error event. Each time a data-related error occurs, this event occurs. You can add code in the Data1_Error event to automatically fix errors, display better error messages, and so on.

Figure 14.12. A sample Microsoft data control error message.

Let's modify the program a bit to show you how you can use the Data1_Error event. First, add a CommonDialog control to your form. Then edit the DatabaseName property of the data control to read C:\ERRORDB.MDB. Next, add the code from Listing 14.11 to the Data1_Error event.

Listing 14.11. Coding the Data1_Error event.

Private Sub Data1_Error(DataErr As Integer, Response As Integer)
    `
    ` add error-trapping for data errors
    `
    Dim strFileName As String
    `
    Select Case DataErr
        Case 3044 ` database not found
            MsgBox "Unable to locate data file", vbExclamation, "Database ÂMissing"
            `
            CommonDialog1.DialogTitle = "Locate ERRORDB.MDB"
            CommonDialog1.filename = "ERRORDB.MDB"
            CommonDialog1.Filter = "*.mdb"
            CommonDialog1.ShowOpen
            Data1.DatabaseName = CommonDialog1.filename
            `
            Response = vbCancel ` cancel auto-message
    End Select
    `
End Sub 


Notice that the code in Listing 14.11 checks to see whether the error code is 3044. This is the error number that corresponds to the "database missing" message. If the 3044 code is reported, the user sees a short message and then the file open dialog, ready to locate and load the database. Finally, notice the line that sets the Response parameter to vbCancel. This step tells Visual Basic not to display the default message.


TIP: Usually, 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. The only time you need to add error-related code is when you want to perform special actions in the Error event of the data control.

You need to add one more bit of code to complete this error trap. Add the following line of code to the Form_Activate event.

Private Sub Form_Activate()
    Data1.Refresh
End Sub

This code makes sure the data entry fields on the form are updated with the most recent data from the database.

Now save and run the project. You first see a message telling you that the database is missing (Figure 14.13).

Figure 14.13. Custom error message in the Data1_Error event.

Next, the open file dialog waits for you to locate and load the requested database (Figure 14.14).

Figure 14.14. Locating the requested database.

Finally, once you load the database, the data entry screen comes up ready for your input.

Database Errors with Microsoft Data Access Objects

If you use Microsoft data access objects instead of 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.

Add the code in Listing 14.12 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.12. Adding code to the Form_Load event.

Private Sub Form_Load()
    `
    ` create recordset using DAO
    `
    On Error GoTo LocalErr
    `
    Dim ws As Workspace
    Dim db As Database
    Dim rs As Recordset
    Dim strSQL As String
    `
    strSQL = "SELECT * FROM Table2"
    Set ws = DBEngine.Workspaces(0)
    Set db = ws.OpenDatabase(App.Path & "\..\..\Data\Errors\ErrorDB.mdb")
    Set rs = db.OpenRecordset(strSQL, dbOpenDynaset)
    Exit Sub
    `
LocalErr:
    MsgBox "<" & CStr(Errors(0).Number) & "> " & Errors(0).Description, ÂvbCritical, "Form_Load Error"
    Unload Me
    `
End Sub 


The code in Listing 14.12 establishes some variables and then opens the database and creates a new Dynaset from a data table called Table2.


NOTE: Notice that instead of the Visual Basic Err object, the DAO Errors collection is used to retrieve the most recent database error. The Errors collection is only available if you loaded the Microsoft DAO library using the Project | References option from the main Visual Basic 5 menu.

Because there is no Table2 in ERRORDB.MDB, you see a database error when the program runs. The error message is displayed, and then the form is unloaded completely (see Figure 14.15).

Figure 14.15. Displaying an error message from the Form_Load event.

It is a good idea to open any data tables or files that you 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.

Creating Your Error Handler OLE Server

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 write a set of routines that you can install in all your Visual Basic programs--the error-handling OLE Server. This OLE Server offers some generic error-handling capabilities along with the ability to log these errors to a disk file and to keep track of the procedure call stack. These last two services can be very valuable when you encounter a vexing bug in your program and need to get additional information on the exact subroutines and functions that were executed before the error occurred.

To build the new error handling OLE Server, you need to start a new Visual Basic ActiveX DLL project. Name the default Class module errHandler and set the project Name to prjErrHandler. You also need to add a BAS module and a form to the project. The form acts as the new customized error dialog box. The BAS module holds a new user-defined type and some API definitions for use with the customized error dialog box.

Building the errHandler Class

Building the errHandler class involves several steps. First, add some code to the general declaration section of the Class object (see Listing 14.13).

Listing 14.13. Adding code to the Declaration section of the errHandler class.

Option Explicit

`
` error types
Enum errType
    erritem = 0
    errcoll = 1
End Enum

`
` return/option values
Enum errReturn
    errExit = 0
    errresume = 1
    errNext = 2
    errselect = 3
End Enum

`
` handler storage
Private errDefRtn As errReturn
Private errDefType As errType
` 


The first two items in the declaration section define enumerated types. These are a special type of user-defined type that are a mix between a standard user-defined type and a Public constant. Enumerated types make it easy to write well-documented code. Along with the enumerated types, you see two Private variables declared for local use.

Next, you need to add some declaration code to the BAS module in your DLL project. This code defines a special custom data type that you can use to control the display of your custom error dialog box. The errHandler DLL allows you to access any possible help topics associated with the error messages, too. For this reason, the BAS module contains the WinHelp API declaration. This is used on the custom dialog box. Open the BAS module, set its Name property to modErrHandler and enter the code in Listing 14.14 into the general declaration section of the module.

Listing 14.14. Adding code to the general declaration section of the modErrHandler BAS module.

Option Explicit

`
` define dialog data type
Public Type errDialog
    Message As String
    Buttons As Variant
    Title As String
    HelpFile As String
    HelpID As Long
    Return As Long
End Type
`
Public udtErrDialog As errDialog

`
` declare winHelp API
Declare Function WinHelp Lib "user32" Alias "WinHelpA" (ByVal hwnd As Long, _
  ByVal lpHelpFile As String, _
  ByVal wCommand As Long, _
  ByVal dwData As Long) As Long
`
Public Const HELP_CONTEXT = &H1
Public Const HELP_QUIT = &H2 


That's all that you need to add to the modErrHandler. Next, you need to add code to the Initialize event of the errHandler Class module. Add the code from Listing 14.15 to the Class_Initialize event.

Listing 14.15. Adding code to the Class_Initialize event
of the errHandler class.

Private Sub Class_Initialize()
    `
    ` set starting values
    errDefRtn = errExit
    errDefType = errItem
    `
    udtErrDialog.Buttons = ""
    udtErrDialog.HelpFile = ""
    udtErrDialog.HelpID = -1
    udtErrDialog.Message = ""
    udtErrDialog.Return = -1
    udtErrDialog.Title = ""
    `
End Sub 


This errHandler class has two Public properties--the DefaultAction and DefaultType properties. These defaults were set in the Initialize event and can be overridden by setting the properties at runtime. Create the DefaultAction property (using the Tools | Add Procedure menu option) and add the code from Listing 14.16 into the Property Let and Property Get routines.

Listing 14.16. Defining the Property Let and Property Get routines for the DefaultAction property.

Public Property Get DefaultAction() As errReturn
    `
    ` return default
    `
    DefaultAction = errDefRtn
    `
End Property

Public Property Let DefaultAction(ByVal vNewValue As errReturn)
    `
    ` verify parm and store
    `
    If vNewValue >= errExit Or vNewValue <= errselect Then
        errDefRtn = vNewValue
    End If
    `
End Property


Note that the data type for the property is errReturn. This is one of the enumerated types defined in the declaration section of the class module. Next, create the Property Let and Property Get routines for the DefaultType property, and enter the code from Listing 14.17 into the project.

Listing 14.17. Coding the Property Let and Property Get routines for the DefaultType property.

Public Property Get DefaultType() As errType
    `
    DefaultType = errDefType
    `
End Property

Public Property Let DefaultType(ByVal vNewValue As errType)
    `
    If vNewValue >= errcoll Or vNewValue <= erritem Then
        errDefType = vNewValue
    End If
    `
End Property 


Finally, you're ready to write the main error handler method. This method can be called from any VBA-compliant program. You pass the Visual Basic Err object (or database Errors collection) along with a few optional parameters that can control the behavior of the message dialog box. After the method has been completed, a value is returned. This value can be used to control program flow and error recovery.

Create a new function method in your class module called errHandler and enter the code from Listing 14.18.

Listing 14.18. Coding the errHandler function.

Public Function errHandler(objErrColl As Variant, Optional intType As errType, ÂOptional errOption As errReturn, Optional errRefName As String) As errReturn

    ` --------------------------------------------------------------------------
    ` produce msg and prompt for response
    `
    ` inputs:
    `   objErrColl  - DAO collection -OR- VBA Err object
    `   intType     - errType enum that describes objErrColl (coll or item)
    `   errOption   - errReturn enum sets dialog behavior (exit, resume, next, 	                 Âselect)
    `   errRefName  - string to reference caller
    `
    ` returns:
    `   errExit     - end program
    `   errResume   - try again
    `   errNext     - skip to next line
    ` --------------------------------------------------------------------------
    `
    Dim strMsg As String
    Dim strTitle As String
    Dim rtnValue As errReturn
    `
    ` retrieve action option
    If IsMissing(errOption) Then
        errOption = errDefRtn
    End If
    `
    ` retrieve reference name
    If IsMissing(errRefName) Then
        errRefName = ""
    Else
        errRefName = " from " & errRefName
    End If
    `
    ` build full message
    strMsg = errMsg(objErrColl, intType)
    `
    ` write it out, if allowed
    If errLogFlag = True Then
        LogError strMsg
    End If
    `
    ` evaluate things
    Select Case errOption
        Case errExit
            udtErrDialog.Title = "Exiting Program"
            udtErrDialog.Message = strMsg
            udtErrDialog.Buttons = Array("&Exit")
            frmErrDialog.Show vbModal
            rtnValue = errExit
            `
        Case errresume, errNext
            udtErrDialog.Title = "Error Message" & errRefName
            udtErrDialog.Message = strMsg
            udtErrDialog.Buttons = Array("&OK")
            frmErrDialog.Show vbModal
            rtnValue = errOption
            `
        Case Else
            udtErrDialog.Title = "Error Message" & errRefName
            udtErrDialog.Message = strMsg
            udtErrDialog.Buttons = Array("&Cancel", "&Retry", "&Ignore")
            frmErrDialog.Show vbModal
            rtnValue = udtErrDialog.Return
    End Select
    `
    ` give it back
    errHandler = rtnValue
    `
End Function 


The code in Listing 14.18 calls a support function (errMsg) and a dialog box (frmErrDialog). You need to code these two remaining objects before you can test your error handler. Create a new function (errMsg) in the class module and enter the code from Listing 14.19 into the project.

Listing 14.19. Adding the errMsg support function.

Public Function errMsg(objErrColl As Variant, intType As errType) As String
    `
    ` build and return complete error msg
    `
    Dim strMsg As String
    Dim objItem As Error
    `
    strMsg = ""
    `
    If intType = errcoll Then
        For Each objItem In objErrColl
            strMsg = strMsg & "<" & CStr(objItem.Number) & "> "
            strMsg = strMsg & objItem.Description
            strMsg = strMsg & " (in " & objItem.Source & ")." & vbCrLf
        Next
    Else ` intType= errItem
        strMsg = "<" & objErrColl.Number & "> "
        strMsg = strMsg & objErrColl.Description
        strMsg = strMsg & " (in " & objErrColl.Source & ")"
        `
        udtErrDialog.HelpFile = objErrColl.HelpFile
        udtErrDialog.HelpID = objErrColl.HelpContext
    End If
    `
    errMsg = strMsg
    `
End Function 


The main job of the errMsg routine is to build a complete error message for display to the user. In order to do this, errMsg needs to know if the object that was passed was a single VBA Err object or the DAO Errors collection. That is why the errType parameter is included in the call. Also note that the errMsg method has been declared as a Public method. You can call this method from your Visual Basic 5 programs, too. That way, even if you don't want to perform all of the error-handing operations, you can use errMsg to get an improved error message for your use.

Coding the frmErrDialog Form

The last main piece of the errHandler class is a custom dialog box to display the error message and get input from the user. Often, you want to do more than just display the message and let the user press "OK." You may ask them to retry the same process, or ask if they want to ignore the error and continue. This dialog box not only displays the message and gives you an opportunity to get a response from the user, it also allows you to provide an optional "Help" button to give the user greater support in discovering how to resolve the error.


NOTE: Creating Help files for your Visual Basic project is covered in Appendix B of this book.

With the prjErrHandler project still open, add a form to the project. Use Table 14.4 and Figure 14.16 as guides in laying out the form.

Table 14.4. Control table for the frmErrDialog form.
Control Property Setting
VB.Form Name frmErrDialog
BorderStyle 3 `Fixed Dialog
Caption "Error Report"
ClientHeight 1755
ClientLeft 45
ClientTop 330
ClientWidth 5460
ControlBox 0 `False
MaxButton 0 `False
MinButton 0 `False
ShowInTaskbar 0 `False
StartUpPosition 2 `CenterScreen
VB.TextBox Name txtErrMsg
BackColor &H80000000&
Height 1035
Left 900
Locked -1 `True
MultiLine -1 `True
ScrollBars 2 `Vertical
Top 120
Width 4395
VB.CommandButton Name cmdBtn
Caption "&Help"
Height 315
Index 3
Left 4080
Top 1320
Visible 0 `False
Width 1215
VB.CommandButton Name cmdBtn
Caption "&Ignore"
Height 315
Index 2
Left 2760
Top 1320
Visible 0 `False
Width 1215
VB.CommandButton Name cmdBtn
Caption "&Retry"
Height 315
Index 1
Left 1440
Top 1320
Visible 0 `False
Width 1215
VB.CommandButton Name cmdBtn
Caption "&OK"
Height 315
Index 0
Left 120
Top 1320
Visible 0 `False
Width 1215
VB.Image Name Image1
Height 600
Left 120
Picture C:\TYSDBVB5\SOURCE\CHAP14\
ERRHANDLER\INTL_NO.BMP
Stretch -1 `True
Top 120
Width 600


Be sure you build the command buttons as a control array and that you set their Visible property to False. You write code to arrange and enable these buttons as needed at runtime.

Figure 14.16. Laying out the frmErrDialog form.

Only two events need to be coded for this form. The Form_Load event handles most of the dirty work. Listing 14.20 shows the code that you should add to the Form_Load event.

Listing 14.20. Coding the Form_Load event of the frmErrDialog form.

Private Sub Form_Load()
    `
    Dim intBtns As Integer
    Dim intLoop As Integer
    `
    txtErrMsg = udtErrDialog.Message
    Me.Caption = udtErrDialog.Title
    `
    intBtns = UBound(udtErrDialog.Buttons)
    For intLoop = 0 To intBtns
        cmdBtn(intLoop).Caption = udtErrDialog.Buttons(intLoop)
        cmdBtn(intLoop).Visible = True
        cmdBtn(intLoop).Top = Me.ScaleHeight - 420
        cmdBtn(intLoop).Left = 120 + (1300 * intLoop)
    Next
    `
    ` check for help file
    If udtErrDialog.HelpFile <> "" Then
        cmdBtn(3).Visible = True
        cmdBtn(3).Top = Me.ScaleHeight - 420
        cmdBtn(3).Left = 120 + (1300 * 3)
    End If
    `
End Sub 


The code in Listing 14.20 first sets the dialog caption and message box. Then, based on the properties of the udtErrDialog type, the buttons are arranged on the form. The only other code that needs to be added to this form is the code that goes behind the command button array. Place the code from Listing 14.21 in the cmdBtn_Click event of the form.

Listing 14.21. Coding the cmdBtn_Click event.

Private Sub cmdBtn_Click(Index As Integer)
    `
    ` return user selection
    `
    Dim lngReturn As Long
    `
    Select Case Index
        Case 0
            udtErrDialog.Return = errExit
            lngReturn = WinHelp(Me.hwnd, udtErrDialog.HelpFile, HELP_QUIT, &H0)
            Unload Me
        Case 1
            udtErrDialog.Return = errresume
            lngReturn = WinHelp(Me.hwnd, udtErrDialog.HelpFile, HELP_QUIT, &H0)
            Unload Me
        Case 2
            udtErrDialog.Return = errNext
            lngReturn = WinHelp(Me.hwnd, udtErrDialog.HelpFile, HELP_QUIT, &H0)
            Unload Me
        Case 3
            lngReturn = WinHelp(Me.hwnd, udtErrDialog.HelpFile, HELP_CONTEXT, udtErrDialog.HelpID)
    End Select
    `
End Sub 


The code in Listing 14.21 allows the user to select the command button appropriate for the moment. If the user presses the Help button, the properties from the udtErrDialog type are used to fill in the parameters of the WinHelp API call.

That's all the code you need to create your errHandler ActiveX DLL. Save the project and then compile it (File | Make prjErrHandler.dll). If it compiles without error, you're all set for a quick test!

Testing the ErrHandler Object Library

Start a new Visual Basic 5 Standard EXE project. Set the form name to frmTest and the project name to prjTest. Add a data control and a command button to the form. Refer to Figure 14.17 while laying out the form.

Figure 14.17. Laying out the frmTest form.

Next you need to add a reference to the error handler object library to your project. Select Project | References and locate and add the prjErrHandler DLL (see Figure 14.18).

Figure 14.18. Adding the error handler object library to the project.

Now you can add a bit of code to the form to set up the error handler, and then cause an error to be handled. First, add the following line to the general declaration section of the form. This declares the object that contains the error handler.

Option Explicit
`
Public objErr As Object

Next, add the code from Listing 14.22 to the Form_Load event of the project.

Listing 14.22. Coding the Form_Load event.

Private Sub Form_Load()
    `
    Data1.DatabaseName = "junk"
    Set objErr = New errHandler
    `
End Sub 


This code creates the new error handler object and then sets up the data control with a bogus database name. Now add the code from Listing 14.23 to the Data1_Error event. This code intercepts the database error and displays the new custom dialog box.

Listing 14.23. Trapping the data control error.

Private Sub Data1_Error(DataErr As Integer, Response As Integer)
    `
   Dim rtn As Long

    Response = 0
    rtn = objErr.errHandler(Errors, errcoll)
    `
End Sub 


Now save the form (FRMTEST.FRM) and the project (PRJTEST.VBP) and run the code. You should see your new object library error dialog telling you about the database error (see Figure 14.19).

Now add some code behind the Command1_click event to create a divide-by-zero error. The code in Listing 14.24 does just that.

Figure 14.19. The new error object library in action.


Listing 14.24. Creating a divide-by-zero error in code.

Private Sub Command1_Click()
    `
    On Error GoTo Localerr
    Dim rtn as Long
    `
   Print 6 / 0
    `
    Exit Sub
    `
Localerr:
   rtn = objErr.errHandler(Err, erritem, errresume, "prjTest. ÂForm1.Command1_Click")
   Resume Next
    `
End Sub 


When you save and run this code, press the command button to see the new error report. You should see that the Help button is active. Press the Help button to display Visual Basic 5 help for dealing with the divide-by-zero error (see Figure 14.20).


NOTE: Check out Appendix B for information on how you can create your own help files for your Visual Basic 5 projects.


Figure 14.20.Viewing help on the divide-by-zero error.

Now let's add an option that creates an error report file whenever the error handler is activated.

Adding Error Logs to the Error Handler

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.

To build error-logging features into the existing errhandler class, you need to declare two new properties (LogFileName and WriteLogFlag), create a LogError method to write the errors to a disk file, add some code to the general declarations area, the Class_Initialize and Class_Terminate events, and add a few lines to the errHandler method to call the LogError method.

First, restart the errhandler DLL project and add the code below to the general declaration section of the class module.

`
` logging storage
Private errLogFileName As String
Private errLogFlag As Boolean

These two lines of code appear at the end of the section. They declare local storage space for the new properties. Now use the Tools | Add Procedure menu option to add two new Public properties to the class: LogFileName and WriteLogFlag. Listing 14.25 shows the code you need to add to the Property Let and Property Get statements for these two new properties.

Listing 14.25. Coding the Property Let and Property Get statements for the LogFileName and WriteLogFlag properties.

Public Property Get LogFileName() As String
    `
    LogFileName = errLogFileName
    `
End Property

Public Property Let LogFileName(ByVal vNewValue As String)
    `
    errLogFileName = vNewValue
    `
End Property

Public Property Get WriteLogFlag() As Boolean
    `
    WriteLogFlag = errLogFlag
    `
End Property

Public Property Let WriteLogFlag(ByVal vNewValue As Boolean)
    `
    errLogFlag = vNewValue
    `

End Property The LogFileName property is used to hold the name of the disk file that holds the log records. The LogFlag property controls the status of the error logging. If the LogFlag property is set to True, log records are created. Now add the following code to the end of the Class_Initialize event. This sets the default values for the two new properties.

    errLogFileName = App.EXEName & ".err"
    errLogFlag = False
    `

Now create a new Private Sub method called LogError and enter the code from Listing 14.26 into the routine. This is the code that actually creates the log entries.

Listing 14.26. Coding the LogError method.

Private Sub LogError(strErrMsg As String)
    `
    ` write error to disk file
    On Error GoTo LocalErr
    `
    Dim intChFile As Integer
    `
    intChFile = FreeFile
    Open errLogFileName For Append As intChFile
        Print #intChFile, Format(Now, "general date")
        Print #intChFile, strErrMsg
        Print #intChFile, ""
    Close intChFile
    `
    Exit Sub
    `
LocalErr:
    ` trouble with file stuff!
    Err.Raise vbObjectError + 1, "errHandler.LogError", "Can't write log file [" & errLogFileName & "]"
    `
End Sub 


Notice 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! Notice also that the internal error is not displayed in a message box. Instead the Raise method of the Err object is used to generate a unique error number and description. This is sent back to the calling application for handling.


TIP: The Visual Basic FreeFile() function is used to return a number that represents the first available file channel that 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.

Now all you need to do is add a call to the LogError method from within the Public errHandler method. Listing 14.27 shows the code you need to add in the routine. Make sure you add these lines of code right after the call to the errMsg function and just before the start of the Select Case statement.

Listing 14.27. Updating the errHandler method.

` build full message
    strMsg = errMsg(objErrColl, intType)
    `
    ` write it out, if allowed           <<< new code
    If errLogFlag = True Then     <<< new code
        LogError strMsg                  <<< new code
    End If                                       <<< new code
    `
    ` evaluate things
Select Case errOption


That's the end of the code to add logging to the error handler. Save the project and compile the ActiveX DLL. Once the DLL has been successfully compiled, close this project and open the test project you built earlier.

Open the Form_Load event of the frmTest form and add two lines to set the LogFileName and WriteLogFlag properties of the errHandler object. Listing 14.28 shows how to modify the code.

Listing 14.28. Modifying the Form_Load event to include error logging.

Private Sub Form_Load()
    `
    Data1.DatabaseName = "junk"
    Set objErr = New errHandler
    `
    objErr.WriteLogFlag = True
    objErr.LogFileName = App.Path & "\" & App.EXEName & ".log"
    `
End Sub 


Now, when you run the project, each error is logged to a file with the same name as the application in the same folder as the application. In the preceding example, a file called errTest.log was created in the C:\TYSDBVB5\SOURCE\CHAP14\ folder. Listing 14.29 shows the contents of this error log file.

Listing 14.29. Contents of the errTest.log file.

05-Feb-97 5:27:01 AM
<3024> Couldn't find file `junk'. (in DAO.Workspace).

05-Feb-97 5:27:08 AM
<11> Division by zero (in prjTest) 


You can easily modify the layout and the contents of the log reports. You need only change a few lines of code in the LogError method.

Adding a Module Trace to the Error Handler

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 it 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.

We implement our module trace routines as a new objclass object in the prjErrHandler project. Reload the ActiveX DLL project and add a new class module to the project. Set its Name property to TraceObject and keep its Instancing property set to the default, 5 - MultiUse.

You need two new properties for this object (TraceFileName and TraceFlag) and a handful of new Public methods:

First, add the code in Listing 14.30 to the general declarations area of the class module.

Listing 14.30. Declaring the TraceObject variables.

Option Explicit
`
` local property storage
Private trcFileName As String
Private trcFlag As Boolean
`
` internal variables
Private trcStack() As String
Private trcPointer As Long 


Next, create the two new Public properties, TraceFile and TraceFlag, and enter the code from Listing 14.31 into the Property Let and Property Get statements for these two new properties.

Listing 14.31. Coding the Property Let and Property Get statements for the TraceFile and TraceLog properties.

Public Property Get TraceFileName() As String
    `
    TraceFileName = trcFileName
    `
End Property

Public Property Let TraceFileName(ByVal vNewValue As String)
    `
    trcFileName = vNewValue
    `
End Property

Public Property Get TraceFlag() As Boolean
    `
    TraceFlag = trcFlag
    `
End Property

Public Property Let TraceFlag(ByVal vNewValue As Boolean)
    `
    trcFlag = vNewValue
    `
End Property 


Now add the code from Listing 14.32 to the Class_Initialize event. This code sets the default values for the two Public properties.

Listing 14.32. Coding the Class_Initialize event.

Private Sub Class_Initialize()
    `
    ` startup stuff
    trcFileName = App.EXEName & ".trc"
    trcFlag = False
    `
End Sub


Now it's time to code the various methods you need to manage call tracing in Visual Basic 5. First, create the Public Sub methods Push and Pop. These two routines handle the details of keeping track of each function or sub as it is executed. Listing 14.33 shows the code for these two Public methods.

Listing 14.33. Coding the Push and Pop methods of the TraceObject.

Public Sub Push(ProcName As String)
    `
    ` push a proc onto the stack
    trcPointer = trcPointer + 1
    ReDim Preserve trcStack(trcPointer)
    trcStack(trcPointer) = ProcName
    `
End Sub

Public Sub Pop()
    `
    ` pop a proc off the stack
    If trcPointer <> 0 Then
        trcPointer = trcPointer - 1
        ReDim Preserve trcStack(trcPointer)
    End If
    `
End Sub 


Now create another Public Sub method (Clear) and a Public Function method (List). Add the code from Listing 14.34 to the class.

Listing 14.34. Coding the List and Clear methods of the TraceObject.

Public Function List() As Variant
    `
    ` return an array of the trace log
    List = trcStack
    `
End Function

Public Sub Clear()
    `
    ` clear off the stack
    trcPointer = 0
    ReDim Preserve trcStack(0)
    `
End Sub



TIP: Notice the use of the Variant data type to return an array of items. This is a very efficient way to pass array data among Visual Basic methods.

Now create a new Public Sub called Dump. This writes the trace list to a disk file. Fill in the method with the code from Listing 14.35.

Listing 14.35. Coding the Dump method of the TraceObject.

Public Sub Dump()
    `
    ` write trace log to file
    Dim intFile As Integer
    Dim intLoop As Integer
    `
    intFile = FreeFile
    Open trcFileName For Append As intFile
        Print #intFile, "***TRACE STACK DUMP***"
        Print #intFile, "***DATE: " & Format(Now(), "general date")
        Print #intFile, ""
        `
        For intLoop = trcPointer To 1 Step -1
            Print #intFile, vbTab & Format(intLoop, "000") & ": " & ÂtrcStack(intLoop)
        Next
        `
        Print #intFile, ""
        Print #intFile, "***EOF"
    Close #intFile
    `
    Exit Sub
    `
LocalErr:
    Err.Raise vbObjectError + 3, "Trace.Dump", "Can't write trace file [" & ÂtrcFileName & "]"
    `
End Sub 


Finally, create the Public Sub method called Show and enter the code from Listing 14.36.

Listing 14.36. Coding the Show method of the TraceObject.

Public Sub Show()
    `
    ` show trace log in dialog
    `
    Dim intLoop As Integer
    Dim strMsg As String
    `
    strMsg = ""
    For intLoop = trcPointer To 1 Step -1
        strMsg = strMsg & Format(intLoop, "000")
        strMsg = strMsg & ": "
        strMsg = strMsg & Trim(trcStack(intLoop))
        strMsg = strMsg & vbCrLf
    Next
    `
    MsgBox strMsg, vbInformation, "Trace Stack"
    `
End Sub


Notice that the code in Listing 14.36 prints the call array in reverse order. This is the conventional way to print trace lists. The top-most entry shows the most recently executed routine, and the bottom-most entry shows the first routine in this trace.

After adding this last code, save and compile the ActiveX DLL and then load your errTest project. After you load the frmTest form, add the following code to the general declarations area of the form.

Public objTrace As Object

Next, update the Form_Load event as shown in Listing 14.37. This adds the use of the trace module to the project.

Listing 14.37. Updating the Form_Load event to include module tracing.

Private Sub Form_Load()
    `
    Data1.DatabaseName = "junk"
    `
    Set objErr = New errHandler
    Set objTrace = New TraceObject
    `
    objTrace.Push "Form_Load"
    `
    objErr.WriteLogFlag = True
    objErr.LogFileName = App.Path & "\" & App.EXEName & ".log"
    `
    objTrace.Pop
    `
End Sub 


Note the use of objTrace.Push to add the name of the method onto the trace stack. This should happen as early as possible in the method code. Note, also the objTrace.Pop line at the very end of the method. This removes the name of the method from the stack just as the method is complete.

Let's also add trace coding to the Command1_click event. Update your form's command1_Click event to match the one in Listing 14.38.

Listing 14.38. Updating the Command1_click event to use module tracing.

Private Sub Command1_Click()
    `
    On Error GoTo Localerr
    Dim varList As Variant
    Dim rtn As Long
    `
    objTrace.Push "Command1_Click"
    `
    Print 6 / 0
    `
    Exit Sub
    `
Localerr:
    `
    rtn = objErr.errHandler(Err, erritem, errresume, "prjTest.Form1.Command1_Click")
    `
    objTrace.Show
    objTrace.Pop
    Resume Next
    `
End Sub 


Save this code and run the project. When you press the command button, you get a trace report on the screen (see Figure 14.21).

Figure 14.21. Viewing the trace message.

Notice that, in order to add module tracing to a project, you only need to add a .Push line at the start of the routine and a .Pop 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.

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 TraceFile option to write the stack to disk rather than displaying it to the user.

Other Error Handler Options

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:

Summary

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 how to use the Err.Raise method to flag errors without resorting to modal dialog boxes.

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 to create an error handler object library that combines local error trapping with global error messages and responses. The error handler object 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.

Quiz

1. What are the three main parts of error handlers in Visual Basic?

2. What are the four ways to exit an error handler routine?

3. When would you use Resume to exit an error handler?

4. When would you use Resume Next to exit an error handler?

5. When would you use Resume label to exit an error handler?

6. When would you use the EXIT or END command to exit an error handler?

7. List the four types of Visual Basic errors.

8. Should you use error trapping for the Visual Basic data control?

9. In what Visual Basic event should you open data tables or files in which the user enters data?

10. What are the advantages and disadvantages of global error handlers?

11. What is the Err.Raise method and why is it useful?

Exercises

1. Create a new project and add code to a command button that opens the file C:\ABC.TXT. Include an error handler that notifies the user that the file cannot be opened, and then terminates the program.

2.
Modify the project started in Exercise 1 by adding a new command button. Attach code to this button that attempts to load a file named C:\ABC.TXT. Notify the user that this file cannot be opened, and give the user the option and the dialog box to search for the file. Exit the program when a selection has been made or if the user chooses not to proceed.
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.