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

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

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

Ответы [ 7 ]

14 голосов
/ 10 мая 2011

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

Во-первых, создайте модуль класса в вашем проекте VBA (пусть он называется clsTextBox - обязательно изменитеСвойство «Имя» модуля класса!)

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
6 голосов
/ 15 ноября 2012

Использование класса, как следует из приведенного выше ответа, является хорошей стратегией для краткого и элегантного обращения со многими элементами управления:

1) Я не вижу проблем в создании 25 событий с 1линия, вызывающая частную процедуру общей пользовательской формы, если количество элементов управления не является динамическим.Это философия 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
5 голосов
/ 10 мая 2011

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

0 голосов
/ 16 марта 2019

Метод двухклассного модуля

Я создал очень простой способ добавления слушателей событий в форму пользователя. Кроме того, он добавляет такие события, как MouseOver и MouseOut. (Круто для выполнения эффектов при наведении)

Два модуля класса, которые необходимо импортировать для работы, можно найти на моей странице Github Прослушиватели событий пользовательской формы VBA


Как использовать код в пользовательской форме

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

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 и сделать запрос на извлечение, поскольку еще не все события регистрируются.

0 голосов
/ 20 ноября 2015

Итак, первые 9 строк, которые мне дали на форуме, я не могу вспомнить, где. Но я основывался на этом, и теперь я хотел бы использовать командную кнопку для пересчета, если использование изменит переменную, перечисленную в этом подпункте.

<code><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'
0 голосов
/ 06 января 2014

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

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

0 голосов
/ 06 декабря 2013

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

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

...