Excel VBA Userform-выполнить Sub, когда что-то меняется

У меня есть пользовательская форма, содержащая много текстовых полей. Когда значения этих текстовых полей изменяются, мне нужно пересчитать значения конечного результата на основе значений textbox, вызвав подпрограмму AutoCalc ().

У меня есть около 25 ящиков, и я не хочу добавлять событие Change() индивидуально в каждое текстовое поле, вызывающее указанную подпрограмму. Каков самый быстрый и эффективный способ вызова AutoCalc () при изменении некоторого значения?

6 ответов


Это может быть достигнуто с помощью модуль класс. В следующем примере я предположу, что у вас уже есть пользовательская форма с некоторыми текстовыми полями.

во-первых, создайте модуль класса в своем проекте 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

Теперь в пользовательской форме добавьте следующий код:

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 событий с 1 строкой, вызывая общую частную процедуру userform, если количество элементов управления не является динамическим. Это поцелуй философия.

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

посмотри этой для создания класса, который реагирует на изменение в любом текстовом поле. Пример для кнопок, но может быть изменен. Однако имейте в виду, что элементы управления Textbox не имеют события Exit (это событие фактически является частью userform), поэтому вам действительно придется использовать событие Change.


однако имейте в виду, что элементы управления Textbox не имеют события Exit (это событие фактически является частью userform), поэтому вам действительно придется использовать событие Change.

Я в замешательстве. Возможно, это было добавлено в 2007 году, или, возможно, я не понимаю нюансов. Я использую событие Exit для элементов управления TextBox. Когда я выхожу из элемента управления или щелкаю мышью на другом элементе управления, он запускает событие выхода.


У меня была аналогичная проблема, когда я хочу проверить приблизительно 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, но они, похоже, наследуются от элементов управления, которые текстовое поле является частью. Определение нового класса с помощью MSForms.TextBox не включает эти события. Если вы попытаетесь определить эти обработчики событий вручную, они будут компилироваться, и кажется, что они будут работать (но они этого не делают). Вместо того, чтобы стать обработчиками событий объекта класса, они будут просто частными подпрограммами, которые отображаются в (общем) в браузере объектов VBE и никогда не выполняются. Похоже, единственный способ, чтобы создать обработчик событий, выберите объект класса в программу обозреватель объектов, а затем выберите нужное событие из списка перечисленных событий.

после многих часов поиска я не смог найти ссылок, чтобы показать, как аналогичная модель наследования может быть построена в частном классе, поэтому AfterUpdate и Exit будут отображаться как доступные события для созданных классов. Таким образом, рекомендация (выше) иметь отдельный обработчик событий для каждого текстового поля в пользовательской форме может быть единственным подходом, который будет работать, если вы хотите использовать AfterUpdate и / или выход.


Итак, первые 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>