VBA: destruye una instancia de UserForm sin modo correctamente
excel-vba excel (3)
De hecho, me he centrado bastante en las formas modales, porque eso es lo que se usa más comúnmente. ¡Gracias por los comentarios sobre ese artículo!
Sin embargo, los principios son los mismos para los formularios no modales: simplemente amplíe el patrón Modelo-Vista-Presentador descrito más o menos en el artículo vinculado y here .
La diferencia es que una forma no modal necesita un cambio de paradigma: ya no responde a una secuencia de eventos preestablecida, sino que necesita responder a algunos eventos asincrónicos que pueden ocurrir en un momento dado o no.
- Cuando se maneja un formulario modal, hay un "antes de mostrar" y luego un "después de ocultarse" que se ejecuta inmediatamente después de que se oculta el formulario. Puede manejar cualquier cosa que ocurra "mientras se muestra" usando eventos.
- Cuando se maneja un formulario no modal, hay un "antes de mostrar", y luego "mientras se muestra" y "después de mostrar" ambos deben manejarse a través de eventos.
Haga que su módulo de clase de presentador sea responsable de mantener la instancia de
UserForm
, a nivel de módulo y
WithEvents
:
Option Explicit
Private WithEvents myModelessForm As UserForm1
El método
Show
del presentador
Set
la instancia del formulario y la mostrará:
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
No desea que la instancia de formulario sea local para el procedimiento aquí, por lo que una variable local
o
un bloque
With
no pueden funcionar: el objeto estará fuera de alcance antes de que lo haga.
Es por eso que almacena la instancia en un campo privado, a nivel de módulo: ahora el formulario dura tanto como la instancia del presentador.
Ahora, debe hacer que el formulario "hable" con el presentador; la forma más fácil es exponer eventos en el código subyacente
UserForm1
; por ejemplo, si queremos que el usuario confirme la cancelación, agregaremos un parámetro
ByRef
al evento , por lo que el controlador en el presentador puede devolver la información al origen del evento (es decir, de vuelta al código del formulario):
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
Ahora el presentador puede manejar ese evento
FormCancelled
:
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
Sin embargo, una forma no modal no
suele
tener los botones "ok" y "cancelar".
Por el contrario, tendría una serie de funcionalidades expuestas, por ejemplo, una que muestra un cuadro de diálogo modal
UserForm2
que hace algo más; nuevamente, simplemente expone un evento y lo maneja en el presentador:
Public Event ShowGizmo()
Private Sub ShowGizmoButton_Click()
RaiseEvent ShowGizmo
End Sub
Y el presentador dice:
Private Sub myModelessForm_ShowGizmo()
With New GizmoPresenter
.Show
End With
End Sub
Tenga en cuenta que el
UserForm2
modal es una preocupación de una clase de presentador separada.
Introducción:
Soy consciente de que, al mostrar UserForms, es una buena práctica
-
manejar
QueryClose
dentro del código deIf CloseMode = vbFormControlMenu ...
(If CloseMode = vbFormControlMenu ...
) -
sin hacer
Unload Me
Me.Hide
, solo una tímida instrucciónMe.Hide
(después de evitar [x] -itting y eventual autodestrucción a través deCancel = True
) -
establecer una variable relacionada / [propiedad] dentro del código [clase] (por ejemplo
.IsCancelled=True
) - para poder tener la UF descargada por el código de llamada .
Enlace útil
Una visión general excepcional "UserForm1.Show?" se puede encontrar en https://rubberduckvba.wordpress.com/2017/10/25/userform1-show/ , así como en numerosas respuestas SO ejemplares (gracias a Mathieu Guindon, también conocido como Mat''s Mug y RubberDuck).
Selección adicional (► editar a partir del 5/1 2019 )
- Desventajas de poner código en formularios de usuario en lugar de módulos
- Pasar variable de formulario a módulo
- Aplicar lógica para el diálogo de forma de usuario (Rubberduck)
- La forma de usuario perfecta (academia Vitosh)
1) Ejemplos de trabajo para formularios de usuario modales
Por lo que entendí, y trato de aprender, el siguiente código debería estar bien para las UF modales :
Caso 1a) ... con una variable local para la instancia de UF, como se ve a menudo:
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
Caso 1b)
... sin una variable local, pero usando un bloque de código
With New
:
'' ----------------------------------------------------------
'' >> 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) problema
Surgen problemas al utilizar una instancia MODELESS UserForm.
Bien, el método with block (cf. 1b) debería ser suficiente para destruir cualquier referencia de objeto después de aplicarle un x-it:
With New UserForm1
.Show vbModeless '' << show modeless uf
End With
Sin embargo, si lo intento
- a) obtener información sobre la posible cancelación de un usuario, así como
-
b)
Unload
un formulario si se bautiza usando una variable local (por ejemplo, "ufA") después de la instrucciónShow
,
todas las líneas de código se ejecutarán a la vez precisamente por la razón de que el formulario NO MODELA:
- el código muestra el formulario, el siguiente momento ...
- el código no encuentra la cancelación del usuario, ya que no hubo tiempo para ninguna acción del usuario, el siguiente momento ...
- [el código descarga el formulario si usa una variable local para el formulario de usuario]
3) Pregunta
¿Cómo puedo manejar a) cancelaciones de UserForm informadas correctamente mediante el código de llamada de un formulario MODELESS así como b) una descarga (¿necesaria?) Si se usa una variable local?
Para formularios sin modo, use DoEvents junto con la propiedad de formulario de usuario personalizada.
Sub test()
Dim frm As New UserForm1
frm.Show vbModeless
Do
DoEvents
If frm.Cancelled Then
Unload frm
Exit Do
End If
Loop Until False
MsgBox "You closed the modeless form."
''/ Using With
With New UserForm1
.Show vbModeless
Do
DoEvents
If .Cancelled Then Exit Do
Loop Until False
End With
MsgBox "You closed the modeless form (with)"
End Sub
''/ Formulario de usuario
Private m_bCancelled As Boolean
Public Property Get Cancelled() As Boolean
Cancelled = m_bCancelled
End Property
Public Property Let Cancelled(ByVal bNewValue As Boolean)
m_bCancelled = bNewValue
End Property
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
Me.Cancelled = True
Cancel = 1
Me.Hide
End Sub
Por lo general, relaciono la vida útil de una instancia de forma de usuario no modal con la del libro de trabajo colocando el código en esas líneas detrás de ThisWorkbook:
Option Explicit
Private m_MyForm As UserForm1
Private Sub Workbook_BeforeClose(Cancel As Boolean)
If Not m_MyForm Is Nothing Then
Unload m_MyForm
Set m_MyForm = Nothing
End If
End Sub
Friend Property Get MyForm() As UserForm1
If m_MyForm Is Nothing Then
Set m_MyForm = New UserForm1
End If
Set MyForm = m_MyForm
End Property
Luego puede consultar el código no modal en todo el código usando, por ejemplo,
ThisWorkbook.MyForm.Show vbModeless
etc.