Inconsistent error handling in VBA when throwing exceptions - vba

I have a class c1 that calls another class c2's methods. I want an error generated by c2 to be caught and handled by c1, which will add the error's description to a collection. Code for the relevant functions as follows:
c1:
Private Function doSomething(ByRef rs As DAO.Recordset) As Collection
Dim errors as Collection
Set errors = New Collection
On Error GoTo logError
Do While Not rs.EOF
c2.doSomethingElse rs!someValue
GoTo continueLoop
logError:
errors.Add (Err.Description)
continueLoop:
rs.MoveNext
Loop
Set doSomething = errors
End Function
c2:
Public Sub doSomethingElse(someValue as string)
If Not xyz(someValue) Then
Err.Raise 516, "doSomethingElse", "xyz: " & someValue
Else
DoOtherThings
End Sub
When I have set Error Trapping to "Break on Unhandled Errors", sometimes the Err.Raise in doSomethingElse will raise the error up to doSomething, but sometimes it will just halt execution with a run-time error as though doSomething doesn't have an On Error condition. The first record in rs will usually result in the error being raised to doSomething, but the second one always results in a run-time error. Sometimes the first record also throws a run-time error.
Is something happening after my first iteration of the Do While loop in doSomething that turns off error handling?

Error handling will only do one error.
When rs.movenext sends the routine back through the loop, the error will not work anymore.
Put Resume continueLoop after your error handling.
The following code works for me.
Private Sub Command1_Click()
doSomething
End Sub
Private Function doSomething() As Collection
Dim errors As Collection
Set errors = New Collection
On Error GoTo logError
Do
doSomethingElse ("someValue")
GoTo continueLoop
logError:
errors.Add (Err.Description)
Resume continueLoop
continueLoop:
Loop
Set doSomething = errors
End Function
Public Sub doSomethingElse(someValue As String)
Err.Raise 516, "doSomethingElse", "xyz: " & someValue
End Sub
Be sure to mark this as the right answer if it works for you.

Related

How to handle error generated inside another workbook's Workbook_Open event?

I have two workbooks in the same folder: bkOpenErrorTest.xlsm and bkOpenErrorTest_dict.xlsm.
bkOpenErrorTest_dict.xlsm has the following code in its ThisWorkbook module:
Private Sub workbook_open()
Dim dict As Dictionary
Set dict = New Dictionary
dict.Add 0, 0
dict.Add 0, 0
End Sub
When this workbook is opened by double-clicking the filename, it throws the expected unhandled error:
This key is already associated with an element of this collection
bkOpenErrorTest.xlsm has the following code in Module1:
Sub testOpen()
Dim bk As Workbook
On Error GoTo errHandler
Workbooks.Open ThisWorkbook.Path & "\bkOpenErrorTest_dict.xlsm"
Exit Sub
errHandler:
Debug.Print "reached error handler"
End Sub
When error trapping is set to Break on Unhandled Errors, and I run testOpen(), the unhandled error is still raised when bkOpenErrorTest_dict.xlsm opens. Why isn't the error caught by testOpen()'s error handler? And how can I handle this error? I have an application where I'd like to cycle through many workbooks in a folder that have buggy code like this in their workbook_open() event, and I can't iterate through them if the program crashes on an unhandled error like this.
The reason that the error is not being handled is that the two processes are not in the same thread. If you were calling a 'helper' sub procedure from a main sub procedure, you remain in the same thread and errors thrown in the 'helper' are caught by error control in the main. This is akin to why an error in a procedure launched by Application.Run will not have thrown errors handled by the error control in the procedure that launched it.
To gain any measure of control over what happens in the newly opened workbook's Workbook_Open, you need to control things on the Application instance level. The following halts execution of the Workbook_Open event procedure; if it isn't necessary to process the code, then this could be your solution.
Application.EnableEvents = False
Set bk = Workbooks.Open(ThisWorkbook.Path & "\bkOpenErrorTest_dict.xlsb")
Application.EnableEvents = True
If the Dictionary populating is the specific error you are trying to overcome use the dictionary shorthand method that overwrites duplicates.
Dim dict As Dictionary
Set dict = New Dictionary
dict.Item(0) = 0
dict.Item(0) = 1
'dict.count = 1 with key as 0 and item as 1
More generally you can wrap potential errors in On Error Resume Next and On Error GoTo 0.
Dim dict As Dictionary
Set dict = New Dictionary
On Error Resume Next
dict.Add 0, 0
dict.Add 0, 1
On Error GoTo 0
'dict.count = 1 with key as 0 and item as 0
The error is unhandled because the newly opened Workbook is running inside of what is basically an asychronous process - Workbook_Open is an event handler, so it is not being called from your code. It is being invoked as a callback function from inside whatever external Excel process is opening the document. You can demonstrate the same behavior with any event handler:
'In Sheet1
Sub Example()
On Error GoTo Handler
Sheet1.Cells(1, 1).Value = "Foo"
Exit Sub
Handler:
Debug.Print "Handled"
End Sub
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Row = 1 And Target.Column = 1 Then
Err.Raise 6
End If
End Sub
If you need to bulk process the files, your only (easy) option is going to be disabling events before the call to open:
Sub testOpen()
Dim bk As Workbook
On Error GoTo errHandler
Application.EnableEvents = False
Set bk = Workbooks.Open ThisWorkbook.Path & "\bkOpenErrorTest_dict.xlsm"
Application.EnableEvents = True
Exit Sub
errHandler:
Debug.Print "reached error handler"
End Sub
If for some reason it is vitally important that the buggy Workbook_Open runs, then you can use the solution Tim Williams outlines here. Just create a public wrapper function in the target workbook, then call that from within the context of your own error handler.

Replacing Resume with Try-Catch in VB.net

I have a code block as follow and need to change it to try catch.
Private test(a as String)
On Error GoTo ErrorHandler
Dim index as String
Statement1
Statement2
Done:
On Error Resume Next
Exit Sub
ErrorHandler:
If (a.equals("a")) Then
Resume
Else
Resume Done
End If
End Function
I looked through the official documentation
https://learn.microsoft.com/en-us/dotnet/visual-basic/language-reference/statements/resume-statement and was able to figure out that this can be written as :
Try
Dim index as String
Statement1
Statement2
Catch
If (a.equals("a")) Then
Resume (How do we replace this)
End If
End Try
Can someone guide me how do I replace the Resume statement?

it is posible to do " if error go to sub "

I need to write code that goes to a specific path and imports data from it,
then goes to another path and do the same.
I need that if path num 1 does not exist, it will jump direct to path num 2.
I wrote a sub for each path. there is a way to do something like:
if error goto sub ___ ?
Thanks in advance
Not directly, but you can do something like
On Error Goto error_sub1
and at the bottom of your function, write
error_sub1:
'ToDo - put your calling code here.
Elsewhere in you function you can switch the error handler to a different label:
On Error Goto error_sub2
and so on.
Try this:
Sub testSO()
On Error GoTo err
I=5/0
Exit Sub
err:
<your sub procedure here>
End Sub
Remember to include Exit Sub or else it will still run even without error!
Would it not be better to avoid the error in the first place and check whether the file exists before attempting to open it?
Sub Test()
Dim sFile1 As String
Dim sFile2 As String
Dim wrkBk As Workbook
On Error GoTo Error_Handler
sFile1 = "C:\Users\Desktop\MyFile1.xls"
sFile2 = "C:\Users\Desktop\MyFile2.xls"
If FileExists(sFile1) Then
Set wrkBk = Workbooks.Open(sFile1)
ElseIf FileExists(sFile2) Then
Set wrkBk = Workbooks.Open(sFile2)
Else
Err.Raise 513, , "File Not Found."
End If
wrkBk.Worksheets(1).Range("A1") = "Opened this file."
On Error GoTo 0
Fast_Exit:
'Any tidying up that needs doing.
Exit Sub
Error_Handler:
MsgBox Err.Description, vbExclamation + vbOKCancel, _
"Error: " & CStr(Err.Number)
Err.Clear
Resume Fast_Exit
End Sub
Public Function FileExists(ByVal FileName As String) As Boolean
Dim oFSO As Object
Set oFSO = CreateObject("Scripting.FileSystemObject")
FileExists = oFSO.FileExists(FileName)
End Function

Getting runtime error 1004 after putting error handler?

Why does this not suppress errors?
For i = 1 To Last_row
On Error GoTo errorhandler1
Set wkb = Workbooks.Open(Filename:=l)
'' my code
errorhandler1:
next I
This is what I get:
Before we begin, your code is not set up properly for error handling.
I found that once the GoTo errohandler1 had been executed in the first instance, future calls were ignored so that's when the errors were thrown.
You current code is set up to ignore errors when opening workbooks, you can achieve this using Resume Next, and then GoTo 0 to reset the error handling method.
For i = 1 To Last_row
On Error Resume Next
Set wkb = Workbooks.Open(Filename:=l)
On Error GoTo 0
If Not wkb Is Nothing Then
'' my code
End If
next I
If you want to actually deal with errors -- rather than ignore them, you should do so outside of your loop (strongly encouraged!)
For i = 1 To Last_row
On Error GoTo CleanFail
Set wkb = Workbooks.Open(Filename:=l)
'' my code
next I
Exit Sub
CleanFail:
'deal with error
On Error GoTo is more than just some kind of conditional GoTo jump.
When the runtime encounters an error, it is in an error state that you need to clear up.
An error-handling subroutine isn't just a label code jumps to in case of error - it's where you handle runtime errors.
By jumping to the Next statement you make that next iteration occur in a runtime error state, because you didn't Clear the error state ...so execution resumes and all the while, as far as VBA runtime is concerned, the loop body itself becomes the error-handling subroutine: VBA is waiting for Err.Clear, or Resume Next, or any other statement that tells it "all good, error is handled, move along, nothing to see here".
errorhandler1:
Err.Clear
On Error GoTo 0
next i
That would fix the immediate problem, but still leave you with a quite convoluted spaghettish piece of code. Best extract the error-handling clean out of the "happy path".
Not sure why you can't bypass a file not found error...
Try using a sub function to return the open file (if found) instead?
Function GetWorkBook(ByVal sFullName As String, Optional ReadOnly As Boolean) As Workbook
Dim sFile As String: sFile = Dir(sFullName)
On Error Resume Next
Set GetWorkBook = Workbooks(sFile)
If GetWorkBook Is Nothing Then Set GetWorkBook = Workbooks.Open(sFullName, ReadOnly:=ReadOnly)
End Function

When is it appropriate to explicitly use Err.Clear?

For example, the following function is used for checking whether a workbook is open:
Function BookOpen(Bk As String) As Boolean
Dim T As Excel.Workbook
Err.Clear
On Error Resume Next
Set T = Application.Workbooks(Bk)
BookOpen = Not T Is Nothing
Err.Clear
On Error GoTo 0
End Function
Are these two Err.Clear statements necessary?
In this example
Function BookOpen(Bk As String) As Boolean
Dim T As Excel.Workbook
Err.Clear
On Error Resume Next
Set T = Application.Workbooks(Bk)
BookOpen = Not T Is Nothing
Err.Clear
On Error GoTo 0
End Function
none of the uses is appropriate, because On Error resets the last error, so Err.Clear is redundant.
It's appropriate after actually handling a failed statement.
Function BookOpen(Bk As String) As Boolean
Dim T As Excel.Workbook
On Error Resume Next
Set T = Application.Workbooks(Bk) ' this can fail...
' so handle a possible failure
If Err.Number <> 0 Then
MsgBox "The workbook named """ & Bk & """ does not exist."
Err.Clear
End If
BookOpen = Not T Is Nothing
End Function
If you have On Error Resume Next in effect, the program will continue after an error as if nothing had happened. There is no exception thrown, there is no warning, this is not structured error handling (i.e. it's nothing like try/catch blocks). Your program might end up in a very weird state if you don't do rigorous error checks.
This means you must check errors after. every. statement. that. can. fail. Be prepared to write a lot of If Err.Number <> 0 Then checks. Note that this is harder to get right than it seems.
Better is: Avoid long sections of code that have On Error Resume Next in effect like the plague. Break up operations into smaller functions/subs that do only one thing instead of writing a big function that does it all but can fail halfway through.
In short: Err.Clear makes your program behave predictably after a failed statement in an On Error Resume Next block. It marks the error as handled. That's its purpose.
Of course in your sample it's easy to avoid error handling by using the commonly accepted way of checking whether a workbook (i.e. member of a collection) exists.
Function BookOpen(Bk As String) As Boolean
Dim wb As Variant
BookOpen = False ' not really necessary, VB inits Booleans to False anyway
For Each wb In Application.Workbooks
If LCase(wb.Name) = LCase(Bk) Then
BookOpen = True
Exit For
End If
Next
End Function