「Excel VBAのクラス」について第12回目です。VBAの「コンストラクタ」は引数を渡すことができないという残念な仕様なので、何とかできないか検討してみたいと思います。
前回「コンストラクタ」と「デストラクタ」について簡単に学びました。「コンストラクタ」に引数を渡せないことがわかったので、今回はそれを代替できる案についてです(^^)
わかりました。「コンストラクタ」をうまく利用できるようにするんですね。
今回もよろしくお願いしますm(_ _)m
はじめに
クラスをインスタンス化(New)したときに実行させるメソッドが「コンストラクタ」です。
Class_Initialize()というプロシージャ内に、インスタンス化の際に実行したいコードを書いておけば自動的に処理されるようになっています。
ただし、VBAの場合引数を渡すことができない仕様なのでそれに代わる対処法を考えていきます。
【この記事でわかることは】
・Excel VBA のクラスで「コンストラクタ」をうまく利用する方法について
使用している表データは前回のままです。
Class_Initialize内でクラスをNewする
Class_Initialize() の動作についていろいろ動かして確認してみたことを書きます。
同一クラスのインスタンス作成はダメでした
次のコードをご覧ください。
' Class1のコンストラクタ
Private Sub Class_Initialize()
Dim obj As New Class1
End Sub
このコードは、Class1 の Class_Initializeイベントです。Class1をNewしています。
この場合、Class1 の Class_Initializeイベントが再帰的に呼び出されて無限ループになります。
これは使えません。注意しましょう。
Class_Initialize内で別のクラスをNewする
Class1 の Class_Initializeイベント内で、別のクラスのインスタンスはNewできます。
' Class1のコンストラクタ
Private Sub Class_Initialize()
Dim obj As New Class2
End Sub
このコード(別クラスのインスタンス化)なら問題ありません。
ということは、Class2 の Class_Initializeイベント内にうまく処理を書けば何か出来そうですね。
あとでこの部分を使っていきたいと思います。
【検討①】コンストラクタで何をさせたいのか
前々回「第10回目」でインスタンスをコレクション化しました。この部分をそっくりコンストラクタと同時に全部終わらせてしまうことが出来ないか検討したいと思います。
ここで使ったコード例を手直ししながらやっていきます。
プロパティ値の設定部分を引数付きメソッドにする
クラスのメソッドを作成して、そこでプロパティの値を取得するようにしたいと思います。
引数には、シートの表データをRangeオブジェクトで渡すように設定します。
クラス側に設定した2つのメソッド(Function)がこちらです。
'ownerのコンストラクタ直後に各プロパティを設定用
Public Function Init1(ByVal rng As Range) As Class1
Set Init1 = Me '自分自身を返す
With Me
.ID = rng(1).Value 'IDに代入
.Name = rng(2).Value 'Nameに代入
.Age = rng(3).Value 'Ageに代入
End With
End Function
'owner.Petのコンストラクタ直後に各プロパティを設定用
Public Function Init2(ByVal rng As Range) As Class1
Set Init2 = Me '自分自身を返す
With Me
.Name = rng(4).Value 'owner.PetのNameに代入
.Age = rng(5).Value 'owner.PetのAgeに代入
.Types = rng(6).Value 'owner.PetのTipesに代入
.Gender = rng(7).Value 'owner.PetのGenderに代入
End With
End Function
元の標準モジュールからコードを移植しています。
Rangeオブジェクトにしないと、引数で渡すには都合が悪いのでその部分は変更しています。
コンストラクタ直後にメソッド実行
標準モジュール側のコードがこちらです。
Option Explicit
'標準モジュール
Sub rngCollectionTest()
'変数宣言
Dim owner As Class1 'ownerクラスインスタンス用
Dim rng As Range 'Rangeオブジェクト用
'コレクションオブジェクト作成
Dim mycol As Collection: Set mycol = New Collection
Dim i As Long: i = 4 'ループ処理用:初期値4
With Sheet1
Do While .Cells(i, 1).Value <> ""
Set rng = .Range(.Cells(i, 1), .Cells(i, 7))
'インスタンス作成:引数付きメソッドで値代入
Set owner = New Class1: owner.Init1 rng
Set owner.Pet = New Class1: owner.Pet.Init2 rng
'コレクションに書き込む
mycol.Add owner, owner.ID
i = i + 1
Loop
End With
'確認用にメッセージを表示
For i = 4 To i - 1
With mycol.Item(i - 3)
MsgBox "OWNER:" & .Name & "(" & .Age & "歳)" _
& vbCrLf & "ペット名:" & .Pet.Name & _
"(" & .Pet.Age & "歳)" & .Pet.Types & _
"/" & .Pet.Gender
End With
Next
Set mycol = Nothing 'コレクション開放
End Sub
前回と変更している部分のみ解説します。
・12行目が表の1行分データの範囲をRangeオブジェクトに設定してる部分です。
Rangeオブジェクトにすることでインデックスで管理できます。
・14行目の ownerインスタンス化でコンストラクタが発生します。直後に「:」をはさみ
Init1メソッドを引数 Rng を渡して実行します。
・15行目は、14行目と同様にコンストラクタ直後に Init2メソッドを引数 Rng 付きで実行します。
・22~28行目では(前回は格納直後に表示していましたが)コレクションに格納したデータを表示確認しています。ちゃんと正しく格納できているかここで確認しています。
・30行目、コレクションオブジェクトを開放しています。解放しておかないとオブジェクトが残ってしまいますので注意しましょう。
コレクション開放でインスタンスのデストラクタが発生する
コレクションオブジェクトを先ほどの30行目で解放していますが、解放と同時にコレクション内に格納されているインスタンスも解放されます。
インスタンスの解放を確認するため、デストラクタ(Class_Terminate)部分にメッセージを表示するようにして検証してみました。すると、今回は3行のデータを2つのインスタンスで分担していたので、6回(3×2=6)デストラクタが発生していました。
行数が多くなるとその分増えるというわけですね。
コレクションは標準モジュールに作成されている
実は、この方法ではコレクションが標準モジュールで宣言されています。この場合、再利用するにはコレクション内のオブジェクトがどのような型であるかを明示的に指定する必要がでてきます。
VBAのコレクションにクラスのインスタンスを格納する場合、宣言する場所はクラスモジュール内で宣言することが一般的のようです。
クラスモジュール内で宣言すれば、コレクションに格納されたオブジェクトが、そのクラスのインスタンスであることが明確ですし、どのようなプロパティやメソッドを持っているかも明確になっているというわけです。
ということで、次にコレクションをクラスで宣言するように変更していきます。
【検討②】クラスでコレクションを処理する
まず、やりたいことをザックリ書いてみます。
Class2をインスタンス化(NEW) → Class_Initialize → コレクション生成 → Class1のインスタンス化 → プロパティ値取得 → コレクションに格納 → すべて処理したら → コレクション破棄 → Class1のインスタンス破棄(Class_Terminate)→ Class2のインスタンス破棄(Class_Terminate)
こんな感じでしょうか。ではこれを反映するように設定してみます。
呼び出し用のクラスモジュールを追加
クラスモジュールを追加して「clsCol」という名前にしました。
コードはこちらです。前の標準モジュールの部分を移してきた感じです。
Option Explicit
'コレクション用変数
Public mycol As Collection
'コンストラクタ
Private Sub Class_Initialize()
'変数宣言
Dim owner As Class1 '別クラス呼び出し用
Dim rng As Range
Dim i As Long: i = 4
'コレクション初期化
If mycol Is Nothing Then Set mycol = New Collection
'Sheet1の表データからプロパティ値取得
With Sheet1
Do While .Cells(i, 1).Value <> ""
Set rng = .Range(.Cells(i, 1), .Cells(i, 7))
Set owner = New Class1: owner.Init1 rng
Set owner.Pet = New Class1: owner.Pet.Init2 rng
'コレクションに書き込む
mycol.Add owner, owner.ID
i = i + 1
Loop
End With
End Sub
'デストラクタ
Private Sub Class_Terminate()
'ここでコレクションオブジェクトを解放
If Not (mycol Is Nothing) Then Set mycol = Nothing
End Sub
・Rangeオブジェクトを使うように修正しています。
・11行目、クラス内でコレクションオブジェクトを生成しています。
・16行目と17行目が、コンストラクタ「Class_Initialize()」内で別クラスをインスタンス化している部分です。直後にメソッドにRangeオブジェクトの引数を付けて実行させています。
・25行目からのデストラクタ「Class_Terminate()」内でコレクションを開放しています。
標準モジュールがこちら
確認用のメッセージ部分を消すと標準モジュールはこれだけになりました。
Option Explicit
'標準モジュール
Sub rngCollectionTest()
Dim table As clsCol
'インスタンス作成⇒コンストラクタ起動
Set table = New clsCol
End Sub
・なんでも良いのですが「table」という名前のインスタンスをNewしています。
・たったこれだけになってスッキリしました。
ローカルウィンドウでコレクションを確認します
最後にローカルウィンドウでコレクション「myCol」を確認します。
※ VBEメニューから表示(V)→ローカルウィンドウ(S)クリックで表示できます。
「clsCol」クラスの「table」インスタンスに作成されていることが確認できました。
まとめ(おわりに)
以上、Excel VBAのクラスで引数を渡すことが出来ない「コンストラクタ」をうまく利用する方法について紹介しました。
クラスの12回目はいかがでしたか。
引数を渡せない「コンストラクタ」の動作について利用方法をいろいろ検討してみましたが、今回紹介した方法が解決策の一つになるのではないでしょうか。
次回はコレクション化したインスタンスからデータを取り出す方法について勉強しましょう。
コンストラクタ内でNewできるケースを知ることが出来てよかったです。
コレクションについても勉強になりました(^^)
次回もよろしくお願いしますm(_ _)m
まとめ
最後に、今回勉強した内容を整理しておきましょう。
次回は、コレクション化したインスタンスを取り出す方法を勉強します。よろしければ引き続きご覧ください。
Excel VBA クラスについての記事一覧
★★★ ランキング参加中! クリックしてね(^^)/ ★★★
過去記事のサンプルファイルをダウンロードできます
この記事のサンプル登録はありません。記事内のコードをご利用ください。
過去の記事で使用したサンプルファイルがダウンロードできるページを設置しています
こちら(このリンク先)からご利用ください