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 is also known as error trapping. Error handling 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.
Without error handling, the user of your application is forced to exit abruptly from your application code. Consider the example in Listing 17.1.
Example 17.1. An example of code without error handling.
Private Sub cmdCallError_Click() Call TestError(txtValue1, txtValue2) End Sub Sub TestError(Numerator As Integer, Denominator As Integer) Debug.Print Numerator / Denominator MsgBox "I am in Test Error" End Sub
The Click
event behind the command button calls the routine TestError
, passing it the values from two text boxes. TestError
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 17.1 shows the error message the user receives. As you can see, the choices are Debug, Continue, End, and Help. If users choose Debug, the module window appears and they are placed in Debug mode on the line of code causing the error. Choosing Continue (this is not always available), tells Access to ignore the error and continue with the execution of the program. Clicking 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 17.1 to handle the error gracefully. The code in Listing 17.2 shows a simple error-handling routine.
Example 17.2. A simple error-handling routine.
Sub TestError(Numerator As Integer, Denominator As Integer) On Error GoTo TestError_Err Debug.Print Numerator / Denominator MsgBox "I am in Test Error" Exit Sub TestError_Err: 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 CHAP17EX.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 17.2 shows.
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 in a form, its error event is triggered. In Listing 17.3, you can see Sub Form_Error
. It contains two parameters. The first parameter is the number of the error. The second parameter 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 CHAP17EX.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 17.3. Viewing Sub Form_Error
from the frmOrders form.
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 sample in Listing 17.3 traps only 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
, which tells Access to display the default error message.
If you want to get a list of all the errors that can occur in Access, as well as a description of what each error number means, search for Error Codes
in the Help index. A list appears, containing each error number and a description of each error. You can click on an error description to get a more detailed explanation of the error.
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 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 On Error Resume
, 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, and it must be unique within a module. Listing 17.4 shows a simple example of error handling.
Example 17.4. An example of error handling using the On Error GoTo
statement.
Sub SimpleErrorHandler(iVar1 As Integer, iVar2 As Integer) On Error GoTo SimpleErrorHandler_Err Dim sngResult As String sngResult = iVar1 / iVar2 Exit Sub SimpleErrorHandler_Err: 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 17.4 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: 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 17.3 shows the resulting error message. The SimpleErrorHandler
routine and all the following examples are located in the basError module of the CHAP17EX.MDB database.
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 17.5 shows an example of such a situation.
Example 17.5. Ignoring an error and continuing execution.
Sub TestResumeNext() On Error Resume Next Kill "AnyFile" MsgBox "We Didn't Die, But the Error Was: " & Err.Description 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 no harm is done by resuming execution after the offending line of code.
You use On Error Goto 0
for two purposes:
When you want Access to return to its default error handler
To have Access return to the error handler of a routine above the current routine
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, you can place the error handling in the calling routine.
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 17.6 shows an example of an inappropriate use of the Resume
statement.
Example 17.6. Using Resume
inappropriately.
Function BadResume(sFileName As String) On Error GoTo BadResume_Err Dim strFile As String strFile = Dir(sFileName) If strFile = "" Then BadResume = False Else BadResume = True End If Exit Function BadResume_Err: MsgBox Error Resume End Function
This function is passed a file name. The Dir
function searches for the file name and returns True or False, depending on whether the specified file name 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 17.7.
Example 17.7. Using Resume
conditionally based on user feedback.
Function GoodResume(sFileName As String) On Error GoTo GoodResume_Err Dim strFile As String strFile = Dir(sFileName) If strFile = "" Then GoodResume = False Else GoodResume = True End If Exit Function GoodResume_Err: Dim intAnswer As Integer intAnswer = MsgBox(Error & ", Would You Like to Try Again?", vbYesNo) 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 17.8 shows.
Example 17.8. Placing a Resume Next
statement in your error handler.
Sub TestResumeNextInError() On Error GoTo TestResumeNextInError_Err Kill "AnyFile" MsgBox "We Didn't Die!" Exit Sub TestResumeNextInError_Err: 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.
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 17.9 shows an example.
Example 17.9. Specifying where code execution should continue after an error occurs.
Sub TestResumeLineLabel(iVar1 As Integer, iVar2 As Integer) On Error GoTo TestResumeLineLabel_Err Dim sngResult As String sngResult = iVar1 / iVar2 TestResumeLineLabel_Exit: Exit Sub TestResumeLineLabel_Err: MsgBox "Error #" & Err.Number & ": " & Err.Description 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 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 this chapter, 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 17.10 shows an example of this process.
Example 17.10. Looking up the call stack for a previous error handler.
Sub Func1() On Error GoTo Func1_Err Debug.Print "I am in Function 1" Call Func2 Debug.Print "I am back in Function 1" Exit Sub Func1_Err: MsgBox "Error in Func1" Resume Next End Sub Sub Func2() Debug.Print "I am in Func 2" Call Func3 Debug.Print "I am still in Func2" End Sub Sub Func3() Dim sngAnswer As Single Debug.Print "I am in Func 3" sngAnswer = 5 / 0 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. It therefore is best to develop a generic error-handling routine that is accessed throughout your application.
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 17.1 lists the properties of the Err
object.
Table 17.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 file name 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. It 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 create a user-defined error is covered in the following sections.
Many times, you want to generate an error when testing so that you can test out 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 17.11 shows.
Example 17.11. Raising an error.
Sub TestRaiseError() On Error GoTo TestRaiseError_Err Dim sngResult As String Err.Raise 11 Exit Sub TestRaiseError_Err: MsgBox "Error #" & Err.Number & ": " & Err.Description Exit Sub End Sub
This code invokes an error 11 (divide by 0). By generating the error, you can test the effectiveness of your error-handling routine.
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 have something that does not generate an Access error generate a user-defined error that you send through the normal error-handling process. 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 17.12.
Example 17.12. Creating a user-defined error.
Sub TestCustomError() On Error GoTo TestCustomError_Err Dim strName As String strName = InputBox("Please Enter Your Name") 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: MsgBox "Error # " & Err.Number & _ " - " & Err.Description Exit Sub End Sub
Although it is very simple, this example 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 (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 errors that have occurred. This is important when dealing with DAO and ODBC, when 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. The Errors collection has the same properties as the Err
object. 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 17.13 shows the code you can use to accomplish this.
Example 17.13. Viewing the errors stored in the Errors collection.
Sub TestErrorsCollection() On Error GoTo TestErrorsCollection_Err Dim db As Database Set db = CurrentDb db.Execute ("qryNonExistent") Exit Sub TestErrorsCollection_Err: Dim ErrorDescrip As Error For Each ErrorDescrip In Errors Debug.Print ErrorDescrip.Description Next ErrorDescrip Exit Sub End Sub
This routine loops through each Err
object in the Errors collection, printing the description of each error contained in the collection.
A generic error handler is an error handler that can be called from anywhere in your application. It is capable of responding 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. A generic error handler should give users information about the error, enable users to print this information, and log the information to a file. A generic error handler should be able to be called from every procedure within your application.
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 17.14.
Example 17.14. A generic error handler for all your functions and subroutines.
Sub AnySub() Dim strSubName As String strSubName = "AnySub" On Error GoTo AnySub_Err MsgBox "This is the rest of your code...." Err.Raise 11 MsgBox "We are Past the Error!!" Exit Sub AnySub_Err: Dim intAction As Integer intAction = ErrorHandler(intErrorNum:=Err.Number, _ strErrorDescription:=Err.Description, _ strModuleName:=mstrModuleName, _ strRoutineName:=strSubName) Select Case intAction Case ERR_CONTINUE Resume Next Case ERR_RETRY Resume Case ERR_EXIT Exit Sub 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 mstrModuleName
. 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 variable called strSubName
. With this approach, you create a local string 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. 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.
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 17.15.
Example 17.15. A type structure declaration to be used for generic error handling.
Type typErrors intErrorNum As Integer strMessage As String strModule As String strRoutine As String strUserName As String datDateTime As Variant End Type Public ptypError As typErrors 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. The Public
variable ptypError
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 17.16 shows the ErrorHandler
function.
Example 17.16. Using the ErrorHandler
function.
Function ErrorHandler(intErrorNum As Integer, _ strErrorDescription As String, _ strModuleName As String, _ strRoutineName As String) As Integer ptypError.intErrorNum = intErrorNum ptypError.strMessage = strErrorDescription ptypError.strModule = strModuleName ptypError.strRoutine = strRoutineName ptypError.strUserName = CurrentUser() ptypError.datDateTime = Now Call LogError Dim db As Database Dim snp As Snapshot Set db = CurrentDb() Set snp = db.OpenRecordset("Select Response from tblErrors Where _ ErrorNum = " & intErrorNum) If snp.EOF Then DoCmd.OpenForm "frmError", WindowMode:=acDialog, _ OpenArgs:=ErrorHandler ErrorHandler = ERR_QUIT Else Select Case snp.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 ptypError
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 error. Start with logging your errors to a table. Listing 17.17 shows the LogError
routine.
Example 17.17. Using the LogError
routine.
Sub LogError() Dim sSQL As String DoCmd.SetWarnings False sSQL = "INSERT INTO tblErrorLog (ErrorDate, ErrorTime, UserName, _ ErrorNum, ErrorString, Module, Routine) " sSQL = sSQL & "VALUES ( #" & ptypError.datDateTime & "#, #" _ & ptypError.datDateTime & "#, '" _ & ptypError.strUserName & "', " _ & ptypError.intErrorNum & ", '" _ & ptypError.strMessage & "', '" _ & ptypError.strModule & "', '" _ & ptypError.strRoutine & "')" DoCmd.RunSQL sSQL DoCmd.SetWarnings True End Sub
This routine uses an SQL statement to add a record to your error table. The record contains all the information from the structure called ptypError
. The information is logged to a table called tblErrorLog. Figure 17.4 shows the structure of this table.
The alternative is to write the information to a textual error log file, as shown in Listing 17.18.
Example 17.18. Writing information to a textual error log file.
Sub LogErrorText() Dim intFile As Integer intFile = FreeFile Open CurDir & "ErrorLog.Txt" For Append Shared As intFile Write #intFile, "LogErrorDemo", Now, Err, Error, CurrentUser() Close intFile End Sub
This code uses the low-level file functions Open
and Write
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 17.5 shows the structure of the tblErrors table. This table should contain a list of all the error numbers 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 uses the code in Listing 17.19 to locate the error code in the tblErrors table.
Example 17.19. Using the ErrorHandler
function.
Dim db As Database Dim snp As Recordset Set db = CurrentDb() Set snp = db.OpenRecordset("Select Response from tblErrors Where _ ErrorNum = " & intErrorNum, dbOpenSnapshot) If snp.EOF Then DoCmd.OpenForm "frmError", WindowMode:=acDialog, _ OpenArgs:="ErrorHandler" ErrorHandler = ERR_QUIT Else Select Case snp!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
This part of the ErrorHandler
function creates both a Database
and a Recordset
object variable. It opens a snapshot type of recordset using a Select
statement. The Select
statement 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 17.19 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. By using this method, you need to add to the table only specific errors that you want to trap.
If no records are found in tblErrors that match the SQL statement, the frmError form is opened, and the return value for the function is set equal to the constant value ERR_QUIT
. If the error number is found in tblErrors, the Response field from the snapshot 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 CHAP17EX.MDB on the sample code CD-ROM. To take full advantage of this table, you must add all errors that you want to trap for along with the action(s) that you want the error handler to take when that error occurs.
The return value from the ErrorHandler
function is used as shown in Listing 17.20.
Example 17.20. Using the return value from the ErrorHandler
function.
Sub AnySub() Dim strSubName As String strSubName = "AnySub" On Error GoTo AnySub_Err MsgBox "This is the rest of your code...." Err.Raise 11 MsgBox "We are Past the Error!!" Exit Sub AnySub_Err: Dim intAction As Integer intAction = ErrorHandler(intErrorNum:=Err.Number, _ strErrorDescription:=Err.Description, _ strModuleName:=mstrModuleName, _ strRoutineName:=strSubName) Select Case intAction Case ERR_CONTINUE Resume Next Case ERR_RETRY Resume Case ERR_EXIT Exit Sub Case ERR_QUIT Quit End Select End Sub
In this example, the AnySub
routine generates an error 11 (divide by 0). Because tblErrors contains the number 0 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 GetSysInfo(Me) Call GetErrorInfo(Me) Me!lblAction.Caption = Me.OpenArgs End Sub
The first subroutine is called GetSystemInfo
. It performs several Windows API calls to fill in the system information on your form. The code is shown in Listing 17.21, and it is discussed in Chapter 31, “Using External Functions: The Windows API.”
Example 17.21. 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 17.22. They are placed in a module called basAPI.
Example 17.22. Declaring Windows API 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 17.23.
Example 17.23. Using the GetErrorInfo
subroutine.
Sub GetErrorInfo(frmAny As Form) frmAny!lblErrorNumber.Caption = ptypError.intErrorNum frmAny!lblErrorString.Caption = ptypError.strMessage frmAny!lblUserName.Caption = ptypError.strUserName frmAny!lblDateTime.Caption = Format(ptypError.datDateTime, "c") frmAny!lblModuleName.Caption = ptypError.strModule frmAny!lblRoutineName.Caption = ptypError.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 17.6 shows the error form.
Users often are not very accurate in describing an error and its corresponding error message. It therefore is important to give them the capability to print their error message. The code in Listing 17.24 prints your 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. Choose Tools | Options and click the Advanced 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.
Error-handling code should be added throughout the Time and Billing application. The following example shows you how to incorporate the generic error handler into the Time and Billing application.
The Time and Billing application contains a routine called GetCompanyInfo
. This routine reads all the company information from the tblCompanyInfo table. The information is read from the type structure, as needed, while the application is running. This routine, like any routine, has the potential for error. The original routine has been modified to incorporate the generic error handler, as shown in Listing 17.25.
Example 17.25. Incorporating the generic error handler into your code.
Sub GetCompanyInfo() On Error GoTo GetCompanyInfo_Err Dim strSubName As String Dim db As DATABASE Dim rs As Recordset strSubName = "GetCompanyInfo" Set db = CurrentDb Set rs = db.OpenRecordset("tblCompanyInfo", dbOpenSnapshot) typCompanyInfo.SetUpID = rs!SetUpID typCompanyInfo.CompanyName = rs!CompanyName typCompanyInfo.Address = rs!Address typCompanyInfo.City = rs!City typCompanyInfo.StateProvince = rs!StateProvince typCompanyInfo.PostalCode = rs!PostalCode typCompanyInfo.Country = rs!Country typCompanyInfo.PhoneNumber = rs!PhoneNumber typCompanyInfo.FaxNumber = rs!PhoneNumber rs.Close db.Close Exit Sub GetCompanyInfo_Err: Dim intAction As Integer intAction = ErrorHandler(intErrorNum:=Err.Number, _ strErrorDescription:=Err.Description, _ strModuleName:=mstrModuleName, _ strRoutineName:=strSubName) Select Case intAction Case ERR_CONTINUE Resume Next Case ERR_RETRY Resume Case ERR_EXIT Exit Sub Case ERR_QUIT Quit End Select End Sub
Notice the On Error Goto
statement at the beginning of the routine and that the local variable strSubName
has been declared and set equal to GetCompanyInfo
. The error handler GetCompanyInfo_Err
calls the ErrorHandler
function and then evaluates its return value.
In this chapter, you learned the alternatives for handling errors in your Access applications. Regardless of the amount of testing done on an application, errors will occur. It is important that you trap properly for those errors.
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.
18.119.105.239