How would I do an inline error handling routine in VBA? I don't want to put the error handler at the end.
This is from CPearson's Error Handling in VBA
Sub testErrHandling()
On Error GoTo ErrHandler:
Debug.print 9 / 0 'divide by zero error
Worksheets("NewSheet").Activate 'missing worksheet error
'more code here
Exit Sub
ErrHandler:
If Err.Number = 9 Then
' sheet does not exist, so create it
Worksheets.Add.Name = "NewSheet"
' go back to the line of code that caused the problem
Resume
End If
End Sub
But I'm looking for something more like a Try/Catch block in VB.net
This code will handle the error inline. This is a very cleanly structured pattern for handling an error. The flow moves very cleanly from top to bottom; no spaghetti code here.
VBA is an old language and has limitations. One of the ways to use error handling is to use Goto statements in the form of On Error Goto <Label> and Resume <Label>. This creates an opportunity.
Traditionally the error handler is placed at the bottom. But with the advances made in VB.net, it seems reasonable to leverage ideas to improve code. Try/Catch is a very structured way of handling errors and is very easy to follow. This pattern attempts to reproduce that in a very clean concise way. The flow is very consistent and doesn't jump from place to place.
Sub InLineErrorHandling()
'code without error handling
BeginTry1:
'activate inline error handler
On Error GoTo ErrHandler1
'code block that may result in an error
Dim a As String: a = "Abc"
Dim c As Integer: c = a 'type mismatch
ErrHandler1:
'handle the error
If Err.Number <> 0 Then
'the error handler is now active
Debug.Print (Err.Description)
End If
'disable previous error handler (VERY IMPORTANT)
On Error GoTo 0
'exit the error handler
Resume EndTry1
EndTry1:
'more code with or without error handling
End Sub
Sources:
Pearson Error Handling In VBA
How to: Handle Run-Time Errors in VBA
Properly Handling Errors in VBA (Excel)
Properly managed this works quite nicely. It is a very clean flowing pattern that is reproducible anywhere it is needed.
You can try assigning your object in a variable and use On Error Resume Next instead.
Dim sh As Worksheet
'This is essentially the "Try" part
On Error Resume Next 'this ignores the error
Set sh = Worksheets("NewSheet")
On Error Goto 0 'this resets the active error handling routine
'Then this is the "Catch" part I guess
If sh Is Nothing Then 'check is something is assigned to sh
'And I think this is "Finally" part
Set sh = Worksheets.Add: sh.Name = "NewSheet" 'add otherwise
End If
Not really familiar with the Try/Catch since I've not done some VB.Net but this is the closest inline error correction I can think of for your example. HTH.
After months of insecurity/confusion about some obscure laws of VBA error handling, never fully satisfied by any of the popular documentation pages (Microsoft VBA/VB, C. Pearson and a others), I dedicated some days of trial and error to reconstruct the complete set of the (written and unwritten/corrected) rules governing VBA error handling:
LEGEND:
1A: On Error Goto 0. 1B: On Error GoTo line/label. 1C: On Error Resume Next
2A: an error occurs. 2B: On Error GoTo -1
3: being in a subprocedure, temporarily, just until coming back
4: Erl
5: Resume, Resume Next, Resume line/label
6: Exit […], End […]
RULES:
With 1A in effect*, error handling is and stays disabled and inactive. This is the default.
With 1B in effect*, error handling is initially enabled and inactive; it's disabled while 3 and by 2A or 6, and it's activated on 2A and inactivated while 3 and by 5 (that also reenable it) or 6.
With 1C in effect*, error handling is initially enabled and inactive; it's disabled only while 3 and by 6 (it's not disabled by 2A!), and it always stays inactive (supposedly activated and inactivated immediately on 2A).
*= If 1A, 1B, 1C are called while error handling is active, the Err object is cleared immediately but the On Error action change effect is delayed until error handling is inactivated (by 5 or 6).
2B (instantaneously) inactivates error handling (if any); error handling returns(1B)/stays(1C) enabled.
1A, 1B, 1C, 2B, 5, 6 also instantaneously clears the Err object.
If an error occurs while in a subprocedure, if unhandled (disabled) in the current subprocedure it's passed to the first calling (parent) procedure where it's enabled and inactive. If no one is found, it stays there (in the subproc.).
4 if error handling is active returns the line (if specified as a number label) of last error, otherwise 0.
5 (correctly) crashes (err 21: "Resume without error") if called while error handling is inactive.
With all these rules fresh in mind, and starting from the D_Bester's solution posted here, here's my revised solution correcting a couple of errors/inefficiences in his code:
Sub InLineErrorHandling()
'code without error handling
On Error GoTo ErrHandler1 'enable error handler
'code block that may result in an error
Dim a As String: a = "Abc"
Dim c As Integer: c = a 'type mismatch
'inline error handler routine
ErrHandler1:
If Err.Number <> 0 Then
Debug.Print err.Description
On Error GoTo -1 ' inactivate error handler
End If
On Error GoTo 0 'disable error handler
'more code without error handling (default mode)
Err.Raise 123
End Sub
... where if you want to avoid using the undocumented On Error Goto -1 you can change the error handling routine this way:
ErrHandler1: If Err.Number <> 0 Then
Debug.Print err.Description
Resume ErrHandler1end 'inactivate & exit error handler
End If
ErrHandler1end: On Error GoTo 0 'disable error handler
... and if no errors are expected in the error handling routine, can be further reduced to this (very standard) alternative:
Sub InLineErrorHandling()
'code without error handling
On Error Resume Next 'enable error handler
'code block that may result in an error
Dim a As String: a = "Abc"
Dim c As Integer: c = a 'type mismatch
'inline error handler routine
If Err.Number <> 0 Then
Debug.Print err.Description
End If
On Error GoTo 0 'disable error handler
'more code without error handling (default mode)
err.Raise 123
End Sub
It also worth mentioning that, in both the choices, if we also want to know what line (first for 1B, last for 1C) of the "code block that may result in an error" resulted in an error, we can use the Erl function, like this:
...
'code block that may result in an error
10 Dim a As String: a = "Abc"
20 Dim c As Integer: c = a 'type mismatch
'inline error handler routine
If Err.Number <> 0 Then
Debug.Print "Error """ & err.Description & """ in line " & Err
...
Related
The following statements (in a loop) end in an error when row 1 is empty.
I would have expected the error to be silently ignored and the next sheet to be processed.
How come the code stops when the on error is active ? I would expect it to ignore the error and continue processing after the skip label.
Sub listSheets()
Dim sh As Worksheet, ar
Dim a
Set a = Application
For Each sh In ThisWorkbook.Sheets
Sheet1.Cells(3 + sh.Index, 1) = sh.Name
On Error GoTo skip
ar = sh.Range("1:1").SpecialCells(xlCellTypeConstants) 'code stops here if row 1 empty
ar = a.Transpose(a.Transpose(ar))
Sheet1.Cells(3 + sh.Index, 2) = Join(ar, "//")
ar = Null
skip:
Next sh
End Sub
Note: Error trapping is set to "Break on unhandled errors"
I believe the reason lays in the fact you allready encountered an error. You however never cleared your error from the handler. A second error won't skip over that line again.
So to replicate this I had three worksheets, a blank row in the second and third. Your On Error Goto went through the second sheet but would return Error 1004 on the third.
You might want to include an Err.Clear
On Error Resume Next
ar = sh.Range("1:1").SpecialCells(xlCellTypeConstants) 'error 1004 on certain sheets
skip:
Err.Clear
Next sh
EDIT: Just found an alternative solution here
And even then, maybe even drop the error handling completely and use something like:
If WorksheetFunction.CountA(sh.Rows(1)) > 0 Then
ar = sh.Range("1:1").SpecialCells(xlCellTypeConstants)
End If
A Resume statement is needed after an error occurs in which an error handler is enabled by the On Error statement. As per VBA reference...
If an error occurs while an error handler is active (between the occurrence of the error and a Resume, Exit Sub, Exit Function, or Exit Property statement), the current procedure's error handler can't handle the error.
For additional information have a look at the following link...
https://learn.microsoft.com/en-us/office/vba/language/reference/user-interface-help/on-error-statement
I have a weird error reported by a user of a VBA based application
and I wonder how this just possible that an error is raised by the VBA IDE considreing the line above is an error handler.
The code is the following. It creates a form of class frmCfgPrjctTm. I used to have some automation errors once in a while with weird message from the VBA IDE.
Run-time error '-2147418105 (800100007)':
Automation error
The object invoked has disconnected from its clients.
I use Excel 2016 32 bits on windows 7.
To work around the problem I had to implement a retry strategy to create the form. This explains the retry loop.
The error is raised on line mForm.Show vbModeless.
The error handler is set with instruction On Error GoTo ErrorHandler the line just before!
Private mForm As frmCfgPrjctTm
''
' U_CfgPrjctTm_OnOpen
Public Sub U_CfgPrjctTm_OnOpen()
Dim iRetry As Integer
iRetry = 0
If (mForm Is Nothing) Then
ErrorExit:
If (iRetry > 1) Then
Call U_UnlockTeam
GoTo ExitSub
End If
iRetry = iRetry + 1
Set mForm = New frmCfgPrjctTm
End If
On Error GoTo ErrorHandler
'>>>>>> the error occurs after this comment
mForm.Show vbModeless
ExitSub:
Exit Sub
ErrorHandler:
Debug.Print "***** Error trapped in U_CfgPrjctTm_OnOpen *****"
GoTo ErrorExit
End Sub
this post raises 2 questions but I am seeking for help or sharing for the following :
why an error is raised by the IDE while there is an error handler set ?
The answer to your question "why an error is raised by the IDE while there is an error handler set ?" is that you use error handling in a wrong way. Look at the following example
Option Explicit
Sub TestErrH()
Dim i As Long
i = 42
ErrorExit:
On Error GoTo ErrorHandler
Debug.Print 42 / i
Debug.Print 1 / 0
Exit Sub
ErrorHandler:
i = 0
GoTo ErrorExit
End Sub
The error handler will raise in the line Debug.print 1/0 and then the error handler will set i = 0 and jump back to the line debug.print 42/i. At that time the error handling is in your hands. You have to make sure the code is fail safe. The error handler will not again jump to the label ErrorHandler instead a run time error will pop up.
I have a macro, and part of the code is concerned with:
1) detecting if a column contains empty cells - if so filling them with some value
2) detecting if a column contains cells containing errors (such as N/A) and if so filling them with some value
Now if there are no error/empty cells in the column, the line that finds them gives a "Run-time error 1004 no cells were found".
I use error handling to skip this with GoTo.
Below is the code - the first error handling GoTo works perfectly, while the second gives the expected error, although there is Error handling GoTo set, that does not seem to work. Code with comments:
On Error GoTo EErrorOne
'depending on file I get, below line will generate error and code successfully skips to ErrorOne label
Workbooks(nazwawb).Sheets(szitnr).Columns(ktorepole).SpecialCells (xlCellTypeBlanks)
' code to be skipped
Workbooks(nazwawb).Sheets(szitnr).Columns(ktorepole).Select
Selection.SpecialCells(xlCellTypeBlanks).Select
Selection.Value = "Some Value"
' end of code to be skipped
EErrorOne:
' successfully skipped error line by now. Below line should set a new Error handling procedure.
On Error GoTo EErrorTwo
Workbooks(nazwawb).Sheets(szitnr).Columns(ktorepole).Select
' Below line generates an error but does not skip to EErrorTwo label as detailed in the current Error handling procedure
Selection.SpecialCells(xlCellTypeFormulas, 16).Select
' code to be skipped
Selection.SpecialCells(xlCellTypeFormulas, 16).Select
Selection.Value = "Some Value"
' end of code to be skipped
EErrorTwo:
' Below line should reset error handling procedure to normal for subsequent handling of other errors:
On Error GoTo 0
It seems that error handling procedure (GoTo specific label) is ignored, and instead, an error message is shown as if the error handling was reset to GoTo 0. How do I skip the second error?
You're not clearing your errors when they occur, just trying to jump over them and the code is wondering what's going on with the error.
As Chip Pearson says on his site:
When the first error is raised, execution transfers to the line
following Err1:. The error hander is still active when the second
error occurs, and therefore the second error is not trapped by the On
Error statement
and continues with
The Resume statement instructs VBA to resume execution at a specified
point in the code. You can use Resume only in an error handling
block; any other use will cause an error. Moreover, Resume is the only
way, aside from exiting the procedure, to get out of an error handling
block. Do not use the Goto statement to direct code execution out of
an error handling block. Doing so will cause strange problems with
the error handlers.
http://www.cpearson.com/excel/errorhandling.htm
The ideal way is to avoid the error in the first place - check the workbook exists before opening it, check the sheet exists in the workbook before trying to reference it and if an error occurs jump out of the main body of the routine, deal with the error and then jump back in again.
As an example:
Sub Test()
On Error GoTo ERR_HANDLE
'.... code ....
FAST_EXIT:
'Code to tidy up, clear references etc before finishing.
Exit Sub
ERR_HANDLE:
Select Case Err.Number
Case 1004
'Code to resolve error
'.
'.
'Resume - clear error, jump back to line that caused it.
'Resume Next - clear error, jump to line after the one that caused error.
'Resume FAST_EXIT - clear error and go to line label.
Case 92 'Do something else to resolve error.
Case Else
End Select
End Sub
Inside error handling routines, it seems as though defining new error handling routines won't work, unless you clear the previously set error routine (https://excelmacromastery.com/vba-error-handling/):
'...
EErrorOne:
On Error GoTo -1 'Clears error trap flag
On Error GoTo EErrorTwo 'Reset error handling
'...
Edit after accepted:
As was discussed in the comments, On Error GoTo -1 clears the error trap flag, while Err.Clear only clears the error.
The code below illustrates this by creating two errors and trying to trap them.
On Error Goto -1 allows the second error to be trapped by the On Error GoTo NextLabel line and the code jumps to the label when the error occurs.
Err.Clear keeps the first error live, so when the second error occurs the error message is displayed rather than the code jumping to the label.
Sub ClearErr()
Dim x As Long
Dim y As Worksheet
'Jump to label when "Division by 0" occurs.
On Error GoTo ErrorLabel
x = 1 / 0
Debug.Print x
ErrorLabel:
'On Error GoTo -1 'Next error is trapped.
Err.Clear 'Untrapped error on y=Worksheets(1)
'Jump to label when "Object Variable not set" occurs.
On Error GoTo NextLabel
y = Worksheets(1)
Debug.Print y.Name
NextLabel:
End Sub
I have the following two lines of code:
On Error Resume Next
myWorkbook.Sheets("x").Columns("D:T").AutoFit
I've stepped into the macro and executed the line On Error Resume Next and then on the next line myWorkbook... it does the following:
Why doesn't the compiler resume the next line of code?
On Error has been liberally used throughout the procedures code; I realize best practice is to use this as little as possible but it seems to fit the purpose of this macro.
Reading this SO QUESTION it says that you can't have one set of error trapping within another. How can I guarantee that one set of error trapping has been "closed off" before the code moves on - does On Error Goto 0 reset the error trapping? If it does reset then why doesn't resume work in the following?:
Sub GetAction()
Dim WB As Workbook
Set WB = ThisWorkbook
On Error GoTo endbit:
'raise an error
Err.Raise 69
Exit Sub
endbit:
On Error GoTo 0
On Error Resume Next
WB.Sheets("x").Columns("D:T").AutoFit
End Sub
There is also a VBA setting that will cause On Error ... statements to be ignored and that dialog box to always appear. See this answer for more details on checking/changing the option: https://stackoverflow.com/a/3440789/381588
An example of an error when the initial error is not closed out.
Sub GetAction()
Dim WB As Workbook
Set WB = ThisWorkbook
On Error GoTo endbit:
'raise an error
Err.Raise 69
Exit Sub
endbit:
On Error Resume Next
WB.Sheets("x").Columns("D:T").AutoFit
End Sub
As you have found, within the same function or subroutine, On Error Resume Next doesn't override On Error Goto ... if it's still active.
You are correct that On Error Goto 0 restores the default error handler.
There are some cases where On Error is the most appropriate way to handle an exceptional condition. I prefer to use the following structure:
On Error Resume Next
statement which might fail
On Error Goto 0
if statement has failed then ...
This keeps everything together, but in other cases a generic error handler at the end of the procedure can be better.
I've found that in functions/subs that iterates over nested objects, errorhandling might be a drag in VBA. A solution that works for me to better handle complex iterations is separating setting of objects in their own functions, e.g.
main function/sub:
set FSOfolder = SetFSOFolder(FSOobject, strFolder)
Private Function SetFSOFolder(FSO as scripting.FileSystemObject, strFolder as string) as Scripting.Folder
on error resume Next
set SetFSOFolder = FSO.GetFolder(strFolder)
on error goto 0
End Function
and then in the next line in main function:
if (not fsofolder is nothing) then
I agree using On Error Resume Next is not best practice but most/many of my Access apps are not overly sensitive to minor nuances in data integrity (i.e. analytical or reporting, not transactions and not audited). For this reason I use OERN quite often because VBA is very sensitive to some situations that you cannot anticipate entirely.
1 - will the error cause data corruption. If yes handle it. Many routines I use are just crunching a large volume of records and there may be errors in imported data that haven't been fixed. Usually I have a lot of conversion processes that will let the system clean up its own data eventually.
2 - is the error frequent and non-critical (ie not a key). If yes it's OERN otherwise the error may not be predicable and you end up crashing out or have to write a bunch of I-T-E or Select Case logic to trap it.
Don't use On Error Resume Next instead write code that shouldn't crash.
Note: I am being careful how I phrase that because you never guaranty code doesn't crash. But if you use On Error Resume Next then part of the natural flow of your code is for it to crash, which is wrong, big time wrong.
Can somebody explain me what this code means ?
I guess its if an error occurs go to section ErrHandler, but why is there a resume next at the end and Exit sub inbetween ?
On Error Goto ErrHandler:
N = 1 / 0 ' cause an error
'
' more code
'
Exit Sub
ErrHandler:
' error handling code
Resume Next
End Sub
I also wrote a short code to get a better understanding.
If you run it in VBA excel you will get 4 numbers that pops up in order : 1,2,2,4
I understand the first 2 numbers , but why is the third number 2 and the last one 4 ?
btw for this example no error occured .
On Error Goto Err:
n = 1
MsgBox n
Err:
n = 2
MsgBox n
Resume Next
MsgBox 4
Resume Next
End Sub
It has already been said that Resume Next will pick up execution at the point after the error occurred. There is also Resume, which picks up execution starting with the line that caused the error and Resume , which picks up execution at a specified label.
So, this is what happens in the code you provided:
N = 1 / 0 raises a divide by zero error
execution is moved to the ErrHandler label.
Error handling code will run till it reaches Resume Next.
Resume next will move execution to the line after N = 1/0. Since you are going back it makes sense that your error handling code might do something like this N = 1, otherwise the rest of the code will try to use N which is uninitialized and you will get another error.
The following link provides a simple explanation oerror handling and the three resume statements:
http://www.cpearson.com/excel/ErrorHandling.htm
It picks up execution of the method body at the point after where the error occured.
From the On Error documentation:
Specifies that when a run-time error occurs, control goes to the statement immediately following the statement where the error occurred, and execution continues from that point.