Getting runtime error 1004 after putting error handler? - vba

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

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.

ErrorHandler stops traping errors in ms access

Am sorry that the title is ambiguous but i didn't know how to describe my problem in the title,any way ,i have this part of my long code that triggers a Save dialog:
Line1:
Dim dlgSaveAs As Object
Dim strFilePath As String
Dim strFileName As String
Set dlgSaveAs = Application.FileDialog(2)
With dlgSaveAs
.InitialFileName = (CurrentProject.Path) & "\Folder" & "_" & Format(Date, "yyyy")
End With
dlgSaveAs.Show
strFilePath = dlgSaveAs.SelectedItems(1)
strFileName = Right(strFilePath, Len(strFilePath) - InStrRev(strFilePath, "\"))
ActiveWorkbook.SaveAs FileName:=CurrentProject.Path & "\" & strFileName, FileFormat:=xlOpenXMLWorkbook
SaveError:
Select Case Err.Number
Case 1004 'if it's want to overwrite an old file and i clicked "no" or "cancel"
GoTo Line1 'reopen the Save Dialog
Case 5 'if i clicked "cancel" on the Save Dialog
rs1.Close
Set rs1 = Nothing
MsgBox ("Canceled")
Exit Sub
End Select
as i explained in the code,if the ErrorHandler detects an '1004' error it will reopen the Save dialog after it was closed.
Every time, the first time the error '1004' occurs the handler detects it normally,but after reopening the dialog(by the ErrorHandler),the handler stops detecting anything neither '1004' nor '5'.
Why is that ?
Ever heard that?
GOTO IS EVIL
Well, GoTo is evil.
First, stick On Error GoTo SaveError at the top of the procedure - that's what will ensure a proper jump on error.
When error 1004 occurs the first time, VBA enters "error-handling mode", and enters the SaveError subroutine.
There are several ways to get VBA out of "error-handling mode" and back into "normal execution mode" without jumping out of the procedure you're in:
Resume will re-run the statement that caused the error (watch for infinite loops here!)
Resume Next will run the next statement after the one that caused the error
Resume {line label} will jump to the specified label
Notice all of them involve the Resume keyword.
When you say GoTo Line1, you re-run the procedure, but VBA still thinks it's handling an error.
And an error that's raised while the runtime thinks it's handling an error will definitely not do what you expect it to do (as you've noticed).
Replace GoTo Line1 with Resume Line1.
Also consider using Exit Sub or Exit Function before the error-handling subroutine / label, so as to only ever run that chunk of code when you're in an error state.
Lastly, consider using the .Show function's return value to determine if the dialog was cancelled, instead of relying on runtime errors.

Inconsistent error handling in VBA when throwing exceptions

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.

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

VBA Import MS Access to MS Word

I have a VBA module in MS-Access that is supposed to load data from a database into Form Fields in a MS-Word document. I thought it was working fine, but it appears to be inconsistent. Sometimes it works and sometimes it doesn't. I can't figure out what keeps it from working. When I step through the debugger it doesn't throw any errors, but sometimes it doesn't open MS-Word.
Here is the relevant code:
Dim appWord As Word.Application
Dim doc As Word.Document
'Avoid error 429, when Word isn't open.
On Error Resume Next
Err.Clear
'Set appWord object variable to running instance of Word.
Set appWord = GetObject(, "Word.Application")
If Err.Number <> 0 Then
'If Word isn't open, create a new instance of Word.
Set appWord = New Word.Application
End If
Set doc = appWord.Documents.Open("\\srifs01\hresourc\EHS Department\EHS Database\IpadUpload\Lab Inspection Deficiency Resolution Report.docx", , True)
'Sometimes word doesn't open and I think the issue is around here.
With doc
.FormFields("frmID").Result = Me!id
.FormFields("frmSupervisor").Result = Me!LabPOC
.FormFields("frmInspector").Result = Me!InspectorName
.FormFields("frmBuilding").Result = Me!BuildingName
.FormFields("frmRoom").Result = Me!Rooms
.FormFields("frmComments").Result = Me!Comments
.Visible = True
.Activate
.SaveAs "'" & Me!id & "'"
.Close
End With
Set doc = Nothing
Set appWord = Nothing
Any help is appreciated. Thanks in advance.
"When I step through the debugger it doesn't throw any errors, but sometimes it doesn't open MS-Word."
That's because you have On Error Resume Next. That instructs VBA to ignore errors.
Assume you've made this change in your code ...
Dim strDocPath As String
strDocPath = "\\srifs01\hresourc\EHS Department\EHS Database" & _
"\IpadUpload\Lab Inspection Deficiency Resolution Report.docx"
Then, when you attempt to open strDocPath, VBA would throw an error if appWord isn't a reference to a Word application instance ... AND you haven't used On Error Resume Next:
Set doc = appWord.Documents.Open(strDocPath, , True)
You can get rid of On Error Resume Next if you change your assignment for appWord to this:
Set appWord = GiveMeAnApp("Word.Application")
If Word was already running, GiveMeAnApp() would latch onto that application instance. And if Word was not running, GiveMeAnApp() would return a new instance.
Either way, GiveMeAnApp() doesn't require you to use On Error Resume Next in your procedure which calls it. Include a proper error handler there instead. And you can reuse the function for other types of applications: GiveMeAnApp("Excel.Application")
Public Function GiveMeAnApp(ByVal pApp As String) As Object
Dim objApp As Object
Dim strMsg As String
On Error GoTo ErrorHandler
Set objApp = GetObject(, pApp)
ExitHere:
On Error GoTo 0
Set GiveMeAnApp = objApp
Exit Function
ErrorHandler:
Select Case Err.Number
Case 429 ' ActiveX component can't create object
Set objApp = CreateObject(pApp)
Resume Next
Case Else
strMsg = "Error " & Err.Number & " (" & Err.Description _
& ") in procedure GiveMeAnApp"
MsgBox strMsg
GoTo ExitHere
End Select
End Function
You could also include a check to make sure appWord references an application before you attempt to use it. Although I don't see why such a check should be necessary in your case, you can try something like this ...
If TypeName(appWord) <> "Application" Then
' notify user here, and bail out '
Else
' appWord.Visible = True '
' do stuff with Word '
End If
I don't use the New keyword when opening or finding an application.
This is the code I use for excel:
On Error Resume Next
Set xlApp = GetObject(, "Excel.Application")
If Err.Number = 429 Then 'Excel not running
Set xlApp = CreateObject("Excel.Application")
End If
On Error GoTo 0
(note also the On Error GoTo 0 - I don't want the resume next to be active all through the code)
The GiveMeAnApp function worked great for me with a similar problem I was experiencing. Except, to avoid Error 462 (cannot connect to server etc) if I closed the Word document after the data merge and attempted another merge of data to Word. (which caused error 462) I did this: Once I call GiveMeAnApp I then called for a New Word document before calling the Word template I wished to transfer data to Word into.
By always having the New Word document present this avoided error 462 in my circumstances. It means I am left with an empty Word doc but this is ok for me and preferable to the only other solution I could come up with which was to quit the db and re open and run the merge to Word aga.
I am grateful for the help set out in this thread. Thanks all.