使用 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 命令按钮),它会填充以下用户窗体之一(标题对应按钮):
现在,我的数据验证适用于 NEC 和 LG 表单,但当它到达 其他形式。这是因为 NEC 和 LG 产品需要一个 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
TL;DR
我使用一个 UserForm
动态分配自己的字幕和一些具有三种不同变体的控制字幕。特别是在此用户窗体上,两个变体需要四个 CheckBox
,而在一个变体上不可见。
我的数据验证检查所有必填字段都输入了值(包括选中这四个复选框之一)所以当使用不需要勾选复选框的表单时(因为控件不可见)我是获取我的 "Please enter a value into each field." MessageBox.
我怎样才能避免这种情况?
在过去的几个月里,我一直在阅读 UserForm1.Show,这对我自己和许多其他人来说帮助我理解了用户窗体是什么以及它是如何工作的。
我正在尝试将 MVP 模式实施到我现有的项目中,该项目或多或少 只是 'completed'。
当我 运行 陷入问题或困惑时,我会自然而然地跳到 google 并且在大多数情况下要么找到另一篇文章或一个 SO 问题,作者给出的答案绰绰有余。但。我找不到用于验证可能存在或可能不存在的 MSForms.Control
的方法 - 即有时在表单上使用,具体取决于表单的变体。
请注意,我觉得我的表单设计方式可能有误(好吧,单数形式),所以如果在这种情况下,识别并涵盖该主题的答案也将是最有帮助的!
这是我的基本表单(测试按钮用于...测试):
当这 3 个按钮中的任何一个被单击时(工作表 ActiveX 命令按钮),它会填充以下用户窗体之一(标题对应按钮):
现在,我的数据验证适用于 NEC 和 LG 表单,但当它到达 其他形式。这是因为 NEC 和 LG 产品需要一个 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