Update:
It seems Visio and Excel on my PC do not use the same Office Object Libraries, at least that is what a look at the used references tells me. Visio uses 15.0, Excel uses 16.0, we use different subscriptions for both, Excel is part of the MS office 365 ProPlus Package, Visio is separate.
I tested it on a PC with a current subscription of Visio with the 16.0 Office Object Libraries. This time closing the Userform did not cause a crash. So I guess the problem was with the old version. If anybody has the ability to cross check this and test the code in a Excel 15.0 installation that would be great.
Original Post
I have been using an Observer Implementation in VBA to create some "better" UserForms that implement an Interface. The solution works great as far as I'm concerned, but there is one minor problem:
Whenever I close the UserForm by pressing the FormControlMenu Close Control (the red X in the top/right corner) my application crashes with a "Out of Stack Space Error".
Crashing occurs in the following way: I get the Message box with the error (standart VBA), when I close it there seems nothing amiss, but as soon as I try to run another piece of code (any) Visio will crash to the Desktop.
Now the strange part: I actually catch the vbQueryClose event, cancel it and run my own closing routine which only hides the userform. When closing (hiding) the UserForm via a commandButton the same way (me.hide), the error does not occur.
This happens only in Visio, the exact same code causes no error/crashing in Excel!
I hope someone with some more knowledge on the whole Reference/Object/COM-Business can shed some light into this
The Code:
Code also available as zipped file (no Excel/Visio Files, just the exported modules) so you don't have to copy/paste and create UserForms: https://www.dropbox.com/s/ziqjv2umcy3co5t/ObserverExample.zip?dl=0
The actual Observer Implementation is a bit longer, but this code is the minimal verifiable example.
Module1 (Module):
'@Folder("ObserverTest")
Option Explicit
Sub StartTest()
With New Foo
.Test
End With
End Sub
Foo (Class):
'@Folder("ObserverTest")
Option Explicit
Private WithEvents myObs As Observer
Private myView As IBar
Public Sub Test()
Set myView = New Bar
Dim myViewAsObservable As IObservable
Set myViewAsObservable = myView
myViewAsObservable.AddObserver myObs
Set myViewAsObservable = Nothing
myView.Show
Debug.Print myView.howClosed
End Sub
Private Sub Class_Initialize()
Set myObs = New Observer
End Sub
Private Sub Class_Terminate()
Set myObs = Nothing
End Sub
Private Sub myObs_Notify(source As Object, arg As Variant)
If VarType(arg) = vbString Then
Debug.Print arg
End If
End Sub
IBar (Class)
'@Folder("ObserverTest")
Option Explicit
Public Sub Show(): End Sub
Public Property Get howClosed() As String: End Property
IObservable(Class)
'@Folder("ObserverTest")
Option Explicit
Public Sub AddObserver(ByVal obs As Observer): End Sub
Bar (UserForm)
'@Folder("ObserverTest")
Option Explicit
Implements IBar
Implements IObservable
Private obsCol As Collection
Private cancelHow As String
'---IBar Stuff
Private Sub IBar_Show()
Me.Show
End Sub
Private Property Get IBar_howClosed() As String
IBar_howClosed = cancelHow
End Property
'--- Closing Stuff
Private Sub btCancel_Click()
cancelHow = "Closed by pressing the >Cancel< Button"
onCancel
End Sub
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
If CloseMode = VbQueryClose.vbFormControlMenu Then
Cancel = True
cancelHow = "Closed by pressing the >X< Control"
onCancel
End If
End Sub
Private Sub onCancel()
Me.hide
End Sub
'---Observer Stuff
Private Sub UserForm_Initialize()
Set obsCol = New Collection
End Sub
Private Sub UserForm_Terminate()
Set obsCol = Nothing
End Sub
Private Sub tbTest_Change()
Notify Me.tbTest.Text
End Sub
Private Sub IObservable_AddObserver(ByVal obs As Observer)
obsCol.Add obs
End Sub
Private Sub Notify(ByVal arg As Variant)
Dim obs As Observer
For Each obs In obsCol
obs.Notify Me, arg
Next obs
End Sub
Observer (Class)
'@Folder("ObserverTest")
Option Explicit
Public Event Notify(source As Object, arg As Variant)
Public Sub Notify(ByVal source As Object, ByVal arg As Variant)
RaiseEvent Notify(source, arg)
End Sub