Intro:
I am aware that - showing UserForms - it's best practice to
QueryClose
within the userform code (If CloseMode = vbFormControlMenu ...
)Unload Me
therein, just a timid Me.Hide
instruction
(after preventing [x]-itting and eventual self-destruction via Cancel = True
).IsCancelled=True
) Useful link
An outstanding overview "UserForm1.Show?" can be found at https://rubberduckvba.wordpress.com/2017/10/25/userform1-show/ as well as in numerous examplary SO answers (thx to Mathieu Guindon aka Mat's Mug and RubberDuck).
Further selection (►edit as of 5/1 2019)
1) Working examples for modal UserForms
As far as I understood - and I do try to learn -, the following code should be okay for modal UF's:
Case 1a) .. with a local variable for the UF instance, as often seen:
Public Sub ShowFormA
Dim ufA As UserForm1
Set ufA = New UserForm1
' show userform
ufA.Show ' equivalent to: ufA.Show vbModal
' handle data after user okay
If Not ufA.IsCancelled Then
' do something ...
End If
' >> object reference destroyed expressly (as seen in some examples)
unload ufA
End Sub
Case 1b) .. without a local variable, but using a With New
codeblock:
' ----------------------------------------------------------
' >> no need to destruct object reference expressly,
' as it will be destroyed whenever exiting the with block
' ----------------------------------------------------------
With New UserForm1
.Show ' equivalent to: ufA.Show vbModal
' handle data after user okay
If Not .IsCancelled Then
' do something ...
End If
End With
2) Problem
Problems arise using a MODELESS UserForm instance.
Okay, the with block method (cf. 1b) should be sufficient to destroy any object reference after x-iting it:
With New UserForm1
.Show vbModeless ' << show modeless uf
End With
If I try, however to
Unload
a form if baptized using a local variable (e.g. "ufA") after the Show
instruction,all code lines will be executed at once for precisely the reason that the form is MODELESS:
3) Question
How can I handle a) correctly reported UserForm cancels by the calling code of a MODELESS form as well as b) a (necessary?) unloading if using a local variable?
Indeed, I've been focusing quite a lot on modal forms - because that's what's most commonly used. Thanks for the feedback on that article!
The principles are the same for non-modal forms though: simply expand on the Model-View-Presenter pattern roughly outlined in the linked article and here.
The difference is that a non-modal form needs a paradigm shift: you're no longer responding to a preset sequence of events - rather, you need to respond to some asynchronous events that may happen at any given time, or not.
Make your presenter class module responsible for holding the UserForm
instance, at module-level and WithEvents
:
Option Explicit
Private WithEvents myModelessForm As UserForm1
The presenter's Show
method will Set
the form instance and display it:
Public Sub Show()
'If Not myModelessForm Is Nothing Then
' myModelessForm.Visible = True 'just to ensure visibility & honor the .Show call
' Exit Sub
'End If
Set myModelessForm = New UserForm1
'...
myModelessForm.Show vbModeless
End Sub
You don't want the form instance to be local to the procedure here, so a local variable or a With
block can't work: the object will be out of scope before you mean it to. That's why you store the instance in a private field, at module level: now the form lives as long as the presenter instance does.
Now, you need to make the form "talk" to the presenter - the easiest way is to expose events in the UserForm1
code-behind - for example if we want the user to confirm cancellation, we'll add a ByRef
parameter to the event, so the handler in the presenter can pass the information back to the event source (i.e. back to the form code):
Option Explicit
'...private fields, model, etc...
Public Event FormConfirmed()
Public Event FormCancelled(ByRef Cancel as Boolean)
'returns True if cancellation was cancelled by handler
Private Function OnCancel() As Boolean
Dim cancelCancellation As Boolean
RaiseEvent FormCancelled(cancelCancellation)
If Not cancelCancellation Then Me.Hide
OnCancel = cancelCancellation
End Function
Private Sub CancelButton_Click()
OnCancel
End Sub
Private Sub OkButton_Click()
Me.Hide
RaiseEvent FormConfirmed
End Sub
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
If CloseMode = VbQueryClose.vbFormControlMenu Then
Cancel = Not OnCancel
End If
End Sub
Now the presenter can handle that FormCancelled
event:
Private Sub myModelessForm_FormCancelled(ByRef Cancel As Boolean)
'setting Cancel to True will leave the form open
Cancel = MsgBox("Cancel this operation?", vbYesNo + vbExclamation) = vbNo
If Not Cancel Then
' modeless form was cancelled and is now hidden.
' ...
Set myModelessForm = Nothing
End If
End Sub
Private Sub myModelessForm_FormConfirmed()
'form was okayed and is now hidden.
'...
Set myModelessForm = Nothing
End Sub
A non-modal form wouldn't typically have "ok" and "cancel" buttons though. Rather, you'd have a number of functionalities exposed, for example one that brings up some modal dialog UserForm2
that does something else - again, you just expose an event for it, and handle it in the presenter:
Public Event ShowGizmo()
Private Sub ShowGizmoButton_Click()
RaiseEvent ShowGizmo
End Sub
And the presenter goes:
Private Sub myModelessForm_ShowGizmo()
With New GizmoPresenter
.Show
End With
End Sub
Note that the modal UserForm2
is a concern of a separate presenter class.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With