如何降低VBA对象事件包装器中的耦合度?
摘要
在事件需与父对象交互的前提下,是否存在一种方式可为内置对象启用事件,同时避免事件与原对象的父对象耦合?
免责声明1:我家中电脑无MS Office权限,所有代码均凭记忆编写,若有疏漏敬请谅解。
免责声明2:本文篇幅较长,因我多年来一直尝试解决该问题却未找到正确的搜索关键词,详细说明旨在帮助有相同困扰的开发者。
原有问题
我长期面临这样的问题:Userform中存在大量事件处理逻辑近乎相同的控件,却无法将代码精简为通用方案。例如,某Userform中有多个CommandButton,点击时执行相同操作。传统方式下,需在Userform1中编写如下重复代码:
Private Sub CommandButton1_Click() Me.DoSomething CommandButton1.Name End Sub Private Sub CommandButton2_Click() Me.DoSomething CommandButton2.Name End Sub '...更多类似代码...' Private Sub CommandButtonN_Click() Me.DoSomething CommandButtonN.Name End Sub
这种方式配置繁琐,且按钮数量较多时可读性差。
初步解决方案
我近期发现可利用包装类为内置对象创建通用WithEvents处理程序。针对上述示例,创建EventCommandButton.cls类,代码如下:
Private WithEvents mCommandButton as MSForms.CommandButton Private Sub mCommandButton_Click() mCommandButton.Parent.DoSomething(mCommandButton.Name) End Sub Property Get CommandButton() as MSForms.CommandButton Set CommandButton = mCommandButton End Property Property Set CommandButton(cmdBtn as MSForms.CommandButton) Set mCommandButton = cmdBtn End Property
此时Userform1的代码简化为:
Private EventCommandButtons() as New EventCommandButton Private Sub Userform1_Initialize() For Each ctl in Me.Controls If TypeName(ctl) = "CommandButton" Then i = i + 1 ReDim Preserve EventCommandButtons(1 to i) Set EventCommandButtons(i).CommandButton = ctl End If Next End Sub
该方案节省代码量,结构更清晰,但存在至少两个主要问题:
Userform1的控件事件逻辑不再存放在自身代码中;EventCommandButton要求父对象必须存在特定的DoSomething(str)过程,否则会报错。
优化后的方案
我当前采用的方案是将事件处理控制权归回预期位置。在EventCommandButton.cls中新增属性指定回调对象:
Private mCommandButton as MSForms.CommandButton Private mCallback as Object Private Sub mCommandButton_Click() '此处应添加错误处理以检查mCallback是否已设置 mCallback.EventCommandButton_Click(mCommandButton) End Sub Property Get Callback() as Object Set Callback = mCallback End Property Property Set Callback(ParentObject as Object) '不假设回调对象始终为.Parent Set mCallback = ParentObject End Property Property Get CommandButton() as MSForms.CommandButton Set CommandButton = mCommandButton End Property Property Set CommandButton(cmdBtn as MSForms.CommandButton) Set mCommandButton = cmdBtn End Property
Userform1的代码调整为:
Private EventCommandButtons() as New EventCommandButton Public Sub EventCommandButton_Click(cmdBtn as MSForms.CommandButton) Me.DoSomething cmdBtn.name End Sub Private Sub Userform1_Initialize() For Each ctl in Me.Controls If TypeName(ctl) = "CommandButton" Then i = i + 1 ReDim Preserve EventCommandButtons(1 to i) Set EventCommandButtons(i).CommandButton = ctl Set EventCommandButtons(i).Callback = Me '设置新增属性 End If Next End Sub
该方案更接近问题的直观解决思路,解决了前一方案的第一个问题,但仍存在以下问题:
- 类与Userform仍存在耦合,要求父对象必须存在形如
Public Sub [ClassName]_[EventName]([OriginalObject], Optional [EventParams])的伪事件过程,既不直观,也与私有事件过程格格不入; - 耦合依赖于类名,重命名类需修改对应事件过程;
- 若要包装类“完整”,需包含所有事件及错误处理以忽略父对象未配置的事件,大量
On Error GoTo EoF语句可能影响性能。
核心问题
是否存在进一步优化的方式,以降低类与窗体代码之间的耦合度?借助VBIDE可检测类名并生成伪事件,但无VBIDE权限时,该方案需持续维护且使用门槛较高。
在Python等语言中,可直接传递函数引用处理事件回调,但VBA似乎不支持该特性。
内容的提问来源于stack exchange,提问作者Vince
相关产品推荐
相关产品推荐

