本記事では、活用編として、WithEventsを使って動的にコントロールとそのイベントを追加する方法を紹介します。個人的には、この方法がVBAのクラスモジュールの使い方として、実用面で一番役に立ちました。
ぜひ、ご一読ください。
④ユーザーフォームと組み合わせる上で便利な使い方
ユーザーフォームを使う際は、ツールボックスにあるコントロールを選択して任意の位置に置いてという風に手作業で作成することが多いと思います。
ただ、同じ種類で異なる動きをするコントロールを複数置きたい場合(例えばカレンダーの日付ボタン)、1つ1つ手作業で作るのは大変ですし、後から機能を追加する場合に全てのクリックイベントを編集しなければならなくなり、効率的とは言えません。
こんなときに便利なのがクラスモジュールです。
クラスモジュールではWithEventsキーワードが使えるため、汎用的なClickイベントなどを自作することが出来ます。
手順としては大まかに3ステップあります。
④-1. ユーザーフォームを作成
1ステップ目です。
ここは本当に作るだけです。プロジェクトエクスプローラーで「挿入」→「ユーザーフォーム」で新しいフォームを追加して、オブジェクト名を”TestForm”などにしておきます。
④-2. クラスモジュールを作成
2ステップ目です。
今度は「挿入」→「クラスモジュール」で新しいクラスを追加して、オブジェクト名を”BtnEvent”などにして、中身は下記のように記述します。
|
1 2 3 4 5 6 7 8 9 10 |
Option Explicit ' Publicで宣言 Public WithEvents btn As MSForms.CommandButton ' イベントはPrivateで定義 Private Sub btn_Click() ' ボタンに登録した表示名をメッセージボックスで表示 MsgBox btn.Caption End Sub |
④-3. 実処理を標準モジュールに作成
そして3ステップ目です。
メイン処理に相当するcreateButtonTestプロシージャとボタンを生成するcreateBtnプロシージャに分けて書いています。
メイン側では、ユーザーフォームオブジェクトの生成とBtnEventクラスオブジェクト変数の宣言をして、これらをcreateBtnプロシージャに渡して、最後にフォーム名を付けて起動するのみです。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
Option Explicit Const BUTTON_NUM As Long = 10 ' 生成ボタン数 Sub createButtonTest() Dim form As TestForm: Set form = New TestForm ' Dim form As New TestFormでも可 Dim btnEv(BUTTON_NUM - 1) As BtnEvent ' ここはDim btnEv(BUTTON_NUM - 1) As New BtnEventはNG ' ※同一プロシージャ内で完結する場合は上記でも動きますが非推奨 ' クリックイベント付きボタン生成 Call createBtn(form, btnEv) ' フォーム名を付けて起動 form.Caption = "テスト画面" form.Show End Sub |
(下記余談については気になる方は読んでみてください)
(余談)btnEv配列を宣言と同時にAs Newすると動かない理由
このボタン配列について、As Newされた時点では配列要素はnothing状態なのですが、同一プロシージャ内でbtnEv(0).name = “hogehoge”のようにすると配列要素も自動インスタンス生成されます。(普通に動きます)
しかし、今回のように自動インスタンス生成される前に別プロシージャに渡すと、nothingの配列を渡すことになるので、btnEv(0).name = “hogehoge”が実行される前にエラーが発生してしまうようです。
別プロシージャに渡す前に各要素を個別にSet btnEv(0) = New BtnEventのようにしておけば動きますが、何度もループを回すのは冗長なので、今回は渡した後のループ内でNew(インスタンス生成)するようにしています。
ちなみに、この配列をcreateBtnプロシージャ内で宣言してしまうと、メイン側にbtnEvインスタンスが渡されることはないので、クリックイベントが動きません。(End SubでPrivate変数オブジェクトは解放されてしまうため)
続いて、createBtnプロシージャ内の処理です。(同一標準モジュール)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
Private Sub createBtn(form As TestForm, btnEv() As BtnEvent) Const WIDTH_BTN As Long = 20 Const HEIGHT_BTN As Long = 20 Const SPACE_WIDTH As Long = 5 Dim i As Long For i = LBound(btnEv) To UBound(btnEv) ' TestFormにbtn〇という名前のボタンを追加 form.Controls.Add "Forms.CommandButton.1", ("btn" & i) ' 生成したボタンのプロパティを設定 With form.Controls("btn" & i) .Width = WIDTH_BTN .Height = HEIGHT_BTN .Top = SPACE_WIDTH + (SPACE_WIDTH + HEIGHT_BTN) * Int(i / 5) .Left = SPACE_WIDTH + (SPACE_WIDTH + WIDTH_BTN) * (i Mod 5) .Caption = i End With ' ボタンイベントのインスタンスを生成して対象ボタンを登録 Set btnEv(i) = New BtnEvent Set btnEv(i).btn = form.Controls("btn" & i) Next i End Sub |
ここでは、冒頭にボタンのサイズや間隔を決めています。次に、ユーザーフォームにbtn0, btn1, btn2, ・・・というようにオブジェクト名指定で追加しています。その後のWithステートメントで追加した各ボタンのプロパティを設定しています。Int(i / 5)や(i Mod 5)で1行に5個ボタンが並ぶようにしています。最後にbtnEv(i).btnに追加したボタンを割り当ててメインに戻ります。
④-4. 動作確認
それでは動作を確認してみます。
createButtonTestプロシージャを実行すると下記のようなユーザーフォームが立ち上がります。

ボタンをクリックすると、書かれている数字をメッセージボックスに表示します。

ボタンの数は冒頭のBUTTON_NUMを変更するだけです。(10 → 20)

⑤おわりに
今回の内容はここまでとなります。VBAのクラスモジュールは少し使い方にクセがあるので、少し慣れが必要ですが、VBAである程度の規模のコードを書く際にはかなり有用だと思います。
他の言語にあるようなクラスの継承機能はVBAにはありませんが、似たようなことは出来たりもするので、次はその内容について書きたいと思います。
以上、最後まで読んでいただき、ありがとうございました。
