VBA - 正确销毁无模式UserForm实例

T.M*_*.M. 7 excel vba excel-vba userform

介绍:

我知道 - 显示UserForms - 这是最好的做法

  • 处理QueryCloseuserform代码(If CloseMode = vbFormControlMenu ...)
  • Unload Me其中不做,只是一个胆小的Me.Hide指令(在阻止[x] -it和最终的自毁之后Cancel = True)
  • 在[class]代码中设置相关变量/ [property](例如.IsCancelled=True)
  • 为了能够通过调用代码卸载UF .

有用的链接

一个出色的概述"UserForm1.Show?" 可以在https://rubberduckvba.wordpress.com/2017/10/25/userform1-show/ 以及许多示例性的SO答案中找到(thx到@ Mats'Mug和RubberDuck).


1)模态UserForms的工作示例

据我所知 - 我确实尝试学习 - ,对于模态 UF ,以下代码应该没问题:

案例1a) ..使用UF实例的局部变量,如常见:

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
Run Code Online (Sandbox Code Playgroud)

情况1b) ..没有局部变量,但使用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
Run Code Online (Sandbox Code Playgroud)

2)问题

使用MODELESS UserForm实例会出现问题.

好吧,with block方法(参见1b)应该足以在x-iting之后销毁任何对象引用:

  With New UserForm1
      .Show vbModeless  ' << show modeless uf
  End With
Run Code Online (Sandbox Code Playgroud)

如果我尝试,但是

  • a)获取有关可能的用户取消的信息以及
  • b)Unload如果在Show指令后使用局部变量(例如"ufA")进行洗礼,则表格为

所有代码行都将立即执行,因为表单是MODELESS的原因:

  • 代码显示表单,下一刻..
  • 代码找不到用户取消,因为没有时间进行任何用户操作,下一刻..
  • [代码在使用userform的局部变量时卸载表单]

3)问题

我如何处理a)正确报告的UserForm取消了MODELESS表单的调用代码以及b)(必要的?)卸载如果使用局部变量?

Mat*_*don 7

确实,我已经将很多注意力集中在模态形式上-因为这是最常用的形式。感谢您对该文章的反馈!

但是,原理与非模式形式相同:只需在链接的文章和此处大致概述的Model-View-Presenter模式上进行扩展。

区别在于,非模态形式需要进行范式转换:您不再响应预设的事件序列-而是需要响应可能在任何给定时间发生的某些异步事件

  • 处理模式表单时,先存在“显示前”,然后有“隐藏后”,在隐藏表单后立即运行。您可以使用事件处理“显示时”发生的任何事情。
  • 当处理一个非模态形式,有一个“显示之前”,然后在“同时显示出”和“展示后” 需要通过事件来处理。

使您的presenter类模块负责UserForm在模块级别和以下位置保存实例WithEvents

Option Explicit
Private WithEvents myModelessForm As UserForm1
Run Code Online (Sandbox Code Playgroud)

演示者的Show方法将Set显示表单实例并显示它:

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
Run Code Online (Sandbox Code Playgroud)

想要的形式实例是本地这里的程序,所以局部变量一个With块不能工作:对象将是范围从你的意思是它之前。这就是为什么将实例存储在模块级别的私有字段中的原因:现在,该窗体的寿命与presenter实例的寿命相同。

现在,您需要与演示者进行“交谈”形式-最简单的方法是在UserForm1代码背后公开事件-例如,如果我们希望用户确认取消,则将ByRef参数添加到事件中,因此演示者中的处理程序可以将信息传递回事件源(即,传递回表单代码):

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
Run Code Online (Sandbox Code Playgroud)

现在演示者可以处理该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
Run Code Online (Sandbox Code Playgroud)

非模式形式通常不会有“确定”和“取消”按钮。相反,您将拥有许多功能,例如,其中一个功能会弹出一个模式对话框UserForm2,该对话框将执行其他操作-同样,您只需公开一个事件,然后在演示者中处理它:

Public Event ShowGizmo()

Private Sub ShowGizmoButton_Click()
    RaiseEvent ShowGizmo
End Sub
Run Code Online (Sandbox Code Playgroud)

演示者去:

Private Sub myModelessForm_ShowGizmo()
    With New GizmoPresenter
        .Show
    End With
End Sub
Run Code Online (Sandbox Code Playgroud)

请注意,模式UserForm2是单独的presenter类的关注点。

  • @ teddy2不是MVVM或MVC,而是MVP。我喜欢这样认为的人说过VBA中正确的OOP代码是“过分杀人”,然后转过头去讨论在这堆粘稠的VBA代码中到底有多少愚蠢的无法维持的痛苦。因此,如果您*正确地*编写VBA代码,那么您将面临过多的负担,否则,您的代码会很烂,因为它无法扩展,而且每个人都知道随着大型VBA项目的发展,这些意大利面条一团糟。抱歉,我坚持我的观点:*可以*,并且*应该*完成可扩展的VBA代码。 (10认同)
  • 这是一个非常有用的答案,并且是很多知识。而这仅仅是一个过度。如果您需要在VBA项目中创建/部署这种类型的解决方案,那么您肯定不会在工作中使用最好的工具。VBA可以做很多事情。但是,仅仅因为它可能就意味着您应该这样做。尝试在VBA中实现MVVM或MVC。可能?是的,正如@ Mat'sMug正确显示的那样。但是,如果您需要MVC实施并且由于任何原因仍然坚持使用Excel-VBA,请重新考虑您的规格并重新进行分析。同样,这个答案很好。 (2认同)