vba prevent error.raise from stopping code execution - vba

I have a bunch of vba code that was written using the .Raise and On Error methods to deal with exceptions. In most cases this works well, but in some functions we want the execution to continue notwithstanding the exception. In such cases, the developers used On Error GoTo 0. The issue is that execution continues only after the error message box is dismissed.
Thus my question is: is there a way to suppress all message boxes created by the .Raise method for a certain range of code? I've tried Application.DisplayAlerts = False but the alerts from .Raise are still displayed.
Thanks!

On Error GoTo 0 restores the normal error handling. You use it to cancel a previously set On Error Goto ....
What you want is On Error Resume Next.

Related

How do I use On Error GoTo?

I'm lead to believe that the vba IDE doesn't allow proper breaking on errors. I asked a question on how to identify runtime errors here:
How do I break on errors?
The solution / workaround seems to be to use On Error GoTo ErrorHandler or similar. I'm trying to make that work but haven't had much success.
According to Microsoft On Error GoTo will send you to the specified code of region when a runtime error occurs (https://msdn.microsoft.com/en-us/library/5hsw66as.aspx). Based on my experience, that's not the whole story. This question is about how to actually make that solution work.
So I have the following function:
Function generateTimeseries() As Variant
On Error GoTo ErrorHandler
Dim currentArray As Range
currentArray = Selection.Range ' this doesn't work - I don't care why
generateTimeseries = currentArray.Rows
Return
ErrorHandler:
Debug.Assert False
Resume ' break here if you want to catch errors
End Function
This function never enters the ErrorHandler code. Instead, it falls over on Selection.Range, with the function simply returning #VALUE. I don't actually care why this breaks, I just want to know how I can get the IDE to tell me that it actually fell over on that line, without me jumping through the code manually.
I just tested and your error handler works, it breaks correctly on
Debug.Assert False
--> check the debugger options.
However this is not a correct way to handle errors in vBA, because if you compile your application and forget to adapt such error handler, when a user will encounter an error in the procedure, the application will fall into an infinite loop.
In mostly all situations, you have to throw a msgbox to let the user know that an error has occurred, and terminate the current procedure. The only situation where this is not applicable is when you do something that can trigger an error but you know it and deliberately want to bypass the error. It's a rare situation.
I have never used Assert method and this is how I handle my errors in every procedure
Function generateTimeseries() As Variant
On Error GoTo ErrorHandler
Dim currentArray As Range
currentArray = Selection.Range ' this doesn't work - I don't care why
generateTimeseries = currentArray.Rows
Exit_Function:
Exit Function
ErrorHandler:
MsgBox Err.Description, vbCritical, "Error " & Err.Number
Resume Exit_Function ' breakpoint here if you want examine the code after error, otherwhise it terminates the function
Resume ' Once the code breaks on the above line, move to this instruction and F8 to return to the statement in error
End Function
I had enabled the feature to "Break on all Errors" in an earlier attempt to break on all errors.
I turned this off by going to Tools->Options->General and replacing Break on All Errors with Break on Unhandled Errors.
It turns out that Break on All Errors is Windows for "Break on Some Errors, but just return garbage if it's a UDF error".

Capture Variable/Object Name causing the error

How can I capture the variable name or object name causing an error in VBA?
Ex
Sub test()
On Error GoTo Handler
i = 0
n = 1/i
Handler:
Select Case variablename
Case "n"
'do something................
The error is not being caused by a variable. The error is caused by the line of code that attempts to divide 1 by 0. The assignment doesn't actually happen.
The best way I've found to diagnose issues when I'm actively developing is to have Stop and then Resume in the error handler.
Public Sub func()
On Error GoTo ErrHandler
'... some code here ...
Exit Sub
ErrHandler:
Stop
Resume
End Sub
The way this works is, when there is an error VBA will break (pause the execution, show the instruction pointer arrow and yellow highlighting) on the Stop. You can step to the Resume and then once more to find out which specific line of code is causing the error.
Once you start to understand what errors you are encountering you can build individual If ... Then cases for them to handle each appropriately. When you are ready to release your code into a production environment (i.e., for other users), you would need to replace the Stop/Resume with an unexpected error handler that either logs it or displays it to the user.
While Blackhawk's answer works fine for a small example, I personally prefer to avoid modifying code to track down errors like that. If I'm working on a lot of files trying to track down a nasty bug, it's entirely possible that I might forget to remove any "special code" like Stop statements, etc. after I've finished debugging. If any such code makes it to production, that can be quite embarrassing for me. In addition, it can be annoying having to add this code to many different places in order to figure out where the error is happening in the first place (it's not always as obvious as in your example!).
For these reasons, I prefer to use the facilities provided by the VBA Editor. So:
Open the VBA Editor.
Go to Tools > Options.
Select the General tab and click the Break on All Errors radio button.
Click OK.
Now run your code and you'll notice that when it reaches this line:
n = 1/i
you'll get an error dialog saying what the problem is ("Run-time error '11': Division by zero") and you'll get the option to click the "Debug" button on the dialog, which will break execution at the above line and will highlight it in yellow.
Once you're done with your debugging and find the error that had been troubling you, you can go back to the VBA Editor options and revert the "Error Trapping" setting back to the default option ("Break on Unhandled Errors").
This way, you've done no changes to the code that you need to remember to revert before releasing the code and the actual operation to set/unset this behaviour only takes a few clicks.

Error on ActiveSelection.Tasks

Does anyone know what this means
Set oProjTasks = ActiveSelection.Tasks
I have a macro that generates status reports from MS project and exports them directly into MS Word. It is a slick tool when it works.
When I run it now it throws "runtime error '424': object required" at this point.
How do I fix this?
The code that you are displaying is a set statement, that is setting the object ProjTasks equal to the task that is selected in the message box. The ActiveSelection property returns a selection object that represents the active selection.
It could be that you are experiencing an issue where there are no items selected, in which case it will throw a trappable error code 424. There is a code snippet that you can modify from the MSDN that will work to prevent this type of error from occuring.
Here is the link to the MSDN article... just remember to not use this code verbatim, but modify it to work with your macro.
http://msdn.microsoft.com/en-us/library/aa169315%28v=office.11%29.aspx
You could try just wrapping the error check around the set statement. I've written a small macro on a non-empty project file:
Sub Testing()
On Error GoTo ActiveSelectionErrHandler:
Set oProjTasks = ActiveSelection.Tasks
If oProjTasks Is Nothing Then
MsgBox "No tasks in current project"
End If
ActiveSelectionErrHandler:
Set oProjTasks = ThisProject.Tasks 'or something like that
Resume Next
End Sub
This handles the error but as Steve has already expressed more work is required to integrate the code.
You will have to follow the code to make changes to handle oProjTasks being empty where it is expected to have some values. Otherwise you will see more errors perhaps where the oProjTasks is found to be empty.
Another alternative solution could be to launch the macro after selecting a project as the code you have quoted will work fine if something is selected.

Err.Number vs try-catch in VB.net

I have inherited an old VB.net-project. The code mostly uses try-catch for error-handling. However in some places I have found If Err.Number <> 0 Then.
If an error occurs, what decides if an Exception should be thrown, or just setting Err?
I don't want to handle error both ways...
The Err object is use with the old-style On Error error handling construct, that is a remainder from classic VB. Try-Catch is the more current .NET style of error handling.
You can learn more about this, and the difference in Error Handling in Visual Basic.NET.
Sounds like the old code was using On Error Resume Next. Make sure you understand what it does, its kind of odd!
The docs explain it
On Error Resume Next causes execution to continue with the statement immediately following the statement that caused the run-time error, or with the statement immediately following the most recent call out of the procedure containing the On Error Resume Next statement. This statement allows execution to continue despite a run-time error. You can place the error-handling routine where the error would occur rather than transferring control to another location within the procedure.
You would then use If Err.Number <> 0 to check whether an error had occurred.

VBA Error "Bubble Up"

I haven't read much about it, but the author at the link below recommends that I don't use "bubble up" to centralize error handling in VBA.
Excel Programming Weekend Crash Course via Google Books
But I'm not sure why he recommends that, and he doesn't really explain.
Can someone tell me why I should put error handling in EVERY procedure instead of using "bubble up"? Or at least, do you know why the author says not to?
Thanks.
The short answer to your first question is "you shouldn't put an error handler in EVERY procedure". But usually some routines do need them!
To say that "every procedure must have an error handler" is in general terrible advice. The flaws with VBA error handling have been much discussed elsewhere. Conceptually, though, it's not all that different from the more standard form of exception handling found in other languages. Most of the best practices from those languages apply. You should handle errors at the lowest level where handling them makes sense. Sometimes this is in the procedure where the error occurred, many times not.
Often the most meaningful thing an internal routine can do when an error occurs is just let it pass on up the stack so it can reach code that knows what to do with it. It really depends on the routine and how it fits with the rest of the program.
Consider these examples:
A handler in a calling procedure will handle all errors raised by the routines it calls. So if a particular routine doesn't need any cleanup, then don't put any error-handler code there:
Sub Caller()
On Error GoTo HANDLER
ChildProc
On Error GoTo 0
Exit Sub
HANDLER:
Debug.Print Error, "Parent cleanup - something happened in either this procedure or a procedure that it called"
End Sub
Sub ChildProc()
Debug.Print 10 / 0 ' causes error
'Don't bother handling errors here since there's nothing this routine can do about them
End Sub
On the other hand, you may need cleanup tasks, in which case you need an error handler.
Sub Caller()
On Error GoTo HANDLER
ChildProc
On Error GoTo 0
Exit Sub
HANDLER:
Debug.Print Error, "Parent cleanup"
End Sub
Sub ChildProc()
'Pretend this routine gets ahold of some resource that must be cleaned up when it's done
call get_resources()
On Error GoTo HANDLER
Debug.Print 10 / 0 ' causes error
On Error GoTo 0
'Clean up once we're done
call release_resources()
Exit Sub
HANDLER:
Debug.Print Error, "Child cleanup"
'Clean up in case of an error
call release_resources()
'Raise another error if necessary to let callers know something went wrong
Err.Raise 10000, "ChildProc", Error
End Sub
The above examples are just meant to illustrate the point about why you might need or not need an error handler in a given routine. So it's worth noting that in real code the "resource" example is usually handled better with an RAII technique where the error handling is encapsulated with the resource acquisition and release - see https://stackoverflow.com/a/3792280/58845 for a VBA example. And things like whether to re-raise a caught error are also situation-dependent. Sometimes it's possible to handle the error entirely locally and then there is no need to tell callers that anything went wrong.
The answer to your second question is that the author doesn't seem to understand exception handling very well. He admits that error handling is context specific, but then seems to suggest that every procedure should locally decide between "correct the problem right here and resume execution" and "terminate the program". He leaves out the usually correct option, which is "clean up locally and kick the problem upstairs". So routines with no need to clean up locally should just let errors "bubble up".
My 2 cents:
You should put error handlers on all Public Procedures and Events. This means that the procedure at the bottom of the call stack will always have an error handler. Then add error handlers in your other procedures as it makes sense. If an error occurs in a procedure that does not have an error handler, it will "bubble up" to the top level error handler where it be logged/displayed in a professional fashion.
A scenario where you might want to add an error handler to a private (lower level) procedure is this:
The code needs to be fast. You have a rare condition that can be avoided, but will force you to perform an expensive logical test inside of a loop (or worse a nested loop).
You might perform the logical test in the error handler, and if it's said "rare occurrence" make the correction and resume. As the condition is rare, you will see performance gains for most conditions. If the error handler can't figure out and correct the problem then re-raise the error to bubble it on up the stack.
Obviously this is just one scenario.
I'm not sure what the default error handling of VBA is, but since its Visual Basic for Applications, and those applications include things like excel and word, I assume just a dialog box will appear which will not be helpful to the user.
I assume that the author has been bitten by code not handling errors so he now recommends all procedures to handle errors.
The full answer is that you have to be aware of every error that can occur and to have code in place to handle it, whether it is as low as possible (where you may not know what to do), or as high as possible (which means less effort writing error handling code, but not knowing why the error occurred), or strategically (which is just in the right places where you should be able to recover from most common errors) or just everywhere (which may be just too much development effort).
I see at least one reason in his explanation: because doing so deprives you from the benefit of Resume (next).
Plus you won't know in which module the error happened.
It is better not to use "bubble-up" part of error handling because errors should be handled & if it is known as to what to do, if such an error occurs - is better known to procedure as to what to do than the calling procedure.
Sub test()
On Error GoTo e
Dim c As Integer
Dim d As Integer
c = add(5, 0)
d = divideWhichManagedItsOwnErrorHandling(5, 0)
d = divide(5, 0)
Exit Sub
e:
MsgBox "error occurred somewhere for which I don't know what to do: " + Err.Description
End Sub
Function add(a As Integer, b As Integer) As Integer
add = a + b
End Function
Function divide(a As Integer, b As Integer) As Integer
divide = a / b 'if error occurs, it will "bubble-up" to the caller.
End Function
Function divideWhichManagedItsOwnErrorHandling(a As Integer, b As Integer) As Integer
On Error Resume Next
Dim result As Integer
result = a / b
If Err.Number = 11 Then 'if divide by zero occurred, user must have passed 0 for b
result = 0 ' return 0 if the divide by zero occurs.
End If
divideWhichManagedItsOwnErrorHandling = result
End Function