除了自定义错误处理程序之外,如何在使用默认错误处理程序时避免级联

时间:2016-04-04 21:29:27

标签: vba excel-vba error-handling excel

我在工作时使用MZ Tools for Excel VBA,并且我在大多数程序中使用自动错误处理程序功能,因为它允许我轻松地将我的联系信息放在错误消息中并自动将显示警报和屏幕更新重新打开。但是如果在VBA中使用错误处理程序,则很难找到触发错误的确切代码行,尤其是在冗长的过程中。默认情况下我唯一可以理解使用自定义错误处理程序并获取触发错误的代码行的方法是将这两行添加到错误处理程序的末尾(以便问题行将重新运行自定义错误处理程序完成其工作后的默认错误处理程序):

On Error GoTo 0
Resume

如果只有一个错误处理程序,这很有效;用户需要单击另一个对话框,但我可以正常调试,同时保持我的自定义错误处理程序内置的功能。但是如果调用例程和子例程都有不同的错误处理程序,则用户开始获得类似外观的冗长级联对话框。确切地说,我得到1 + n!对话框,其中n是具有错误处理程序的子程序的级别数。

说明问题的最简单方法是,当我运行第一个例程时,我收到4个错误消息而不是2个错误消息:

Sub TstErrHndlr()

   On Error GoTo TstErrHndlr_Error1

    Call TstErrHndlrA

   On Error GoTo 0
   Exit Sub

TstErrHndlr_Error1:
    Application.ScreenUpdating = True
    Application.DisplayAlerts = True
    Call MsgBox("Error " & Err.Number & " (" & Err.Description _
            & ") in procedure TstErrHndlr " _
            & "of Module Create_Package." _
            & "  Contact [My Name] for assistance " _
            & "(myemal@company.com, (123)456-7890)")
    On Error GoTo 0
    Resume
End Sub

Sub TstErrHndlrA()

   On Error GoTo TstErrHndlrA_Error1

    Dim X As Double
    X = 1 / 0

   On Error GoTo 0
   Exit Sub

TstErrHndlrA_Error1:
    Application.Calculation = xlCalculationAutomatic
    Application.ScreenUpdating = True
    Application.DisplayAlerts = True
    Call MsgBox("Error " & Err.Number & " (" & Err.Description _
            & ") in procedure TstErrHndlrA " _
            & "of Module Create_Package." _
            & "  Contact [My Name] for assistance " _
            & "(myemal@company.com, (123)456-7890")
   On Error GoTo 0
   Resume
End Sub

在调试模式下执行代码后,似乎每当一个过程被另一个过程调用时,无论在调用函数中启用哪个错误处理程序,都会成为行{{1}启用的错误处理程序无论重复多少次。我想知道为什么VBA会以这种方式运行,如何使它不以这种方式运行,和/或是否有更好的方法来实现我在使用错误处理程序时获取触发错误的代码行的目标。 / strong>我知道在使用新的错误处理程序(例如On Error GoTo 0)调用函数之前,我可以恢复默认的错误处理程序,但这会造成难看的混乱代码,并且不会处理在功能调用。

2 个答案:

答案 0 :(得分:2)

我建议您重新构建错误处理程序,如下所示

  • 添加一个自用的调试模式,该模式在错误处理程序中断,并提供恢复的可能性以查看导致错误的行
  • 仅在引起实际错误的级别引发错误弹出
  • 仅在顶级重置应用程序属性
  • 低级别例程调用传递未处理的错误

Option Explicit

' Debug Mode Flag (or you could use Conditional Compilation)
' Set to TRUE for developer mode debugging
Const DebugMode As Boolean = False ' True

Sub TstErrHndlr()
   On Error GoTo TstErrHndlr_Error1

   TstErrHndlrA

Exit Sub
TstErrHndlr_Error1:
    Application.Calculation = xlCalculationAutomatic
    Application.ScreenUpdating = True
    Application.DisplayAlerts = True

    ' display message if error is raised in this module
    If Err.Source = Application.VBE.ActiveVBProject.Name Then
        MsgBox "Error " & Err.Number & " (" & Err.Description & ")" & vbLf & _
          "in procedure TstErrHndlr" & vbLf & _
          "Contact [My Name] for assistance " & _
          "(myemal@company.com, (123)456-7890)"
    End If
    ' Break in Debug mode
    If DebugMode Then
        Debug.Assert False
        Resume
    End If

End Sub

Sub TstErrHndlrA()
    On Error GoTo TstErrHndlrA_Error1

    Dim X As Double
    X = 1 / 0

Exit Sub
TstErrHndlrA_Error1:
' These should be handled at top level for unhandled errors only
'    Application.Calculation = xlCalculationAutomatic
'    Application.ScreenUpdating = True
'    Application.DisplayAlerts = True

    ' display message if error is raised in this module
    If Err.Source = Application.VBE.ActiveVBProject.Name Then
        MsgBox "Error " & Err.Number & " (" & Err.Description & ")" & vbLf & _
          "in procedure TstErrHndlrA" & vbLf & _
          "Contact [My Name] for assistance " & _
          "(myemal@company.com, (123)456-7890)"
    End If
    ' Break in Debug mode
    If DebugMode Then
        Debug.Assert False
        Resume
    End If
    ' Pass unhandled errors up the tree
    Err.Raise Err.Number, "TstErrHndlrA", Err.Description
End Sub

在调试模式关闭的情况下,用户会获得一个弹出窗口,以识别错误并在

中发生例行程序

在调试模式开启的情况下,您还会在导致错误的例程中获得中断,并且可能会重新生成导致错误的行。 (或使用Ctrl-F9跳过简历)

答案 1 :(得分:1)

来自MSDN的On Error页面:

  

“启用”错误处理程序是由On Error语句打开的错误处理程序;   “活动”错误处理程序是进程中的已启用处理程序   处理错误。如果错误处理程序发生错误   活动(在错误发生和Resume,Exit Sub之间,   退出函数,或退出属性语句),当前过程的   错误处理程序无法处理错误。控制返回到调用   程序。如果调用过程有一个启用的错误处理程序,它   被激活以处理错误。如果调用程序的错误   处理程序也是活动的,控制权通过之前的调用返回   程序,直到找到启用但非活动的错误处理程序。如果   没有找到非活动的,启用的错误处理程序,错误是致命的   它实际发生的点。每次错误处理程序   将控制权传递回调用程序,该程序成为   现行程序。一旦错误由任何错误处理程序处理   程序,执行在当前程序中恢复   由简历声明指定。

所以回答“为什么VBA会这样做”:因为这就是他们制作它的方式。

为了使它不以这种方式运行,您必须(如您所述)在调用子/函数之前禁用当前错误处理程序。

使用ERL作为@Rory提到的将为您提供代码失败的确切行,并且您可能能够在广泛的通用错误捕获例程中使用On Error Goto -1。它实际上归结为要小心调用其他子/函数,或者具有可以返回错误代码作为其值的函数(即,手动冒泡错误)。例如,这是一个函数,它返回错误作为函数的值,而不是在函数调用期间尝试引发任何类型的异常。您可能还注意到它调用的某些函数也可能返回错误。

Public Function SetTask(ByVal strHost As String, strUser As String, strDomain as String, strPass As String) As String
Dim service As Object
Dim rootFolder As Object
Dim taskDefinition As Object
Dim strCMD As String
Dim strResult As String

On Error GoTo TaskNotSet
SetTask = "Task Not Set"

'Open the firewall
strResult = OpenFirewall (strHost)
If strResult <> "Ok" Then
    SetTask = "Error Opening Firewall (" & err.Number & ") " & err.Description
    Exit Function
End If

Set service = CreateObject("Schedule.Service")
service.Connect strHost, strUser, strDomain, strPass

Set rootFolder = service.GetFolder("\")
Set taskDefinition = service.newtask(0)
taskDefinition.XmlText = TaskXML
Call rootFolder.RegisterTaskDefinition("Weekly VMC Inventory", taskDefinition, 6, , , 3)

'Close the firewall
strResult = CloseFirewall (strHost)
If strResult <> "Ok" Then
    SetTask = "Error Closing Firewall (" & err.Number & ") " & err.Description
    Exit Function
End If

SetTask = "Task Set"
Set taskDefinition = Nothing
Set rootFolder = Nothing
Set service = Nothing
Exit Function

TaskNotSet:
CloseFirewall (strHost)
SetTask = "Error Setting Task (" & err.Number & ") " & err.Description
Set taskDefinition = Nothing
Set rootFolder = Nothing
Set service = Nothing
End Function