In VBA error handling is done by on error statement.
I want to temporarily change the error handling and then go back to the previous behavior afterward. How would it be possible to check the current error handling and store it in a variable (I couldn't find anything in the references)?
'set the error handling to s.th. "on error... "
'some code with the regular error handling
'change the error handling to "on error ..." (regardless of what it was before)
'some code with the new error handling
'change back to the previous error handling
'some code with the regular error handling
Background: I needed to do a is nothing check on a Variant array to exclude empty object indexes from being used, but is nothing applied to an array index that holds a value throws an exception, so I temporary wanted to change the error handling to on error resume next. Eventually is solved this using a different approach but I'm still wondering if I can determine the current error handling somehow during runtime Here's the question and answer to my original problem.
EDIT: I know I can check my previous code manually to find out what type of error handling has been used. However I want to avoid that (to save time).
I suppose as a workaround I could set an additional variable with the state which I can then check for the current state, although this will result in quite a bit of overhead. Something like this:
Dim errorHandling as String
errorHandling = "resumeNext"
on error resume next
'some code
'changing the error handling temp.
'some other code
'changing the error handling to it's previous state
if errorhandling = "resumeNext" then
On Error Resume Next
elseif errorhandling = "GoToErrorhandler" then
On Error GoTo errorhandler
End If
'Rest of the code
Read/Write to Array
Option Explicit
Sub ReadWriteArrayExample()
Dim myArray() As Variant: ReDim myArray(1 To 10)
Dim i As Long
Dim n As Long
' Fill the array.
For i = 1 To 10
n = Application.RandBetween(0, 1)
If n = 1 Then ' write a random number between 1 and 10 inclusive
myArray(i) = Application.RandBetween(1, 10)
'Else ' "n = 0"; leave the element as-is i.e. 'Empty';do nothing
End If
Next i
' Debug.Print the result.
Debug.Print "Position", "Value"
For i = 1 To 10
If Not IsEmpty(myArray(i)) Then ' write the index and the value
Debug.Print i, myArray(i)
'Else ' is empty; do nothing
End If
Next i
End Sub
Error Handling
Sub ErrorHandling()
Const ProcName As String = "ErrorHandling"
On Error GoTo ClearError ' enable error trapping
' Some code
On Error Resume Next ' defer error trapping
' Some tricky code
On Error GoTo ClearError ' re-enable error trapping
' Some Code
ProcExit:
Exit Sub
ClearError:
Debug.Print "'" & ProcName & "' Run-time error '" _
& Err.Number & "':" & vbLf & " " & Err.Description
Resume ProcExit
End Sub
I currently have this, but I dont necessarily need a loop. I just need to see if there is any error in the usedrange, and if yes, stop the code and display the message. If no error, then just continue with the rest of the steps that are after this part of the code. Right now this loop displays this message for every error detected. I just need it to be displayed once if any error is found, and then stop the code.
Dim r As Range
For Each r In ws_DST.UsedRange
If IsError(r.Value) Then
MsgBox "Please fix any errors", vbOKOnly
End If
Next
End
How about:
Sub errortest()
On Error Resume Next
Set Rng = Cells.SpecialCells(xlCellTypeFormulas, xlErrors)
On Error GoTo 0
If Rng Is Nothing Then
Exit Sub
Else
For Each r In Rng
MsgBox r.Address(0, 0)
Next r
End If
End Sub
I am running a bit of VBA to switch an entire Excel worksheet to upper case.
However it trips over and gives a Type Mismatch error and fails half way through.
Sub MyUpperCase()
Application.ScreenUpdating = False
Dim cell As Range
For Each cell In Range("$A$1:" & Range("$A$1").SpecialCells(xlLastCell).Address)
If Len(cell) > 0 Then cell = UCase(cell)
Next cell
Application.ScreenUpdating = True
End Sub
I'm assuming it is tripping over a specific cell however there are hundreds of lines. Is there a way to get it to skip errors
If you want to convert all cells to upper case text (including formulas):
Sub MyUpperCase()
Application.ScreenUpdating = False
Dim cell As Range, v As String
For Each cell In Range("$A$1:" & Range("$A$1").SpecialCells(xlLastCell).Address)
v = cell.Text
If Len(v) > 0 Then cell.Value = UCase(v)
Next cell
Application.ScreenUpdating = True
End Sub
Be aware that all formulas not returning Null will also be converted to Text.
To see what cell (or cells) is the problem, you could try:
On Error Resume Next 'to enable in-line error-catching
For Each cell In Range("$A$1:" & Range("$A$1").SpecialCells(xlLastCell).Address)
If Len(cell) > 0 Then cell = UCase(cell)
If Err.Number > 0 Then
Debug.Print cell.Address
Err.Clear
End If
Next cell
On Error GoTo 0 'Turn off On Error Resume Next
On Error Resume Next is often abused, especially by new VBA programmers. Don't turn it on at the beginning of a sub and never turn it off and never check Err.Number. I find it a very good idea to think of it having a specific scope, and emphasizing that scope by indenting the statements in it, as I have done above. #MacroMan raises a good point that errors shouldn't be simply ignored (which is what happens if you abuse this construct).
Add the following error trapping in the middle of your code:
On Error Resume Next
If Len(cell) > 0 Then cell = UCase(cell)
If Err.Number <> 0 Then
MsgBox "Cell " & cell.Address & " has an error !"
End If
On Error GoTo 0
Note: Your code is fine with Numeric values, it's the #NA and #DIV/0 that's raising the errors when running your original code.
I have this piece of code getting a "No Cells Found" error. I don't understand why if I am using on error resume next and there will be times there won't be any "INATIVE" in that column.
Intersect(.UsedRange, .UsedRange.Offset(1)).SpecialCells(12).EntireRow.Delete
where 12 can also be written as xlCellTypeVisible.
Here are the lines before and after for more clarity:
With Sheets("Temp Activos")
On Error Resume Next
.UsedRange.AutoFilter 6, "INATIVE"
On Error GoTo 0
Intersect(.UsedRange, .UsedRange.Offset(1)).SpecialCells(12).EntireRow.Delete
r = WorksheetFunction.CountA(.Range("A:A"))
.UsedRange.AutoFilter
Converting comment to answer:
The On Error GoTo 0 clears the On Error Resume Next condition. So you'll need to move the On Error GoTo 0 down until just before the End With.
New to vba, trying an 'on error goto' but, I keep getting errors 'index out of range'.
I just want to make a combo box that is populated by the names of worksheets which contain a querytable.
For Each oSheet In ActiveWorkbook.Sheets
On Error GoTo NextSheet:
Set qry = oSheet.ListObjects(1).QueryTable
oCmbBox.AddItem oSheet.Name
NextSheet:
Next oSheet
I'm not sure whether the problem is related to nesting the On Error GoTo inside a loop, or how to avoid using the loop.
The problem is probably that you haven't resumed from the first error. You can't throw an error from within an error handler. You should add in a resume statement, something like the following, so VBA no longer thinks you are inside the error handler:
For Each oSheet In ActiveWorkbook.Sheets
On Error GoTo NextSheet:
Set qry = oSheet.ListObjects(1).QueryTable
oCmbBox.AddItem oSheet.Name
NextSheet:
Resume NextSheet2
NextSheet2:
Next oSheet
As a general way to handle error in a loop like your sample code, I would rather use:
on error resume next
for each...
'do something that might raise an error, then
if err.number <> 0 then
...
end if
next ....
How about:
For Each oSheet In ActiveWorkbook.Sheets
If oSheet.ListObjects.Count > 0 Then
oCmbBox.AddItem oSheet.Name
End If
Next oSheet
Actualy the Gabin Smith's answer needs to be changed a bit to work, because you can't resume with without an error.
Sub MyFunc()
...
For Each oSheet In ActiveWorkbook.Sheets
On Error GoTo errHandler:
Set qry = oSheet.ListObjects(1).QueryTable
oCmbBox.AddItem oSheet.name
...
NextSheet:
Next oSheet
...
Exit Sub
errHandler:
Resume NextSheet
End Sub
There is another way of controlling error handling that works well for loops. Create a string variable called here and use the variable to determine how a single error handler handles the error.
The code template is:
On error goto errhandler
Dim here as String
here = "in loop"
For i = 1 to 20
some code
Next i
afterloop:
here = "after loop"
more code
exitproc:
exit sub
errhandler:
If here = "in loop" Then
resume afterloop
elseif here = "after loop" Then
msgbox "An error has occurred" & err.desc
resume exitproc
End if
I do not want to craft special error handlers for every loop structure in my code so I have a way of finding problem loops using my standard error handler so that I can then write a special error handler for them.
If an error occurs in a loop, I normally want to know about what caused the error rather than just skip over it. To find out about these errors, I write error messages to a log file as many people do. However writing to a log file is dangerous if an error occurs in a loop as the error can be triggered for every time the loop iterates and in my case 80 000 iterations is not uncommon. I have therefore put some code into my error logging function that detects identical errors and skips writing them to the error log.
My standard error handler that is used on every procedure looks like this. It records the error type, procedure the error occurred in and any parameters the procedure received (FileType in this case).
procerr:
Call NewErrorLog(Err.number, Err.Description, "GetOutputFileType", FileType)
Resume exitproc
My error logging function which writes to a table (I am in ms-access) is as follows. It uses static variables to retain the previous values of error data and compare them to current versions. The first error is logged, then the second identical error pushes the application into debug mode if I am the user or if in other user mode, quits the application.
Public Function NewErrorLog(ErrCode As Variant, ErrDesc As Variant, Optional Source As Variant = "", Optional ErrData As Variant = Null) As Boolean
On Error GoTo errLogError
'Records errors from application code
Dim dbs As Database
Dim rst As Recordset
Dim ErrorLogID As Long
Dim StackInfo As String
Dim MustQuit As Boolean
Dim i As Long
Static ErrCodeOld As Long
Static SourceOld As String
Static ErrDataOld As String
'Detects errors that occur in loops and records only the first two.
If Nz(ErrCode, 0) = ErrCodeOld And Nz(Source, "") = SourceOld And Nz(ErrData, "") = ErrDataOld Then
NewErrorLog = True
MsgBox "Error has occured in a loop: " & Nz(ErrCode, 0) & Space(1) & Nz(ErrDesc, "") & ": " & Nz(Source, "") & "[" & Nz(ErrData, "") & "]", vbExclamation, Appname
If Not gDeveloping Then 'Allow debugging
Stop
Exit Function
Else
ErrDesc = "[loop]" & Nz(ErrDesc, "") 'Flag this error as coming from a loop
MsgBox "Error has been logged, now Quiting", vbInformation, Appname
MustQuit = True 'will Quit after error has been logged
End If
Else
'Save current values to static variables
ErrCodeOld = Nz(ErrCode, 0)
SourceOld = Nz(Source, "")
ErrDataOld = Nz(ErrData, "")
End If
'From FMS tools pushstack/popstack - tells me the names of the calling procedures
For i = 1 To UBound(mCallStack)
If Len(mCallStack(i)) > 0 Then StackInfo = StackInfo & "\" & mCallStack(i)
Next
'Open error table
Set dbs = CurrentDb()
Set rst = dbs.OpenRecordset("tbl_ErrLog", dbOpenTable)
'Write the error to the error table
With rst
.AddNew
!ErrSource = Source
!ErrTime = Now()
!ErrCode = ErrCode
!ErrDesc = ErrDesc
!ErrData = ErrData
!StackTrace = StackInfo
.Update
.BookMark = .LastModified
ErrorLogID = !ErrLogID
End With
rst.Close: Set rst = Nothing
dbs.Close: Set dbs = Nothing
DoCmd.Hourglass False
DoCmd.Echo True
DoEvents
If MustQuit = True Then DoCmd.Quit
exitLogError:
Exit Function
errLogError:
MsgBox "An error occured whilst logging the details of another error " & vbNewLine & _
"Send details to Developer: " & Err.number & ", " & Err.Description, vbCritical, "Please e-mail this message to developer"
Resume exitLogError
End Function
Note that an error logger has to be the most bullet proofed function in your application as the application cannot gracefully handle errors in the error logger. For this reason, I use NZ() to make sure that nulls cannot sneak in. Note that I also add [loop] to the second identical error so that I know to look in the loops in the error procedure first.
What about?
If oSheet.QueryTables.Count > 0 Then
oCmbBox.AddItem oSheet.Name
End If
Or
If oSheet.ListObjects.Count > 0 Then
'// Source type 3 = xlSrcQuery
If oSheet.ListObjects(1).SourceType = 3 Then
oCmbBox.AddItem oSheet.Name
End IF
End IF