It just bit me - you see in this simple C # code:
try { SomeEvent?.Invoke(this, EventArgs.Empty); } catch {
If any handler from SomeEvent throws an exception, AFAIK has a breakpoint in this catch , and I expected VBA to do the same ... and this is not the case.
By parsing the event handler and then checking the call stack, you can see that between the event source there is a call with a RaiseEvent call and the event handler procedure:

I assume that the [<Non-Basic code>] here will be the VBA runtime itself, sending the event to any object that listens for events on this particular instance of the event source: and this "person in the middle" is quite likely why time errors are not duplicated : The runtime probably protects itself and throws an error here, regardless of whether the frame of the parent stack has an On Error statement.
In this screenshot, you can see a hint of my work - add a new class module, name 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, when you define an event, add a parameter to it:
Public Event Something(ByVal e As ErrorInfo)
When you raise this event, put an instance, handle the errors, examine your ErrorInfo object, call Err.Raise accordingly, and you can normally handle this error in the call-call area with which you want to handle the error event handler 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 code of the event handler just needs to handle its errors (any runtime error in the handler is basically not processed otherwise) and sets 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 that occur in the event handler, in the event source, without involving global variables or losing the actual error information (in my case, an error that occurred in some third-party APIs) to some useless ones (but maybe "good enough" in most cases) the message is "oops, does not work".
Important warning
As in the case of Cancel events, if the event has several handlers, then what state is returned to the event call site, well, undefined - if only one handler gives an error, throwing handlers do not affect the ErrorInfo parameter, then theoretically the call site receives one error. "Fun" begins when two or more handlers throw an error.
In this case, the handlers should check what the ErrorInfo state is before changing it.
Or another solution could be to have the ErrorInfo class encapsulate an array of error information and possibly add indexers to the Property Get members, or, like any other mechanism you might think of, to โaggregate errorsโ. Heck, you can even encapsulate a collection of ErrorInfo instances in the AggregateErrorInfo collection class and make your "event with multiple listeners" instead in your signature.
In most cases, you only need one handler, so this will not be a problem.