I have a simple question about error-handling in VBA.
I know how to use the On Error GoTo ErrHandler statement but instead using my own code at the specified label, I would rather use a prefabricated VBA-message. Something like this in C#:
catch(Exception ex){
Console.Writeline(ex.Message);
}
Create an ErrorHandler Module and place this sub in it.
Public Sub messageBox(moduleName As String, procName As String, Optional style As VbMsgBoxStyle = vbCritical)
MsgBox "Module: " & moduleName & vbCrLf & _
"Procedure: " & procName & vbCrLf & _
Err.Description, _
style, _
"Runtime Error: " & Err.number
End Sub
Call it from anywhere in your project like so.
Private sub Foo()
On Error GoTo ErrHandler
'do stuff
ExitSub:
' clean up before exiting
Exit Sub
ErrHandler:
ErrorHandler.messageBox "ThisModuleName","Foo"
Resume ExitSub
End Sub
I use a module scoped constant to hold the module name.
Modify to suit your needs.
In your error handler code your can access the Err.Number and Err.Description. The Description in the error message you would have seen without error handling, so is the equivalent of ex.Message in your code sample.
Related
I have written very ugly VBA code, but I don't know how I can make this look better.
I have a function (say Function Main) that calls another 3 functions inside it. These 3 functions have to happen in order, and if one fails, the rest shouldn't happen. Also, I need to retrieve proper, descriptive error message. I have another software that invokes VBA code and calls this Main function, and if there is an error, I need to use that information, so I am returning a value (either a descriptive error message or "Completed") from Main as well.
Function Main
Dim result As String
result = ProcessA();
If result <> "OK" Then
Main = result
Exit Function
End If
result = ProcessB();
If result <> "OK" Then
Main = result
Exit Function
End If
result = ProcessC();
If result <> "OK" Then
Main = result
Exit Function
End If
Main = "Completed"
End Function
Function ProcessA()
On Error GoTo Errored
'Do some operation
ProcessA = "OK" 'At the end of operation, assign OK and
Exit Function 'Exit function here
Errored: 'In case of an error, get error information and return it
ProcessA = "Error Line: " & Erl & Chr(13) _
& "Error Description: " & Err.Description & Chr(13) _
& "Error at Process A"
End Function
'ProcessB and ProcessC also have a very similar style.
As you can see, my Main function has so many redundant code, but I can't think of any other better way. Could someone give me advice on how to make this better?
I would suggest re-throwing the error then catching it in the calling method:
Function Main()
On Error GoTo haveError
ProcessA
ProcessB
ProcessC
Main = "Completed"
Exit Function
haveError:
Main = err.Description
End Function
Sub ProcessA()
On Error GoTo Errored
'Do some operation
Exit Sub
Errored:
err.Raise Number:=err.Number, Description:=DescribeError(err, "ProcessA")
End Sub
Sub ProcessB()
On Error GoTo Errored
'Do some operation
Debug.Print 1 / 0 'eg. error here....
Exit Sub
Errored:
err.Raise Number:=err.Number, Description:=DescribeError(err, "ProcessB")
End Sub
Sub ProcessC()
On Error GoTo Errored
'Do some operation
Exit Sub
Errored:
err.Raise Number:=err.Number, Description:=DescribeError(err, "ProcessC")
End Sub
'utility
Function DescribeError(err As ErrObject, procName As String)
DescribeError = "Error in method: " & procName & vbLf & _
"Error Line: " & erl & vbLf & _
"Error Description: " & err.Description
End Function
Function Main
Dim result As String
result = ProcessA(): If result <> "OK" Then goto errResult
result = ProcessB(): If result <> "OK" Then goto errResult
result = ProcessC(): If result <> "OK" Then goto errResult
Main = "Completed"
Exit Function
errResult:
Main=result
End Function
Private errorMsg As String
Function Main()
If ProcessA Then
If ProcessB Then
If ProcessC Then
Main = "Completed"
End If
End If
End If
If Main = vbNullString Then Main = errorMsg
End Function
Private Function ProcessA() As Boolean
ProcessA = True
Exit Function
Err:
ReportError "Process A"
End Function
Private Sub ReportError(argSrc As String)
errorMsg = "Error Line: " & Erl & Chr(13) _
& "Error Description: " & Err.Description & Chr(13) _
& "Error at " & argSrc
End Sub
Using Application.Run(), you can process any number of steps in a loop:
Function Main()
Dim f, result
For Each f In Array("ProcessA", "ProcessB", "ProcessC") ' and so on
result = Application.Run(f)
If result <> "OK" Then
Main = result
Exit Function
End If
Next
Main = "Completed"
End Function
I have a form called ConsultaInfo which display a list (LNome) of all partners name and I want to run a query that will bring back to the same form all the rest of information from the selected partner name from the that list.
My intention is when I double click the partner name, a query (ConsultaSocio) will be ran bringing all the other personal information from the selected partner from the Table Socios and then these information will be displayed on the right side of the same form.
Therefore, I would like to know how can I accomplish that.
There are several ways of doing this, all of which assuming that in addition to the name being displayed in the list box, there is also an ID field.
If the form is bound to the query, then you can use the .RecordsetClone to find the matching record and then set the Form's position:
Private Sub lstName_DblClick(Cancel As Integer)
On Error GoTo E_Handle
Me.RecordsetClone.FindFirst "ID=" & Me!lstName
Me.Bookmark = Me.RecordsetClone.Bookmark
sExit:
On Error Resume Next
Exit Sub
E_Handle:
MsgBox Err.Description & vbCrLf & vbCrLf & "ConsultaInfo!lstName_DblClick", vbOKOnly + vbCritical, "Error: " & Err.Number
Resume sExit
End Sub
Another option is to change the Form's RecordSource property to only have that record:
Private Sub lstName_DblClick(Cancel As Integer)
On Error GoTo E_Handle
Me.RecordSource = "SELECT * FROM Socio WHERE ID=" & Me!lstName
sExit:
On Error Resume Next
Exit Sub
E_Handle:
MsgBox Err.Description & vbCrLf & vbCrLf & "ConsultaInfo!lstName_DblClick", vbOKOnly + vbCritical, "Error: " & Err.Number
Resume sExit
End Sub
Regards,
In a form, create a new record, edit some data but before saving it use a combo box on the form to select another record to navigate to. This triggers the cboSalePicker_AfterUpdate. Then during this sub Form_BeforeUpdate executes. The user clicks no on the MsgBox to not save the new record. Then the rest of cboSalePicker_AfterUpdate is executed however the following error message is displayed:
Error Message
Error number -2147417848: Method ‘FindFirst’ of object ‘Recordset2’ failed.
Associated with the line Me.Recordset.FindFirst "[SaleID] = " & Str(Nz(cboSalePicker.Value, 0))
However, if the new record is saved no error is produced and the code performs as expected.
Form_BeforeUpdate
Private Sub Form_BeforeUpdate(Cancel As Integer)
On Error GoTo ErrorHandler
Dim strMsg As String
Dim iResponse As Integer
'Specify the mesage to display
strMsg = "Do you wish to save the changes?" & Chr(10)
strMsg = strMsg & "Click Yes to Save or No to Discard changes."
'Display the msg box
iResponse = MsgBox(strMsg, vbQuestion + vbYesNo, "Save Record?")
'Check response
If iResponse = vbNo Then
'Undo the change.
DoCmd.RunCommand acCmdUndo
'Cancel the update
Cancel = True
End If
Exit Sub
ErrorHandler:
MsgBox "Error number " & Err.Number & ": " & Err.Description
End Sub
cboSalePicker_AfterUpdate
Private Sub cboSalePicker_AfterUpdate()
On Error GoTo ErrorHandler
Me.Recordset.FindFirst "[SaleID] = " & Str(Nz(cboSalePicker.Value, 0))
Exit Sub
ErrorHandler:
MsgBox "Error number " & Err.Number & ": " & Err.Description
End Sub
Thanks
You are converting Your SaleID into a String using this
Str(Nz(cboSalePicker.Value, 0))
But your find first is looking for a number. If SaleID is a number then remove the Str() function from your code around the combobox value.
To show the concatenation try this
Private Sub cboSalePicker_AfterUpdate()
On Error GoTo ErrorHandler
Dim sCriteria as String
sCriteria = "[SaleID] = " & Nz(Me.cboSalePicker, 0)
debug.print sCriteria
Me.Recordset.FindFirst sCriteria
Exit Sub
ErrorHandler:
MsgBox "Error number " & Err.Number & ": " & Err.Description
End Sub
Comment out the first error handler line whilst you are debugging things.
I want manage SQL server error code in access form
sample duplicate error from SQL server
In Access VBA, you need to use:
On Error GoTo Error_Handler
' YOUR CODE HERE
.
.
.
Return_Label:
Exit Function
Error_Handler:
'What goes here depends on the data access model
Resume Return_Label
You may have to retrieve the Errors collection of the Error object as described here.
It shows this example code:
Sub DescriptionX()
Dim dbsTest As Database
On Error GoTo ErrorHandler
' Intentionally trigger an error.
Set dbsTest = OpenDatabase("NoDatabase")
Exit Sub
ErrorHandler:
Dim strError As String
Dim errLoop As Error
' Enumerate Errors collection and display properties of
' each Error object.
For Each errLoop In Errors
With errLoop
strError = _
"Error #" & .Number & vbCr
strError = strError & _
" " & .Description & vbCr
strError = strError & _
" (Source: " & .Source & ")" & vbCr
strError = strError & _
"Press F1 to see topic " & .HelpContext & vbCr
strError = strError & _
" in the file " & .HelpFile & "."
End With
MsgBox strError
Next
Resume Next
End Sub
I created an error-handler using On Error Goto statement, and I put a few lines of cleaning code and display the error message, but now I don't want to lose the comfortableness of the default handler which also points me to the exact line where the error has occured. How can I do that?
First the good news. This code does what you want (please note the "line numbers")
Sub a()
10: On Error GoTo ErrorHandler
20: DivisionByZero = 1 / 0
30: Exit Sub
ErrorHandler:
41: If Err.Number <> 0 Then
42: Msg = "Error # " & Str(Err.Number) & " was generated by " _
& Err.Source & Chr(13) & "Error Line: " & Erl & Chr(13) & Err.Description
43: MsgBox Msg, , "Error", Err.HelpFile, Err.HelpContext
44: End If
50: Resume Next
60: End Sub
When it runs, the expected MsgBox is shown:
And now the bad news:
Line numbers are a residue of old versions of Basic. The programming environment usually took charge of inserting and updating them. In VBA and other "modern" versions, this functionality is lost.
However, Here there are several alternatives for "automatically" add line numbers, saving you the tedious task of typing them ... but all of them seem more or less cumbersome ... or commercial.
HTH!
There is a simpler way simply disable the error handler in your error handler if it does not match the error types you are doing and resume.
The handler below checks agains each error type and if none are a match it returns error resume to normal VBA ie GoTo 0 and resumes the code which then tries to rerun the code and the normal error block pops up.
On Error GoTo ErrorHandler
x = 1/0
ErrorHandler:
if Err.Number = 13 then ' 13 is Type mismatch (only used as an example)
'error handling code for this
end if
If err.Number = 1004 then ' 1004 is Too Large (only used as an example)
'error handling code for this
end if
On Error GoTo 0
Resume
This answer does not address the Debug button (you'd have to design a form and use the buttons on that to do something like the method in your next question). But it does address this part:
now I don't want to lose the comfortableness of the default handler which also point me to the exact line where the error has occured.
First, I'll assume you don't want this in production code - you want it either for debugging or for code you personally will be using. I use a compiler flag to indicate debugging; then if I'm troubleshooting a program, I can easily find the line that's causing the problem.
# Const IsDebug = True
Sub ProcA()
On Error Goto ErrorHandler
' Main code of proc
ExitHere:
On Error Resume Next
' Close objects and stuff here
Exit Sub
ErrorHandler:
MsgBox Err.Number & ": " & Err.Description, , ThisWorkbook.Name & ": ProcA"
#If IsDebug Then
Stop ' Used for troubleshooting - Then press F8 to step thru code
Resume ' Resume will take you to the line that errored out
#Else
Resume ExitHere ' Exit procedure during normal running
#End If
End Sub
Note: the exception to Resume is if the error occurs in a sub-procedure without an error handling routine, then Resume will take you to the line in this proc that called the sub-procedure with the error. But you can still step into and through the sub-procedure, using F8 until it errors out again. If the sub-procedure's too long to make even that tedious, then your sub-procedure should probably have its own error handling routine.
There are multiple ways to do this. Sometimes for smaller programs where I know I'm gonna be stepping through it anyway when troubleshooting, I just put these lines right after the MsgBox statement:
Resume ExitHere ' Normally exits during production
Resume ' Never will get here
Exit Sub
It will never get to the Resume statement, unless you're stepping through and set it as the next line to be executed, either by dragging the next statement pointer to that line, or by pressing CtrlF9 with the cursor on that line.
Here's an article that expands on these concepts: Five tips for handling errors in VBA. Finally, if you're using VBA and haven't discovered Chip Pearson's awesome site yet, he has a page explaining Error Handling In VBA.
For Me I just wanted to see the error in my VBA application so in the function I created the below code..
Function Database_FileRpt
'-------------------------
On Error GoTo CleanFail
'-------------------------
'
' Create_DailyReport_Action and code
CleanFail:
'*************************************
MsgBox "********************" _
& vbCrLf & "Err.Number: " & Err.Number _
& vbCrLf & "Err.Description: " & Err.Description _
& vbCrLf & "Err.Source: " & Err.Source _
& vbCrLf & "********************" _
& vbCrLf & "...Exiting VBA Function: Database_FileRpt" _
& vbCrLf & "...Excel VBA Program Reset." _
, , "VBA Error Exception Raised!"
*************************************
' Note that the next line will reset the error object to 0, the variables
above are used to remember the values
' so that the same error can be re-raised
Err.Clear
' *************************************
Resume CleanExit
CleanExit:
'cleanup code , if any, goes here. runs regardless of error state.
Exit Function ' SUB or Function
End Function ' end of Database_FileRpt
' ------------------