Error Handling within a Loop for PivotTable object sorting - vba

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.

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

Resume to a label not working as expected

I am having an issue using Resume and although I have found another solution by using an On Error GoTo, I am still confused as to why the code below doesn't work. The initial error occurs because the sheet name "Sheet_1" is already taken. This means that, in the watch window, err.number has a value of 1004 just before the Resume NameAgain is executed. Rather than clear the error and jump back up to the label, an error 20 occurs(resume without error), and the code moves to the End If line.
Given that there is an active error 1004, I can't understand why it acts as though there isn't an error. I have searched the site for Error 20 issues but nothing really resolved this for me or made me understand the logic behind it. Any help is much appreciated.
Sub ErrorTest()
Dim i as integer:i=1
NameAgain: On Error Resume Next
Worksheets("Main").Name = "Sheet_" & i
If Err.Number = 1004 Then
i = i + 1
Resume NameAgain
End If
End Sub
Update after paxdiablo comment:
The above was a poor attempt at trying to replicate but simplify a problem I was having. The section of code I am working with is below:
Activate CheckBook to use ActiveWindow
CheckBook.Activate
Set DestSheet = CheckBook.Worksheets.Add(After:=CheckBook.Sheets(1))
On Error Resume Next
v = 1
NameAgain: DestSheet.Name = ExpBookName & "_" & v
If Err.Number = 1004 Then
v = v + 1
Resume NameAgain
End If
On Error GoTo 0
ActiveWindow.DisplayGridlines = False
Set DestCell = DestSheet.Range("A2")
So the solution is to move the On Error Resume Next to the label line and use GoTo in place of Resume.
The resume statement is a means to, from within an error handler, go back to some point in your main (non-error-handling) code and resume execution.
In this case, you've explicitly stated you want to automatically resume next in the event of an error.
This is functionally equivalent to (VB-like pseudo-code):
line:
on error goto handler
cause error
resume line ' not in an error handler at this point '
handler:
resume next
So you're not technically in an error handler at the point where you try to resume to the label.
The right statement for what you're trying to do would be a simple goto rather than resume.
A more correct solution is to write code that does not deliberately generate errors or which does not use Goto.
Public Function GetNextSheetName(ByVal ipWb As Excel.Workbook, ByVal ipStemName As String) As String
Dim mySheet As Variant
Dim mySD As Scripting.Dictionary
Set mySD = New Scripting.Dictionary
For Each mySheet In ipWb.Sheets
' mySd.Count is a dummy entry to satisfy
' the Key and Item requirements for .Add.
' we are only interested in the Keys
' for use with the .Exists method later
mySD.Add mySheet.Name, mySD.Count
Next
Do
DoEvents
Dim myIndex As Long
myIndex = myIndex + 1
Dim myNextSheetName As String
myNextSheetName = ipStemName + "_" & CStr(myIndex)
Loop While mySD.Exists(myNextSheetName)
GetNextSheetName = myNextSheetName
End Function
Which now allows
Set DestSheet = checkbook.Worksheets.Add(After:=checkbook.Sheets(1))
DestSheet.Name = GetNextSheetName(checkbook, ExpBookName)
ActiveWindow.DisplayGridlines = False
Set DestCell = DestSheet.Range("A2")

Excel VBA error handling not working for second error

In the below code Errorhandler correctly takes care of first error when I enter a workbook that is not open or any random string. But when I click on retry and again enter a random name I get "Subscript out of Range" error # Workbooks(workbookname). Activate.
Can anyone help me why it is happening and how can I make it work. I have tried a lot of things. But nothing is working. This code is part of a larger program.
Sub test()
Dim workbkname As String
On Error GoTo Errorhandler
Retry:
workbookname = InputBox("Enter workbook name:", _
"Name Enrty")
If StrPtr(workbookname) = 0 Then
MsgBox ("Aborting Program")
End
End If
Workbooks(workbookname).Activate
Exit Sub
Errorhandler:
Response = MsgBox("Workbook " & workbookname & " not found", vbRetryCancel)
If Response = 4 Then
GoTo Retry
End If
End Sub
The issue here is that the VBA Error Handler does not clear the error once given a directive like GoTo. As a result, the code thinks that it has encountered an error within your error handling routine and thus throws the error up the stack.
In order to clear the error, you must either call Resume with a place to resume (either Resume alone to run the erroneous code again, Resume Next to resume the line of code following the error, or Resume can be called followed by a label, as below:
Sub ErrTest
On Error Goto ErrHndl
For i = 0 to 5
a = 1/i
nextiLabel:
Next i
Exit Sub
ErrHndl:
Resume nextiLabel
End Sub
You can also use Err.Clear() to remove the error, but that is less recommended.
See here for more info: Error Handling in Excel

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

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

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 '