简体   繁体   中英

MS-Access, VBA and error handling

This is more an observation than a real question: MS-Access (and VBA in general) is desperately missing a tool where error handling code can be generated automatically, and where the line number can be displayed when an error occurs. Did you find a solution? What is it? I just realized how many hundreds of hours I spared since I found the right answer to this basic problem a few years ago, and I'd like to see what are your ideas and solutions on this very important issue.

What about using "Erl", it will display the last label before the error (eg, 10, 20, or 30)?

Private Sub mySUB()
On Error GoTo Err_mySUB
10:
    Dim stDocName As String
    Dim stLinkCriteria As String
20:
    stDocName = "MyDoc"
30:
    DoCmd.openform stDocName, acFormDS, , stLinkCriteria    
Exit_mySUB:
    Exit Sub
Err_mySUB:
    MsgBox Err.Number & ": " & Err.Description & " (" & Erl & ")"
    Resume Exit_mySUB
End Sub

My solution is the following:

  1. install MZ-Tools , a very interesting add-on for VBA. No they did not pay me to write this. Version 3 was free, but since version 8.0, the add-in is commercially sold.
  2. program a standard error handler code such as this one (see MZ-Tools menu/Options/Error handler):

On Error GoTo {PROCEDURE_NAME}_Error
{PROCEDURE_BODY}
On Error GoTo 0
Exit {PROCEDURE_TYPE}

{PROCEDURE_NAME}_Error:
debug.print "#" & Err.Number, Err.description, "l#" & erl, "{PROCEDURE_NAME}", "{MODULE_NAME}"

This standard error code can be then automatically added to all of your procs and function by clicking on the corresponding button in the MZ-Tools menu. You'll notice that we refer here to a hidden and undocumented function in the VBA standard library, 'Erl', which stands for 'error line'. You got it! If you ask MZ-Tools to automatically number your lines of code, 'Erl' will then give you the number of the line where the error occured. You will have a complete description of the error in your immediate window, such as:

#91, Object variable or With block variable not set, l# 30, addNewField, Utilities

Of course, once you realize the interest of the system, you can think of a more sophisticated error handler, that will not only display the data in the debug window but will also:

  1. display it as a message on the screen
  2. Automatically insert a line in an error log file with the description of the error or
  3. if you are working with Access or if you are connected to a database, automatically add a record to a Tbl_Error table!

meaning that each error generated at the user level can be stored either in a file or a table, somewhere on the machine or the network. Are we talking about building an automated error reporting system working with VBA?

Well there are a couple of tools that will do what you ask MZ Tools and FMS Inc come to mind.

Basically they involve adding an:

On Error GoTo ErrorHandler

to the top of each proc and at the end they put an:

ErrorHandler:
  Call MyErrorhandler Err.Number, Err.Description, Err.LineNumber

label with usually a call to a global error handler where you can display and log custom error messages

You can always roll your own tool like Chip Pearson did. VBA can actually access it's own IDE via the Microsoft Visual Basic for Applications Extensibility 5.3 Library . I've written a few class modules that make it easier to work with myself. They can be found on Code Review SE .

I use it to insert On Error GoTo ErrHandler statements and the appropriate labels and constants related to my error handling schema. I also use it to sync up the constants with the actual procedure names (if the function names should happen to change).

There is no need to buy tools DJ mentioned. Here is my code for free:

Public Sub InsertErrHandling(modName As String)
    Dim Component As Object
    Dim Name As String
    Dim Kind As Long
    Dim FirstLine As Long
    Dim ProcLinesCount As Long
    Dim Declaration As String
    Dim ProcedureType As String
    Dim Index As Long, i As Long
    Dim LastLine As Long
    Dim StartLines As Collection, LastLines As Collection, ProcNames As Collection, ProcedureTypes As Collection
    Dim gotoErr As Boolean

    Kind = 0
    Set StartLines = New Collection
    Set LastLines = New Collection
    Set ProcNames = New Collection
    Set ProcedureTypes = New Collection

    Set Component = Application.VBE.ActiveVBProject.VBComponents(modName)
        With Component.CodeModule

            ' Remove empty lines on the end of the code
            For i = .CountOfLines To 1 Step -1
                If Component.CodeModule.Lines(i, 1) = "" Then
                  Component.CodeModule.DeleteLines i, 1
                Else
                    Exit For
                End If
            Next i

            Index = .CountOfDeclarationLines + 1
            Do While Index < .CountOfLines
                gotoErr = False
                Name = .ProcOfLine(Index, Kind)
                FirstLine = .ProcBodyLine(Name, Kind)
                ProcLinesCount = .ProcCountLines(Name, Kind)
                Declaration = Trim(.Lines(FirstLine, 1))
                LastLine = FirstLine + ProcLinesCount - 2
                If InStr(1, Declaration, "Function ", vbBinaryCompare) > 0 Then
                    ProcedureType = "Function"
                Else
                    ProcedureType = "Sub"
                End If
                Debug.Print Component.Name & "." & Name, "First: " & FirstLine, "Lines:" & ProcLinesCount, "Last: " & LastLine, Declaration
                Debug.Print "Declaration: " & Component.CodeModule.Lines(FirstLine, 1), FirstLine
                Debug.Print "Closing Proc: " & Component.CodeModule.Lines(LastLine, 1), LastLine

                ' do not insert error handling if there is one already:
                For i = FirstLine To LastLine Step 1
                    If Component.CodeModule.Lines(i, 1) Like "*On Error*" Then
                        gotoErr = True
                        Exit For
                    End If
                Next i
                If Not gotoErr Then
                    StartLines.Add FirstLine
                    LastLines.Add LastLine
                    ProcNames.Add Name
                    ProcedureTypes.Add ProcedureType
                End If

                Index = FirstLine + ProcLinesCount + 1
            Loop

            For i = LastLines.Count To 1 Step -1
                If Not (Component.CodeModule.Lines(StartLines.Item(i) + 1, 1) Like "*On Error GoTo *") Then
                    Component.CodeModule.InsertLines LastLines.Item(i), "ExitProc_:"
                    Component.CodeModule.InsertLines LastLines.Item(i) + 1, "    Exit " & ProcedureTypes.Item(i)
                    Component.CodeModule.InsertLines LastLines.Item(i) + 2, "ErrHandler_:"
                    Component.CodeModule.InsertLines LastLines.Item(i) + 3, "    Call LogError(Err, Me.Name, """ & ProcNames.Item(i) & """)"
                    Component.CodeModule.InsertLines LastLines.Item(i) + 4, "    Resume ExitProc_"
                    Component.CodeModule.InsertLines LastLines.Item(i) + 5, "    Resume ' use for debugging"

                    Component.CodeModule.InsertLines StartLines.Item(i) + 1, "    On Error GoTo ErrHandler_"
                End If
            Next i
        End With
End Sub

Put it in a module and call it from Immediate Window every time you add new function or sub to a form or module like this (Form1 is name of your form):

MyModule.InsertErrHandling "Form_Form1"

It will alter your ode in Form1 from this:

Private Function CloseIt()
    DoCmd.Close acForm, Me.Name
End Function

to this:

Private Function CloseIt()
    On Error GoTo ErrHandler_
        DoCmd.Close acForm, Me.Name
ExitProc_:
Exit Function
ErrHandler_:
    Call LogError(Err, Me.Name, "CloseIt")
    Resume ExitProc_
    Resume ' use for debugging
End Function

Create now in a module a Sub which will display the error dialog and where you can add inserting the error to a text file or database:

Public Sub LogError(ByVal objError As ErrObject, moduleName As String, Optional procName As String = "")
    On Error GoTo ErrHandler_
    Dim sql As String
    MsgBox "Error " & Err.Number & " Module " & moduleName & Switch(procName <> "", " in " & procName) & vbCrLf & " (" & Err.Description & ") ", vbCritical
Exit_:
    Exit Sub
ErrHandler_:
    MsgBox "Error in LogError procedure " & Err.Number & ", " & Err.Description
    Resume Exit_
    Resume ' use for debugging
End Sub

This code does not enter error handling if there is already "On Error" statement in a proc.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM