vba excel-vba userform excel

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 de If CloseMode = vbFormControlMenu ... ( If CloseMode = vbFormControlMenu ... )
  • sin hacer Unload Me Me.Hide , solo una tímida instrucción Me.Hide (después de evitar [x] -itting y eventual autodestrucción a través de Cancel = 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 )

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ón Show ,

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.