功能化事件驱动的无模式UserForm类 [英] Functionalize Event Driven Modeless UserForm Class
问题描述
我一直在移动我的一些代码来利用 UserForms
和 Modeless
,以便用户可以从工作表中复制数据.我终于通用"这个答案并使其起作用.但是,我要避免为每个UserForm创建一个类,事实是我不是100%理解代码中发生了什么.有没有一种方法可以方便地迁移我的所有用户表单,以利用事件驱动的无模式用户表单"的以下功能.基本上,我想将UserForm作为变量传递,并在类中包含一堆子对象,这些子对象通常是通用子对象和特定子对象,或者可能只是使用 If/Then
在 _Closed
事件.
I've been moving some of my code to utilize UserForms
utilizing Modeless
so the user can copy data from the worksheet. I finally "generalized" this answer and got it working. But, I want to avoid having to create a class for each UserForm and truth is I don't 100% understand what is going on in the code. Is there a way I can easily migrate all my UserForms to utilize the below functions of "Event Driven Modeless UserForms". Basically, I'd like to pass the UserForm as a variable and have a bunch of subs in the class, mostly general and a specific or perhaps just using If/Then
to call the correct exit module after the _Closed
event.
希望如此,如果您需要进一步说明,请告诉我.
Hope that makes sense, let me know if you require further clarification.
通用代码:
模块名称无关紧要
UserForm名称= UserForm1
UserForm Name = UserForm1
Class Name = Class1
Class Name = Class1
模块代码:
Private UserFormNameStr As Class1
Public Sub DoStuff()
Set UserFormNameStr = New Class1
UserFormNameStr.ClassSubNameStrSubName
End Sub
Public Sub CallMeWhenUserFormClosed()
Debug.Print "Module Code Run"
End Sub
班级代码:
Private WithEvents UserFormNameStr As UserForm1
Private Sub Class_Initialize()
Set UserFormNameStr = New UserForm1
End Sub
Public Sub ClassSubNameStrSubName()
UserFormNameStr.Show vbModeless
End Sub
Private Sub UserFormNameStr_Closed()
'_Closed is required syntax
Debug.Print "Closed Event"
Call CallMeWhenUserFormClosed
End Sub
用户表单代码:
Public Event Closed()
Private Sub UserForm_Initialize()
'
End Sub
Sub OkButton_Click()
Debug.Print "Raising Events from OK Button!"
RaiseEvent Closed
Unload Me
End Sub
Private Sub CancelButton_Click()
Unload Me
End
End Sub
更新:
也许我在Workbook_Open上寻找带有挂钩"的东西吗?
Perhaps I'm looking for something on Workbook_Open with a "hook"?
推荐答案
使所有工作都起作用的是 WithEvents
声明.用 WithEvents
修饰符声明的实例变量将出现在编辑器的左侧下拉列表中.
What's making it all work is the WithEvents
declaration. Instance variables declared with the WithEvents
modifier will appear in the editor's left-side dropdown.
要为事件提供程序创建事件处理程序,请从左侧的下拉列表中选择变量,然后在右侧的下拉列表中选择要处理的事件.
To create an event handler procedure for an event provider, select the variable from the left dropdown, then pick an event to handle in the right-side dropdown.
最终,该模块看起来像这样,即对于要处理以下事件的每种无模式形式,都有一个 WithEvents
声明:
Ultimately the module would look something like this, i.e. with a WithEvents
declaration for each modeless form you want to handle events for:
Private WithEvents UserFormNameStr As UserForm1
Private WithEvents SomeOtherUserForm As UserForm2
Private WithEvents AnotherUserForm As UserForm3
Private Sub Class_Initialize()
Set UserFormNameStr = New UserForm1
Set SomeOtherUserForm = New UserForm2
Set AnotherUserForm = New UserForm3
End Sub
Public Sub ClassSubNameStrSubName() 'weird name, consider methods that begin with a verb
UserFormNameStr.Show vbModeless
End Sub
Public Sub ShowSomeOtherForm()
SomeOtherUserForm.Show vbModeless
End Sub
Public Sub ShowAnotherForm()
AnotherUserForm.Show vbModeless
End Sub
Private Sub UserFormNameStr_Closed() 'select "UserFormNameStr" from the left-side dropdown: NEVER hand-write event handler signatures.
Debug.Print "Closed Event (UserFormNameStr)"
CallMeWhenUserFormClosed
End Sub
Private Sub SomeOtherUserForm_Closed() 'select "SomeOtherUserForm" from the left-side dropdown: NEVER hand-write event handler signatures.
Debug.Print "Closed Event (SomeOtherUserForm)"
CallMeWhenSomeOtherUserFormClosed
End Sub
Private Sub AnotherUserForm_Closed() 'select "AnotherUserForm" from the left-side dropdown: NEVER hand-write event handler signatures.
Debug.Print "Closed Event (AnotherUserForm)"
CallMeWhenAnotherUserFormClosed
End Sub
一个处理程序,多种形式?
如果所有 _Closed
处理程序都需要做完全相同的事情,那么我们可以涉及到接口和多态性,并在3个实例中存在一个类,每个类以各自的形式进行各自的工作-但是VBA不会在类的默认接口上公开 Public Event
声明,因此此处的范例有些不同,并且因为它不涉及 Event
和WithEvents
,也可以说这样更简单.
One Handler, Many Forms?
If all _Closed
handlers need to do exactly the same thing, then we can get interfaces and polymorphism involved and have one class exist in 3 instances that each do their own thing for their respective form - but VBA does not expose Public Event
declarations on a class' default interface, so the paradigm is a little bit different here, and because it doesn't involve Event
and WithEvents
, it's arguably simpler that way, too.
定义一个 IHandleClosingForm
接口:向您的项目中添加一个新的类模块,但是不注意实现-只是您想要的功能的非常高级的抽象(此处为 Rubberduck注释):
Define an IHandleClosingForm
interface: add a new class module to your project, but give no attention to the implementation - just a very high-level abstraction of the functionality you want (here with Rubberduck annotations):
'@ModuleDescription "An object that handles a form being closed."
'@Interface
'@Exposed
Option Explicit
'@Description "A callback invoked when a form is closed."
Public Sub Closing(ByVal Form As UserForm)
End Sub
在每个表单模块中,持有对该接口的引用,并在表单的 QueryClose
处理程序中调用其 Closing
方法:
In each form module, hold a reference to that interface, and invoke its Closing
method in the form's QueryClose
handler:
Option Explicit
Public CloseHandler As IHandleClosingForm
`...
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
If CloseMode = VbQueryClose.vbFormControlMenu Then
'user clicked the [X] button, form instance is going to be destroyed!
Cancel = True 'prevents a self-destructing form instance.
Me.Hide
End If
'don't assume the caller set the CloseHandler:
If Not CloseHandler Is Nothing Then CloseHandler.Closing(Me)
'...
End Sub
现在在presenter类中实现该接口:
Now implement that interface in the presenter class:
Option Explicit
Implements IHandleClosingForm
'...rest of the module...
Private Sub IHandleClosingForm_Closing(ByVal Form As UserForm)
'NOTE: procedure exits to the still-closing form's QueryClose handler
If TypeOf Form Is UserForm1 Then
CallMeWhenForm1Closes
Else
CallMeWhenAnyOtherFormCloses
End If
End Sub
最后一步是通过在显示表单之前设置公共 CloseHandler
属性,在表单和演示者之间引入循环引用:
The final step is to introduce a circular reference between the form and the presenter, by setting the public CloseHandler
property before showing the form:
Set theForm.CloseHandler = Me
theForm.Show vbModeless
这将起作用,但是会出现内存泄漏,并且表单和presenter实例都不会终止(处理 Class_Terminate
来查找!),而您想要为避免这种情况(尽管Excel将/应该在关闭VBA环境时将其清理干净).
This will work, but then there's a memory leak and neither the form nor the presenter instance would terminate (handle Class_Terminate
to find out!), and you will want to strive to avoid that (Excel will/should clean it all up when it shuts down the VBA environment though).
解决方案是尽早解开结,因此请确保表单的 QueryClose
处理程序将 IHandleClosingForm
引用设置为 Nothing
一旦不再有用:
The solution is to untie the knot at the first opportunity, so make sure your forms' QueryClose
handler sets the IHandleClosingForm
reference to Nothing
as soon as it is no longer useful:
'don't assume the caller set the CloseHandler:
If Not CloseHandler Is Nothing Then CloseHandler.Closing(Me)
Set CloseHandler = Nothing 'release the handler reference
下次显示表单并设置处理程序时,它将在表单的另一个实例上.
The next time the form is shown and the handler is set, it's going to be on another instance of the form.
如果您需要表单的 state 在显示和关闭之间持续存在,则必须将 state 与 form 分开(并保持状态,但仍然可以正确销毁表单对象),...但这又是另一天的主题:)
If you need the state of the form to persist between it being shown and closed, then you must separate the state from the form (and keep the state around but still properly destroy the form object), ...but that's another topic for another day :)
这篇关于功能化事件驱动的无模式UserForm类的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!