Alison Balteramp;#039;s Mastering Microsoft Office Access 1002003 [Electronic resources] نسخه متنی

اینجــــا یک کتابخانه دیجیتالی است

با بیش از 100000 منبع الکترونیکی رایگان به زبان فارسی ، عربی و انگلیسی

Alison Balteramp;#039;s Mastering Microsoft Office Access 1002003 [Electronic resources] - نسخه متنی

Alison Balter

| نمايش فراداده ، افزودن یک نقد و بررسی
افزودن به کتابخانه شخصی
ارسال به دوستان
جستجو در متن کتاب
بیشتر
تنظیمات قلم

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

روز نیمروز شب
جستجو در لغت نامه
بیشتر
لیست موضوعات
توضیحات
افزودن یادداشت جدید



Creating a Generic Error Handler


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. You might even want to email this information to yourself, or to someone else in charge of maintaining the database.

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.

Listing 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.Chapter 31, "Third-Party Tools That Can Help You Get Your Job Done Effectively." Among 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 of the routines that you create.

Now that you have seen how to implement error handling in your procedures, take a look at the function that's called when an error occurs, as shown in Listing 16.14.

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

Listing 16.15 Using the ErrorHandler Function

Function ErrorHandler(lngErrorNum As Long, _
strErrorDescription As String, _
strModuleName As String, _
strRoutineName As String) As Integer
Dim strUserInfo As String
Dim strErrorInfo As String
'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
'E-mail the error
strUserInfo = gtypError.strUserName & _
" Date: " & gtypError.datDateTime
strErrorInfo = "Module: " & gtypError.strModule & vbCrLf & _
"Routine: " & gtypError.strRoutine & vbCrLf & _
"Error Number: " & gtypError.lngErrorNum & vbCrLf & _
"Error Message: " & gtypError.strMessage
Call MailError(strUserInfo, strErrorInfo)
'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
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 it does not find the error code in the error table, the code displays an error form, and it sends a return value to the calling function, indicating that application execution is to be terminated. If it finds the error code in the tblErrors table and determines it to be critical or severe, the code displays an error form before it returns control to the calling routine. In any case, the code returns a severity code for the error to the calling function. The following section discusses the details involved in each step of the process.

Logging the Error


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. You can log errors to a text file or to 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.

Listing 16.16 Using the LogError Routine

[View full width]

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 code logs the information to a table called tblErrorLog. Figure 16.4 shows the structure of this table.

Figure 16.4. The structure of the tblErrorLog table.


The alternative error-logging method is to write the information to a textual error log file, as shown in Listing 16.17.

Listing 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. The code writes all the pertinent information about the error 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.

Determining the Appropriate Response to an Error


After the code logs the error, 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 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.

Figure 16.5. The structure of the tblErrors table.


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.

Listing 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 code uses the Response column 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 code evaluates the Response field and takes the appropriate action (via the Case statement). If it is not found, the code opens the frmError form, 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 code evaluates the Response field from the recordset. If the Response field contains the constant value ERR_QUIT or ERR_EXIT, the frmError form appears before the code returns the constant value to the offending function or subroutine. If the Response field contains the constant value for ERR_RETRY or ERR_CONTINUE, the code returns the constant value without displaying the frmError form.

NOTE

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 the errors you want to trap, along with the actions you want the error handler to take when a particular error occurs.

Listing 16.19 shows how the code uses the return value from the ErrorHandler function.

Listing 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 displays, and the AnySub routine exits with an Exit Sub statement.

NOTE

To test what happens when the error code is not found in the tblErrors table, run the SubWithUnknownError routine found in basError. To test what happens when the code returns the ERR_CONTINUE code, execute the SubWithContinue routine.

Emailing the Error


Whereas the LogError routine is responsible for logging the error to a database, the MailError routine, shown in Listing 16.20, is responsible for mailing the error information to a key system administrator.

Listing 16.20 The MailError Routine

Sub MailError(strUserInfo As String, _
strErrorInfo As String)
'Declare necessary variables
Dim objCurrentMessage As Outlook.MailItem
Dim objNamespace As Outlook.NameSpace
Dim objMessage As Outlook.MAPIFolder
'Get reference to a namespace variable
Set objNamespace = GetOutlook()
'Use GetDefaultFolder method of the NameSpace object
'to get a reference to a MAPIFolder object
Set objMessage = objNamespace.GetDefaultFolder(olFolderOutbox)
'Use the Add method of the Items collection
'of the MAPIFolder object to add a mail item
With objMessage.Items.Add(olMailItem)
'Set properties of the mail item
.To = "guru@somecompany.com"
.Subject = strUserInfo
.Body = strErrorInfo
'Save the mail item as a draft
.Save
End With
End Sub

The MailError routine receives user and error information from the calling routine. It instantiates Outlook MailItem, NameSpace, and MAPIFolder objects. It then points the NameSpace object at the namespace returned from the GetOutlook function. The GetOutlook function appears in Listing 16.21.

Listing 16.21 The GetOutlook Function

Function GetOutlook() As Outlook.NameSpace
Dim objOutlook As New Outlook.Application
Dim objNamespace As Outlook.NameSpace
Dim strProfile As String
Dim strPassword As String
strProfile = "alison"
strPassword = "mypassword"
'Use the GetNamespace method of the Outlook
'Application object to get a pointer to a
'MAPI namespace
Set objNamespace = objOutlook.GetNamespace("MAPI")
'Use the Logon method of the NameSpace object
'to Logon to the namespace
Call objNamespace.Logon(strProfile, _
strPassword, False, True)
'Return a reference to the namespace
Set GetOutlook = objNamespace
End Function

The GetOutlook function declares an Outlook NameSpace object and an Outlook Application object. It uses the GetNamespace method of the Outlook Application object to obtain a reference to a MAPI namespace. It then uses the Logon method of the namespace to log on to the namespace, using a given profile name and password. You will need to change the values of strProfile and strPassword to a valid profile name and password for this code to run properly on your machine. Finally, this code returns a reference to the namespace that it created.

After the MailError routine has a reference to a MAPI namespace, it can use that reference to point to the Outbox. The code uses the Add method of the Items collection of the Outbox to add a mail item to the outbox. The code then sets the appropriate properties (such as To, Subject, and so on) to the appropriate pieces of the error information. Finally, the code can either save the message as a draft, or else send it so that it's immediately sent to the administrator and appears in the Sent Items folder of the sender.

NOTE

Listing 16.21 requires that you know the username and profile of the user sending the email. You could provide a dialog where the user can enter this information each time an error occurs, or this could be part of the initial logon to the system. I generally recommend that this be integrated as part of the initial logon, so that the user does not have to take extra steps each time an error occurs.

Creating an Error Form


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 Application Programming Interface (API) calls to fill in the system information on your form. The code is shown in Chapter 23, "Exploiting the Power of the Windows API."

Listing 16.22 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.23. You will find them in a module called basAPI.

Listing 16.23 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
dwNumberOfProcessors 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.24.

Listing 16.24 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.

Figure 16.6. The frmErrors form displays important information about the error that occurred.


Printing the Error Form


Users often are not very accurate in describing an error and the corresponding error message. It's therefore important to give them the capability to print their error message. The code in Listing 16.25 prints your error form. You will find it behind the click event of the Print button on the error form.

Listing 16.25 Printing an Error Form

Sub cmdPrint_Click()
On Error GoTo Err_cmdPrint_Click
'Use the PrintOut method to print the form
DoCmd.PrintOut
Exit_cmdPrint_Click:
Exit Sub
Err_cmdPrint_Click:
MsgBox Err.Description
Resume Exit_cmdPrint_Click
End Sub

/ 544