Luc*_*nda 22 excel user-interface vba userform
将代码放入VBA Userform而不是"普通"模块中是否有缺点?
这可能是一个简单的问题,但我在搜索Web和stackoverflow时没有找到一个确定的答案.
背景:我正在Excel-VBA中开发数据库的前端应用程序.要选择不同的过滤器,我有不同的用户形式.我问一般的程序设计更好:(1)将控制结构放入单独的模块中或(2)将下一个用户形式或操作的代码放入用户窗体中.
让我们举个例子.我有一个Active-X按钮,可以触发我的过滤器和表单.
Variant1:模块
在CommandButton中:
Private Sub CommandButton1_Click()
call UserInterfaceControlModule
End Sub
Run Code Online (Sandbox Code Playgroud)
在模块中:
Sub UserInterfaceControllModule()
Dim decisionInput1 As Boolean
Dim decisionInput2 As Boolean
UserForm1.Show
decisionInput1 = UserForm1.decision
If decisionInput1 Then
UserForm2.Show
Else
UserForm3.Show
End If
End Sub
Run Code Online (Sandbox Code Playgroud)
在变体1中,控制结构在正常模块中.关于下一个要显示哪个userform的决定与userform分开.决定下一个要显示的用户表单所需的任何信息都必须从用户表单中提取.
Variant2:Userform
在CommadButton中:
Private Sub CommandButton1_Click()
UserForm1.Show
End Sub
Run Code Online (Sandbox Code Playgroud)
在Userform1中:
Private Sub ToUserform2_Click()
UserForm2.Show
UserForm1.Hide
End Sub
Private Sub UserForm_Click()
UserForm2.Show
UserForm1.Hide
End Sub
Run Code Online (Sandbox Code Playgroud)
在变体2中,控制结构直接在用户表单中,每个用户表单都有关于它后面的内容的说明.
我已经开始使用方法2进行开发.如果这是一个错误,并且这个方法有一些严重的缺点,我想早点知道它.
Mat*_*don 48
你的两种选择都不理想.回归本源.
要选择不同的过滤器,我有不同的(原文如此)用户形式.
您的规范要求用户需要能够选择不同的过滤器,并且您选择使用a来实现UI UserForm.到目前为止,这么好......而且从那里开始走下坡路.
使表单对表示问题以外的任何事情负责是一个常见的错误,它有一个名称:它是智能UI [反]模式,它的问题是它不能扩展.它非常适合原型制作(即快速制作"有效"的东西 - 请注意恐慌报价),而不是那些需要多年维护的东西.
您可能已经看到过这些表单,160个控件,217个事件处理程序和3个私有程序各自关闭2000行代码:Smart UI的扩展程度非常糟糕,而且这是唯一可能的结果.
你看,a UserForm是一个类模块:它定义了一个对象的蓝图.对象通常希望被实例化,但后来有人曾授予的所有实例的天才的想法一个预先声明的ID,这在COM而言意味着你基本上得到一个全局对象是免费的.MSForms.UserForm
大!没有?没有.
Run Code Online (Sandbox Code Playgroud)UserForm1.Show decisionInput1 = UserForm1.decision If decisionInput1 Then UserForm2.Show Else UserForm3.Show End If
如果UserForm1是"X'd-out" 会发生什么?或者如果UserForm1是Unloaded?如果表单没有处理它的QueryClose事件,那么该对象将被销毁 - 但由于这是默认实例,VBA会自动/静默地为您创建一个新的实例,就在您的代码读取之前UserForm1.decision- 因此您可以获得初始全局状态对UserForm1.decision.
如果它不是默认实例,并且QueryClose未被处理,则访问已.decision销毁对象的成员将为您提供用于访问空对象引用的经典运行时错误91.
UserForm2.Show并且UserForm3.Show两者都做同样的事情:即发生 - 忘记 - 无论发生什么事情,并且要确切地找出它包含的内容,你需要在形式'各自的代码隐藏中挖掘它.
换句话说,表单正在运行show.他们负责收集数据,呈现数据,收集用户输入以及完成需要完成的任何工作.这就是为什么它被称为"智能UI":UI知道一切.
还有更好的方法.MSForms是.NET的WinForms UI框架的COM祖先,它的祖先与其.NET继承者的共同之处在于它与着名的模型 - 视图 - 展示器(MVP)模式一起工作得特别好.
那是你的数据.从本质上讲,这是您的应用程序逻辑需要知道的形式.
UserForm1.decision 让我们一起去吧.添加一个新类,调用它,比如说FilterModel.应该是一个非常简单的类:
Option Explicit
Private Type TModel
SelectedFilter As String
End Type
Private this As TModel
Public Property Get SelectedFilter() As String
SelectedFilter = this.SelectedFilter
End Property
Public Property Let SelectedFilter(ByVal value As String)
this.SelectedFilter = value
End Property
Public Function IsValid() As Boolean
IsValid = this.SelectedFilter <> vbNullString
End Function
Run Code Online (Sandbox Code Playgroud)
这就是我们所需要的:一个封装表单数据的类.该类可以负责某些验证逻辑,或者其他任何东西 - 但它不收集数据,它不会将其呈现给用户,也不会消耗它.这是数据.
这里只有1个属性,但你可以有更多:想想表格上的一个字段=>一个属性.
该模型也是表单需要从应用程序逻辑中获知的内容.例如,如果表单需要一个显示许多可能选择的下拉列表,则模型将是暴露它们的对象.
那是你的形式.它负责了解控件,写入和读取模型,以及......这就是全部.我们期待在这里的对话:我们要拿出来,用户填写起来,关闭它,程序作用于它-形式本身并不做处理收集的数据什么.该模型可以验证它的形式可能会决定禁用它的Ok按钮,直到模型说,它的数据是有效的,好走,但在任何情况下一个UserForm读取或从一个工作表,数据库,文件,URL,或任何写.
表单的代码隐藏很简单:它使用模型实例连接UI,并根据需要启用/禁用其按钮.
要记住的重要事项:
Hide,不要Unload:视图是一个对象,对象不会自毁.QueryClose,以避免自毁对象("X-out out"的形式否则会破坏实例).在这种情况下,代码隐藏可能如下所示:
Option Explicit
Private Type TView
Model As FilterModel
IsCancelled As Boolean
End Type
Private this As TView
Public Property Get Model() As FilterModel
Set Model = this.Model
End Property
Public Property Set Model(ByVal value As FilterModel)
Set this.Model = value
Validate
End Property
Public Property Get IsCancelled() As Boolean
IsCancelled = this.IsCancelled
End Property
Private Sub TextBox1_Change()
this.Model.SelectedFilter = TextBox1.Text
Validate
End Sub
Private Sub OkButton_Click()
Me.Hide
End Sub
Private Sub Validate()
OkButton.Enabled = this.Model.IsValid
End Sub
Private Sub CancelButton_Click()
OnCancel
End Sub
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
If CloseMode = VbQueryClose.vbFormControlMenu Then
Cancel = True
OnCancel
End If
End Sub
Private Sub OnCancel()
this.IsCancelled = True
Me.Hide
End Sub
Run Code Online (Sandbox Code Playgroud)
这就是所有形式的确如此.它不负责了解数据来自何处或如何处理数据.
这是连接点的"胶水"对象.
Option Explicit
Public Sub DoSomething()
Dim m As FilterModel
Set m = New FilterModel
With New FilterForm
Set .Model = m 'set the model
.Show 'display the dialog
If Not .IsCancelled Then 'how was it closed?
'consume the data
Debug.Print m.SelectedFilter
End If
End With
End Sub
Run Code Online (Sandbox Code Playgroud)
如果模型中的数据需要来自数据库或某个工作表,那么它使用的是一个类实例(是的,另一个对象!),它负责这样做.
调用代码可以是您的ActiveX按钮的单击处理程序, - 调New出演示者并调用其DoSomething方法.
这并不是VBA中有关OOP的所有知识(我甚至没有提到接口,多态,测试存根和单元测试),但是如果你想要客观可扩展的代码,你会想要走下MVP兔子洞并探索真正面向对象的代码为VBA带来的可能性.
代码("业务逻辑")根本不属于表单的代码隐藏,在任何代码库中都意味着可以在几年内进行扩展和维护.
在"变体1"中,代码很难遵循,因为您在模块之间跳转并且演示问题与应用程序逻辑混合在一起:在按下按钮A或按钮B的情况下知道要显示的其他表单不是表单的工作.相反,它应该让演示者知道用户的意思,并采取相应的行动.
在"变体2"中,代码很难遵循,因为一切都隐藏在userforms的代码隐藏中:我们不知道应用程序逻辑是什么,除非我们深入研究代码,现在故意混合表示和业务逻辑问题.这正是 "智能UI"反模式的作用.
换句话说,变体1稍微好于变体2,因为至少逻辑不在代码隐藏中,但它仍然是"智能UI",因为它正在运行节目而不是告诉其调用者发生了什么.
在这两种情况下,对表单的默认实例进行编码都是有害的,因为它将状态置于全局范围内(任何人都可以从代码中的任何位置访问默认实例并对其状态执行任何操作).
像对象一样处理表单:实例化它们!
在这两种情况下,由于表单的代码与应用程序逻辑紧密耦合并与表示问题交织在一起,因此完全不可能编写单个单元测试,甚至涵盖正在发生的事情的一个方面.使用MVP模式,您可以完全解耦组件,在接口后面抽象它们,隔离职责,编写几十个自动化单元测试,涵盖每一项功能并准确记录规范是什么 - 无需编写一些文档:代码成为自己的文档.