Excel VBA Userform - 当某些内容发生变化时执行Sub

时间:2011-05-09 17:53:12

标签: excel vba excel-vba excel-2007 userform

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

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

7 个答案:

答案 0 :(得分:14)

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

首先,在VBA项目中创建一个类模块(让我们调用它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

现在,在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 :(得分:6)

如上面的答案所示,课堂使用是一种以简洁优雅的方式处理许多控件的好策略,但是:

1)我发现使用1行创建25个事件没有问题,调用一个常见的userform私有例程,除非控件的数量是动态的。这是 KISS 理念。

2)一般来说,我认为更改事件非常令人不安,因为他会对每个输入的数字进行所有重新计算。使用退出事件或更新前事件更为明智和适度,因为它仅在决定值时才进行重新计算。例如, Google Instant 让我感到烦恼,试图在没有用户定义问题的情况下返回响应,消耗资源。

3)存在验证问题。我同意您可以通过更改事件避免错误的密钥,但是如果您需要验证数据,则无法知道用户是否会继续输入或者数据是否已准备好进行验证。

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

答案 2 :(得分:5)

查看this,了解如何创建一个响应任何文本框中的更改的类。该示例适用于按钮,但可以进行修改。但是,请注意Textbox控件没有Exit事件(该事件实际上是userform的一部分),所以你真的必须使用Change事件。

答案 3 :(得分:0)

  

但是,请注意,Textbox控件没有Exit事件(该事件实际上是userform的一部分),所以你真的必须使用Change事件。

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

答案 4 :(得分:0)

我遇到了类似的问题,我希望使用常规例程验证大约48个不同的文本框,并且类模块方法看起来很有趣(重复的代码行少很多)。但我不想验证输入的每个字符,我只想在更新后检查。如果输入的数据无效,我想清除文本框并保持在同一文本框中,这需要在Exit例程中使用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将显示为已创建类的可用事件。因此,对于UserForm上的每个TextBox具有单独的事件处理程序的建议(上文)可能是唯一可用于AfterUpdate和/或Exit的方法。

答案 5 :(得分:0)

所以在论坛中给我的前9行我记不起来了。但我建立在此基础上,现在我想使用命令按钮重新计算是否使用更改此子中列出的变量。

<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>

答案 6 :(得分:0)

两类模块方法

我创建了一种非常简单的方法来将事件侦听器添加到用户窗体。此外,它添加了诸如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页并提出拉取请求,因为尚未捕获所有事件。