功能化事件驱动的无模式UserForm类 [英] Functionalize Event Driven Modeless UserForm Class

查看:44
本文介绍了功能化事件驱动的无模式UserForm类的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我一直在移动我的一些代码来利用 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆