On Error Goto doesn't work inside EventHandler subs

南楼画角 提交于 2020-12-30 09:45:24

问题


Lets assume this code:

Module1:

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

Class1:

Event MyEvent()

Public Sub DoWork()
    RaiseEvent MyEvent
End Sub

Class2:

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

I expect errorhandler1 to launch and MsgBox with err.Description to be shown. But it throws me runtime error instead.

What I have to do to handle errors within EventHandlers routines?


回答1:


As we can read here:

If you use the Raise method of the Err object to raise an error, you can force Visual Basic to search backward through the calls list for an enabled error handler.

But in this case there is no enabled error handler.

Maybe you could inform the client of class2 that the work failed. Here because the client of class2 is a standard module you can't use events from class2, so maybe just a simple read-only property might help here?

Module:

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

errorhandler1:
    MsgBox (Err.Description)

End Sub

Class2:

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



回答2:


This has just bitten me - you see in this simple C# code:

try
{
    SomeEvent?.Invoke(this, EventArgs.Empty);
}
catch
{
    // break here
}

If any handler of SomeEvent throws an exception, AFAIK a breakpoint in that catch block would be hit - and I was expecting VBA to do the same... and it doesn't.

By breaking in an event handler, and then inspecting the call stack, you can see that there's something slipping between the event source with the RaiseEvent call, and the event handler procedure:

I presume that [<Non-Basic code>] here would be the VBA runtime itself, dispatching the event to whatever object is listening for events on that particular event source instance: and this "man-in-the-middle" is quite likely why run-time errors aren't bubbling back up: the runtime is likely protecting itself and throwing an error here, regardless of whether the parent stack frame has an On Error statement.

You can see a hint of my work-around in that screenshot - add a new class module, call it ErrorInfo, and give it some useful members:

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

Now whenever you define an event, add a parameter to it:

Public Event Something(ByVal e As ErrorInfo)

When you raise that event, supply the instance, handle errors, inspect your ErrorInfo object, invoke Err.Raise accordingly, and you can handle that error normally, in the event-invoking scope you want to handle event-handler errors in:

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

The event handler code simply needs to handle its errors (any run-time error in a handler is basically unhandled otherwise), and set the error state in the ErrInfo parameter:

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

And bingo, now you can cleanly handle errors raised in an event handler, at the event source, without involving global variables or losing the actual error information (in my case, an error thrown in some 3rd-party API) to some useless (but arguably "good-enough" in most cases) "oops, didn't work" message.

Important caveat

As is the case with Cancel events, if an event has multiple handlers, then which state goes back to the event invocation site is, well, undefined - if only one handler throws an error, and the non-throwing handlers don't tamper with the ErrorInfo parameter, then in theory the invocation site gets the one error. The "fun" begins when two or more handlers throw an error.

In that case, the handlers need to verify what the state of the ErrorInfo is before they modify it.

Or, another solution could be to make the ErrorInfo class encapsulate an array of error information, and perhaps add indexers to the Property Get members - or whatever other mechanism you could think about, to "aggregate errors". Heck, you could even encapsulate a collection of ErrorInfo instances in an AggregateErrorInfo collection class, and make your "multiple-listeners event" use that in its signature instead.

Most of the time you only need a single handler though, so this wouldn't be a concern.



来源:https://stackoverflow.com/questions/30935147/on-error-goto-doesnt-work-inside-eventhandler-subs

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!