皆さんこんにちは!!
今日は、「ユーザーフォーム」のコントロールをクラスを使用してメンテナンス性に優れたコードを書く方法を書いていきたいと思います。
以前、下記の記事で、ユーザーフォームへ動的にコントロールを追加する方法を書きました。

この記事でもクラスを利用して、ボタンコントロールを動的に扱っていました。
動的にコントロールを追加できると、例えば売上伝票の明細行の様なもの(明細行が何行必要か不明)を扱う際に、動的に明細行を追加したり出来ます。
最初から必要な分を用意しておいても良いのですが、「必要な時に必要なだけ用意」できた方が、ユーザーは使い勝手が良いですよね!!
今回は、クラスとユーザーフォームを使用して、次の内容をやってみたいと思います。
- テキストボックスをクラス化し、入力制限をかける
- オプションボタンのクリックイベントをグループ化してまとめる
- イベントを作成して、フォーカスの移動情報を扱う
3つ目の「イベントを作成」については、こちらの記事で簡単に解説しています。
VBAで自作イベントをハンドルする方法は、結構高度な技術で書籍でも殆ど扱われていません。

是非、イベントの自作をマスターして下さい。
ExcelVBAで出来ることの幅が、とても広くなりますよ!!
もくじ
テキストボックスをクラス化し、入力制限をかける
それでは、一つずつやっていきましょう!!
ユーザーフォームを挿入して次の様にコントロールを配置して下さい。
※テキストボックスの名称は、デフォルトの「TextBox~」のままで良いです。
テキストボックスが5つ並んだユーザーフォームです。
クラスモジュールの作成
クラスモジュールを一つ挿入し、次のコードを記述してください。
※クラスの名前は、デフォルトのClass1で構いません。
Option Explicit
Private WithEvents myTb As MSForms.TextBox
Private myCkType As Long
Public Property Set Tb(setTb As MSForms.TextBox)
Set myTb = setTb
End Property
Public Property Get Tb() As MSForms.TextBox
End Property
Public Property Let ck(setCk As Long)
myCkType = setCk
End Property
Public Property Get ck() As Long
End Property
Private Sub myTb_KeyPress(ByVal KeyAscii As MSForms.ReturnInteger)
Select Case myCkType
Case 1
'数字以外の入力を拒否
If KeyAscii < Asc("0") Or KeyAscii > Asc("9") Then
KeyAscii = 0: Beep
End If
Case 2
'数字,"-"以外の入力を拒否
If (KeyAscii >= Asc("0") And _
KeyAscii <= Asc("9")) Or KeyAscii = Asc("-") Then
Else
KeyAscii = 0: Beep
End If
Case 3
'数字,"英大文字"以外の入力を拒否
If (KeyAscii >= Asc("0") And KeyAscii <= Asc("9")) Or _
(KeyAscii >= Asc("A") And KeyAscii <= Asc("Z")) Then
Else
KeyAscii = 0: Beep
End If
End Select
End Sub
ポイントは『Private WithEvents myTb As MSForms.TextBox』でWithEventsを使用してTextBox型のオブジェクトを設定しているところです。
Propertyプロシージャは簡単に言うと、オブジェクトのプロパティを設定したり、使用したりします。
オブジェクト指向のプログラムではカプセル化と言い、クラスの外部からオブジェクトのプロパティを直接操作させないようにしています。
プロパティの設定時に値をチェックしたり出来るので、使える様になるとFunctionプロシージャと同じように使用できます。
詳しくは、以前の記事をご覧ください。
『myTb_KeyPress』イベントは、クラスのプロパティの「myCkType」に設定された値によって、入力の可否を分けているだけです。
myCkTypeは、インスタンス化する際に設定します。
ユーザーフォームのコードを作成
クラスだけでは、イメージが湧きませんよね。
ユーザーフォームのコードを記述していきます。
ユーザーフォームのイニシャライズイベントを作成してください。
Option Explicit
Dim myTb() As Class1
Private Sub UserForm_Initialize()
Dim i As Long
ReDim myTb(1 To 5)
For i = 1 To 5
Set myTb(i) = New Class1
Set myTb(i).Tb = Me.Controls("TextBox" & CStr(i))
Next
myTb(1).ck = 1
myTb(2).ck = 2
myTb(3).ck = 3
myTb(4).ck = 2
myTb(5).ck = 1
End Sub
ついでに標準モジュールも一つ挿入し下記コードを記述してください。
Sub show()
UserForm1.Show
End Sub
標準モジュールの『show()』メソッドを実行し、フォームのテキストボックスへ色々、入力してみてください。
いかがでしたか?
期待通りの入力制御が掛かっているかと思います。
「Dim myTb() As Class1」と動的配列型の変数を定義し、イニシャライズイベントのループの中で「Set myTb(i) = New Class1」とクラスをインスタンス化します。
あとは、「myTb(1).ck = 1」の様にクラスの「myCkType」プロパティへ値を代入しています。
クラスの部分で解説しましたが、myCkTypeはプライベート変数なので、Propertyプロシージャを利用して、代入しています。
あんまり知らなくても良いのですが、PropertyプロシージャのLetというのは、オブジェクト型では無い変数の代入に使うキーワードです。
通常の変数宣言では、Letを省略できるので、あまり見かける機会がないかと思います。
オプションボタンのクリックイベントをグループ化してまとめる
続いては、【オプションボタンのクリックイベントをグループ化してまとめる】です。
これは、もしかしたら以前の「VBA:ユーザーフォームへ動的にコントロールを追加する」を読まれた方は、作れるかもしれません。
基本はボタンでもオプションボタンでも同じです。
それでは、作っていきましょう!!
ユーザーフォームを挿入して次の様にコントロールを配置して下さい。
※オプションボタンの名称は、デフォルトの「OptionButton~」のままで良いです。
5つオプションボタンを並べ、テキストボックスを一つ用意します。
クラスモジュールの作成
クラスを一つ挿入し、次のコードを記述してください。
Option Explicit
Private WithEvents myOpt As MSForms.OptionButton
Private myRtnTb As MSForms.TextBox
Public Property Set Opt(setOpt As MSForms.OptionButton)
Set myOpt = setOpt
End Property
Public Property Get Opt() As MSForms.OptionButton
End Property
Public Property Set Tb(setTb As MSForms.TextBox)
Set myRtnTb = setTb
End Property
Public Property Get Tb() As MSForms.TextBox
End Property
Private Sub myOpt_Click()
myRtnTb.Value = myOpt.Name
End Sub
基本的に同じなので、解説は省略します。
ユーザーフォームのコードを作成
Option Explicit
Dim myOpt() As Class1
Private Sub UserForm_Initialize()
Dim i As Long
ReDim myOpt(1 To 5)
For i = 1 To 5
Set myOpt(i) = New Class1
Set myOpt(i).Opt = Me.Controls("OptionButton" & CStr(i))
Set myOpt(i).Tb = TextBox1
Next
End Sub
さっきと同じですね!!
動的配列型の変数へクラスのインスタンスを代入してクラスのイベントを使用できるようにしています。
標準モジュールや、実行方法は先ほどと同じなので省略します。
イベントを作成してフォーカスの移動情報を扱う
最後のサンプルは「イベントを作成して、フォーカスの移動情報を扱う」です。
ここまで見てきたように基本はどれも同じです。
形を覚えることで実務でコーディングを行う際に、「こんな機能があったな。」くらいに思い出していただければ、大丈夫です。
さあ、始めましょう。
図のようなフォームを作成してください。
※コントロールは何でも良いです。複数個コントロールがあれば今回のサンプルは確認できます。
なんかごちゃごちゃしていますが、取り合えず適当にコントロールを配置しただけです。
皆さんも適当に配置してください。
クラスモジュールの作成
クラスを一つ挿入し、次のコードを記述してください。
Option Explicit
Private RunFlg As Boolean
Private ExitControl As String
Private EnterControl As String
Public Event MoveFocus(ExitControl As String, EnterControl As String)
Public Property Get RFlg() As Boolean
RFlg = RunFlg
End Property
Public Property Let RFlg(myFlg As Boolean)
RunFlg = myFlg
End Property
Public Sub Actck(myForm As MSForms.UserForm)
ExitControl = ActCntck(myForm)
On Error GoTo err
Do While RunFlg
DoEvents
EnterControl = ActCntck(myForm)
If EnterControl = "" Then GoTo errlabel
If ExitControl <> EnterControl Then
myForm.Controls(EnterControl).SetFocus
RaiseEvent MoveFocus(ExitControl, EnterControl)
ExitControl = EnterControl
End If
Loop
err:
Exit Sub
End Sub
Private Function ActCntck(myForm As MSForms.UserForm) As String
Dim myCnt1 As MSForms.Control
Dim myCnt2 As MSForms.Control
On Error Resume Next
Set myCnt1 = myForm.ActiveControl
On Error GoTo 0
If myCnt1 Is Nothing Then GoTo errlabel
Select Case TypeName(myCnt1)
Case "MultiPage" ', "TabStrip"
Set myCnt1 = myCnt1.SelectedItem
End Select
Do
Set myCnt2 = Nothing
On Error Resume Next
Set myCnt2 = myCnt1.ActiveControl
On Error GoTo 0
If myCnt2 Is Nothing Then Exit Do
Set myCnt1 = myCnt2
Loop Until myCnt2 Is Nothing
ActCntck = myCnt1.Name
Exit Function
err:
ActCntck = ""
Exit Function
End Function
ちょっとコードは長いですが、頑張ってください。
基本は同じです。解説はフォームのコードを記述した後に行います。
ユーザーフォームのコードを作成
ユーザーフォームへ下記のコードを記述してください。
Option Explicit
Private WithEvents myForm As Class1
Private Sub UserForm_Initialize()
Set myForm = New Class1
End Sub
Private Sub UserForm_Activate()
myForm.RFlg = True
myForm.Actck Me
End Sub
Private Sub myForm_MoveFocus(ExitControl As String, EnterControl As String)
MsgBox ExitControl & "→" & EnterControl
End Sub
Private Sub UserForm_Deactivate()
myForm.RFlg = False
End Sub
Private Sub UserForm_Terminate()
myForm.RFlg = False
Set myForm = Nothing
End Sub
ユーザーフォームのコードはスッキリしてますね。
恐らく分からない部分はないのではないでしょうか。
クラスの解説
今回のサンプルが先ほどまでのコードと違うところは、『コントロールをクラス化してもEnterとExitイベントが用意されていない。』というところです。
イベントが無ければ自分でイベントを作れば良いので、クラス内で無限ループを発生させ、アクティブなコントロールを常に監視しておきます。
Do
Set myCnt2 = Nothing
On Error Resume Next
Set myCnt2 = myCnt1.ActiveControl
On Error GoTo 0
If myCnt2 Is Nothing Then Exit Do
Set myCnt1 = myCnt2
Loop Until myCnt2 Is Nothing
アクティブコントロールが移動したタイミングで、移動前と移動後のコントロールを引数としたフォームのイベント(Public Event MoveFocus(ExitControl As String, EnterControl As String))を発生させています。
コントロール毎に書いていたEnterとExitが一つのイベントコードで処理出来るようになるので、メンテナンスが簡単になります。
コード量もグッと減りますね!!
まとめ
お疲れ様でした。
今回の記事はいかがでしたか?分からない部分があれば、コメントいただければ、すぐお答えします。
ちょっと難しい内容ですが、実際の業務で使用するユーザーフォームはコントロール数が多くなりやすく、イベント処理だけで膨大なコード量になります。
クラス化して処理をひとまとめにすることで、大幅なコードの簡略化が図れると共に、変化に強いプログラムが作成できます。
お気づきの方もおられるかと思いますが、フォーム自体のコードはとてもシンプルになっています。
最後のサンプルを全てフォームのイベントで書いたら大変な事になりますよね(笑)
ExcelVBAは使い捨てのコードの様に考え、ロクなリファクタリングを行わない方もいますが、Excelを操作するならVBAに勝るものはありません。
皆さんも自分の書いたコードを常に改善点が無いか見る癖を付けると良いかと思います。
最後までお読みいただきありがとうございました!!