VBA - 正确销毁无模式用户窗体实例
VBA - destroy a modeless UserForm instance properly
简介:
我知道 - 显示用户表单 - 最好的做法是
- 在用户表单代码中处理
QueryClose
(If CloseMode = vbFormControlMenu ...
)
- 里面没有做
Unload Me
,只是一个胆怯的Me.Hide
指令
(在通过 Cancel = True
防止 [x]-itting 和最终的自我毁灭之后)
- 在 [class] 代码中设置相关变量/[属性](例如
.IsCancelled=True
)
- 为了能够通过调用代码卸载UF。
有用link
出色的概述 "UserForm1.Show?" 可在 https://rubberduckvba.wordpress.com/2017/10/25/userform1-show/ 找到
以及许多示例性 SO 答案(感谢 Mathieu Guindon 又名 Mat's Mug 和 RubberDuck)。
进一步选择(►2019 年 5 月 1 日编辑)
- Passing variable from form to module
- Apply logic for userform dialog (Rubberduck)
- The perfect userform (Vitosh academy)
1) 模态用户窗体的工作示例
据我所知——我确实在努力学习——以下代码对于 modal 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
案例 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
2) 问题
使用 MODELESS UserForm 实例时出现问题。
好的,with 块方法(参见 1b)应该足以在 x-iting 之后销毁任何对象引用:
With New UserForm1
.Show vbModeless ' << show modeless uf
End With
如果我尝试,但是
- a) 获取有关可能的用户取消以及
的信息
如果在 Show
指令之后使用局部变量(例如 "ufA")进行洗礼,- b) 到
Unload
形式,
由于表单是无模式的,所有代码行将立即执行:
- 代码显示表格,下一刻..
- 代码发现没有用户取消,因为没有时间进行任何用户操作,下一刻..
- [如果为用户窗体使用局部变量,代码将卸载窗体]
3) 问题
我如何处理 a) 通过无模式窗体的调用代码正确报告的用户窗体取消,以及 b) 如果使用局部变量(必要?)卸载?
我通常将无模式用户窗体实例的生命周期与工作簿的生命周期联系起来,方法是将代码放在 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
然后您可以在整个代码中引用无模式代码,例如
ThisWorkbook.MyForm.Show vbModeless
等等
对于无模式表单,使用 DoEvents 和自定义用户表单 属性。
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
'/ 用户表单
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
事实上,我一直非常关注模态形式 - 因为这是最常用的形式。感谢您对该文章的反馈!
非模态形式的原则是相同的:只需扩展链接文章和 中粗略概述的 Model-View-Presenter 模式。
区别在于非模态形式需要范式转变:您不再响应预设的事件序列 - 相反,您需要响应一些异步 事件 可能在任何给定时间发生,也可能不发生。
- 处理模态表单时,有一个 "before showing",然后是一个 "after hiding",它在隐藏表单后立即运行。您可以使用事件处理 "while showing" 发生的任何事情。
- 处理非模态形式时,有一个"before showing",然后"while showing"和"after showing" 两个都需要通过事件。
让您的演示者 class 模块负责在模块级别保存 UserForm
实例,并且 WithEvents
:
Option Explicit
Private WithEvents myModelessForm As UserForm1
演示者的 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
您不希望表单实例是此处过程的局部变量,因此局部变量或 a With
块无法工作:该对象将在您想要之前超出范围。这就是您将实例存储在模块级别的私有字段中的原因:现在表单与演示者实例一样存在。
现在,您需要向演示者制作表单 "talk" - 最简单的方法是在 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
现在演示者可以处理 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
非模态表单 通常 没有 "ok" 和 "cancel" 按钮。相反,您会公开许多功能,例如一个会调出一些模态对话框的功能 UserForm2
做其他事情 - 同样,您只需为其公开一个事件,并在演示者中处理它:
Public Event ShowGizmo()
Private Sub ShowGizmoButton_Click()
RaiseEvent ShowGizmo
End Sub
主持人接着说:
Private Sub myModelessForm_ShowGizmo()
With New GizmoPresenter
.Show
End With
End Sub
请注意,模态 UserForm2
是单独的演示者 class 关注的问题。
简介:
我知道 - 显示用户表单 - 最好的做法是
- 在用户表单代码中处理
QueryClose
(If CloseMode = vbFormControlMenu ...
) - 里面没有做
Unload Me
,只是一个胆怯的Me.Hide
指令 (在通过Cancel = True
防止 [x]-itting 和最终的自我毁灭之后) - 在 [class] 代码中设置相关变量/[属性](例如
.IsCancelled=True
) - 为了能够通过调用代码卸载UF。
有用link
出色的概述 "UserForm1.Show?" 可在 https://rubberduckvba.wordpress.com/2017/10/25/userform1-show/ 找到 以及许多示例性 SO 答案(感谢 Mathieu Guindon 又名 Mat's Mug 和 RubberDuck)。
进一步选择(►2019 年 5 月 1 日编辑)
- Passing variable from form to module
- Apply logic for userform dialog (Rubberduck)
- The perfect userform (Vitosh academy)
1) 模态用户窗体的工作示例
据我所知——我确实在努力学习——以下代码对于 modal 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
案例 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
2) 问题
使用 MODELESS UserForm 实例时出现问题。
好的,with 块方法(参见 1b)应该足以在 x-iting 之后销毁任何对象引用:
With New UserForm1
.Show vbModeless ' << show modeless uf
End With
如果我尝试,但是
- a) 获取有关可能的用户取消以及 的信息 如果在
- b) 到
Unload
形式,
Show
指令之后使用局部变量(例如 "ufA")进行洗礼,由于表单是无模式的,所有代码行将立即执行:
- 代码显示表格,下一刻..
- 代码发现没有用户取消,因为没有时间进行任何用户操作,下一刻..
- [如果为用户窗体使用局部变量,代码将卸载窗体]
3) 问题
我如何处理 a) 通过无模式窗体的调用代码正确报告的用户窗体取消,以及 b) 如果使用局部变量(必要?)卸载?
我通常将无模式用户窗体实例的生命周期与工作簿的生命周期联系起来,方法是将代码放在 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
然后您可以在整个代码中引用无模式代码,例如
ThisWorkbook.MyForm.Show vbModeless
等等
对于无模式表单,使用 DoEvents 和自定义用户表单 属性。
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
'/ 用户表单
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
事实上,我一直非常关注模态形式 - 因为这是最常用的形式。感谢您对该文章的反馈!
非模态形式的原则是相同的:只需扩展链接文章和
区别在于非模态形式需要范式转变:您不再响应预设的事件序列 - 相反,您需要响应一些异步 事件 可能在任何给定时间发生,也可能不发生。
- 处理模态表单时,有一个 "before showing",然后是一个 "after hiding",它在隐藏表单后立即运行。您可以使用事件处理 "while showing" 发生的任何事情。
- 处理非模态形式时,有一个"before showing",然后"while showing"和"after showing" 两个都需要通过事件。
让您的演示者 class 模块负责在模块级别保存 UserForm
实例,并且 WithEvents
:
Option Explicit
Private WithEvents myModelessForm As UserForm1
演示者的 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
您不希望表单实例是此处过程的局部变量,因此局部变量或 a With
块无法工作:该对象将在您想要之前超出范围。这就是您将实例存储在模块级别的私有字段中的原因:现在表单与演示者实例一样存在。
现在,您需要向演示者制作表单 "talk" - 最简单的方法是在 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
现在演示者可以处理 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
非模态表单 通常 没有 "ok" 和 "cancel" 按钮。相反,您会公开许多功能,例如一个会调出一些模态对话框的功能 UserForm2
做其他事情 - 同样,您只需为其公开一个事件,并在演示者中处理它:
Public Event ShowGizmo()
Private Sub ShowGizmoButton_Click()
RaiseEvent ShowGizmo
End Sub
主持人接着说:
Private Sub myModelessForm_ShowGizmo()
With New GizmoPresenter
.Show
End With
End Sub
请注意,模态 UserForm2
是单独的演示者 class 关注的问题。