Home » excel » excel – VBA: WithEvents puzzle

excel – VBA: WithEvents puzzle

Posted by: admin April 23, 2020 Leave a comment


I have a UserForm, xForm, that is being instantiated in a class module (let’s say TestClass) as:

Dim Form as New xForm
Private WithEvents EvForm as MSForms.UserForm
Set EvForm = Form

At the class module of the xForm itself I have some code that must be executed on Form Closing, ONLY if the form actually closes:

'xForm class module
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
    'Do some cleanup, otherwise the app would hang
    'If not closing, don't cleanup anything, otherwise the app would hang
End Sub

The QueryClose event is also treated in TestClass, and could avoid the form from closing:

Private Sub EvForm_QueryClose(Cancel As Integer, CloseMode As Integer)
    'Verify if closing is allowed based on User Control values
    Cancel = Not ClosingIsAllowed '<-- Pseudocode on the right side of "="
End Sub

How can I test for Cancel = True, set in TestClass, in the xForm class module?
Let’s rephrase it: If Cancel is set to True in TestClass, I must not do the cleanup code in the xForm class module. How can I accomplish that?

Until now, I have thought off of implementing another event in the xForm class (My_QueryClose?) and raise it on the QueryClose event. Outside the Code Behind Form I would deal only with the My_QueryClose event, so taking full control over what is happening. Is this a viable/better approach?

How to&Answers:

Can’t make heads or tails of your custom event idea, but the way to get one class to talk to another (form or anything else, doesn’t matter) is to link them up; here’s a clean example:

Basic TestClass holds form object (no events needed here, let the form handle that)

'TestClass code
Private MyForm          As UserForm
Private mbleCanClose    As Boolean

Public Property Get CanClose() As Boolean
    CanClose = mbleCanClose
End Property
Public Property Let CanClose(pbleCanClose As Boolean)
    mbleCanClose = pbleCanClose
End Property

Public Property Get MyFormProp() As UserForm1
    Set MyFormProp = MyForm
End Property

Add a custom object and property to the form itself

'UserForm1 code
Private mParent As TestClass

Public Property Get Parent() As TestClass
    Set Parent = mParent
End Property
Public Property Set Parent(pParent As TestClass)
    Set mParent = pParent
End Property

Invoking the form on TestClass creation looks like this:

'TestClass code
Private Sub Class_Initialize()
    Set MyForm = New UserForm1
    Load MyForm
    Set MyForm.Parent = Me
End Sub

And then when it’s time to close the form, you check whether you can:

'UserForm1 code
Public Function WillMyParentLetMeClose() As Boolean
    If Not (mParent Is Nothing) Then
        WillMyParentLetMeClose = mParent.CanClose
    End If
End Function

Private Sub CommandButton1_Click()
    If WillMyParentLetMeClose = True Then
        Unload Me
    End If
End Sub

Here’s what it would like to invoke

'standard module code
Public Sub Test_TestClass()
    Dim myclass As TestClass
    Set myclass = New TestClass
End Sub


A work around declaring another event

The code bellow do what I was expecting, although it is not as neat as I wish it could be.

In the UserForm1 code:

'***** UserForm1
Public Event MyQueryClose(ByRef Cancel As Integer, ByRef CloseMode As Integer, ByRef Status As String)

Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
   Dim Status As String
   Cancel = True
   Status = "QueryClose"
   Debug.Print "Entered QueryClose"
   Debug.Print "Cancel = " & Cancel
   Debug.Print "Status = " & Status
   Debug.Print "Just before raising MyQueryClose"
   RaiseEvent MyQueryClose(Cancel, CloseMode, Status)
   Debug.Print "Just got back from MyQueryClose"
   Debug.Print "Cancel = " & Cancel
   Debug.Print "Status = " & Status
End Sub

In the Class1 code:

'***** Class1
Dim UserForm As New UserForm1
Private WithEvents UF As UserForm1

Sub DoIt()
   Set UF = UserForm
End Sub

Private Sub UF_MyQueryClose(Cancel As Integer, CloseMode As Integer, Status As String)
   Debug.Print "Just entered MyQueryClose"
   Cancel = False
   Status = "MY QueryClose"
End Sub

In a basic module, to test the Class:

'***** Basic module
Sub TestClass()
   Dim C As New Class1
End Sub

And here’s the end result (debug window):

Entered QueryClose
Cancel = -1
Status = QueryClose
Just before raising MyQueryClose
Just entered MyQueryClose
Just got back from MyQueryClose
Cancel = 0
Status = MY QueryClose