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>