我有一个包含大量文本框的用户表单。当这些文本框的值发生变化时,我需要通过调用子例程 AutoCalc() 根据文本框的值重新计算最终结果值。
我有大约 25 个框,我不想将 Change() 事件单独添加到调用所述子例程的每个文本框。每当某些值发生变化时,调用 AutoCalc() 的最快和最有效的方法是什么?
我有一个包含大量文本框的用户表单。当这些文本框的值发生变化时,我需要通过调用子例程 AutoCalc() 根据文本框的值重新计算最终结果值。
我有大约 25 个框,我不想将 Change() 事件单独添加到调用所述子例程的每个文本框。每当某些值发生变化时,调用 AutoCalc() 的最快和最有效的方法是什么?
这可以通过使用类模块来实现。在下面的示例中,我将假设您已经有一个带有一些文本框的用户表单。
首先,在您的 VBA 项目中创建一个类模块(让我们调用它clsTextBox
——确保更改类模块的“名称”属性!)
Private WithEvents MyTextBox As MSForms.TextBox
Public Property Set Control(tb As MSForms.TextBox)
Set MyTextBox = tb
End Property
Private Sub MyTextBox_Change()
AutoCalc() //call your AutoCalc sub / function whenever textbox changes
End Sub
现在,在用户表单中,添加以下代码:
Dim tbCollection As Collection
Private Sub UserForm_Initialize()
Dim ctrl As MSForms.Control
Dim obj As clsTextBox
Set tbCollection = New Collection
For Each ctrl In Me.Controls
If TypeOf ctrl Is MSForms.TextBox Then
Set obj = New clsTextBox
Set obj.Control = ctrl
tbCollection.Add obj
End If
Next ctrl
Set obj = Nothing
End Sub
正如上面的答案所暗示的那样,类使用是一种以简洁优雅的方式处理许多控件的好策略,但是:
1)我认为用 1 行创建 25 个事件没有问题,调用一个常见的用户窗体私有例程,除非控件的数量是动态的。这是KISS哲学。
2) 一般来说,我认为Change事件非常令人不安,因为他会重新计算每个输入的数字。使用Exit事件或Before Update事件执行此操作更为明智和适度,因为它仅在确定值时才进行重新计算。例如,Google Instant惹恼了我试图返回响应,消耗资源,而用户没有定义问题。
3) 存在验证问题。我同意您可以通过Change事件避免错误的键,但是如果您需要验证数据,您无法知道用户是否会继续输入或数据是否已准备好进行验证。
4)您应该记住,更改或退出事件不会强制用户传入文本字段,因此在尝试退出表单而不取消时,系统需要重新验证和重新计算。
下面的代码很简单,但对静态表单很有效。
Private Sub TextBox1_Exit(ByVal Cancel As MSForms.ReturnBoolean)
Call AutoCalc(Cancel)
End Sub
Private Sub TextBox2_Exit(ByVal Cancel As MSForms.ReturnBoolean)
Call AutoCalc(Cancel)
End Sub
.....
Private Sub TextBox25_Exit(ByVal Cancel As MSForms.ReturnBoolean)
Call AutoCalc(Cancel)
End Sub
Private Function Valid
.....
End Function
Private Sub AutoCalc(Canc As Variant)
If Not Valid() Then Canc=True
' Calculation
End Sub
如果您沉迷于节省时间,您可以创建一个通用 VBA 例程,以便以适合掩码的形式为与控件相关的事件生成代码。此代码可以在草稿表中(直接生成代码更安全,在某些 Excel 版本中存在错误),而不是复制并粘贴到表单模块中。
Sub GenerateEvent(Form As String, Mask As String, _
Evento As String, Code As String)
' Form - Form name in active workbook
' Mark - String piece inside control name
' Evento - Event name to form procedure name
' Code - Code line inside event
Dim F As Object
Dim I As Integer
Dim L As Long
Dim R As Range
Dim Off As Long
Set F = ThisWorkbook.VBProject.VBComponents(Form)
Set R = ActiveCell ' Destination code
Off = 0
For I = 0 To F.Designer.Controls.Count - 1
If F.Designer.Controls(I).Name Like "*" & Mask & "*" Then
R.Offset(Off, 0) = "Private Sub " & _
F.Designer.Controls(I).Name & "_" & Evento & "()"
R.Offset(Off + 1, 0) = " " & Code
R.Offset(Off + 2, 0) = "End Sub"
Off = Off + 4
End If
Next I
End Sub
Sub Test()
Call GenerateEvent("FServCons", "tDt", "Exit", _
"Call AtuaCalc(Cancel)")
End Sub
看一下如何创建一个响应任何文本框中更改的类。该示例用于按钮,但可以修改。但是,请注意文本框控件没有 Exit 事件(该事件实际上是用户窗体的一部分),因此您确实必须使用 Change 事件。
我有一个类似的问题,我想使用一个通用例程验证大约 48 个不同的文本框,并且类模块方法看起来很有趣(重复的代码行少得多)。但我不想验证输入的每个字符,我只想在更新后检查。如果输入的数据无效,我想清除文本框并留在同一个文本框中,这需要在退出例程中使用 Cancel = True。经过几个小时的尝试并且没有我的 AfterUpdate 和 Exit 事件处理程序从未触发我发现了原因。
如果您创建一个如下所示的类:
Private WithEvents MyTextBox As MSForms.TextBox
Public Property Set** Control(tb As MSForms.TextBox)
Set MyTextBox = tb
End Property
然后你进入 VBE 对象浏览器并选择 MyTextBox,你会看到支持的枚举事件不包括 AfterUpdate 或 Exit。如果您进入 UserForm 并使用 VBE 对象浏览器并查看 TextBox 的实例,则这些事件可用,但它们似乎是从 TextBox 所属的控件继承的。使用 MSForms.TextBox 定义新类不包括这些事件。如果您尝试手动定义这些事件处理程序,它们将编译并且看起来它们会工作(但它们不会)。它们不会成为类对象的事件处理程序,而只是显示在 VBE 对象浏览器下的 (General) 中的私有子例程,并且永远不会被执行。
经过数小时的搜索,我无法找到任何参考资料来说明如何在私有类中构建类似的继承模型,因此 AfterUpdate 和 Exit 将显示为已创建类的可用事件。因此,如果您想使用 AfterUpdate 和/或 Exit,建议(上面)为 UserForm 上的每个 TextBox 设置一个单独的事件处理程序,这可能是唯一可行的方法。
但是,请注意文本框控件没有 Exit 事件(该事件实际上是用户窗体的一部分),因此您确实必须使用 Change 事件。
我很困惑。也许这是在 2007 年添加的,或者我不明白其中的细微差别。我在 TextBox 控件上使用 Exit 事件。当我跳出控件时,或者在另一个控件上单击鼠标时,它会触发 Exit 事件。
我创建了一种非常简单的方法来将事件侦听器添加到用户窗体。此外,它还添加了诸如 MouseOver 和 MouseOut 之类的事件。(做悬停效果很酷)
需要导入才能工作的两个类模块可以在我的 Github 页面VBA Userform Event Listeners上找到
上手很容易,一旦添加了我的类模块,只需将下面的示例代码添加到用户窗体中。
Private WithEvents Emitter As EventListnerEmitter
Private Sub UserForm_Activate()
Set Emitter = New EventListnerEmitter
Emitter.AddEventListnerAll Me
End Sub
就是这样! 现在你可以开始监听不同的事件了。
有主要事件EmittedEvent。这将传入事件所在的控件和事件名称。所以所有事件都通过这个事件处理程序。
Private Sub Emitter_EmittedEvent(Control As Object, ByVal EventName As String, EventValue As Variant)
If TypeName(Control) = "Textbox" And EventName = "Change" Then
'DO WHATEVER
End If
End Sub
您也可以只监听特定事件。o 在这种情况下,更改事件。
Private Sub Emitter_Change(Control As Object)
If TypeName(Control) = "Textbox" Then
'DO WHATEVER
End If
End Sub
请随时查看我的Github页面并提出拉取请求,因为尚未捕获所有事件。
所以前9行是在论坛中给我的,我不记得在哪里。但是我以此为基础,现在我想使用命令按钮重新计算使用是否更改了此子中列出的变量。
Private Sub txtWorked_Exit(ByVal Cancel As MSForms.ReturnBoolean)
11 Dim OTRate As Double
OTRate = Me.txtHourlyRate * 1.5
If Me.txtWorked > 40 Then
Me.txtBasePay.Value = Format(Me.txtHourlyRate.Value * 40, "$#,##0.00")
Me.txtOvertime = Format((Me.txtWorked - 40) * OTRate, "$#,##0.00")
Else
Me.txtOvertime.Value = "0"
Me.txtBasePay.Value = Format(Me.txtHourlyRate.Value * Me.txtWorked.Value, "$#,##0.00")
End If
Dim Gross, W2, MASSTax, FICA, Medi, Total, Depends, Feds As Double
Gross = CDbl(txtBonus.Value) + CDbl(txtBasePay.Value) + CDbl(txtOvertime.Value)
W2 = txtClaim * 19
Me.txtGrossPay.Value = Format(Gross, "$#,##0.00")
FICA = Gross * 0.062
Me.txtFICA.Value = Format(FICA, "$#,##0.00")
Medi = Gross * 0.0145
Me.txtMedicare.Value = Format(Medi, "$#,##0.00")
MASSTax = (Gross - (FICA + Medi) - (W2 + 66)) * 0.0545
If chkMassTax = True Then
Me.txtMATax.Value = Format(MASSTax, "$#,##0.00")
Else: Me.txtMATax.Value = "0.00"
End If
If Me.txtClaim.Value = 1 Then
Depends = 76.8
ElseIf Me.txtClaim.Value = 2 Then
Depends = 153.8
ElseIf Me.txtClaim.Value = 3 Then
Depends = 230.7
Else
Depends = 0
End If
If (Gross - Depends) < 765 Then
Feds = ((((Gross - Depends) - 222) * 0.15) + 17.8)
Me.txtFedIncome.Value = Format(Feds, "$#,##.00")
ElseIf (Gross - Depends) > 764 Then
Feds = ((((Gross - Depends) - 764) * 0.25) + 99.1)
Me.txtFedIncome.Value = Format(Feds, "$#,##.00")
Else:
Feds = 0
End If
Total = (txtMATax) + (FICA) + (Medi) + (txtAdditional) + (Feds)
Me.txtTotal.Value = Format(Total, "$#,##0.00")
Me.txtNetPay.Value = Format(Gross - Total, "$#,##0.00")
End Sub
Private Sub cmdReCalculate_Click()
End Sub