☆Yuus Memo☆
非エンジニアの方でも業務を効率化できるプログラムを紹介します!
VBA

【VBA】ユーザーフォームでクラスを利用したオブジェクト操作

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

以前、下記の記事で、ユーザーフォームへ動的にコントロールを追加する方法を書きました。

VBA ユーザーフォーム
【VBAユーザーフォーム:動的にコントロールを追加する】皆さんの中に、ExcelVBAのフォームで、コントロールを動的に作成していくことってできないのかなと考えている方がいるかもしれません。 ...

この記事でもクラスを利用して、ボタンコントロールを動的に扱っていました。

Yuu

動的にコントロールを追加できると、例えば売上伝票の明細行の様なもの(明細行が何行必要か不明)を扱う際に、動的に明細行を追加したり出来ます。

最初から必要な分を用意しておいても良いのですが、「必要な時に必要なだけ用意」できた方が、ユーザーは使い勝手が良いですよね!!

今回は、クラスとユーザーフォームを使用して、次の内容をやってみたいと思います。

  • テキストボックスをクラス化し、入力制限をかける
  • オプションボタンのクリックイベントをグループ化してまとめる
  • イベントを作成して、フォーカスの移動情報を扱う

3つ目の「イベントを作成」については、こちらの記事で簡単に解説しています。
VBAで自作イベントをハンドルする方法は、結構高度な技術で書籍でも殆ど扱われていません。

EXCEL_VBA_EVENT
【VBA】イベントについて皆さんは、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型のオブジェクトを設定しているところです。

Yuu

Propertyプロシージャは簡単に言うと、オブジェクトのプロパティを設定したり、使用したりします。

オブジェクト指向のプログラムではカプセル化と言い、クラスの外部からオブジェクトのプロパティを直接操作させないようにしています。

プロパティの設定時に値をチェックしたり出来るので、使える様になるとFunctionプロシージャと同じように使用できます。

詳しくは、以前の記事をご覧ください。

myTb_KeyPress』イベントは、クラスのプロパティの「myCkType」に設定された値によって、入力の可否を分けているだけです。

Yuu

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プロシージャを利用して、代入しています。

Yuu

あんまり知らなくても良いのですが、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

基本的に同じなので、解説は省略します。

myRtnTbは、今回のサンプルでコントロール名を表示するテキストボックスを利用するために宣言している物です。

実務では、無駄なものは宣言しないようにしましょう。

ユーザーフォームのコードを作成

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))を発生させています。

Yuu

コントロール毎に書いていたEnterとExitが一つのイベントコードで処理出来るようになるので、メンテナンスが簡単になります。

コード量もグッと減りますね!!

まとめ

お疲れ様でした。
今回の記事はいかがでしたか?分からない部分があれば、コメントいただければ、すぐお答えします。

ちょっと難しい内容ですが、実際の業務で使用するユーザーフォームはコントロール数が多くなりやすく、イベント処理だけで膨大なコード量になります。

クラス化して処理をひとまとめにすることで、大幅なコードの簡略化が図れると共に、変化に強いプログラムが作成できます。

お気づきの方もおられるかと思いますが、フォーム自体のコードはとてもシンプルになっています。
最後のサンプルを全てフォームのイベントで書いたら大変な事になりますよね(笑)

ExcelVBAは使い捨てのコードの様に考え、ロクなリファクタリングを行わない方もいますが、Excelを操作するならVBAに勝るものはありません。

皆さんも自分の書いたコードを常に改善点が無いか見る癖を付けると良いかと思います。

最後までお読みいただきありがとうございました!!


コメントを残す