it-swarm.com.ru

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

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

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

7
Ashok

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

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

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

1) Я не вижу проблем в создании 25 событий с одной строкой, вызывающих обычную пользовательскую частную процедуру, если количество элементов управления не является динамическим. Это ПОЦЕЛУЙ философия.

2) Как правило, я считаю событие Изменить очень тревожным, поскольку он выполняет весь пересчет каждой введенной цифры. Это более разумно и умеренно делать это, используя событие Exit или Before Update, потому что оно производит пересчет только при выборе значения. Например, Google Instant раздражает меня, пытаясь вернуть ответы, потребляя ресурсы, но пользователь не определил вопрос.

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

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

Следующий код прост, но эффективен для статических форм.

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
6
Paulo Buchsbaum

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

5
Doug Glancy

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

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

0
BiggerDon

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

У меня была похожая проблема, когда я хотел проверить приблизительно 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
user3164282