On Error Goto在EventHandler subs中不起作用

Sze*_*zer 5 error-handling vba

让我们假设这段代码:

模块1:

Sub main()

    Dim cl As New Class2
    On Error GoTo errorhandler1
        cl.DoWork
     On Error GoTo 0
Exit Sub

errorhandler1:
    MsgBox (Err.Description)

End Sub
Run Code Online (Sandbox Code Playgroud)

1类:

Event MyEvent()

Public Sub DoWork()
    RaiseEvent MyEvent
End Sub
Run Code Online (Sandbox Code Playgroud)

等级2:

Private WithEvents cl As Class1

Private Sub cl_MyEvent()
    Call Err.Raise(123, , "ErrorInClass")
End Sub

Private Sub Class_Initialize()
    Set cl = New Class1
End Sub

Public Sub DoWork()
    cl.DoWork
End Sub
Run Code Online (Sandbox Code Playgroud)

我希望启动errorhandler1并显示带有err.Description的MsgBox.但它反而引发了我的运行时错误.

我需要做些什么来处理EventHandlers例程中的错误?

Mat*_*don 5

这让我很受伤——你可以在这个简单的 C# 代码中看到:

try
{
    SomeEvent?.Invoke(this, EventArgs.Empty);
}
catch
{
    // break here
}
Run Code Online (Sandbox Code Playgroud)

如果任何处理程序SomeEvent抛出异常,AFAIK命中该catch块中的一个断点- 我希望 VBA 做同样的事情......但事实并非如此。

通过中断事件处理程序,然后检查调用堆栈,您可以看到在带有调用的事件源和事件处理程序过程之间存在一些滑动RaiseEvent

事件源和事件处理程序之间的“非基本代码”

我认为这[<Non-Basic code>]将是 VBA 运行时本身,将事件分派给正在侦听该特定事件源实例上的事件的任何对象:这个“中间人”很可能是为什么运行时错误不是冒泡备份:运行时可能会保护自己并在此处抛出错误,无论父堆栈帧是否有On Error语句。

您可以在该屏幕截图中看到我的解决方法的提示 - 添加一个新的类模块,调用它ErrorInfo,并为其提供一些有用的成员:

Option Explicit
Private Type TErrorInfo
    Number As Long
    Description As String
    Source As String
End Type
Private this As TErrorInfo

Public Property Get Number() As Long
    Number = this.Number
End Property

Public Property Get Description() As String
    Description = this.Description
End Property

Public Property Get Source() As String
    Source = this.Source
End Property

Public Property Get HasError() As Boolean
    HasError = this.Number <> 0
End Property

Public Property Get Self() As ErrorInfo
    Set Self = Me
End Property

Public Sub SetErrInfo(ByVal e As ErrObject)
    With e
        this.Number = .Number
        this.Description = .Description
        this.Source = .Source
    End With
End Sub
Run Code Online (Sandbox Code Playgroud)

现在,无论何时定义事件,都要为其添加一个参数:

Public Event Something(ByVal e As ErrorInfo)
Run Code Online (Sandbox Code Playgroud)

当您引发该事件时,提供实例,处理错误,检查您的ErrorInfo对象,相应地调用Err.Raise,并且您可以正常处理错误,在您想要处理事件处理程序错误的事件调用范围内:

Public Sub DoSomething()
    On Error GoTo CleanFail
    With New ErrorInfo
        RaiseEvent Something(.Self)
        If .HasError Then Err.Raise .Number, .Source, .Description
    End With
    Exit Sub
CleanFail:
    MsgBox Err.Description, vbExclamation
End sub
Run Code Online (Sandbox Code Playgroud)

事件处理程序代码只需要处理它的错误(处理程序中的任何运行时错误基本上都是未处理的),并在ErrInfo参数中设置错误状态:

Private Sub foo_Something(ByVal e As ErrorInfo)
    On Error GoTo CleanFail
    Err.Raise 5
    Exit Sub
CleanFail:
    e.SetErrInfo Err
End Sub
Run Code Online (Sandbox Code Playgroud)

和宾果游戏,现在您可以在事件源中干净地处理事件处理程序中引发的错误,而无需涉及全局变量或实际错误信息(在我的情况下,某些 3rd 方 API 中抛出的错误)丢失到一些无用的(但是在大多数情况下可以说是“足够好”)“哎呀,没有用”消息。

重要警告

由于是与案件Cancel的事件,如果事件有多个处理程序,那么该状态可以追溯到事件调用网站,好了,不确定的-如果只有一个处理程序抛出一个错误,并且不抛出处理程序做的不是篡改ErrorInfo参数,那么理论上调用站点会得到一个错误。当两个或多个处理程序抛出错误时,“乐趣”就开始了。

在这种情况下,处理程序需要ErrorInfo在修改它之前验证它的状态。

或者,另一种解决方案可能是使ErrorInfo类封装错误信息数组,并可能向Property Get成员添加索引器- 或者您可以想到的任何其他机制,以“聚合错误”。哎呀,你甚至可以封装一个集合ErrorInfo情况下,在AggregateErrorInfo集合类,让你的“多听众事件”的使用,在其签名代替。

大多数时候你只需要一个处理程序,所以这不会成为问题。


dee*_*dee 2

我们可以在这里读到:

如果使用 Err 对象的 Raise 方法引发错误,则可以强制 Visual Basic 在调用列表中向后搜索已启用的错误处理程序。

但在这种情况下,没有启用错误处理程序。

也许你可以通知class2的客户工作失败了。在这里,因为 class2 的客户端是一个标准模块,所以您不能使用 class2 中的事件,所以也许只是一个简单的只读属性可能会有所帮助?

模块:

Sub main()
    cl.DoWork
    If Not cl.IsWorkOk Then MsgBox "Work failed..."
    On Error GoTo 0
    Exit Sub

errorhandler1:
    MsgBox (Err.Description)

End Sub
Run Code Online (Sandbox Code Playgroud)

2类:

Private m_isWorkOk As Boolean

Private Sub cl_MyEvent()
    On Error GoTo ErrMyEvent
    Call Err.Raise(123, , "ErrorInClass")
    m_isWorkOk = True
    Exit Sub
ErrMyEvent:
    m_isWorkOk = False
End Sub

Public Property Get IsWorkOk() As Boolean
    IsWorkOk = m_isWorkOk
End Property
Run Code Online (Sandbox Code Playgroud)