皆さんはVBAでクラスモジュールを使っていますか?
何となく、クラスモジュールってややこしそう、使うメリットがよくわからない、など使用したことがないという方も多いように感じています。
そこで今回は、クラスモジュールを使うメリットの中から、私が特に有用と感じた使い方について紹介します。
普段、他の言語でもクラスを使ったことがない方でもわかりやすいようにお伝えできればと思いますので、ご一読いただけると幸いです。
①はじめに
新しいファイルでは、プロジェクトエクスプローラーは下図のようにMicrosoft Excel ObjectsとしてSheet1とThisWorkbookオブジェクトのみがあります。
(古いバージョンではSheetが複数ある場合もあります)
詳細は割愛しますが、基本的に標準モジュールと同じような使い方ができます。

プロジェクトエクスプローラー内で右クリックメニューを開いて「挿入」にカーソルを乗せると、下図のように「ユーザーフォーム」、「標準モジュール」、「クラスモジュール」を追加することができます。

他の言語を扱ったことがある方は、1つのモジュールが1ファイルに相当すると考えるとイメージしやすいと思います。
②標準モジュールと何が違う?
まず、クラスモジュールというのは、標準モジュールと何が違うのでしょうか?
大きく違う点として、下記2点がすぐに思い浮かびます。
- プロパティを使用することができる
- 外部から呼び出す際はインスタンスが必要
②-1. プロパティについて
プロパティというのは、Range(“A1”).ValueのValueの部分ですね。これを自作できます。
例えば、下記のようにするとnameプロパティを持つクラスを作ることができます。
|
1 2 3 4 5 6 7 8 9 10 11 |
Option Explicit Private name_ As String Public Property Get name() As String name = name_ End Property Public Property Let name(ByVal val As String) name_ = val End Property |
ここは、私も最初は躓いた箇所なので、少し詳しく書きたいと思います。
まず、Private name_ as stringの部分ですが、ここはname_である必要はありません。ただ、プロパティ名と同じ変数名にしておくことで、後で見たときにわかりやすいので、このように定義しておくのがおすすめです。
当然、この変数name_はPrivateなので、このクラスモジュール内でのみアクセスできます。
これを外部から書き換えできるようにするのが、一番下のLetプロシージャです。所謂アクセサですね。間のGetプロシージャはその名の通り、外部から値を取得するためのアクセサとなります。
この、GetやLetの後に書かれた部分 “name” が外部からアクセスするプロパティ名になります。なお、これらは外部からアクセスする必要があるのでPublicにする必要があります。
つまり、外部から クラス.name = “hogehoge”で値をname_に書き込み、MsgBox クラス.nameとすると、nameにname_の値が代入され、メッセージボックスに”hogehoge”が表示されるという流れとなります。もちろん、name_に値を設定した後は、クラス内のどこからでもname_の値を取得できます。
また、今回はstring型でしたが、オブジェクトを扱う場合はLetの代わりにSetを使い、代入の際にもSetステートメントが必要となります。例えば下記のようになります。
|
1 2 3 4 5 |
Private obj_ As Object Public Property Set obj(ByVal o As Object) Set obj_ = o End Property |
ちなみに、GetとLetはセットで使うことが多いですが、外部からプロパティを直接設定することがない場合(読み取り専用)はLet/Setは不要だったり、必要に応じてどちらかのみ実装するというケースもあります。
プロパティを使うことで、グローバル変数を使わずにモジュール間で変数のやり取りが出来、自動メンバー表示が使えるなどのメリットがあります。
②-2. 外部からプロパティやメソッドの呼び出し
他の言語で既にクラスを扱った経験のある方にとっては当たり前のことかもしれませんが、インスタンスを生成しないと、クラス内に作成したプロパティやメソッドを呼び出すことはできません。
インスタンスは実体のことですが、1の例で紹介したクラスをTestClassというクラス名にしたとして、下記のように標準モジュールに書いただけでは動きません。
|
1 2 3 4 |
Sub test() TestClass.name = "hogehoge" Debug.Print TestClass.name End Sub |
当然といえばそうなのですが、クラス初心者はなぜこれがダメなのか、すぐにわからなかったりします。(筆者がそうでした)
クラスというのはあくまで職種のようなもので、その職種に就いている人(実体)がいないと何もできないという感じのイメージでしょうか。
動くように書き直すと下記のようになります。
|
1 2 3 4 5 6 7 8 |
Sub test() Dim hito As TestClass Set hito = New TestClass ' もしくはDim hito As New TestClass hito.name = "hogehoge" Debug.Print hito.name End Sub |
また、クラスモジュール内に下記のようなメソッド(Public)を実装したとして、これを標準モジュール(外部)からそのままCall TestClassFuncとしても呼び出せません。
|
1 2 3 |
Public Sub TestClassFunc() MsgBox "test" End Sub |
先程と同様にインスタンスが必要です。
|
1 2 3 4 5 6 7 |
Sub test() ' Call TestClassFunc ← NG Dim hito As New TestClass Call hito.TestClassFunc End Sub |
ちなみに、クラスモジュール内のPrivateプロシージャはクラスモジュール内でも普通に呼び出すことができます。
|
1 2 3 4 5 6 7 |
Public Sub TestClassFunc() Call testMsg("hogehoge") End Sub Private Sub testMsg(ByVal val As String) MsgBox val End Sub |
ここまでの話だけ聞くと、インスタンス化しないと使えないなんて手間が増えて大変だと感じるかもしれませんが、このようにオブジェクトを扱うことに慣れるとメンテナンス性や可読性の高いコードを書くことが出来るので、結果的に生産性向上に繋がります。
③実はユーザーフォームもクラスモジュールとして機能する?
これは、補足ではありますが、下記の記事にも以前書きましたが、ユーザーフォームでもプロパティが使えます。また、この後出てくるWithEventsもユーザーフォームでも使うことができます。つまり、ユーザーフォームはGUIを持ったクラスモジュールのように扱えるということですね。
以前の記事:ユーザーフォームをオブジェクト化するメリット
④ユーザーフォームと組み合わせる上で便利な使い方
ユーザーフォームを使う際は、ツールボックスにあるコントロールを選択して任意の位置に置いてという風に手作業で作成することが多いと思います。
ただ、同じ種類で異なる動きをするコントロールを複数置きたい場合(例えばカレンダーの日付ボタン)、1つ1つ手作業で作るのは大変ですし、後から機能を追加する場合に全てのクリックイベントを編集しなければならなくなり、効率的とは言えません。
こんなときに便利なのがクラスモジュールです。
クラスモジュールではWithEventsキーワードが使えるため、汎用的なClickイベントなどを自作することが出来ます。
手順としては大まかに3ステップあります。
④-1. ユーザーフォームを作成
今回は本当に作るだけです。プロジェクトエクスプローラーで「挿入」→「ユーザーフォーム」で新しいフォームを追加して、オブジェクト名を”TestForm”などにしておきます。
④-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. 実処理を標準モジュールに作成
メイン処理に相当するcreateButtonTestプロシージャとボタンを生成するcreateBtnプロシージャに分けて書いています。
メイン側では、ユーザーフォームオブジェクトの生成とBtnEventクラスオブジェクト変数の宣言をして、これらを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 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
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 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 |
(この余談はちょっと複雑なので興味がない方は読み飛ばして構いません)
ここで少し余談ではありますが、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プロシージャ内の処理です。ここでは、冒頭にボタンのサイズや間隔を決めています。次に、ユーザーフォームにbtn0, btn1, btn2, ・・・というようにオブジェクト名指定で追加しています。その後のWithステートメントで追加した各ボタンのプロパティを設定しています。Int(i / 5)や(i Mod 5)で1行に5個ボタンが並ぶようにしています。最後にbtnEv(i).btnに追加したボタンを割り当ててメインに戻ります。
④-4. 動作確認
それでは動作を確認してみます。
createButtonTestプロシージャを実行すると下記のようなユーザーフォームが立ち上がります。

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

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

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

コメント