使用 MVP 方法验证控件是动态的用户窗体输入

Validate userform inputs where controls are dynamic using MVP approach

TL;DR

我使用一个 UserForm 动态分配自己的字幕和一些具有三种不同变体的控制字幕。特别是在此用户窗体上,两个变体需要四个 CheckBox,而在一个变体上不可见。

我的数据验证检查所有必填字段都输入了值(包括选中这四个复选框之一)所以当使用不需要勾选复选框的表单时(因为控件不可见)我是获取我的 "Please enter a value into each field." MessageBox.

我怎样才能避免这种情况?


在过去的几个月里,我一直在阅读 UserForm1.Show,这对我自己和许多其他人来说帮助我理解了用户窗体是什么以及它是如何工作的。

我正在尝试将 MVP 模式实施到我现有的项目中,该项目或多或少 只是 'completed'。

当我 运行 陷入问题或困惑时,我会自然而然地跳到 google 并且在大多数情况下要么找到另一篇文章或一个 SO 问题,作者给出的答案绰绰有余。但。我找不到用于验证可能存在或可能不存在的 MSForms.Control 的方法 - 即有时在表单上使用,具体取决于表单的变体。

请注意,我觉得我的表单设计方式可能有误(好吧,单数形式),所以如果在这种情况下,识别并涵盖该主题的答案也将是最有帮助的!

这是我的基本表单(测试按钮用于...测试):

当这 3 个按钮中的任何一个被单击时(工作表 ActiveX 命令按钮),它会填充以下用户窗体之一(标题对应按钮):

现在,我的数据验证适用于 NECLG 表单,但当它到达 其他形式。这是因为 NECLG 产品需要一个 Product Type CheckBox,但不适用于 Other 产品,如果没有 Product Type.

,数据验证将失败

这里我将包含 CommandButton1_Click(测试按钮)事件和 class 模块代码。我的数据验证是在 UserForm 模块中完成的,但我最近在阅读我 should 把它放在模型中所以我 think 我需要把它移到模块做所有其他的事情。

用户窗体代码模块 - MCVE

Option Explicit
Public DataEntryForm As New TestForm

Private Sub CommandButton1_Click()

With Me
    If .CheckBox1.Value = True Then
        DataEntryForm.TestProduct = .CheckBox1.Caption
    ElseIf .CheckBox2.Value = True Then
        DataEntryForm.TestProduct = .CheckBox2.Caption
    ElseIf .CheckBox3.Value = True Then
        DataEntryForm.TestProduct = .CheckBox3.Caption
    ElseIf .CheckBox4.Value = True Then
        DataEntryForm.TestProduct = .CheckBox4.Caption
    End If
End With

If Not FormIsComplete Then
    MsgBox "Please enter a value into each field.", vbCritical, "Missing Values"
    Exit Sub
End If

End Sub

Private Function FormIsComplete() As Boolean
FormIsComplete = False

If DataEntryForm.TestProduct = "" Then Exit Function

FormIsComplete = True

End Function

Class 模块 - TestForm (MCVE)

Private pTestProduct As String

Public Property Get TestProduct() As String
    TestProduct = pTestProduct
End Property
Public Property Let TestProduct(NewValue As String)
    pTestProduct = NewValue
End Property

所以,更具体地说;

问题出在DataEntryForm.TestProduct。它在 IsFormCompleted 函数内,因为 2/3 形式要求此 属性 具有值,但对于没有 任何产品类型的形式 自然不需要.

我的想法是简单的解决方法是为 Other Products 版本创建 另一个 单独的表格,它可以有单独的数据验证功能,但我想 try 保持可维护性并避免拥有超过 1 个这种形式。

如何让这种类型的数据验证适应以识别控件 是否应该 有值?

你的模型 class 被命名为 *Form 让我困惑了一分钟;我可能会这样命名 form(或 TestView)并使用 TestModel 作为模型 class :)

如果 view/form 的作用是呈现数据,那么模型的作用就是 数据。 TestProduct 就是这样一条数据:它的有效性是也是 可呈现的数据。您可以考虑这个 元数据 并疯狂地使用一些 TestModelValidator class 实现一些 IModelValidator 接口,可能看起来像这样:

Public Function IsValid() As Boolean
End Function

...但这可能有点矫枉过正。如果我们愿意 model 负责数据及其验证,那么模型 class 可能如下所示:

Option Explicit
Private Type TState
    ValidationErrors As Collection
    ProductName As String
    '...other state members
End Type
Private this As TState

Private Sub Class_Initialize()
    Set this.ValidationErrors = New Collection
End Sub

Public Property Get ProductName() As String
    ProductName = this.ProductName
End Property

Public Property Let ProductName(ByVal value As String)
    this.ProductName = value
End Property

'...other model properties...

Public Property Get IsValid() As Boolean

    Dim validProductName As Boolean
    validProductName = Len(this.ProductName) <> 0
    this.ValidationErrors.Remove "ProductName" '<~ NOTE air code, verify this works
    If Not validProductName Then this.ValidationErrors("ProductName") = "Product name cannot be empty"
    '...validation logic for other properties...

    IsValid = validProductName
End Property

Public Property Get ValidationErrors() As String
    ReDim result(0 To this.ValidationErrors.Count)
    Dim e As Variant, i As Long
    For Each e In this.ValidationErrors
        result(i) = e
        i = i + 1
    Next
    ValidationErrors = Join(vbNewLine, result)
End Property

现在 视图 可以操纵模型 - 而不是这里发生的事情:

Private Sub CommandButton1_Click()

With Me
    If .CheckBox1.Value = True Then
        DataEntryForm.TestProduct = .CheckBox1.Caption
    ElseIf .CheckBox2.Value = True Then
        DataEntryForm.TestProduct = .CheckBox2.Caption

而不是查询 UI,当 UI 告诉您发生了什么时, 监听 - 处理每个控件的 Change 事件,然后让模型驱动 UI:

的状态
Private Sub CheckBox1_Change()
    If Me.CheckBox1.Value Then
        Model.ProductName = Me.CheckBox1.Caption
        Validate
    End If
End Sub

Private Sub CheckBox2_Change()
    If Me.CheckBox2.Value Then
        Model.ProductName = Me.CheckBox2.Caption
        Validate
    End If
End Sub

Private Sub CodeBox_Change()
    Model.Code = CodeBox.Text
    Validate
End Sub

Private Sub DescriptionBox_Change()
    Model.Description = DescriptionBox.Text
    Validate
End Sub

Private Sub Validate()
    Dim valid As Boolean
    valid = Model.IsValid

    Me.OkButton.Enabled = valid
    Me.ValidationErrorsLabel.Caption = Model.ValidationErrors
    Me.ValidationErrorsLabel.Visible = Not valid
End Sub

希望对您有所帮助!


Edit/Addendum:使用模型状态来驱动这样的控件是否可见;你的模型 class 应该封装尽可能多的逻辑(相对于将它放在表单的代码隐藏中) - 这样你就可以轻松地针对你的模型 class 编写测试来验证和记录它的行为,每次进行可能会破坏某些内容的更改时,无需手动测试实际形式中的每个边缘情况! 换句话说,如果 view/form 需要一组供应商名称来填充组合框或创建尽可能多的复选框控件,那么模型的工作就是封装这些数据。

换句话说,如果您需要一个标志来驱动某些模型逻辑,请将该标志作为模型状态的一部分:

Private Type TState
    '...
    ProductTypes As Collection
End Type

Public Property Get HasProductTypes() As Boolean
    HasProductTypes = this.ProductTypes.Count > 0
End Property

Public Property Get ProductTypes() As Variant
    Dim result(0 To ProductTypes.Count)
    Dim pt As Variant, i As Long
    For Each pt In this.ProductTypes
        result(i) = pt
        i = i + 1
    Next
    ProductTypes = result
End Property

Public Property Get IsValid() As Boolean

    Dim validProductName As Boolean
    validProductName = Len(this.ProductName) <> 0
    this.ValidationErrors.Remove "ProductName" '<~ NOTE air code, verify this works
    If Not validProductName Then this.ValidationErrors("ProductName") = "Product name cannot be empty"
    '...validation logic for other properties...

    Dim validProductType As Boolean '<~ model is valid with an empty ProductType if there are no product types
    validProductType = IIf(HasProductTypes, Len(this.ProductType) > 0, True)

    IsValid = validProductName And validProductType
End Property