たくさんのテキストボックスを含むユーザーフォームがあります。これらのテキストボックスの値が変更されるたびに、サブルーチンAutoCalc()を呼び出して、テキストボックスの値に基づいて最終結果の値を再計算する必要があります。
約25個のボックスがあり、上記のサブルーチンを呼び出す各テキストボックスにChange()イベントを個別に追加したくありません。値が変更されるたびにAutoCalc()を呼び出す最も迅速で効率的な方法は何ですか?
これは、クラスモジュールを使用して実現できます。次の例では、いくつかのテキストボックスを含むユーザーフォームがすでにあると想定します。
まず、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)コントロールの数が動的でない限り、共通のユーザーフォームプライベートルーチンを呼び出して、1行で25のイベントを作成しても問題はありません。それは[〜#〜] kiss [〜#〜]哲学です。
2)一般的に、Changeイベントは、入力された各桁のすべての再計算を行うため、非常に厄介だと思います。 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
テキストボックスの変更に応答するクラスを作成する方法については、 this を参照してください。例はボタン用ですが、変更できます。ただし、TextboxコントロールにはExitイベントがないことに注意してください(このイベントは実際にはユーザーフォームの一部です)。そのため、実際にはChangeイベントを使用する必要があります。
共通のルーチンを使用して約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が含まれていないことがわかります。これらのイベントは、ユーザーフォームに移動してVBEオブジェクトブラウザーを使用し、TextBoxのインスタンスを確認すると利用できますが、TextBoxが含まれているコントロールから継承されているように見えます。 MSForms.TextBoxを使用して新しいクラスを定義する場合、これらのイベントは含まれません。これらのイベントハンドラーを手動で定義しようとすると、コンパイルされ、機能するように見えます(ただし、機能しません)。クラスオブジェクトのイベントハンドラになる代わりに、VBEオブジェクトブラウザの下の(一般)に表示され、実行されることのないプライベートサブルーチンになります。有効なイベントハンドラーを作成する唯一の方法は、VBEオブジェクトブラウザーでクラスオブジェクトを選択し、列挙されたイベントリストから目的のイベントを選択することです。
何時間も検索した後、プライベートクラス内で同様の継承モデルを構築する方法を示す参照を見つけることができなかったため、AfterUpdateとExitは作成されたクラスで使用可能なイベントとして表示されます。したがって、ユーザーフォームのテキストボックスごとに個別のイベントハンドラーを使用することをお勧めします(上記)が、AfterUpdateやExitを使用する場合に機能する唯一のアプローチである可能性があります。
ただし、TextboxコントロールにはExitイベントがないことに注意してください(このイベントは実際にはユーザーフォームの一部です)。そのため、実際にはChangeイベントを使用する必要があります。
よくわかりません。おそらくこれは2007年に追加されたか、あるいは微妙な違いがわかりません。 TextBoxコントロールでExitイベントを使用します。コントロールからTabキーで移動するか、別のコントロールをマウスでクリックすると、Exitイベントがトリガーされます。
ですから、フォーラムで私に与えられた最初の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>
イベントリスナーをユーザーフォームに追加する非常に簡単な方法を作成しました。さらに、MouseOverやMouseOutなどのイベントを追加します。 (ホバー効果を行うためのクール)
動作するためにインポートする必要がある2つのクラスモジュールは、私の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 ページをチェックして、プルリクエストを行ってください。