Trapping Program Errors

     

Your VBA procedures are, in final analysis, really quite dumb. After all, they can only do what you, the programmer, tell them to do. For example, if you tell a program to copy a file to a nonexistent disk, the dim brute just doesn't have the smarts to pull back from the abyss.

graphics/note_icon.gif

There's a popular and appealing tale of how the word bug came about. Apparently, an early computer pioneer named Grace Hopper was working on a machine called the Mark II in 1947. While investigating a glitch, she found a moth among the vacuum tubes, so from then on glitches were called bugs. Appealing, yes, but true? Not quite. In fact, engineers had already been referring to mechanical defects as " bugs " for at least 60 years before Ms. Hopper's discovery. As proof, the Oxford English Dictionary offers the following quotation from an 1889 edition of the Pall Mall Gazette :

"Mr. Edison, I was informed, had been up the two previous nights discovering 'a bug' in his phonograph ”an expression for solving a difficulty, and implying that some imaginary insect has secreted itself inside and is causing all the trouble."


Later in this chapter you'll learn quite a few techniques that will prove invaluable for stomping on program bugs, so hopefully you'll be able to ship problem-free applications. However, a good and careful programmer always assumes that something, somewhere, at some time can and will go wrong (think of this as a kind of "Murphy's Law of Coding"). Given this heightened (enlightened?) state of paranoia , you must code your projects to allow for potential errors, no matter how obscure. A properly designed program doesn't leave you or another user out in the cold if an error rears its ugly head. Instead, you need to install code that traps these errors and either fixes the problem (if possible), alerts the user to the error so that he or she can fix it (such as by inserting a disk in a floppy drive), or reports a meaningful explanation of what went wrong so that the user can give you feedback. To that end, this section takes you through VBA's error-trapping techniques.

A Basic Error-Trapping Strategy

For many programmers, adding error-trapping code to a procedure can usually be found near the bottom of their to-do lists (probably just before adding comments to a procedure!). That's because error-trapping code isn't even remotely glamorous, and the optimistic (some would say foolhardy) programmer assumes it will never be needed.

That's a shame, because setting up a bare-bones error trap takes very little time. Even a more sophisticated trap can be reused in other procedures, so you really only have a one-time expenditure of energy. To help you get started down this crucial path toward good program hygiene, this section presents a basic strategy for writing and implementing error-trapping code. This strategy will unfold in four parts :

  • Setting the error trap

  • Coding the error handler

  • Resuming program execution

  • Disabling the error trap

Setting the Trap

In the simplest error-trapping case, VBA offers what I call the "never mind" statement:

 On Error Resume Next 

When inserted within a procedure, this statement tells VBA to bypass any line in the procedure that generates an error and to resume execution with the line that immediately follows the offending statement. No error message is displayed, so the user remains blissfully unaware that anything untoward has occurred. There are three things to note about implementing this statement:

  • The trap applies to any executable statement that occurs after the On Error Resume Next statement.

  • The trap also applies to any executable statement within each procedure that is called by the procedure containing the On Error Resume Next statement.

  • The trap is disabled when the procedure ends.

Because the On Error Resume Next statement does nothing to resolve whatever caused the error, and because skipping the offending statement might cause further errors, this error trap is used only rarely.

To set a true error trap, use the On Error GoTo statement instead:

 On Error GoTo  line  

Here, line is a line label, which is a statement that's used to mark a spot within a procedure (line labels aren't executable). The idea is that, if an error occurs, the procedure containing the On Error GoTo statement will branch immediately to the first statement after the line label. This statement should be the beginning of the error handler code that processes the error in some way (see the next section). Here's the general structure that sets up a procedure to trap and handle an error:

 Sub Whatever()     On Error GoTo ErrorHandler  [regular procedure statements go here]  '     ' If no error occurs, bypass the error handler     '     Exit Sub     '     ' If an error occurs, the code will branch here     ' ErrorHandler:  [error handler code goes here]  End Sub 

Here are some notes about this structure:

  • To ensure that all statements are protected, place the On Error GoTo statement at the top of the procedure.

  • The last statement before the error handler line label should be Exit Sub (or Exit Function if you're working with a Function procedure). This ensures that the procedure bypasses the error handler if no error occurs.

  • The line label is a string ”without spaces or periods ”followed by a colon (:) at the end to tell VBA that it's just a label and should not be executed.

Coding the Error Handler

The On Error GoTo statement serves as the mechanism by which errors are trapped, but the nitty-gritty of the trap is the error handler. The handler is a group of statements designed to process the error, either by displaying a message to the user or by resolving whatever problem raised the error.

graphics/note_icon.gif

This chapter's code can be found on my Web site in both Excel and Word versions:

http://www.mcfedries.com/ABGVBA/Chapter15.xls

http://www.mcfedries.com/ABGVBA/Chapter15.doc


The simplest error handler just displays a message that tells the user that a problem occurred. Listings 15.1 and 15.2 provide an example. Listing 15.1 uses a couple of InputBox functions to get two numbers from the user: a dividend and a divisor. With these values in hand, the procedure calls the Divide function, as shown in Listing 15.2.

Listing 15.1. The GetNumbers Procedure Prompts the User for a Dividend and a Divisor
 Sub GetNumbers()     Dim done As Boolean     Dim divisor As Variant     Dim dividend As Variant     '     ' Prompt user for dividend and divisor.     '     done = False     Do While Not done         dividend = InputBox("Enter the dividend:", "Divider")         divisor = InputBox("Enter the divisor:", "Divider")         done = Divide(dividend, divisor)     Loop End Sub 

The purpose of the Divide function, shown in Listing 15.2, is to divide the dividend argument by the divisor argument. To trap a "division by zero" error, an On Error GoTo statement tells VBA to branch to the DivByZeroHandler label. (Actually, this statement will trap any error, not just a division by zero error.) The division is performed, and, if all goes well, a MsgBox displays the result. However, if the divisor value is zero, an error will occur, and the code will branch to the DivByZeroHandler label. This error handler displays a message and asks the user if he wants to try again. The function's return value is set according to the user's choice.

Listing 15.2. The Divide Function Divides the Dividend by the Divisor and Traps "Division by Zero" Errors
 Function Divide(dividend, divisor) As Boolean     Dim msg As String     Dim result As Single     '     ' Set the trap     '     On Error GoTo DivByZeroHandler     '     ' Perform the division     '     result = dividend / divisor     '     ' If it went okay, display the result     '     msg = dividend & _           " divided by " & _           divisor & _           " equals " & _           result     MsgBox msg     '     ' Set the return value and bypass the error handler     '     Divide = True     Exit Function     '     ' Code branches here if an error occurs     ' DivByZeroHandler:     '     ' Display the error message     '     result = MsgBox("You entered 0 as the divisor! Try again?", _                     vbYesNo + vbQuestion, _                     "Divider")     '     ' Return the user's choice     '     If result = vbYes Then         Divide = False     Else         Divide = True     End If End Function 

In this example, setting up the error handler was no problem because the potential error ”division by zero ”was a fairly obvious one. (Also note that in a production application you'd confirm a nonzero divisor as soon as the user entered the value rather than wait for the division to occur.) In practice, however, your error handlers will require a more sophisticated approach that tests for multiple error types. For this you need to know about error numbers. I'll discuss those later in this chapter, in the section "Err Object Properties."

Resuming Program Execution

In Listing 15.2, the error message displayed to the user asks if he or she wants to input the values again, and an If...Then tests the response and sets the function's return value accordingly . This example is a bit contrived because your errors won't necessarily occur inside a Function procedure or loop. However, you'll often still need to give the user a choice of continuing with the program or bailing out. To do this, you can add one or more Resume statements to your error handlers. VBA defines three varieties of Resume statements:

Resume

Tells VBA to resume program execution at the same statement that caused the error.

Resume Next

Tells VBA to resume program execution at the first executable statement after the statement that caused the error.

Resume line

Tells VBA to resume program execution at the label specified by line .

Listing 15.3 shows an example. The BackUpToFloppy procedure is designed to get a drive letter from the user and then save the active workbook to that drive. If a problem occurs (such as having no disk in the drive), the procedure displays an error message and gives the user the option of trying again or quitting.

Listing 15.3. This Procedure Backs Up the Active Workbook to a Drive Specified by the User and Traps any Errors (Such As Having No Disk in the Drive)
 Sub BackUpToFloppy()     Dim backupDrive As String     Dim backupName As String     Dim msg As String     Dim done As Boolean     Dim result As Integer     '     ' Define the location of the error handler     '     On Error GoTo ErrorHandler     '     ' Initialize some variables and then loop     '     Application.DisplayAlerts = False     done = False     backupDrive = "A:"     While Not done         '         ' Get the drive to use for the backup         '         backupDrive = InputBox( _             Prompt:="Enter the drive letter for the backup:", _             Title:="Backup", _             Default:=backupDrive)         '         ' Check to see if OK was selected         '         If backupDrive <> "" Then             '             ' Make sure the backup drive contains a colon (:)             '             If InStr(backupDrive, ":") = 0 Then                 backupDrive = Left(backupDrive, 1) & ":"             End If             '             ' First, save the file             '             ActiveWorkbook.Save             '             ' Assume the backup will be successful,             ' so set done to True to exit the loop             '             done = True             '             ' Concatenate drive letter and workbook name             '             backupName = backupDrive & ActiveWorkbook.Name             '             ' Make a copy on the specified drive             '             ActiveWorkbook.SaveCopyAs FileName:=backupName         Else             Exit Sub         End If     Wend     '     ' Bypass the error handler     '     Exit Sub     '     ' Code branches here if an error occurs     ' ErrorHandler:     msg = "An error has occurred!" & vbCr & vbCr & _           "Select Abort to bail out, Retry to re-enter the drive" & _           vbCr & "letter, or Ignore to attempt the backup again."     result = MsgBox(msg, vbExclamation + vbAbortRetryIgnore)     Select Case result         Case vbAbort             done = True         Case vbRetry             done = False             Resume Next         Case vbIgnore             Resume     End Select End Sub 

The bulk of the procedure asks the user for a drive letter, saves the workbook, concatenates the drive letter and workbook name, and saves a copy of the workbook on the specified drive.

The error routine is set up with the following statement at the top of the procedure:

 On Error GoTo ErrorHandler 

If an error occurs, the procedure jumps to the ErrorHandler label. The error handler's MsgBox function gives the user three choices (see Figure 15.1), which get processed by the subsequent Select Case structure:

Figure 15.1. If an error occurs, the error handler displays this dialog box.

graphics/15fig01.jpg

Abort

Selecting this option ( Case vbAbort ) bails out of the While...Wend loop by setting the done variable to True.

Retry

Selecting this option ( Case vbRetry ) means the user wants to re-enter the drive letter. The done variable is set to False, and then the Resume Next statement is run. If the error occurs during the SaveCopyAs method, the next statement is Wend , so the procedure just loops back (because we set done to False) and runs the InputBox function again.

Ignore

Selecting this option ( Case vbIgnore ) means the user wants to attempt the backup again. For example, if the user forgot to insert a disk in the drive, or if the drive door wasn't closed, the user would fix the problem and then select this option. In this case, the error handler runs the Resume statement to retry the SaveCopyAs method (or whatever).

Disabling the Trap

Under normal circumstances, an error trap set by the On Error GoTo statement is disabled automatically when the procedure containing the statement is finished executing. However, there might be times when you want to disable an error trap before the end of a procedure. For example, when you're testing a procedure, you might want to enable the trap for only part of the code and let VBA generate its normal runtime errors for the rest of the procedure.

To disable an error trap at any time during a procedure, even within an error handler, use the following statement:

 On Error GoTo 0 

Working with the Err Object

The problem with the error traps we've set so far is a lack of information. For example, the Divide function (in Listing 15.2) assumes that any error that occurs is a result of an attempted division by zero. However, there are two other runtime error possibilities:

  • Overflow ” This error is raised if both the dividend and divisor are 0.

  • Type mismatch ” This error is raised if either value is nonnumeric.

It's likely that you'll want your error handler to treat these errors differently. For example, a division by zero error requires only that the divisor be re-entered, but an overflow error requires that both the dividend and the divisor be re-entered.

To handle different errors, VBA provides the Err object, which holds information about any runtime errors that occur. You can use the properties of this object to get specific error numbers and descriptions.

Err Object Properties

The Err object has a number of properties, but the following three are the ones you'll use most often:

Err.Description ” Returns the error description.

Err.Number ” Returns the error number.

Err.Source ” Returns the name of the project in which the error occurred.

For example, Listing 15.4 shows a procedure that attempts to divide two numbers. The Err object is used in two places within the error handler:

  • The error message displayed to the user contains both Err.Number and Err.Description .

  • A Select Case structure examines Err.Number to allow the handler to perform different actions depending on the error.

Listing 15.4. This Procedure Divides Two Numbers and Traps Three Specific Errors: Division by Zero, Overflow, and Type Mismatch
 Sub DivideNumbers()     '     ' Set the trap     '     On Error GoTo DivByZeroHandler     '     ' Declare variables     '     Dim divisor As Variant     Dim dividend As Variant     Dim result As Single     Dim msg As String     '     ' Prompt user for the dividend     ' GetDividendAndDivisor:     dividend = InputBox("Enter the dividend:", "Divider")     If dividend = "" Then Exit Sub     '     ' Prompt user for the divisor     ' GetDivisorOnly:     divisor = InputBox("Enter the divisor:", "Divider")     If divisor = "" Then Exit Sub     '     ' Perform the division     '     result = dividend / divisor     '     ' If it went okay, display the result     '     msg = dividend & _           " divided by " & _           divisor & _           " equals " & _           result     MsgBox msg     '     ' Bypass the error handler     '     Exit Sub     '     ' Code branches here if an error occurs     ' DivByZeroHandler:     '     ' Display the error message     '     msg = "An error occurred!" & Chr(13) & Chr(13) & _           "Error number:  " & Err.Number & Chr(13) & _           "Error message: " & Err.Description     MsgBox msg, vbOKOnly + vbCritical     '     ' Check the error number     '     Select Case Err.Number         '         ' Division by zero         '         Case 11             Resume GetDivisorOnly         '         ' Overflow         '         Case 6             Resume GetDividendAndDivisor         '         ' Type mismatch         '         Case 13             If Not IsNumeric(dividend) Then                 Resume GetDividendAndDivisor             Else                 Resume GetDivisorOnly             End If         '         ' Anything else, just quit         '         Case Else             Exit Sub     End Select End Sub 


Absolute Beginner's Guide to VBA
Absolute Beginners Guide to VBA
ISBN: 0789730766
EAN: 2147483647
Year: 2003
Pages: 146

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net