Excel VBA: On Error Goto statement not working inside For-Loop - vba

I'm trying to cycle through a table in excel. The first three columns of this table have text headings, the rest of them have dates as headings. I want to assign those dates, sequentially, to a Date-type variable, and then perform some operations based on the date
To do this I am using a foreach loop on myTable.ListColumns. Since the first three columns do not have date headers, I have tried to set the loop up so that, if there is an error assigning the header string to the date-type variable, the loop goes straight to the next column
This seems to work for the first column. However, when the second column's header is 'assigned' to the date-type variable, the macro encounters an error even though it is within an error-handling block
Dim myCol As ListColumn
For Each myCol In myTable.ListColumns
On Error GoTo NextCol
Dim myDate As Date
myDate = CDate(myCol.Name)
On Error GoTo 0
'MORE CODE HERE
NextCol:
On Error GoTo 0
Next myCol
To reiterate, the error is thrown on the second round of the loop, at the statement
myDate = CDate(myCol.Name)
Can anyone explain why the On Error statement stops working?

With the code as shown, you're actually still considered to be within the error handling routine when you strike the next statement.
That means that subsequent error handlers are not allowed until you resume from the current one.
A better architecture would be:
Dim myCol As ListColumn
For Each myCol In myTable.ListColumns
On Error GoTo ErrCol
Dim myDate As Date
myDate = CDate(myCol.Name)
On Error GoTo 0
' MORE CODE HERE '
NextCol:
Next myCol
Exit Sub ' or something '
ErrCol:
Resume NextCol
This clearly delineates error handling from regular code and ensures that the currently executing error handler finishes before you try to set up another handler.
This site has a good description of the problem:
Error Handling Blocks And On Error Goto
An error handling block, also called an error handler, is a section of code to which execution is tranferred via a On Error Goto <label>: statement. This code should be designed either to fix the problem and resume execution in the main code block or to terminate execution of the procedure. You can't use the On Error Goto <label>: statement merely skip over lines. For example, the following code will not work properly:
On Error GoTo Err1:
Debug.Print 1 / 0
' more code
Err1:
On Error GoTo Err2:
Debug.Print 1 / 0
' more code
Err2:
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.

You need to add resume of some sorts in your error handling code to indicate the error handling is over. Otherwise, the first error handler is still active and you are never "resolved."
See http://www.cpearson.com/excel/errorhandling.htm (specifically the heading "Error Handling Blocks And On Error Goto" and following section)

Follow-up to paxdiablo's accepted answer. This is possible, allowing two error traps in the same sub, one after the other :
Public Sub test()
On Error GoTo Err1:
Debug.Print 1 / 0
' more code
Err1:
On Error GoTo -1 ' clears the active error handler
On Error GoTo Err2: ' .. so we can set up another
Debug.Print 1 / 0
' more code
Err2:
MsgBox "Got here safely"
End Sub
Using On Error GoTo -1 cancels the active error handler and allows another to be set up (and err.clear doesn't do this!). Whether this is a good idea or not is left as an exercise for the reader, but it works!

Clearing all property settings of the Err object is not the same as resetting the error handler.
Try this:
Sub TestErr()
Dim i As Integer
Dim x As Double
On Error GoTo NextLoop
For i = 1 To 2
10: x = i / 0
NextLoop:
If Err <> 0 Then
Err.Clear
Debug.Print "Cleared i=" & i
End If
Next
End Sub
You'll notice the just like the OP, it will catch the error properly when i =1 but it will fail on line 10 when i = 2, even though we used Err.Clear

Dim ws As worksheets
For Each myCol In myTable.ListColumns
On Error GoTo endbit
Dim myDate As Date
myDate = CDate(myCol.Name)
On Error GoTo 0
'MORE CODE HERE
endbit:
Resume NextCol
NextCol:
' Next myCol
Exit Sub ' or something '

Related

Handling Recurring VBA Errors Within Subroutine

I am stuck on something that I have a workaround for, but it bugs me I don't have a direct answer for how to address the issue of using On Error Goto for recurring errors. My question is essentially the same as this one, however the answers provided are alternatives to the OP's overall approach rather than how to handle the specific issue.
I've simplified an example below. I am WELL AWARE that there are probably a dozen ways this code could be rewritten to avoid any error -- I am just using this to illustrate the issue of trying to figure out why/how does this code work properly when i = 0, but not when i = 3,6,9?
Sub vbaErrorConfusion()
Dim theArray(9) As Long, i As Long
For i = 0 To 9
On Error GoTo fixingErrors
'next line will intentionally create an error when
'when i = {0} this works as expected, but at i=3 next line throws an error.
theArray(i) = i + 1 / (i Mod 3)
On Error GoTo 0
next_i:
Next i
Exit Sub
'----Error Handling----
'this works as expected when i=0 but not when i = 3,6,9
fixingErrors:
Err.Clear
On Error GoTo 0
'at this point I would expect my error state to be the same as the start of procedure?
theArray(i) = -1
GoTo next_i
End Sub
What my Err variable shows after first instance of 0.
After I run Err.Clear I would expect the behavior for i=3 to be the same as when i=0, however my procedure stops with the below VBA error one would expect WITHOUT any error catching.
I presume there's some way to reset the Error variable or handle this type of situation without a workaround? Any quality responses will get upvoted. Thanks.
To tell VBA that an you have dealt with the error you need a Resume statement. Therefore, replacing the line GoTo next_i with Resume next_i you give you the outcome you expect.
You do not need Err.Clear. Also, On Error GoTo 0 will disable the error handler. However, neither of these lines will tell VBA that you have dealt with an error.
In your code at i=0 the error handler is activated but VBA is not told that the error has been dealt with (i.e. no Resume statement). At i=3 another error occurs while a previous error hadn't been dealt with. In this case the current procedure/function/property cannot deal with the second error, which is therefore, fatal.
You should also take the On Error GoTo fixingErrors line outside the loop.
Sub vbaErrorConfusion()
On Error GoTo fixingErrors
Dim theArray(9) As Long, i As Long
For i = 0 To 9
'*On Error GoTo fixingErrors
'next line will intentionally create an error when
'when i = {0} this works as expected, but at i=3 next line throws an error.
theArray(i) = i + 1 / (i Mod 3)
'*On Error GoTo 0
next_i:
Next i
Exit Sub
'----Error Handling----
'this works as expected when i=0 but not when i = 3,6,9
fixingErrors:
'*Err.Clear
'*On Error GoTo 0
'at this point I would expect my error state to be the same as the start of procedure?
theArray(i) = -1
Resume next_i
End Sub
Please take some time to read up on how Error handling in VBA works.
Sub vbaErrorConfusion()
Dim theArray(9) As Long, i As Long
For i = 0 To 9
On Error GoTo fixingErrors
'next line will intentionally create an error when
'when i = {0} this works as expected, but at i=3 next line throws an error.
theArray(i) = i + 1 / (i Mod 3)
'On Error GoTo 0
'next_i:
Next i
Exit Sub
'----Error Handling----
'this works as expected when i=0 but not when i = 3,6,9
fixingErrors:
Err.Clear
Debug.Print "An error was generated for i= ", i
'On Error GoTo 0
'at this point I would expect my error state to be the same as the start of procedure?
theArray(i) = -1
Resume Next
End Sub

Skip code if no error occurs by using an "if statement" in VBA

I need some input, since I'm quite new to VBA.
I have a part of a code that is looking for a file in three different path location. The code tries path 1, if error, it will continue to the next path (that's perfect)
My problem is, if the file path is working for code "test 2" and "test 3", It will always run until last one (i.e. "test 3") instead of jumping to next part of code. If the location path works for example for test 1 or test 2 location, I don't need to run the following lines after.
How can I make my code skip that part?
'Test 1 location path
On Error GoTo Err1:
sFldr = "Path1"
Err1:
Resume Next
'Test 2 location path
On Error GoTo Err2:
sFldr = "Path2"
Err2:
Resume Next
'Test 3 location path
On Error GoTo Err3:
sFldr = "Path3"
Err3:
Resume Next
'next part of big code
more code here
If I understand correctly, you need to progressively try values for (in this contrived example) sFldr, and the operation might error. Instead of using On Error Goto, just test the Err object for an error directly:
On Error Resume Next
sFldr = "Path1"
If Err.Number <> 0 Then
Err.Clear
sFldr = "Path2"
If Err.Number <> 0 Then
Err.Clear
sFldr = "Path3"
End If
End If
On Error GoTo 0 'Or resume your normal error handling.
'next part of big code
Call it like this:
If GetFldrValue = vbNullString Then
'whatever you need to do if all 3 fail.
End If
'next part of big code
Another option is to extract the whole thing into its own function (which might not be a bad idea - the comment "next part of big code" indicates that the routine might be doing too much). If you do that, you can just turn error handling off entirely and return the first valid value found:
Function GetFolderValue() As String
On Error Resume Next
GetFolderValue = "Path1"
If Err.Number <> 0 Then Exit Function
GetFolderValue = "Path2"
If Err.Number <> 0 Then Exit Function
GetFolderValue = "Path3"
End Function
One Procedure = One Error Handler.
Simple as that.
Make sure the error-handling subroutine only ever runs in an error state.
I can't really give you a more concrete answer than that, because the code you're showing us does literally nothing; assigning a string literal to a string variable is never going to raise an error, ...and a workflow that jumps around up and down from one line to another in the "normal" execution path isn't sane - you need to restructure things. I'd love to help, but I've no idea what your code does.
To put it shortly, you should have small procedures that look like this:
Private Sub DoSomething()
On Error GoTo CleanFail
'procedure code here
CleanExit:
'cleanup code here
Exit Sub
CleanFail:
'error-handling code here
Resume CleanExit
End Sub
Option 1: Wrap in Function
It would be best to wrap this in a function that is responsible for retrieving the value of sFldr.
Option 2: GoTo Statement - not recommended
Perhaps add a GoTo if the value of sFldr is not null
'Test 1 location path
On Error GoTo Err1:
sFldr = "Path1"
If(sFldr <> "") Then Goto ContinueFunc
Err1:
Resume Next
'Test 2 location path
On Error GoTo Err2:
sFldr = "Path2"
If(sFldr <> "") Then Goto ContinueFunc

Error Handling within a Loop for PivotTable object sorting

I've been trying to sort PivotTable objects in VBA using a function I've attempted:
Public Function PTSort(PTName As String, PTFieldName as String, SortArray As Variant)
Dim m as Integer: m = 1 'Row counter
Dim i as Long 'Dummy Variable for cycling
With ActiveSheet.PivotTables(PTName).PivotFields(PTFieldName)
.Parent.ManualUpdate = True
For i = LBound(SortArray) To UBound(SortArray)
With .PivotItems(SortArray(m - 1)) 'For in-code array
.Position = m
End With
m = m + 1
Next i
.Parent.ManualUpdate = False
End With
End Function
Whilst this works well with a known set of elements within SortArray, I have a master list to follow whilst sorting (so as to standardise a few orders accross several PivotTables), in which the PivotTable need not necessarily contain all said PivotItems. I have hence improved it to the following:
Sub PTSort(PTName As String, PTFieldName as String, SortArray As Variant)
Dim m as Integer: m = 1
Dim i as Long
Dim k As Integer: k = 1 'To cycle the position independently from the array in the event of disjoint.
With ActiveSheet.PivotTables(PTName).PivotFields(PTFieldName)
.Parent.ManualUpdate = True
For i = LBound(SortArray) To UBound(SortArray)
On Error GoTo ERRHANDLER:
With .PivotItems(SortArray(k)) 'After parsing from range of cells into VariantArray, then does one not require the "-1"
.Position = m
End With
m = m + 1
ExitHandler:
k = k + 1
Next i
.Parent.ManualUpdate = False
End With
GoTo ENDEND:
ERRHANDLER:
GoTo EXITHANDLER:
ENDEND:
End Sub
The OnError GoTo seems to only work once though, irregardless of how high up placed it?
Help would be greatly appreciated. Thanks in advance!
This is from MSDN on Visual Studio but I think it applies to VBA in the same way.
An "enabled" error handler is one that is turned on by an On Error statement. An "active" error handler is an enabled handler that is in the process of handling an error.
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 cannot handle the error. Control returns to the calling procedure
So after your code first gets to On Error GoTo ERRHANDLER, ERRHANDLER is enabled. Then when an error occurs ERRHANDLER is activated and handles the error. When you GoTo EXITHANDLER it stays active and still handles the error. The On Error GoTo ERRHANDLER has no effect at this point.
To re-enable the ERRHANDLER you need to use Resume EXITHANDLER instead of GoTo EXITHANDLER.
Edit on the Resume statement. There are three ways to use Resume: Resume, Resume Next and Resume label
Resume without an argument causes the code to resume at the line that caused the error. This obviously has to be used very careful as you must be absolutely certain that you fixed the problem or you'll end up in an infinite loop.
Resume Next causes the code to resume at the line after the line that caused the error.
Resume Label is almost the same as GoTo Label but with this you exit the error handler and resume normal code execution. The error handler is re-enabled.

Does "On Error GoTo errorHandler:" remain active for the rest of the sub if it is not triggered?

This Code tries to select a checkbox; if it does not exist it will create the checkbox via the errorHandler. I was wondering; if an error occurs later in this sub, will it still be directed to that errorHandler? In other words, will VBA attempt to create a checkbox anytime I have an error during this sub? Or does ErrorHandling only look at the next line that occurs after "On Error Goto ...:"
Sub PrepNewPurchaseEntryForm()
'no relevant VBA happens here
Dim companyCount As Integer: companyCount = colCount("CompanyUserEntries")
Call FocusOnSheet("NewPurchaseEntry")
For i = 3 To companyCount
Dim companyName As String: companyName = Sheets("CompanyUserEntries").Cells(1, i).Value
On Error GoTo Err1:
ActiveSheet.Shapes.Range(Array(companyName)).Select
''if error occurs here, will Err1 still be called?
Next i
''if error occurs here, will Err1 still be called?
Exit Sub
Err1:
Call PlaceCheckBox(("B" & (i + 7)), companyName)
Resume Next
End Sub
Sorry this might seem like a stupid question but I have been reading up on ErrorHandling but i couldn't really find anything that explicitly explained this.
On Error GoTo xxxx error handler will stay active as long as you you don't set another error handler or remove it with On Error Goto 0

Error (Run-time error 5) will not raise when "On error resume next" is active

I've provided the actual code I'm using below.
The exact condition I'm trying to handle is the strCurrentRev argument as a zero-length string. (e.g. strCurrentRev="")
If I comment out the error handling statements, trying to execute the ASC method on a zero-length string throws Run-Time Error 5 for "invalid procedure call or argument".
If I then check err.Number it's = 5.
If I try to run the exact same statement with on error resume next active, it will not raise any errors, e.g. after execution err.number is always = 0.
If on error resume next is active, and you try to execute the ASC method from the immediate window (e.g. Type asc(strcurrentrev) and hit Enter) it will throw the run-time error and set the err.number property to 5.
I've never experienced this before. Why would having on error resume next active cause the error not to raise in normal execution???
Function NextRevLetter(strCurrentRev As String) As String
'This function returns the next revision letter given an existing revision letter.
'Declare variables
Dim lngX As Long
Dim strX As String
Dim strY As String
'First, check if we are dealing with rev A-Z or AA-ZZ
If Len(strCurrentRev) <= 1 Then
'Check that we can work with revision letter ***THIS IS WHERE I AM HAVING A PROBLEM!***
On Error Resume Next
Err.Clear
'Procedure call to flag errors with ASC method without changing any values
lngX=Asc(strCurrentRev)
lngX=0
On Error GoTo 0
If Err.Number > 0 Then
Err.Clear
If Len(strCurrentRev) < 1 Then
'No revision specified, assign first revision"
strCurrentRev = "-"
Else
MsgBox "The revision letter specified is not compliant. The next revision letter cannot be determined.", vbOKOnly, "Error: Revision does not follow rules"
'Return the existing revision (no change) and exit function
NextRevLetter = strCurrentRev
Exit Function
End If
End If
'Code continues - not important for this question...
Exit Function
You're not using the right tool for the job. Runtime errors should be handled, not shoved under the carpet (because that's what On Error Resume Next does - execution happily continues as if nothing happened).
You need to try to avoid raising that error in the first place. What's causing it?
lngX=Asc(strCurrentRev)
You already know what's happening:
The exact condition I'm trying to handle is the strCurrentRev argument as a zero-length string.
Well then, the correct way to handle this is to verify the length of strCurrentRev before you pass it to the Asc function, which you know will raise a runtime error #5 if you give it an empty string!
If strCurrentRev <> vbNullString Then
'calling Asc(strCurrentRev) here will not fail!
End If
I was asked to elaborate on a better way to handle the error, and this is the easiest place to do so. I think it's okay, because in a way it does answer the original question as well. However, let me say first that the right thing to do here is to avoid the error entirely, but for the sake of completeness, there is a way to do this cleanly with an error handler.
The idea is to check the error number, handle it by fixing the value, and then resuming the next line of code.
Function NextRevLetter(strCurrentRev As String) As String
'This function returns the next revision letter given an existing revision letter.
On Error GoTo ErrHandler
'Declare variables
Dim lngX As Long
Dim strX As String
Dim strY As String
'First, check if we are dealing with rev A-Z or AA-ZZ
If Len(strCurrentRev) <= 1 Then
'Procedure call to flag errors with ASC method without changing any values
lngX = Asc(strCurrentRev)
lngX = 0
'Code continues - not important for this question...
End If
Exit Function
ErrHandler:
If Err.Number = 5 Then
lngX = 0
If Len(strCurrentRev) < 1 Then
'No revision specified, assign first revision"
strCurrentRev = "-"
Resume Next
Else
MsgBox "The revision letter specified is not compliant. The next revision letter cannot be determined.", vbOKOnly, "Error: Revision does not follow rules"
'Return the existing revision (no change) and exit function
NextRevLetter = strCurrentRev
Exit Function
End If
Else If Err.Number = someOtherExpectedError
'handle it appropriately
Else
' !!! This is important.
' If we come across an error we don't know how to handle, we re-raise it.
Err.Raise Err.Number, Err.Source, Err.Description
End If
End Function
Note that the flow of your program is not interrupted by all of this error handling and we only handle the error that we're expecting. So, if an error is raised, we recover only if we know how to. Otherwise, execution is halted.
I would still prefer just to check to see if the value is = vbNullString though.
I just figured this out. The On Error GoTo 0 statement resets the Err.Number property to 0.
Sorry for wasting anyones time!!!!