Excel VBA用户窗体 – 当有东西发生变化时执行Sub

我有一个包含大量文本框的用户窗体。 当这些文本框的值发生变化时,我需要通过调用子程序AutoCalc()来根据文本框值重新计算最终结果值。

我有大约25个盒子,我不想单独添加Change()事件到每个调用该子例程的文本框。 当某个值发生变化时,调用AutoCalc()的最快和最有效的方法是什么?

这可以通过使用类模块来实现。 在下面的例子中,我将假设你已经有一个用户窗体,上面有一些文本框。

首先,在你的VBA项目中创build一个类模块(让我们称之为clsTextBox – 一定要改变类模块的'Name'属性!)

 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行创build25个事件没有问题,调用一个普通的用户窗体私人例程,除非控件的数量是dynamic的。 这是一个KISS哲学。

2)一般来说,我认为变化事件非常令人不安,因为他进行了每个数字的所有重新计算。 使用Exit事件或Before Update事件来做这件事情是比较合理和温和的,因为它仅在决定一个值时才进行重新计算。 例如, Google即时通讯让我烦恼,试图返回响应,消耗资源,而用户没有定义问题。

3)有一个validation问题。 我同意你可以用Change事件避免错误的键,但是如果你需要validation数据,你不能知道用户是否会继续input或者数据是否准备好被validation。

4)您应该记住, 更改退出事件不会强制用户在文本字段中传递,因此在尝试退出表单而不取消时需要重新validation和重新计算系统。

下面的代码对于静态表单很简单但是有效。

 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 

为了节省时间,你可以创build一个通用的VBA例程,以便以适合一个模板的forms生成与控件相关的事件的代码。 这个代码可以放在草稿中(直接生成代码比较安全,在某些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 

看看这个如何创build一个响应任何文本框中的变化的类。 这个例子是为button,但可以修改。 但是,请注意Textbox控件没有Exit事件(该事件实际上是用户窗体的一部分),因此您将不得不使用Change事件。

但是,请注意Textbox控件没有Exit事件(该事件实际上是用户窗体的一部分),因此您将不得不使用Change事件。

我很困惑。 也许这是2007年增加的,或者我不了解细微之处。 我使用TextBox控件上的Exit事件。 当我退出控件,或者在另一个控件上单击鼠标时,它会触发Exit事件。

我有一个类似的问题,我想validation大约48个不同的文本框使用一个共同的例程和类模块的方法看起来很有趣(很less重复的代码行)。 但是我不想validation每个input的字符,我只想在更新之后检查。 如果input的数据是无效的,我想清除文本框,并保持在相同的文本框,这需要在退出程序中使用取消= True。 经过几个小时的尝试,并没有我的AfterUpdate和退出事件处理程序从来没有触发我发现了为什么。

如果你创build一个类如下:

 Private WithEvents MyTextBox As MSForms.TextBox Public Property Set** Control(tb As MSForms.TextBox) Set MyTextBox = tb End Property 

然后进入VBE对象浏览器并selectMyTextBox,您将看到支持的枚举事件不包括AfterUpdate或Exit。 如果进入UserForm并使用VBE对象浏览器查看TextBox的实例,但这些事件似乎是从TextBox所属的控件inheritance的,则这些事件可用。 使用MSForms.TextBox定义一个新类不包括这些事件。 如果你试图手动定义这些事件处理程序,它们将被编译,看起来他们会工作(但他们不)。 而不是成为类对象的事件处理程序,它们将只是在VBE对象浏览器下显示在(General)中的私有子例程,并且永远不会被执行。 看来创build有效事件处理程序的唯一方法是在VBE对象浏览器中select类对象,然后从枚举事件列表中select所需的事件。

经过许多小时的search之后,我一直无法find任何引用来展示如何在私有类中构造一个类似的inheritance模型,所以AfterUpdate和Exit将显示为创build的类的可用事件。 因此,对UserForm上的每个TextBox具有一个单独的事件处理程序的build议(上面)可能是唯一的方法,如果要使用AfterUpdate和/或Exit,将会工作。

所以在论坛上给我的第一个9行我不记得在哪里。 但我build立在这个,现在我想用一个命令button重新计算,如果使用更改此子列出的variables。

 <pre><code>'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'</pre></code>