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
Related
I have tried using If Error GoTo "blank" multiple times, so that it skips the next section of code if a sheet won't activate. It will work the first time, then the second time it won't skip past the error, just presents it as if there was no If Error command at all. The code looks like this:
On Error GoTo errorHandler1
Windows(MyFile).Activate
Sheets(secondDetailTab).Activate
'bunch of code
errorHandler1:
'bunch of code
On Error GoTo errorHandler2
Windows(MyFile).Activate
Sheets(secondDetailTab).Activate
'bunch of code
errorHandler2:
'bunch of code
If Sheets(secondDetailTab).activate has an error the first time, it will skip to errorHandler1 like it's supposed to, but if there's an error the second time then it just presents the error normally and ignores the On Error GoTo errorHandler2 command. Any suggestions would be greatly appreciated!
You could do it indeed in the way as you want to do it although this is not best practise to do it like that. You need to reset the error with On Error Goto -1 before you "activate" another error handler.
On Error GoTo errorHandler1
Windows(myFile).Activate
Sheets(secondDetailTab).Activate
'bunch of code
errorHandler1:
'bunch of code
On Error GoTo -1
On Error GoTo errorHandler2
Windows(myFile).Activate
Sheets(secondDetailTab).Activate
'bunch of code
errorHandler2:
'bunch of code
Here you find a pretty good tutorial on error handling. There is also an explanation for On Error Goto -1
In the Remarks section of the On Error MS doc
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.
My suggestion is that instead of stacking error handlers, you test and avoid errors in the main code. If Sheets(secondDetailTab).Activate is predictably causing errors then first test if the sheet exists and is valid. I wrote a short function that does that:
Function SheetExists(ByRef ExcelBook As Workbook, ByVal SheetName As String) As Boolean
Dim ws As Object
For Each ws In ExcelBook.Sheets
If ws.Name = SheetName Then Exit For
Next ws
SheetExists = Not ws Is Nothing
'True = it Exists, False = Doesn't Exist
End Function
Sub Example()
Const sName As String = "Sheet4"
Dim B As Boolean
B = SheetExists(ThisWorkbook, sName)
MsgBox B
End Sub
You could implement this into your code by changing Sheets(secondDetailTab).Activate into If SheetExists(WB, secondDetailTab) Then Sheets(secondDetailTab).Activate
Error handling in VBA is primitive compared to other languages. It is much better to check for error conditions preemptively than to catch errors after they occur. Check to see if files exist before you try to open them, don't assume the function you called succeeded--check the return value. If you practice these steps consistently, you almost never need to handle errors.
When you do need to handle errors, you can be very specific about the scope of the error trap and which errors you're handling. Here is a pattern I have found myself using a lot. You wrap a single line in the error handler, then check the error code. For example, I prefer Toddleson's code above, which checks to see if a worksheet exists by enumerating the existing worksheets, but this an alternative implementation which illustrates the use of tightly focused error handling.
‘ Get worksheet by name. If the worksheet does not exist, create a new worksheet.
Function GetWorksheet(ByVal name As String) As Worksheet
Dim ws As Worksheet
' Turn on error handling.
On Error GoTo Error_NoSuchSheet
Set ws = Worksheets(name)
' Turn off error handling.
On Error GoTo 0
' Create new worksheet.
If ws Is Nothing Then
Set ws = Worksheets.Add
ws.name = name
End If
Set GetWorksheet = ws
Exit Function
Error_NoSuchSheet:
If Err.Description = "Subscript out of range" Then
' Resume execution on the line following the one that threw the error.
Resume Next
Else
' Invoke the default VBA error handler.
Err.Raise Err.Number
End If
End Function
Note that only a specific error on one specific line is handled. An error on any other line will receive the default error handling (VBA pops up a dialog box). The same thing if any other error occurs on that one line. This pattern provides a much more finely granulated control of error conditions. This is not necessarily the best way to handle an error in every situation and it is far from the only way, but I think it has significant advantages over the pattern you described.
Of course any error handling, or no error handling at all, is significantly better than this.
Function IgnoreAllErors()
' Ignore all errors.
On Error Resume Next
' Your code here
End Function
Please don't do this.
I am not able to access the error description (or error number) in Excel VBA error handling.
If I run the following code, the error description remains Empty:
Option Explicit
Sub TestError()
On Error GoTo ErrorHandler
Debug.Print 1 / 0
'Alternative: (Does not display error number either):
'Err.Raise 10000, , "Some error description"
ErrorExit:
Exit Sub
ErrorHandler:
On Error Resume Next
MsgBox Err.Description
Resume ErrorExit
End Sub
If I replace the Debug.Print 1 / 0 with an Err.Raise statement, the description remains empty as well.
However, if I remove the On Error GoTo ErrorHandler, the description and error number is shown as expected.
What am I doing wrong? Is there any other way to access the error description? Thank you!
The only problem here, is the presence of an On Error statement inside the error-handling subroutine: when that code runs, it's too late to tell VBA what to do "on error": it's here because there was an error.
Just remove the On Error Resume Next line, and you're good to go!
However, if I remove the On Error GoTo ErrorHandler, the description and error number is shown as expected.
What you're seeing is an unhandled runtime error, not the MsgBox you're expecting.
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
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
...
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.