On Error Goto does not work inside the EventHandler subsystem

Assume this code:

Module 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 

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 look forward to starting errorhandler1 and MsgBox with err.Description to be shown. But instead, it gives me a runtime error.

What do I need to do to handle errors in EventHandlers procedures?

+5
source share
2 answers

How can we read here :

If you use the Raise method of the Err object to raise the error, you can force Visual Basic to look back through the call list for the resolved error handler.

But in this case, the error handler is not enabled.

Perhaps you could tell a Class 2 client that the job failed. Here, because the class 2 client is a standard module, you cannot use events from class2, so maybe a simple read-only property can 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
source

It just bit me - you see in this simple C # code:

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

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:

"Non-Basic Code

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.

+3
source

All Articles