Excel VBA Userform-在发生更改时执行Sub

Excel VBA Userform-在发生更改时执行Sub,vba,excel,excel-2007,userform,Vba,Excel,Excel 2007,Userform,我有一个包含很多文本框的用户表单。当这些文本框的值发生变化时,我需要通过调用子例程AutoCalc()根据文本框的值重新计算最终结果值 我有大约25个框,我不想在调用上述子例程的每个文本框中单独添加Change()事件。每当某些值发生更改时,调用AutoCalc()的最快捷有效方法是什么?了解如何创建一个类来响应任何文本框中的更改。该示例适用于按钮,但可以修改。但是,请注意Textbox控件没有退出事件(该事件实际上是userform的一部分),因此您必须使用更改事件。这可以通过使用类模块来实现

我有一个包含很多文本框的用户表单。当这些文本框的值发生变化时,我需要通过调用子例程AutoCalc()根据文本框的值重新计算最终结果值


我有大约25个框,我不想在调用上述子例程的每个文本框中单独添加Change()事件。每当某些值发生更改时,调用AutoCalc()的最快捷有效方法是什么?

了解如何创建一个类来响应任何文本框中的更改。该示例适用于按钮,但可以修改。但是,请注意Textbox控件没有退出事件(该事件实际上是userform的一部分),因此您必须使用更改事件。

这可以通过使用类模块来实现。在下面的示例中,我假设您已经有了一个带有一些文本框的userform

首先,在VBA项目中创建一个类模块(将其称为
clsTextBox
——确保更改类模块的“Name”属性!)

现在,在userform中,添加以下代码:

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) 我认为用一行代码创建25个事件,调用一个公共userform私有例程没有问题,除非控件的数量是动态的。这是一个吻哲学

2)一般来说,我认为<强>变化>强>事件非常令人不安,因为他完成了每一个数字的重新计算。使用Exit事件或Before Update事件执行此操作更为明智和适度,因为它仅在确定值时进行重新计算。例如,googleinstant让我很恼火,我试图在用户没有定义问题的情况下返回回复,消耗资源

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
但是,请注意Textbox控件没有退出事件(该事件实际上是userform的一部分),因此您必须使用Change事件


我很困惑。也许这是在2007年添加的,或者我不理解其中的细微差别。我在TextBox控件上使用退出事件。当我将Tab键移出控件,或在另一个控件上单击鼠标时,它会触发退出事件。

我遇到了一个类似的问题,我想使用一个通用例程验证大约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对象浏览器下的私有子例程,永远不会执行。创建有效事件处理程序的唯一方法似乎是在VBE对象浏览器中选择类对象,然后从枚举事件列表中选择所需的事件


经过数小时的搜索,我找不到任何引用来说明如何在私有类中构造类似的继承模型,因此AfterUpdate和Exit将显示为所创建类的可用事件。因此,如果您想使用AfterUpdate和/或Exit,那么为用户表单上的每个文本框提供一个单独的事件处理程序的建议(如上)可能是唯一可行的方法。

因此,在论坛中给我的前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
Private Sub txtwork\u退出(ByVal Cancel As MSForms.ReturnBoolean)
11分光光度计为双色
OTRate=Me.txtHourlyRate*1.5
如果Me.txt工作时间>40,则
Me.txtBasePay.Value=格式(Me.txtHourlyRate.Value*40,“$”、“$”0.00”)
Me.txt加班=格式((Me.txtWorked-40)*OTRate,“$”和“$”0.00”)
其他的
Me.txtowards.Value=“0”
Me.txtBasePay.Value=格式(Me.txtHourlyRate.Value*Me.txtWorked.Value,“$”、“$”0.00”)
如果结束
Dim Gross,W2,MASSTax,FICA,Medi,Total,视情况而定,联邦调查局双倍
格罗斯
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