tee*_*pee 5 excel vba excel-vba
我有一个包含多个UserForms的excel文件.要打开UserForm我有代码,如
Sub runAdjuster()
Adjuster.Show
End Sub
Run Code Online (Sandbox Code Playgroud)
其中大约有5个.在保留此代码的位置方面,什么是最佳实践?我最初在一个模块中使用它,但已决定将其移动到ThisWorkbook对象.寻找通常用于保持代码清洁的提示.
Mat*_*don 12
假设Adjuster是表单的名称,您在这里使用默认实例,这不是理想的.
这已经更好了:
Dim view As Adjuster
Set view = New Adjuster
view.Show
Run Code Online (Sandbox Code Playgroud)
是的,这是更多的代码.但是你正在使用一个专用对象(即view),如果该对象的状态被修改,这些更改不会影响默认实例.将该默认实例视为全局对象:它是全局的,不是非常OOP.
现在,您可能会争辩,为什么不在声明的同一行"新建"对象呢?
考虑一下:
Sub DoSomething()
Dim c As New Collection
Set c = Nothing
c.Add "test"
End Sub
Run Code Online (Sandbox Code Playgroud)
此代码是否访问了空引用并且导致运行时错误91?没有!混乱?是! 因此,请避免使用As New快捷方式,除非您希望让VBA自动执行隐藏的操作.
所以,你问的是最佳实践 ...我倾向于将VBA UserForms视为winforms的早期.NET版本,而WinForms的最佳实践设计模式是Model-View-Presenter模式(又名"MVP") .
遵循此模式,您将拥有严格负责演示的 UserForms ,并且您将在演示者对象或演示者使用的专用对象中实现业务逻辑.像这样的东西:
课程模块:MyPresenter
的演示类接收来自事件模型,并且根据所述模型的状态来执行应用逻辑.它知道一个概念一的观点,但并不必须紧密结合一个具体的执行方式(例如MyUserForm) -适当的工具,你可以编写单元测试,以编程的方式验证你的逻辑,而无需实际运行代码,并显示表单并随处点击.
Option Explicit
Private Type TPresenter
View As IView
End type
Public Enum PresenterError
ERR_ModelNotSet = vbObjectError + 42
End Enum
Private WithEvents viewModel As MyModel
Private this As TPresenter
Public Sub Show()
If viewModel Is Nothing Then
Err.Raise ERR_ModelNotSet, "MyPresenter.Show", "Model is not set to an object reference."
End If
'todo: set up model properties
view.Show
If Not view.IsCancelled Then DoSomething
End Sub
Public Property Get View() As IView
Set View = this.View
End Property
Public Property Set View(ByVal value As IView)
Set this.View = value
If Not this.View Is Nothing Then Set this.View.Model = viewModel
End Property
Public Property Get Model() As MyModel
Set Model = viewModel
End Property
Public Property Set Model(ByVal value As MyModel)
Set viewModel = value
If Not this.View Is Nothing Then Set this.View.Model = viewModel
End Property
Private Sub Class_Terminate()
Set this.View.Model = Nothing
Set this.View = Nothing
Set viewModel = Nothing
End Sub
Private Sub viewModel_PropertyChanged(ByVal changedProperty As ModelProperties)
'todo: execute logic that needs to run when something changes in the form
End Sub
Private Sub DoSomething()
'todo: whatever needs to happen after the form closes
End Sub
Run Code Online (Sandbox Code Playgroud)
课程模块:IView
这就是抽象表示概念一的观暴露一切演示需要了解的任何用户窗体-请注意,它需要知道的一切,是没有多大:
Option Explicit
Public Property Get Model() As Object
End Property
Public Property Set Model(ByVal value As Object)
End Property
Public Property Get IsCancelled() As Boolean
End Property
Public Sub Show()
End Sub
Run Code Online (Sandbox Code Playgroud)
类模块:MyModel
模型类封装了表单所需和操作的数据.它不了解视图,也不知道演示者:它只是封装数据的容器,具有简单的逻辑,使得视图和演示者在修改任何属性时都能执行代码.
Option Explicit
Private Type TModel
MyProperty As String
SomeOtherProperty As String
'todo: wrap members here
End Type
Public Enum ModelProperties
MyProperty
SomeOtherProperty
'todo: add enum values here for each monitored property
End Enum
Public Event PropertyChanged(ByVal changedProperty As ModelProperties)
Private this As TModel
Public Property Get MyProperty() As String
MyProperty = this.MyProperty
End Property
Public Property Let MyProperty(ByVal value As String)
If this.MyProperty <> value Then
this.MyProperty = value
RaiseEvent PropertyChanged(MyProperty)
End If
End Property
Public Property Get SomeOtherProperty() As String
SomeProperty = this.SomeOtherProperty
End Property
Public Property Let SomeOtherProperty(ByVal value As String)
If this.SomeOtherProperty <> value Then
this.SomeOtherProperty = value
RaiseEvent PropertyChanged(SomeOtherProperty)
End If
End Property
'todo: expose other model properties
Run Code Online (Sandbox Code Playgroud)
UserForm:MyUserForm
UserForm严格负责视觉呈现; 它的所有事件处理程序都是改变模型中属性的值 - 然后模型告诉主持人"嘿,我已被修改!",并且演示者相应地行动.表单还会侦听模型上的已修改属性,因此当演示者更改模型时,视图可以执行代码并相应地更新自身.这是一个简单的形式将MyProperty模型属性"绑定" 到某些文本的示例TextBox1; 我添加了一个监听器,SomeOtherProperty仅用于说明在模型更改时也可以间接更新视图.
显然,视图不会对作为演示者改变的相同属性做出反应,否则你会进入无休止的回调,这最终会炸毁堆栈......但是你明白了.
请注意,表单实现了IView界面,以便演示者可以与之交谈,而无需真正了解其内部工作原理.接口实现只是指具体成员,但具体成员甚至不需要实际存在,因为它们甚至不会被使用!
Option Explicit
Implements IView
Private Type TView
IsCancelled As Boolean
End Type
Private WithEvents viewModel As MyModel
Private this As TView
Private Property Get IView_Model() As Object
Set IView_Model = Model
End Property
Private Property Set IView_Model(ByVal value As Object)
Set Model = value
End Property
Private Property Get IView_IsCancelled() As Boolean
IView_IsCancelled = IsCancelled
End Property
Private Sub IView_Show()
Show vbModal
End Sub
Public Property Get Model() As MyModel
Set Model = viewModel
End Property
Public Property Set Model(ByVal value As MyModel)
Set viewModel = value
End Property
Public Property Get IsCancelled() As Boolean
IsCancelled = this.IsCancelled
End Property
Private Sub CancelButton_Click()
this.IsCancelled = True
Me.Hide
End Sub
Private Sub OkButton_Click()
Me.Hide
End Sub
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
'"x-ing out" of the form is like clicking the Cancel button
If CloseMode = VbQueryClose.vbFormControlMenu Then
this.IsCancelled = True
End If
End Sub
Private Sub UserForm_Activate()
If viewModel Is Nothing Then
MsgBox "Model property must be assigned before the view can be displayed.", vbCritical, "Error"
Unload Me
Else
Me.TextBox1.Text = viewModel.MyProperty
Me.TextBox1.SetFocus
End If
End Sub
Private Sub TextBox1_Change()
'UI elements update the model properties
viewModel.MyProperty = Me.TextBox1.Text
End Sub
Private Sub viewModel_PropertyChanged(ByVal changedProperty As ModelProperties)
If changedProperty = SomeOtherProperty Then
Frame1.Caption = SomeOtherProperty
End If
End Sub
Run Code Online (Sandbox Code Playgroud)
模块:宏
假设您的电子表格有一个形状,并且您希望在单击时运行该逻辑.你需要将一个宏附加到该形状 - 我喜欢重新组合一个名为"宏"的标准模块(.bas)中的所有宏,它只包含公共过程,它们都是这样的:
Option Explicit
Public Sub DoSomething()
Dim presenter As MyPresenter
Set presenter = New MyPresenter
Dim theModel As MyModel
Set theModel = New MyModel
Dim theView As IView
Set theView = New MyUserForm
Set presenter.Model = theModel
Set presenter.View = theView
presenter.Show
End Sub
Run Code Online (Sandbox Code Playgroud)
现在,如果您想以编程方式测试演示者逻辑而不显示表单,那么您需要做的就是实现"假"视图,并编写一个可以满足您需求的测试方法:
类:MyFakeView
Option Explicit
Implements IView
Private Type TFakeView
IsCancelled As Boolean
End Type
Private this As TFakeView
Private Property Get IView_Model() As Object
Set IView_Model = Model
End Property
Private Property Set IView_Model(ByVal value As Object)
Set Model = value
End Property
Private Property Get IView_IsCancelled() As Boolean
IView_IsCancelled = IsCancelled
End Property
Private Sub IView_Show()
IsCancelled = False
End Sub
Public Property Get IsCancelled() As Boolean
IsCancelled = this.IsCancelled
End Property
Public Property Let IsCancelled(ByVal value As Boolean)
this.IsCancelled = value
End Property
Run Code Online (Sandbox Code Playgroud)
模块:TestModule1
可能还有其他工具,但是因为我实际上已经编写了这个工具而且我喜欢它如何工作而没有大量的样板设置代码或包含可执行指令的注释我将热烈推荐使用Rubberduck单元测试.这是一个[非常简单]测试模块的样子:
'@TestModule
Option Explicit
Option Private Module
Private Assert As New Rubberduck.AssertClass
'@TestMethod
Public Sub Model_SomePropertyInitializesEmpty()
On Error GoTo TestFail
'Arrange
Dim presenter As MyPresenter
Set presenter = New MyPresenter
Dim theModel As MyModel
Set theModel = New MyModel
Set presenter.Model = theModel
Set presenter.View = New MyFakeView
'Act
presenter.Show
'Assert
Assert.IsTrue theModel.SomeProperty = vbNullString
TestExit:
Exit Sub
TestFail:
Assert.Fail "Test raised an error: #" & Err.Number & " - " & Err.Description
End Sub
Run Code Online (Sandbox Code Playgroud)
Rubberduck单元测试允许您使用此解耦代码来测试您要测试的有关应用程序逻辑的所有内容 - 只要您保持应用程序逻辑解耦并编写可测试代码,您就会有单元测试来记录您的VBA应用程序的方式应该表现,测试记录规范是什么 - 就像你将它们用C#或Java,或任何其他OOP语言,可以编写单元测试.
重点是,VBA也可以做到.
矫枉过正?要看.规格一直在变化,代码会相应变化.在电子表格的代码隐藏中实现所有应用程序逻辑变得非常烦人,因为Project Explorer不会深入到模块成员,所以找到实现的地方很容易让人讨厌.
当逻辑在表单的代码隐藏中实现,然后你有Button_Click处理程序进行数据库调用或电子表格操作时,情况会更糟.
在具有尽可能少责任的对象中实现的代码,使代码可重用,并且更易于维护.
您的问题与"具有多个用户表单的Excel文件"的确切含义并不完全准确,但如果您需要,您可以拥有一个"主要"演示者类,可以接收4-5个"孩子"演示者,每个人都负责对于与每个"子"形式相关的特定逻辑.
也就是说,如果您有工作代码(完全符合预期),您希望重构并提高效率,或更容易阅读/维护,您可以将其发布在Code Review Stack Exchange上,这就是该网站的用途.
免责声明:我维护Rubberduck项目.