I have a macro that intermittently throws a run-time error when doing a PasteSpecial. Most times it runs through to completion without failing, but sometimes it doesn't. When it throws the error, if I click on "Debug" and then just let it continue, it runs through without a problem. This sounds like a timing thing, but I don't understand what's causing it. Here is a snippet of the code:
Dim SourceDataWB As Workbook
Dim RawDataWS As Worksheet
Dim LastDataRow As Long
Dim SrcRange As Range
<Lots of other code in here...>
SourceDataWB.Activate
Set SrcRange = Range("A1:A" & LastDataRow)
SrcRange.Copy
RawDataWS.Range("A:A").PasteSpecial xlPasteValues
The RawDataWS worksheet is in a different workbook than the SourceDataWB. The error occurs on the PasteSpecial line. And if I just press "Play" at that point, it continues without error. Any ideas?
For anyone else experiencing a random/intermittent runtime error in VBA, one key may be to take control of the error handling. Instead of having your code go to this onscreen error, have VBA do something else.
One idea is -- when the error occurs -- have VB sleep for a moment and then retry the offending action. In many cases, that may be all you need to resolve the issue.
Using the above example, you might code:
' Tell VB not to handle errors with onscreen/debug behavior (just for now)
On Error Resume Next
' This is the example line that intermittently throws the runtime error
RawDataWS.Range("A:A").PasteSpecial xlPasteValues
' Check if there was an error and -- if so -- do something about it
If Err Then
' Pause
Sleep (1000) ' Pause in milliseconds
' retry offending line...
RawDataWS.Range("A:A").PasteSpecial xlPasteValues
Err.Clear ' Clear Err object fields
End If
On Error GoTo 0 ' set VB error handling back to normal
... of course this code assumes that it runs OK the second time. If failure that time is unacceptable, you may need to add a condition for that...
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.
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
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
This is my code:
Sub VerticalLoop()
Dim i As Integer
Range("VerticalLoop").Activate
i = 1
Do While i <= Range("VerticalLoop").Rows.Count
ActiveCell.Value = i
ActiveCell.Offset(1, 0).Activate
i = i + 1
Loop
End Sub
For some reason I can't run this. When I press F8, the error pops up when I reach the line "i=1", so I don't know exactly what I did wrong.
This code will fail if the Sheet containing the Defined Name has not been activated first!
The error is in the line
Range("VerticalLoop").Activate
The error is probably a typo in the name of the range. Are you sure it isn't supposed to be "Vertical_Loop" (with the underscore)?
On the other hand, Gary's Student is correct that a '1004' error could be triggered by trying to activate a range in an inactive sheet. The error message itself (and not just the error number) can tell you more about the exact source of the error. Alternatively, you could break that line into two lines like thus:
Dim VLoop As Range
Set VLoop = Range("VericalLoop")
VLoop.Activate
If the Set VLoop line throws the error then the problem is with your name. If the second line throws the error then then problem is with what is active.
As an added benefit -- once you debug this error you now have a useful range variable allowing you to use expressions like VLoop.Rows.Count
I have the following two lines of code:
On Error Resume Next
myWorkbook.Sheets("x").Columns("D:T").AutoFit
I've stepped into the macro and executed the line On Error Resume Next and then on the next line myWorkbook... it does the following:
Why doesn't the compiler resume the next line of code?
On Error has been liberally used throughout the procedures code; I realize best practice is to use this as little as possible but it seems to fit the purpose of this macro.
Reading this SO QUESTION it says that you can't have one set of error trapping within another. How can I guarantee that one set of error trapping has been "closed off" before the code moves on - does On Error Goto 0 reset the error trapping? If it does reset then why doesn't resume work in the following?:
Sub GetAction()
Dim WB As Workbook
Set WB = ThisWorkbook
On Error GoTo endbit:
'raise an error
Err.Raise 69
Exit Sub
endbit:
On Error GoTo 0
On Error Resume Next
WB.Sheets("x").Columns("D:T").AutoFit
End Sub
There is also a VBA setting that will cause On Error ... statements to be ignored and that dialog box to always appear. See this answer for more details on checking/changing the option: https://stackoverflow.com/a/3440789/381588
An example of an error when the initial error is not closed out.
Sub GetAction()
Dim WB As Workbook
Set WB = ThisWorkbook
On Error GoTo endbit:
'raise an error
Err.Raise 69
Exit Sub
endbit:
On Error Resume Next
WB.Sheets("x").Columns("D:T").AutoFit
End Sub
As you have found, within the same function or subroutine, On Error Resume Next doesn't override On Error Goto ... if it's still active.
You are correct that On Error Goto 0 restores the default error handler.
There are some cases where On Error is the most appropriate way to handle an exceptional condition. I prefer to use the following structure:
On Error Resume Next
statement which might fail
On Error Goto 0
if statement has failed then ...
This keeps everything together, but in other cases a generic error handler at the end of the procedure can be better.
I've found that in functions/subs that iterates over nested objects, errorhandling might be a drag in VBA. A solution that works for me to better handle complex iterations is separating setting of objects in their own functions, e.g.
main function/sub:
set FSOfolder = SetFSOFolder(FSOobject, strFolder)
Private Function SetFSOFolder(FSO as scripting.FileSystemObject, strFolder as string) as Scripting.Folder
on error resume Next
set SetFSOFolder = FSO.GetFolder(strFolder)
on error goto 0
End Function
and then in the next line in main function:
if (not fsofolder is nothing) then
I agree using On Error Resume Next is not best practice but most/many of my Access apps are not overly sensitive to minor nuances in data integrity (i.e. analytical or reporting, not transactions and not audited). For this reason I use OERN quite often because VBA is very sensitive to some situations that you cannot anticipate entirely.
1 - will the error cause data corruption. If yes handle it. Many routines I use are just crunching a large volume of records and there may be errors in imported data that haven't been fixed. Usually I have a lot of conversion processes that will let the system clean up its own data eventually.
2 - is the error frequent and non-critical (ie not a key). If yes it's OERN otherwise the error may not be predicable and you end up crashing out or have to write a bunch of I-T-E or Select Case logic to trap it.
Don't use On Error Resume Next instead write code that shouldn't crash.
Note: I am being careful how I phrase that because you never guaranty code doesn't crash. But if you use On Error Resume Next then part of the natural flow of your code is for it to crash, which is wrong, big time wrong.