Errors happen, even in the absence of programmer error. You need to protect your programs and your data from the adverse effects of errors by practicing error handling.
Error handling (also known as error trapping) is the process of intercepting Jet’s or VBA’s response to an error. It enables the developer to determine the severity of an error and to take the appropriate action in response to the error. This chapter shows you the techniques required to successfully implement error handling within your applications.
Without error handling, the user of your application is forced to exit abruptly from your application code. Consider the example in Listing 16.1.
Example 16.1. An Example of Code Without Error Handling
Private Sub cmdNoErrorHandler_Click() 'Call TestError1, passing the values in the txtValue1 'and txtValue2 text boxes Call TestError1(Me.txtValue1.Value, Me.txtValue2.Value) End Sub Sub TestError1(Numerator As Integer, Denominator As Integer) 'Divide the value received as the first parameter 'by the value received as the second parameter Debug.Print Numerator / Denominator 'If successful, display a message to the user MsgBox "I am in Test Error" End Sub
The click event behind the command button calls the routine TestError1
, passing it the values from two text boxes. TestError1
accepts those parameters and attempts to divide the first parameter by the second parameter. If the second parameter is equal to 0
, a runtime error occurs. Because no error handling is in effect, the program terminates.
Figure 16.1 shows the error message the user receives. As you can see, the choices are Continue, End, Debug, and Help. If users click Debug, the module window appears, and they are placed in Debug mode on the line of code causing the error. Clicking Continue (this is not always available) tells Access to ignore the error and continue with the execution of the program. End terminates execution of the programming code. If the application is running with the runtime version of Access, it shuts down, and users are returned to Windows.
With error handling in effect, you can attempt to handle the error in a more appropriate way whenever possible.
You can add error-handling code to the error event procedure of a form or report. You also can add it to any VBA subroutine, function, or event routine. You easily can modify the code in Listing 16.1 to handle the error gracefully. The code in Listing 16.2 shows a simple error-handling routine.
Example 16.2. A Simple Error-Handling Routine
Sub TestError2(Numerator As Integer, Denominator As Integer) On Error GoTo TestError2_Err 'Divide the value received as the first parameter 'by the value received as the second parameter Debug.Print Numerator / Denominator 'If successful, display a message to the user MsgBox "I am in Test Error" Exit Sub TestError2_Err: 'If a divide by zero (error 11) occurs, display an 'appropriate message to the user If Err = 11 Then MsgBox "Variable 2 Cannot Be a Zero", , "Custom Error Handler" End If Exit Sub End Sub
This code is located in the basError module, which is in the CHAP16EX.MDB database on the accompanying CD-ROM.
The routine now invokes error handling. If a divide-by-zero error occurs, a message box alerts the user to the problem, as Figure 16.2 shows.
On Error
statements activate error handling. Each routine must contain its own On Error
statement if you want that routine to do its own error handling. Otherwise, error handling is cascaded up the call stack. If no On Error
statements are found in the call stack, VBA’s own error handling is invoked.
Suppose that Func1
calls Func2
, and Func2
calls Func3
. Only Func1
contains error handling. An error occurs in Func3
. Func3
passes control up to Func2
. Func2
has no error handling, so it passes control up to Func1
. Func1
handles the error. Needless to say, the error handler found in Func1
is not necessarily appropriate to handle the error that occurred in Func3
.
Using an On Error
statement, you can cause the application to branch to error-handling code, resume code execution on the line immediately following the error, or attempt to re-execute the problem line of code.
You must decide the most appropriate response to a particular error. Sometimes it is most appropriate for your application to halt in response to an error. At other times, it is best if the routine skips the offending line entirely. By combining the use of On Error Goto
, On Error Resume Next
, and the Resume
statement, you can handle each error appropriately.
The statement On Error Goto <
label
>
tells VBA that, from this point forward in the subroutine or function, if an error occurs, it should jump to the label specified in the statement. This is the most common form of error handling.
The label specified in the On Error
statement must be located in the current procedure. Listing 16.3 shows a simple example of error handling.
Example 16.3. An Example of Error Handling Using the On Error GoTo
Statement
Sub SimpleErrorHandler(iVar1 As Integer, iVar2 As Integer) 'Invoke error handling On Error GoTo SimpleErrorHandler_Err 'Declare a variable to hold the result Dim sngResult As Single 'Divide the first parameter received by the 'second parameter received sngResult = iVar1 / iVar2 'Exit the subroutine if all went as planned Exit Sub SimpleErrorHandler_Err: 'If an error occurs, display a message and exit 'the subroutine MsgBox "Oops!" Exit Sub End Sub
You can learn some important things from this simple routine. The routine receives two integer values. It then invokes the error handler. When an error occurs, execution continues at the label. Notice that this routine contains two Exit Sub
statements. If you remove the first Exit Sub
statement, the code falls through to the label regardless of whether an error occurred. The Exit Sub
statement at the bottom gracefully exits the procedure, setting the error code back to 0
.
The error-handling code in Listing 16.3 did not give a very descriptive message to users. The Description and Number properties of the Err
object give users more meaningful error messages. The Err
object is covered in detail later in this chapter in the section “Using the Err
Object.” For now, take a look at the Description and Number properties to see how you can use them to enhance an error-handling routine. To display the error number and description, you must modify the error-handling code to look like this:
SimpleErrorHandler_Err: 'If an error occurs, display a message and exit 'the subroutine MsgBox "Error #" & Err.Number & ": " & Err.Description Exit Sub
This time, instead of hard-coding the error message, you display the error number and VBA’s internal error string. Figure 16.3 shows the resulting error message. The SimpleErrorHandler
routine and all the following examples are located in the basError module of the CHAP16EX.MDB database.
You use On Error Goto 0
for two purposes:
When you want Access to return to its default error handler
When you have other error handling in a routine, but want Access to return to the calling routine when a specific condition occurs
Generally, you don’t want Access to return to its default error handler. You might do this only if you are unable to handle the error or if you are in the testing phase and not yet ready to implement your own error handler.
The reason why you want Access to return the error to a higher-level routine is much clearer. You do this if you want to centralize the error handling, meaning that one routine might call several others. Instead of placing error-handling code in each routine that is called, it is appropriate in certain situations to place the error handling in the calling routine.
On Error Resume Next
continues program execution on the line immediately following the error. This construct generally is used when it is acceptable to ignore an error and continue code execution. Listing 16.4 shows an example of such a situation.
Example 16.4. Ignoring an Error and Continuing Execution
Sub TestResumeNext() 'Instruct VBA to continue on the next line if an error 'occurs On Error Resume Next 'Attempt to delete a file Kill "AnyFile" 'If no error occurred, do nothing. Otherwise, display 'a message with the description of the error that occurred If Err.Number = 0 Then Else MsgBox "We Didn't Die, But the Error Was: " & Err.Description End If End Sub
You use the Kill
statement to delete a file from disk. If the specified file is not found, an error results. You delete the file only if it exists, so you are not concerned about an error. On Error Resume Next
is very appropriate in this situation because resuming execution after the offending line of code does no harm. The example illustrates that, although code execution proceeds, the properties of the error object are still set.
While you are in your error-handling code, you can use the Resume
, Resume Next
, and Resume <
LineLabel
>
statements to specify how you want VBA to respond to the error. Resume
attempts to re-execute the offending line of code, Resume Next
resumes execution after the offending line of code, and Resume <
LineLabel
>
continues execution at a specified line label. The following sections cover these statements in detail.
The Resume
statement resumes code execution on the line of code that caused the error. You must use this statement with extreme care because it can throw the code into an unrecoverable endless loop. Listing 16.5 shows an example of an inappropriate use of the Resume
statement.
Example 16.5. Using Resume
Inappropriately
Function BadResume(strFileName As String) 'Invoke error handling On Error GoTo BadResume_Err Dim strFile As String 'Perform the Dir function to determine if 'the file passed as a parameter exists strFile = Dir(strFileName) 'If the file doesn't exist, return False 'Otherwise, return True If strFile = "" Then BadResume = False Else BadResume = True End If 'Exit the function if all goes well Exit Function BadResume_Err: 'Display an error message with the 'description of the error that occurred MsgBox Error.Description 'Attempt to re-execute the offending line of code Resume End Function
This function is passed a filename. The Dir
function searches for a file with that name and returns True
or False
, depending on whether the specified file is found. The problem occurs when the drive requested is not available or does not exist. This code throws the computer into an endless loop. To remedy the problem, you should modify your code to look like the code in Listing 16.6.
Example 16.6. Using Resume
Conditionally Based on User Feedback
Function GoodResume(strFileName As String) 'Invoke error handling On Error GoTo GoodResume_Err Dim strFile As String 'Perform the Dir function to determine if 'the file passed as a parameter exists strFile = Dir(strFileName) 'If the file doesn't exist, return False 'Otherwise, return True If strFile = "" Then GoodResume = False Else GoodResume = True End If 'Exit the function if all goes well Exit Function GoodResume_Err: Dim intAnswer As Integer 'Ask user if they want to try again intAnswer = MsgBox(Error & ", Would You Like to Try Again?", vbYesNo) 'If they respond yes, attempt to re-execute the offending line 'of code. Otherwise, exit the function If intAnswer = vbYes Then Resume Else Exit Function End If End Function
In this example, the error handler enables the user to decide whether to try again. The Resume
occurs only if the user’s response is affirmative.
Just as you can invoke error handling using an On Error Resume Next
statement, you can place a Resume Next
statement in your error handler, as Listing 16.7 shows.
Example 16.7. Placing a Resume Next
Statement in Your Error Handler
Sub TestResumeNextInError() 'Invoke error handling On Error GoTo TestResumeNextInError_Err 'Attempt to delete a file Kill "AnyFile" 'If no error occurred, do nothing. Otherwise, display 'a message with the description of the error that occurred If Err.Number = 0 Then Else MsgBox "We Didn't Die, But the Error Was: " & Err.Description End If Exit Sub TestResumeNextInError_Err: 'Reset error information and resume execution on the 'line of code following the line on which the error cocurred Resume Next End Sub
In this example, the code is instructed to go to the label called TestResumeNextInError_Err
when an error occurs. The TestResumeNextInError_Err
label issues a Resume Next
statement. This clears the error and causes execution to continue on the line after the line on which the error occurred.
Note the difference between On Error Resume Next
and Resume Next
. On Error Resume Next
is placed in the body of the routine. It causes code execution to continue on the line of code following the line that caused the error. It does not reset any error information.
Resume Next
is placed within the error handler. It also causes code execution to continue on the line of code following the line that caused the error. It does reset the error information.
The Resume <
LineLabel
>
statement enables you to specify a line of code where you want code execution to continue after an error occurs. This is a great way to eliminate the two Exit Sub
or Exit Function
statements required by the error-handling routines you have looked at so far. Listing 16.8 shows an example.
Example 16.8. Using the Resume <LineLabel>
Statement to Specify Where Execution Continues After an Error Occurs
Sub TestResumeLineLabel(intVar1 As Integer, intVar2 As Integer) 'Invoke error handling On Error GoTo TestResumeLineLabel_Err Dim sngResult As Single 'Divide the value received as the first parameter 'by the value received as the second parameter sngResult = intVar1 / intVar2 TestResumeLineLabel_Exit: 'Exit subroutine Exit Sub TestResumeLineLabel_Err: 'If an error occurs, display message with the error 'number and description MsgBox "Error #" & Err.Number & ": " & Err.Description 'Resume execution at the TestResumeLineLable_Exit label Resume TestResumeLineLabel_Exit End Sub
Notice that this routine contains only one Exit Sub
statement. If no error occurs, Access drops through the TestResumeLineLabel_Exit
label to the Exit Sub
statement. If an error does occur, the code in the TestResumeLineLabel_Err
label executes. Notice that the last line of the error label resumes execution at the TestResumeLineLabel_Exit
label.
This method of resolving an error is useful because any code required to execute as the routine is exited can be written in one place. Object variables might need to be set equal to Nothing
as the routine is exited, for example. You can place these lines of code in the exit routine.
When an error occurs, the Err
object remains set with the error information until one of the following clears the error:
Resume
, Resume Next
, or Resume <
LineLabel
>
Exit Sub
, Exit Function
, or Exit Property
Any Goto
statement
Explicitly using the Clear
method on the Err
object
Until the error is cleared somehow, all the information remains set in the Err
object. After the error is cleared, no information is found in the Err
object.
As mentioned earlier in the section “Using On Error
Statements,” if Access does not find any error handling in a particular subroutine or function, it looks up the call stack for a previous error handler. Listing 16.9 shows an example of this process.
Example 16.9. Looking Up the Call Stack for a Previous Error Handler
Sub Func1() 'Invoke error handling On Error GoTo Func1_Err 'Print to the immediate window Debug.Print "I am in Function 1" 'Execute the Func2 routine Call Func2 'Print to the immediate window Debug.Print "I am back in Function 1" 'Exit the subroutine Exit Sub Func1_Err: 'Display a message to the user, 'indicating that an error occurred MsgBox "Error in Func1" 'Resume execution Resume Next End Sub Sub Func2() 'No error handling in this routine! 'Print to the immediate window Debug.Print "I am in Func2" 'Execute Func3 Call Func3 'Print to the immediate window Debug.Print "I am still in Func2" End Sub Sub Func3() 'No error in this routine either! Dim sngAnswer As Single 'Print to the immediate window Debug.Print "I am in Func3" 'Opps, an error occurred sngAnswer = 5 / 0 'This line of code will never execute Debug.Print "I am still in Func3" End Sub
In this situation, the error occurs in Func3
. Because Func3
does not have its own error handling, it refers back to Func2
. Func2
does not have any error handling either, so Func2
relinquishes control to Func1
. VBA executes the error code in Func1
. The real problem occurs because of the Resume Next
statement. The application continues executing within Func1
on the Debug.Print "I am back in Function 1"
statement. This type of error handling is dangerous and confusing. Therefore, it is best to develop a generic error-handling routine that is accessed throughout your application. The creation of a generic error handler is discussed in the section “Creating a Generic Error Handler.”
The Err
object contains information about the most recent error that occurred. As with all Access objects, it has its own built-in properties and methods. Table 16.1 lists the properties of the Err
object.
Table 16.1. Properties of the Err
Object
Property | Description |
---|---|
Description | Description of the error that occurred |
HelpContext | Context ID for the Help file |
HelpFile | Path and filename of the Help file |
LastDLLError | Last error that occurred in a 32-bit DLL |
Number | Number of the error that was set |
Source | System in which the error occurred (which is extremely useful when you are using OLE automation to control another application, such as Excel) |
The Err
object has only two methods: Clear
and Raise
. The Clear
method enables you to clear an error condition explicitly. This is used primarily when you write code that uses the On Error Resume Next
statement. This statement does not clear the error condition. Remember that there is no reason to issue the Clear
method explicitly with any type of Resume
, Exit Sub
, Exit Function
, Exit Property
, or On Error Goto
statement. The Clear
method is implicitly issued when these constructs are used. The Raise
method of the Err
object is covered in the next section.
You use the Raise
method of the error object in these situations:
When you want to generate an error on purpose (for example, in testing)
When you want to generate a user-defined error
When no code in the error routine handles the current error, and you want to allow other parts of the call stack to attempt to handle the error
When you want to nest an error handler
Using the Raise
method to generate an error on purpose and creating a user-defined error are covered in the following sections.
Many times during testing, you want to generate an error so that you can check your own error handling. Instead of figuring out how to cause the error condition, you can use the Raise
method of the Err
object to accomplish this task, as Listing 16.10 shows.
Example 16.10. Raising an Error
Sub TestRaiseError() 'Invoke error handling On Error GoTo TestRaiseError_Err Dim sngResult As String 'Raise a divide-by-zero error Err.Raise 11 'Exit the subroutine Exit Sub TestRaiseError_Err: 'Display a message with the error number and description MsgBox "Error #" & Err.Number & ": " & Err.Description 'Exit the subroutine Exit Sub End Sub
Another important use of the Raise
method of the Err
object is the generation of a custom error condition. This is useful when you want to force an error in response to something that the user did. For example, assume that the user must enter five characters into an unbound text box. Entering only two characters would not generate an Access error. Rather than handling this user-generated error in some other manner, you can raise the error and have your standard error handler respond to the error condition. Because the Raise
method enables you to set all the properties of the Err
object, you can create a user-defined error complete with a number, description, source, and so on, as shown in Listing 16.11.
Example 16.11. Creating a User-Defined Error
Sub TestCustomError() 'Invoke error handling On Error GoTo TestCustomError_Err Dim strName As String 'Prompt the user to enter their name strName = InputBox("Please Enter Your Name") 'If the length of the name is less than five 'characters, raise an error number 11111 If Len(strName) < 5 Then Err.Raise Number:=11111, _ Description:="Length of Name is Too Short" Else MsgBox "You Entered " & strName End If Exit Sub TestCustomError_Err: 'Display a message with the error number 'and description MsgBox "Error # " & Err.Number & _ " - " & Err.Description Exit Sub End Sub
Although it is very simple, Listing 16.11 illustrates an important use of generating user-defined errors. The code tests to see whether the value entered has less than five characters. If it does, a user-defined error message (number 11111
) is generated. The routine drops into the normal error-handling routine. The section “Creating a Generic Error Handler,” later in this chapter, explores how to put together a generic error handler. By passing user-defined errors through your generic error handler, all errors—user-defined or not—are handled in the same way.
The Errors collection is part of Access’s Jet Engine. It stores the most recent set of DAO errors that have occurred. This is important when dealing with DAO (Data Access Objects) and ODBC (Open Database Connectivity), in which one operation can result in multiple errors. If you are concerned with each error that was generated by one operation, you need to look at the Errors collection. Each error object in the Errors collection contains information about an error that occurred. If you want to view the errors stored in the Errors collection, you must loop through it, viewing the properties of each Err
object. Listing 16.12 shows the code you can use to accomplish this.
Example 16.12. Viewing the Errors Stored in the Errors Collection
Sub TestErrorsCollection() 'Invoke error handling On Error GoTo TestErrorsCollection_Err 'Declare a DAO database object Dim db As DAO.Database 'Point the database object at the database 'referenced by the CurrentDB object Set db = CurrentDb 'Attempt to execute a query that doesn't exist db.Execute ("qryNonExistent") Exit Sub TestErrorsCollection_Err: Dim ErrorDescrip As DAO.Error 'Loop through the errors collection, 'sending the error number and description to 'the Immediate window For Each ErrorDescrip In Errors Debug.Print ErrorDescrip.Number Debug.Print ErrorDescrip.Description Next ErrorDescrip Exit Sub End Sub
This routine loops through each Error
object in the Errors collection, printing the description of each error contained in the collection.
A generic error handler can be called from every procedure in your application to respond to any type of error.
A generic error handler prevents you from having to write specific error handling in each of your subroutines and functions. This enables you to invoke error handling throughout your application in the most efficient manner possible.
You can take many approaches to create a generic error handler. It should give users information about the error, enable users to print this information, and log the information to a file.
The On Error
routine (in this case, the label AnySub_Err
) of every procedure that performs error handling should look like the error-handling routine contained in the subroutine in Listing 16.13.
Example 16.13. A Generic Error Handler for All Your Functions and Subroutines
Sub AnySub() 'Declare constant with the name of the routine Const SUBNAME As String = "AnySub" 'Invoke error handling On Error GoTo AnySub_Err 'Beginning of any routine MsgBox "This is the rest of your code...." 'Oops! Something causes an error! Err.Raise 11 'Code after the error MsgBox "We are Past the Error!!" AnySub_Exit: 'Generic exit point for routine Exit Sub AnySub_Err: Dim intAction As Integer 'Call generic error handler, passing it the error 'number and description, as well as the module name 'and subroutine name intAction = ErrorHandler(lngErrorNum:=Err.Number, _ strErrorDescription:=Err.Description, _ strModuleName:=MODULENAME, _ strRoutineName:=SUBNAME) 'Evaluate return value to determine what action to take Select Case intAction Case ERR_CONTINUE Resume Next Case ERR_RETRY Resume Case ERR_EXIT Resume AnySub_Exit Case ERR_QUIT Quit End Select End Sub
This error-handling routine in AnySub
creates an Integer
variable that holds the return value from the error system. The intAction
variable holds an appropriate response to the error that occurred. The error routine calls the generic error-handling function ErrorHandler
, passing it the error number (Err.Number
), a description of the error (Err.Description
), the name of the module containing the error, and the name of the subroutine or function containing the error. The name of the module is stored in a Private
constant called MODULENAME
. The Private
constant is declared in the General section of the module and needs to be created for every module you make. The name of the subroutine or function is stored in a local constant called SUBNAME
. With this approach, you create a local constant and assign it the name of the sub at the beginning of each procedure. This requires upkeep because procedure names can change, and you need to remember to change your string. Unfortunately, because the VBA environment does not expose the subroutine and module names to you when an error occurs, this sort of brute force is necessary if you want your error handler to track the subroutine and module. When the code returns from the ErrorHandler
function, a return value is placed in the intAction
variable. This return value is used to determine the fate of the routine.
The process of adding error handling to every routine that you create is a tedious one. Fortunately, a tool is available that makes the process much more palatable. The tool is produced by FMS Software and is called FMS Total Access Code Tools. This tool is covered in detail in Chapter 31, “Third-Party Tools That Can Help You to Get Your Job Done Effectively.” Amongst its many features, it allows you to create a custom error template that you can use to quickly and easily add error handling to all the routines that you create. A scaled-down version of FMS Total Access Code Tools is built into the Microsoft Office Developer.
Now that you have seen how to implement error handling in your procedures, take a look at the function that is called when an error occurs, as shown in Listing 16.14.
Example 16.14. A Type Structure Declaration to Be Used for Generic Error Handling
'Type structure used to hold error information Type typErrors lngErrorNum As Long strMessage As String strModule As String strRoutine As String strUserName As String datDateTime As Variant End Type 'Declaration of public type structure variable Public gtypError As typErrors 'Constants used by global error handler Public Const ERR_CONTINUE = 0 'Resume Next Public Const ERR_RETRY = 1 'Resume Public Const ERR_QUIT = 2 'End Public Const ERR_EXIT = 3 'Exit Sub or Func
This code is placed in the General section of basHandleErrors
. The type structure declared holds all the pertinent information about the error. A type structure is a special kind of variable that is made up of various parts, each of which stores a different piece of information. (Type structures are covered in Chapter 12, “Advanced VBA Techniques.”)
In Listing 16.14, the public variable gtypError
holds all the information from the type structure. The constants are used to help determine the fate of the application after an error occurs. Listing 16.15 shows the ErrorHandler
function.
Example 16.15. Using the ErrorHandler
Function
Function ErrorHandler(lngErrorNum As Long, _ strErrorDescription As String, _ strModuleName As String, _ strRoutineName As String) As Integer 'Populate elements of the type structure variable 'with information about the error that occurred gtypError.lngErrorNum = lngErrorNum gtypError.strMessage = strErrorDescription gtypError.strModule = strModuleName gtypError.strRoutine = strRoutineName gtypError.strUserName = CurrentUser() gtypError.datDateTime = Now 'Log the error Call LogError 'Locate the error number in tblErrors to 'determine how you should respond to the error Dim rst As adodb.Recordset Set rst = New adodb.Recordset rst.Open "Select Response from tblErrors Where ErrorNum = " & _ lngErrorNum, CurrentProject.Connection, adOpenStatic 'If the error number that occurred is not found 'in tblErrors, display the error form and return 'ERR_QUIT to the problem routine If rst.EOF Then DoCmd.OpenForm "frmError", WindowMode:=acDialog, _ OpenArgs:="ErrorHandler" ErrorHandler = ERR_QUIT 'If the error is in tblErrors, evaluate the contents of 'the Response field. Response appropriately, displaying the appropriate 'form and returning the appropriate value to the offending routine Else Select Case rst!Response Case ERR_QUIT DoCmd.OpenForm "frmError", WindowMode:=acDialog, _ OpenArgs:="Critical Error: Application will Terminate" ErrorHandler = ERR_QUIT Case ERR_RETRY ErrorHandler = ERR_RETRY Case ERR_EXIT DoCmd.OpenForm "frmError", WindowMode:=acDialog, _ OpenArgs:="Severe Error: Processing Did Not Complete" ErrorHandler = ERR_EXIT Case ERR_CONTINUE ErrorHandler = ERR_CONTINUE End Select End If End Function
The ErrorHandler
function receives the error number, error description, module name, and subroutine or function name as parameters. It then fills in the gtypError
type structure with the information that it was passed, as well as the current user and date. Next, it calls a routine that logs the error into an Access table. The routine looks up the severity of the error code in an Access table called tblErrors to decide the most appropriate way to handle the error. If the error code is not found in the error table, an error form is displayed, and a return value is sent to the calling function, indicating that application execution is to be terminated. If the error code is found in the tblErrors table and determined to be critical or severe, an error form appears before control is returned to the calling routine. In any case, a severity code for the error is returned to the calling function. The following section discusses the details involved in each step of the process.
The LogError
routine is responsible for logging all the error information into an Access table. Because users often decide not to print the error form or provide you with inaccurate information about what was happening when the error occurred (or neglect to tell you about the error), it is important that you log each error so that you can review the error log at any time. Errors can be logged to a text file or a data table. This section shows you both methods of logging your errors. Start with logging your errors to a table, as shown in Listing 16.16 with the LogError
routine.
Example 16.16. Using the LogError
Routine
Sub LogError() 'Declare a Connection object Dim cnn As adodb.Connection Dim strSQL As String 'Point the Connection object at the connection 'associated with the current project Set cnn = CurrentProject.Connection 'Build a SQL statement that inserts error information 'into the tblErrorLog table strSQL = "INSERT INTO tblErrorLog ( ErrorDate, ErrorTime, UserName, ErrorNum, ErrorString, ModuleName, RoutineName) " strSQL = strSQL & "Select #" & gtypError.datDateTime & "#, #" _ & gtypError.datDateTime & "#, '" _ & gtypError.strUserName & "', " _ & gtypError.lngErrorNum & ", '" _ & gtypError.strMessage & "', '" _ & gtypError.strModule & "', '" _ & gtypError.strRoutine & "'" 'Execute the SQL statement cnn.Execute strSQL, , adExecuteNoRecords End Sub
This routine uses the Execute
method of the ADO Connection
object to add a record to your error table. The record contains all the information from the structure called gtypError
. The information is logged to a table called tblErrorLog. Figure 16.4 shows the structure of this table.
The alternative error-logging method is to write the information to a textual error log file, as shown in Listing 16.17.
Example 16.17. Writing Information to a Textual Error Log File
Sub LogErrorText() Dim intFile As Integer 'Store a free file handle into a variable intFile = FreeFile 'Open a file named ErrorLog.txt in the current directory 'using the file handle obtained above Open CurDir & "ErrorLog.Txt" For Append Shared As intFile 'Write the error information to the file Write #intFile, "LogErrorDemo", Now, Err, Error, CurrentUser() 'Close the file Close intFile End Sub
This code uses low-level file functions to Open
and Write
to an ASCII text file. All the pertinent information about the error is written to this text file. The routine then uses the Close
command to close the text file. The potential advantage of this routine is that, if the problem is with the database (for example, the network is down), the error logging process still succeeds.
After the error is logged, you are ready to determine the best way to respond to the error. By making your error system data driven, you can handle each error a little differently. Figure 16.5 shows the structure of the tblErrors table. This table should contain a list of all the error numbers that you want to trap. It contains three fields: ErrorNum, ErrorDescription, and Response. When an error occurs, the ErrorHandler
function searches for a record with a value in the ErrorNum field that matches the number of the error that occurred.
The ErrorHandler
function, as shown in Listing 16.15, uses the code in Listing 16.18 to locate the error code in the tblErrors table.
Example 16.18. Locating the Error Code in the tblErrors Table
'Locate the error number in tblErrors to 'determine how you should respond to the error Dim rst As adodb.Recordset Set rst = New adodb.Recordset rst.Open "Select Response from tblErrors Where ErrorNum = " & lngErrorNum, _ CurrentProject.Connection, adOpenStatic 'If the error number that occurred is not found 'in tblErrors, display the error form and return 'ERR_QUIT to the problem routine If rst.EOF Then DoCmd.OpenForm "frmError", WindowMode:=acDialog, _ OpenArgs:="ErrorHandler" ErrorHandler = ERR_QUIT 'If the error is in tblErrors, evaluate the contents of 'the Response field. Respond appropriately, displaying the appropriate 'form and returning the appropriate value to the offending routine Else Select Case rst!Response Case ERR_QUIT DoCmd.OpenForm "frmError", WindowMode:=acDialog, _ OpenArgs:="Critical Error: Application will Terminate" ErrorHandler = ERR_QUIT Case ERR_RETRY ErrorHandler = ERR_RETRY Case ERR_EXIT DoCmd.OpenForm "frmError", WindowMode:=acDialog, _ OpenArgs:="Severe Error: Processing Did Not Complete" ErrorHandler = ERR_EXIT Case ERR_CONTINUE ErrorHandler = ERR_CONTINUE End Select End If
The part of the ErrorHandler
function shown in Listing 16.18 creates an ADO Recordset
object variable. It opens a recordset using a Select
statement, which in turn searches a table called tblErrors. If a match is found, the Response column is used to determine the response to the error. Notice in Listing 16.18 that, if the error number is not found in tblErrors, default error handling occurs, which means that the code handles all other errors as a group. (This is my default error handling, not Access’s.) If the error number is found, the Response field is evaluated and the appropriate action is taken (via the Case
statement). If it is not found, the frmError form is opened, and the ERR_QUIT
constant value is returned from the ErrorHandler
function. When using this method, you need to add to the table only specific errors that you want to trap.
If the error number is found in tblErrors, the Response field from the recordset is evaluated. If the Response field contains the constant value ERR_QUIT
or ERR_EXIT
, the frmError form appears before the constant value is returned to the offending function or subroutine. If the Response field contains the constant value for ERR_RETRY
or ERR_CONTINUE
, the constant value is returned without displaying the frmError form.
The tblErrors table is included in CHAP16EX.MDB on the sample code CD-ROM. To take full advantage of this table, you must add all errors that you want to trap, along with the actions that you want the error handler to take when that error occurs.
Listing 16.19 shows how the return value from the ErrorHandler
function is used.
Example 16.19. Using the Return Value from the ErrorHandler
Function
Sub AnySub() 'Declare constant with the name of the routine Const SUBNAME As String = "AnySub" 'Invoke error handling On Error GoTo AnySub_Err 'Beginning of any routine MsgBox "This is the rest of your code...." 'Oops! Something causes an error! Err.Raise 11 'Code after the error MsgBox "We are Past the Error!!" AnySub_Exit: 'Generic exit point for routine Exit Sub AnySub_Err: Dim intAction As Integer 'Call generic error handler, passing it the error 'number and description, as well as the module name 'and subroutine name intAction = ErrorHandler(lngErrorNum:=Err.Number, _ strErrorDescription:=Err.Description, _ strModuleName:=MODULENAME, _ strRoutineName:=SUBNAME) 'Evaluate return value to determine what action to take Select Case intAction Case ERR_CONTINUE Resume Next Case ERR_RETRY Resume Case ERR_EXIT Resume AnySub_Exit Case ERR_QUIT Quit End Select End Sub
In Listing 16.19, the AnySub
routine generates an error 11
(divide by 0
). Because tblErrors contains the number 3
in the Response column and the ERR_CONTINUE
constant is equal to 3
, the error form is displayed, and the AnySub
routine is exited with an Exit Sub
statement.
The code in the error form’s Load
event calls two subroutines: GetSystemInfo
and GetErrorInfo
, as shown here:
Private Sub Form_Load() 'Call routine to obtain system information Call GetSysInfo(Me) 'Call routine to obtain error information Call GetErrorInfo(Me) 'If FormCaption property contains a value, use the 'value as the caption for the form If Not IsNull(Me.OpenArgs) Then Me.lblAction.Caption = Me.OpenArgs End If End Sub
The first subroutine is called GetSysInfo
. It performs several Windows API calls to fill in the system information on your form. The code is shown in Listing 16.20, and it is discussed in Chapter 23, “Exploiting the Power of the Windows API.”
Example 16.20. Getting System Information Through Code
Sub GetSysInfo(frmAny As Form) 'Get Free Memory Dim MS As MEMORYSTATUS MS.dwLength = Len(MS) GlobalMemoryStatus MS frmAny.lblMemoryTotal.Caption = Format(MS.dwTotalPhys, "Standard") frmAny.lblMemoryAvail.Caption = Format(MS.dwAvailPhys, "Standard") 'Get Version Information Dim OSInfo As OSVERSIONINFO OSInfo.dwOSVersionInfoSize = Len(OSInfo) If GetVersionEx(OSInfo) Then frmAny.lblOSVersion.Caption = OSInfo.dwMajorVersion & "." & OSInfo.dwMinorVersion frmAny.lblBuild.Caption = OSInfo.dwBuildNumber And &HFFFF& If OSInfo.dwPlatformId = 0 Then frmAny.lblPlatform.Caption = "Windows 95" Else frmAny.lblPlatform.Caption = "Windows NT" End If End If 'Get System Information Dim SI As SYSTEM_INFO GetSystemInfo SI frmAny.lblProcessor.Caption = SI.dwProcessorType End Sub
These API calls require the Declare
statements and constants shown in Listing 16.21. They are placed in a module called basAPI.
Example 16.21. Declaring Windows API Calls
'Declarations required by WinAPI Calls Option Compare Database Option Explicit Private Declare Sub GlobalMemoryStatus Lib "Kernel32"_ (lpBuffer As MEMORYSTATUS) Private Type MEMORYSTATUS dwLength As Long dwMemoryLoad As Long dwTotalPhys As Long dwAvailPhys As Long dwTotalPageFile As Long dwAvailPageFile As Long dwTotalVirtual As Long dwAvailVirtual As Long End Type Private Declare Function GetVersionEx Lib "Kernel32" _ Alias "GetVersionExA" (lpOSInfo As OSVERSIONINFO) As Boolean Type OSVERSIONINFO dwOSVersionInfoSize As Long dwMajorVersion As Long dwMinorVersion As Long dwBuildNumber As Long dwPlatformId As Long strReserved As String * 128 End Type Private Declare Sub GetSystemInfo Lib "Kernel32"_ (lpSystemInfo As SYSTEM_INFO) Private Type SYSTEM_INFO dwOemID As Long dwPageSize As Long lpMinimumApplicationAddress As Long lpMaximumApplicationAddress As Long dwActiveProcessorMask As Long dwNumberOrfProcessors As Long dwProcessorType As Long dwAllocationGranularity As Long dwReserved As Long End Type
The second subroutine, GetErrorInfo
, fills in the labels on the error form with all the information from your structure, as shown in Listing 16.22.
Example 16.22. Using the GetErrorInfo
Subroutine
Sub GetErrorInfo(frmAny As Form) 'Populate form controls with error information 'contained in the type variable frmAny.lblErrorNumber.Caption = gtypError.lngErrorNum frmAny.lblErrorString.Caption = gtypError.strMessage frmAny.lblUserName.Caption = gtypError.strUserName frmAny.lblDateTime.Caption = Format(gtypError.datDateTime, "c") frmAny.lblModuleName.Caption = gtypError.strModule frmAny.lblRoutineName.Caption = gtypError.strRoutine End Sub
Finally, the disposition of the error, sent as an OpenArg
from the ErrorHandler
function, is displayed in a label on the form. Figure 16.6 shows the error form.
Users often are not very accurate in describing an error and corresponding error message. It therefore is important to give them the capability to print their error message. The code in Listing 16.23 prints your error form. It is found behind the click event of the Print button on the error form.
When you are testing your application, you do not want your own error handling to be triggered. Instead, you want VBA’s error handling to be activated. The trick is in the Options dialog box of the VBE. Choose Tools|Options and click the General tab. Enable the option Break on All Errors located in the Error Trapping section. As long as this option is set, your error handling is ignored, and Access’s default error handling is invoked. Using this setting, you can turn error handling on and off from one central location.
Other settings for error trapping are Break in Class Module
and Break on Unhandled Errors
(the default). With the latter setting, handled errors do not cause the application to enter break mode. Unhandled errors do cause the project to enter break mode and place you, or the user, on the line of code that invoked the error handler. The Break in Class Module
option causes only unhandled errors in a class module to invoke break mode.
While in the debugger, it is very easy to view the call stack. Unfortunately, the call stack information cannot be accessed programmatically when an error occurs. If you wish to keep track of the sequence of procedures that brought you to the error condition, you must do it yourself. The code in Listing 16.24 shows three routines. Func1
calls Func2
, and then Func2
calls Func3
. Func3
renders an error.
Example 16.24. Routines That Call One Another
Sub Func1() 'Invoke error handling On Error GoTo Func1_Err 'Put routine in call stack ERH_PushStack_TSB ("Func1") 'Print to the immediate window Debug.Print "I am in Function 1" 'Execute the Func2 routine Call Func2 'Print to the immediate window Debug.Print "I am back in Function 1" Func1_Exit: 'Pop error stack ERH_PopStack_TSB 'Exit the subroutine Exit Sub Func1_Err: 'Display a message to the user, 'indicating that an error occurred MsgBox "Error in Func1" 'Resume execution Resume Func1_Exit End Sub Sub Func2() 'Put routine in call stack ERH_PushStack_TSB ("Func2") On Error GoTo Func2_Err Debug.Print "I am in Func2" 'Execute Func3 Call Func3 'Print to the immediate window Debug.Print "I am still in Func2" Func2_Exit: 'Pop error stack ERH_PopStack_TSB 'Exit the subroutine Exit Sub Func2_Err: 'Display a message to the user, 'indicating that an error occurred MsgBox "Error in Func1" 'Resume execution Resume Func2_Exit End Sub Sub Func3() Dim sngAnswer As Single 'Put routine in call stack ERH_PushStack_TSB ("Func3") On Error GoTo Func3_Err 'Print to the immediate window Debug.Print "I am in Func3" 'Opps, an error occurred sngAnswer = 5 / 0 'This line of code will never execute Debug.Print "I am still in Func3" Func3_Exit: 'Pop error stack ERH_PopStack_TSB 'Exit the subroutine Exit Sub Func3_Err: Dim intCounter As Integer Dim strCallStack As String For intCounter = LBound(gaERH_Stack_TSB) To UBound(gaERH_Stack_TSB) If Len(gaERH_Stack_TSB(intCounter)) Then strCallStack = strCallStack & _ gaERH_Stack_TSB(intCounter) & vbCrLf End If Next intCounter MsgBox Err.Number & ": " & Err.Description & _ vbCrLf & strCallStack Resume Func3_Exit End Sub
Notice that at the beginning of each routine, the ERH_PushStack_TSB
subroutine is called, as shown in Listing 16.25.
Example 16.25. The ERH_PushStack_TSB
Function Pushes the Error into the Stack
Sub ERH_PushStack_TSB(strProc As String) ' Comments : Pushes the supplied procedure name onto the error handling stack ' Parameters: strProc - name of the currently executing procedure ' Returns : Nothing ' gintERH_Pointer_TSB = gintERH_Pointer_TSB + 1 If gintERH_Pointer_TSB <= ERH_MAXITEMS_TSB Then gaERH_Stack_TSB(gintERH_Pointer_TSB) = strProc Else gaERH_Stack_TSB(gintERH_Pointer_TSB + 2) = strProc End If End Sub
The code adds the name of the procedure to the gaERH Stack_TSB array. The ERH_PopStack_TSB
subroutine, shown in Listing 16.26, is executed in the exit code for each procedure.
Example 16.26. The ERH_PopStack_TSB
Function Removes the Error from the Stack
Sub ERH_PopStack_TSB() ' Comments : Pops the current procedure name off the error handling stack ' Parameters: None ' Returns : Nothing ' If gintERH_Pointer_TSB <= ERH_MAXITEMS_TSB Then gaERH_Stack_TSB(gintERH_Pointer_TSB) = "" End If gintERH_Pointer_TSB = gintERH_Pointer_TSB - 1 If gintERH_Pointer_TSB < 0 Then gintERH_Pointer_TSB = 0 End If End Sub
The ERH_PopStack_TSB
subroutine removes the text in the largest array element.
The ERH_PushStack_TSB
and ERH_PullStack_TSB
subroutines are found in the FMS SourceBook library. For more information about the FMS SourceBook, see Chapter 32.
As the code goes in and out of routines, entries are added to and then removed from the array. Because the array is Public, you can review its contents at any time. Notice in Func3
in Listing 16.24 that the error handler iterates through the array, pulling out the error information.
Implementing error handling within an application can be very tedious, especially if you attempt to place specific error-handling logic in each routine that you write. Although implementing a generic error handler does not mandate the use of a class module, using a class module greatly facilitates the process of implementing error handling within your applications. Listing 6.27 illustrates this point.
Example 16.27. An Example of an Access Subroutine
Sub AnySub2() 'Declare constant with the name of the routine Const SUBNAME As String = "AnySub" 'Invoke error handling On Error GoTo AnySub2_Err 'Beginning of any routine MsgBox "This is the rest of your code...." 'Oops! Something causes an error! Err.Raise 11 'Code after the error MsgBox "We are Past the Error!!" AnySub2_Exit: 'Generic exit point for routine Exit Sub AnySub2_Err: Dim intAction As Integer 'Instantiate the error handler class Set gobjErrorHandler = New ErrorHandler 'Execute the ErrorProcess method, 'passing the error information intAction = gobjErrorHandler.ErrorProcess(ModuleName, _ SUBNAME, Err.Number, Err.Description) 'Evaluate return value to determine what action to take Select Case intAction Case ERR_CONTINUE Resume Next Case ERR_RETRY Resume Case ERR_EXIT Resume AnySub2_Exit Case ERR_QUIT Quit End Select End Sub
When an error occurs, the ErrorHandler
class is instantiated. The Initialize
event of the class executes, as shown in Listing 16.28.
The Initialize
event of the class sets the module-level variables mstrUserName
and mdatDateTime
equal to the CurrentUser
and the current date and time, respectively. The Username and DateTime properties of the class use these variables.
The ErrorProcess
method of the ErrorHandler
class is then executed. It appears in Listing 16.29.
Example 16.29. The ErrorProcess
Method of the ErrorHandler
Class Logs the Error and Then Takes Appropriate Action in Response to the Error
Public Function ErrorProcess(strRoutine As String, _ strModule As String, _ lngErrorNumber As Long, _ strErrorMessage As String) As Integer 'Store error information into module-level variables mstrRoutine = strRoutine mstrModule = strModule mlngErrorNumber = lngErrorNumber mstrErrorMessage = strErrorMessage 'Log error Call LogError 'Locate the error number in tblErrors to 'determine how you should respond to the error Dim rst As ADODB.Recordset Set rst = New ADODB.Recordset rst.Open "Select Response from tblErrors Where ErrorNum = " & lngErrorNumber, _ CurrentProject.Connection, adOpenStatic 'If the error number that occurred is not found 'in tblErrors, display the error form and return 'ERR_QUIT to the problem routine If rst.EOF Then DoCmd.OpenForm "frmError2", WindowMode:=acDialog, _ OpenArgs:="ErrorHandler" ErrorProcess = ERR_QUIT 'If the error is in tblErrors, evaluate the contents of 'the Response field. Respond appropriately, displaying the appropriate 'form and returning the appropriate value to the offending routine Else Select Case rst!Response Case ERR_QUIT DoCmd.OpenForm "frmError2", WindowMode:=acDialog, _ OpenArgs:="Critical Error: Application will Terminate" ErrorProcess = ERR_QUIT Case ERR_RETRY ErrorProcess = ERR_RETRY Case ERR_EXIT DoCmd.OpenForm "frmError2", WindowMode:=acDialog, _ OpenArgs:="Severe Error: Processing Did Not Complete" ErrorProcess = ERR_EXIT Case ERR_CONTINUE ErrorProcess = ERR_CONTINUE End Select End If End Function
The routine first sets the ModuleName
, Routine
, ErrorMessage
, and ErrorNumber
variables within the class to the values of the parameters passed to the ErrorProcess
method. The Property Get
routines for the ModuleName, Routine, ErrorMessage, and ErrorNumber properties appear in Listing 16.30. Because we only want the properties to be set via the ErrorProcess
method, no Property Let
routines exist.
Example 16.30. The Property Get
Routines of the Class Are Responsible for Manipulating Error Information
Public Property Get ModuleName() As String ModuleName = mstrModule End Property Public Property Get Routine() As String Routine = mstrRoutine End Property Public Property Get ErrorMessage() As String ErrorMessage = mstrErrorMessage End Property Public Property Get ErrorNumber() As Integer ErrorNumber = mlngErrorNumber End Property Public Property Get UserName() As String UserName = mstrUsername End Property Public Property Get DateTime() As Date DateTime = mdatDateTime End Property
As you can see, the Property Get
routines retrieve the values from their associated module-level variables.
Next, the function calls a routine that logs the error that occurred. This LogError routine is shown in Listing 16.31. The LogError
routine utilizes ADO code to add a record to the tblErrorLog table. The record contains all the information about the error that occurred. Notice that the error information is retrieved from the module level variables populated by the ErrorHandler
class’s ErrorProcess
method.
After the error is logged, the number of the error that occurred is looked up in the tblErrors table. If it is not found in the tblErrors table, a form is displayed, containing all the critical information about the error that occurred. The value contained in the constant ERR_QUIT
is returned from the ErrorHandler
function. If the error number is found in the tblErrors table, the value contained in the Response field is evaluated. If it is the value contained in the constant ERR_QUIT
, the frmError form is displayed, and the value in the constant ERR_QUIT
is returned from the ErrorHandler
method. If the Response field contains the value of the ERR_RETRY
constant, that value is returned from the method, without the frmError form being displayed. If the Response field contains the value associated with the ERR_EXIT
constant, the frmError form is displayed, and the ERR_EXIT
value is returned from the ErrorHandler
method. Finally, if the value in the Response field is the value associated with the ERR_CONTINUE
constant, no error information is displayed, and the ERR_CONTINUE
value is returned from the ErrorHandler
method.
Example 16.31. The LogError
Subroutine Uses ADO Code to Add the Error Information to tblErrorLog
Sub LogError() 'Declare a Connection object Dim cnn As ADODB.Connection Dim strSQL As String 'Point the Connection object at the connection 'associated with the current project Set cnn = CurrentProject.Connection 'Build a SQL statement that inserts error information 'into the tblErrorLog table strSQL = "INSERT INTO tblErrorLog ( ErrorDate, ErrorTime, UserName, ErrorNum, ErrorString, ModuleName, RoutineName) " strSQL = strSQL & "Select #" & Me.DateTime & "#, #" _ & Me.DateTime & "#, '" _ & Me.UserName & "', " _ & Me.ErrorNumber & ", '" _ & Me.ErrorMessage & "', '" _ & Me.ModuleName & "', '" _ & Me.Routine & "'" 'Execute the SQL statement cnn.Execute strSQL, , adExecuteNoRecords End Sub
All the code contained in the ErrorHandler
class is similar to that contained in the basErrorHandler module. The code has been modified so that it is implemented using properties and methods of a Class
object.
The other code that is changed to utilize classes is the code behind the error form. Listing 16.32 shows the load event of the error form.
Example 16.32. The Form_Load
Event Is Modified to Call Methods of the Appropriate Classes
Private Sub Form_Load() Dim objSys As SystemInformation Set objSys = New SystemInformation 'Call routine to obtain system information Call objSys.GetSysInfo(Me) 'Call routine to obtain error information Call gobjErrorHandler.GetErrorInfo(Me) 'If FormCaption property contains a value, use the 'value as the caption for the form If Not IsNull(Me.OpenArgs) Then Me.lblAction.Caption = Me.OpenArgs End If End Sub
Notice that instead of calling the GetSysInfo
function and the GetErrorInfo
function, the load event executes the GetSysInfo
method of the SystemInformation
object and the GetErrorInfo
method of the ErrorHandler object.
The GetSystemInfo
function and associated declarations were moved to a SystemInformation class. No other changes were made to the code.
The GetErrorInfo
function was moved to the ErrorHandler
class and modified as shown in Listing 16.33.
Example 16.33. The GetErrorInfo method of the ErrorHandler
Class Retrieves Properties of the Class
Sub GetErrorInfo(frmAny As Form) 'Populate form controls with error information 'contained in the type variable frmAny.lblErrorNumber.Caption = Me.ErrorNumber frmAny.lblErrorString.Caption = Me.ErrorMessage frmAny.lblUserName.Caption = Me.UserName frmAny.lblDateTime.Caption = Format(Me.DateTime, "c") frmAny.lblModuleName.Caption = Me.ModuleName frmAny.lblRoutineName.Caption = Me.Routine End Sub
Notice that instead of using a type structure, the code references its own properties. The Private
variables associated with these properties were set by the ErrorProcess
method of the class.
Every form and report contains an error event procedure. This event is triggered by any interface or Jet Database Engine error. It is not triggered by a programming error made by the Access developer.
Errors often occur in the interface of a form or report, as well as in the Jet Database Engine. A user might try to enter an order for a customer who doesn’t exist, for example. Instead of displaying Access’s default error message, you might want to intercept and handle the error in a particular way.
After an error occurs within a form, its error event is triggered. In Listing 16.34, you can see Sub Form_Error
. It contains two parameters. The first parameter is the number of the error. The second is the way you want to respond to the error. The error number is an Access-generated number.
This code, which is located in the frmOrders form in the CHAP16EX.MDB database, tests to see whether a referential integrity error has occurred. If it has, a message box asks whether the user wants to add the customer. If the user answers Yes
, the customer form is displayed.
Example 16.34. Viewing Sub Form_Error
From the Form frmOrders
Private Sub Form_Error(DataErr As Integer, Response As Integer) Dim intAnswer As Integer If DataErr = 3201 Then 'Referential Integrity Error intAnswer = MsgBox("Customer Does Not Exist... _ Would You Like to Add Them Now", vbYesNo) If intAnswer = vbYes Then DoCmd.OpenForm "frmCustomer", , , , acAdd, acDialog End If End If Response = acDataErrContinue End Sub
Be aware that the code in Listing 16.34 only traps referential integrity errors. It does not handle any other error.
The Response = acDataErrContinue
line is very important. It instructs Access to continue the code execution without displaying the standard error message. The other option for Response
is AcDataErrDisplay
. It tells Access to display the default error message.
Many people ask me how to create a list of error numbers and descriptions. The code in Listing 16.35 creates a table of all the errors that can occur in your VBA code, with a description of what each error number means. You can copy this code into any module and run it.
Example 16.35. Code That Creates a Table of Errors and Descriptions
Sub CreateErrorsTable() Dim cnn As ADODB.Connection Dim rst As New ADODB.Recordset Dim lngCode As Long Const conAppObjectError = "Application-defined or object-defined error" Set cnn = CurrentProject.Connection ' Open recordset on Errors table. rst.Open "tblErrorMessages", cnn, adOpenStatic, adLockOptimistic ' Loop through first 10000 Visual Basic error codes. For lngCode = 1 To 10000 On Error Resume Next ' Raise each error. Err.Raise lngCode DoCmd.Hourglass True ' Skip error codes that generate application or object-defined errors. If Err.Description <> conAppObjectError Then ' Add each error code and string to Errors table. rst.AddNew rst!ErrorCode = Err.Number rst!ErrorString = Err.Description rst.Update End If ' Clear Err object. Err.Clear Next lngCode ' Close recordset. rst.Close DoCmd.Hourglass False MsgBox "Errors table created." End Sub
The code opens a recordset based on the tblErrorMessages table. It loops through from 1 to 10000, raising an error with each number. Each time through the loop, it appends the error number and the associated error description to the tblErrorMessages table.
Example 16.36. Incorporating the Generic Error Handler into Your Code
Sub GetCompanyInfo() Dim strSubName As String Dim rst As ADODB.Recordset 'Declare constant with the name of the routine Const SUBNAME As String = "GetCompanyInfo" 'Invoke error handling On Error GoTo GetCompanyInfo_Err 'Instantiate the CompanyInformation class Set gobjCompanyInfo = New CompanyInformation Set rst = New ADODB.Recordset 'Open a recordset based on the tblCompanyInfo table rst.Open "tblCompanyInfo", CurrentProject.Connection 'Populate the properties of the public class instance 'with values from the tblCompanyInfo table With gobjCompanyInfo .SetupID = rst!SetupID .CompanyName = rst!CompanyName .Address = rst!Address .City = rst!City .StateProvince = rst!StateProvince .PostalCode = rst!PostalCode .Country = rst!Country .PhoneNumber = rst!PhoneNumber .FaxNumber = rst!PhoneNumber End With rst.Close GetCompanyInfo_Exit: 'Generic exit point for routine Exit Sub GetCompanyInfo_Err: Dim intAction As Integer 'Instantiate the error handler class Set gobjErrorHandler = New ErrorHandler 'Execute the ErrorProcess method, 'passing the error information intAction = gobjErrorHandler.ErrorProcess(ModuleName, _ SUBNAME, Err.Number, Err.Description) 'Evaluate return value to determine what action to take Select Case intAction Case ERR_CONTINUE Resume Next Case ERR_RETRY Resume Case ERR_EXIT Resume GetCompanyInfo_Exit Case ERR_QUIT Quit End Select End Sub
Notice the On Error Goto
statement at the beginning of the routine and that the local constant SUBNAME
is declared and set equal to GetCompanyInfo
. The generic error handler uses the value in the constant to display the routine within which the error occurred. The error handler GetCompanyInfo_Err
instantiates the ErrorHandler
class. It executes the ErrorProcess
method of the class and then evaluates its return value.
In this chapter, you learned the alternatives for handling errors in your Access applications. This chapter covered how you can use the error event to trap for application and Jet Engine errors in forms and reports. You also learned how to use the On Error
statement. Finally, you learned how to build a generic error system. Regardless of the amount of testing done on an application, errors will occur. It is important that you properly trap for those errors.
18.216.201.93