Sub函数显示UserForm

时间:2016-01-27 17:26:28

标签: excel vba excel-vba

我有一个包含多个UserForms的excel文件。要打开UserForm,我有

等代码
Sub runAdjuster()
   Adjuster.Show
End Sub

其中大约有5个。在保留此代码的位置方面,什么是最佳实践?我最初在一个模块中使用它,但已决定将其移动到ThisWorkbook对象。寻找有关保持代码清洁的常用方法的提示。

2 个答案:

答案 0 :(得分:12)

假设Adjuster是表单的名称,您在此处使用默认实例,这不是理想的。

这已经更好了:

Dim view As Adjuster
Set view = New Adjuster
view.Show

是的,它的代码更多。但是您使用专用的对象(即view),如果该对象的状态被修改,这些更改将不会影响默认值实例。将该默认实例视为全局对象:它的全局,它不是OOP。

现在,你可能会争辩说,为什么不呢?#34; new up"与声明在同一行的对象呢?

考虑一下:

Sub DoSomething()
    Dim c As New Collection
    Set c = Nothing
    c.Add "test"
End Sub

此代码是否正在访问空引用并且遇到运行时错误91?没有!混乱?是!因此,请避免使用As New快捷方式,除非您希望让VBA自动执行隐藏的操作。

所以,你问的是最佳实践 ...我倾向于将VBA UserForms视为的早期.NET版本,以及最佳实践设计模式for WinForms是 Model-View-Presenter 模式(又名" MVP")。

遵循此模式,您将拥有严格负责演示文稿的UserForms,并且您的业务逻辑要么在演示者对象中实现,或者在演示者使用的专用对象中。像这样:

课程模块:MyPresenter

演示者类从模型接收事件,并根据模型的状态执行应用程序逻辑。它知道视图概念,但它不必与具体实现紧密耦合(例如{{ 1}}) - 使用适当的工具,您可以write unit tests以编程方式验证您的逻辑,而无需实际运行代码并显示表单并在任何地方单击。

MyUserForm

课程模块:IView

抽象代表 View 概念,它公开 Presenter 的所有内容需要了解任何UserForm - 请注意它需要知道的所有内容,并不是很多:

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

课程模块:MyModel

模型类封装了表单所需和操作的数据。它不了解视图,也不了解演示者:它只是封装数据的容器,使用简单的逻辑,使视图和演示者在修改任何属性时执行代码。

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

UserForm:MyUserForm

UserForm严格负责视觉呈现;它的所有事件处理程序都是改变模型中属性的值 - 模型然后告诉演示者&#34;嘿我已经被修改了!&#34;,并且演示者相应地行动。表单还会侦听模型上的已修改属性,因此当演示者更改模型时,视图可以执行代码并相应地更新自身。这是一个简单形式的示例&#34;绑定&#34; 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 模型属性到某些MyProperty的文本;我为TextBox1添加了一个监听器,只是为了说明在模型更改时也可以间接更新视图。

显然,视图不会对与演示者相同的属性做出反应,否则你会进入无休止的回调,这最终会炸毁堆栈......但是你明白了。< / p>

请注意,表单实现了SomeOtherProperty接口,以便演示者可以在不知道其内部工作原理的情况下与之交谈。界面实现只是指具体成员,但具体成员甚至不需要实际存在,因为他们甚至不会被使用!

IView

模块:宏

假设您的电子表格有一个形状,并且您希望在单击时运行该逻辑。你需要将一个宏附加到那个形状 - 我喜欢重新组合一个名为&#34;宏&#34;的标准模块(.bas)中的所有宏,它们只包含公共程序,它们都是这样的:

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

现在,如果您想以编程方式测试演示者逻辑而不显示表单,那么您需要做的就是实现一个&#34;假的&#34;查看,并编写一个可以满足您需求的测试方法:

类:MyFakeView

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

模块:TestModule1

可能还有其他工具,但是因为我实际上写了这个,我喜欢它的工作方式,如果没有大量的样板设置代码或含有可执行指令的注释,我会热烈推荐使用Rubberduck单元试验。这是一个[非常简单]测试模块的样子:

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

Rubberduck单元测试允许您使用此解耦代码来测试您要测试的有关应用程序逻辑的所有内容 - 只要您保持该应用程序逻辑解耦并且您编写< em> testable 代码,您将有单元测试来记录您的VBA应用程序应该如何表现,测试记录规范是什么 - 就像您在C#或Java中使用它们一样,或任何其他OOP语言可以用。编写单元测试。

重点是,VBA也可以这样做。

过度破坏?要看。规格一直在变化,代码会相应变化。在电子表格中实现所有应用程序逻辑&#39;代码隐藏变得非常烦人,因为the Project Explorer doesn't drill down to module members,所以找到实现的地方很容易让人讨厌。

当逻辑以表格形式实施时,情况会更糟。代码隐藏,然后你有'@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 个处理程序进行数据库调用或电子表格操作。

在责任尽可能少的对象中实施的代码,使代码可以重复使用,并且更易于维护。

您的问题并不完全准确地说明了您的意思&#34;一个包含多个用户形式的Excel文件&#34;,但如果您需要,您可以拥有一个&#34; main&#34;接收4-5&#34;孩子的主持人类#34;演示者,每个人都负责与每个孩子相关的特定逻辑。形式。

也就是说,如果您有工作代码(完全符合预期),您希望重构并提高效率,或更容易阅读/维护,您可以将其发布在Code Review Stack Exchange,即&#39那个网站是为了什么。

  

免责声明I maintain the Rubberduck project.

答案 1 :(得分:0)

这取决于启动这些潜艇的原因。如果它们附加到按钮或形状(这是我倾向于启动用户形式),那么将它们放入包含该形状的工作表的模块中是有意义的。如果几张纸上的按钮/形状引用它 - 将它们放在通用代码模块中。我不知道这里是否真的有“最佳实践”。最重要的是保持一致性,这样你就不必去寻找东西了。