Microsoft Excel VBA - クラスモジュールを使ったプログラミングを行う方法
◆概要
このページは、Excel VBAでクラスモジュールを使ったプログラミングを行う方法について記載しています。
◆クラスとインスタンス
クラスモジュールとは「クラス」を作成するためのモジュールである。では、クラスとは一体何なのであろう。これは、ひと言で言うと、「オブジェクトのひな型」のことである。「オブジェクトの設計図」と呼んでもよいかも知れない。
しかし、ここで多くの方が、「オブジェクト」とオブジェクトのひな型」をう相違点について疑問を感じたり、混同したりするであろう。こんなときに私がいつも利用しているものは「タイヤキ」である。
私たちが食べられるのは「タイヤキ」であって、鉄で作られた「タイヤキの器」ではない。そして、これと同じことがそっくりそのまま、クラスとオブジェクトにも当てはまるのだ。
クラスは「タイヤキの器」に相当する。そして、「タイヤキの器」に「材料」を流し込むわけだが、後述するように「材料」流し込むために使う「器」が標準モジュールであり、標準モジュール上に記述したNewキーワードがその「材料」ということになる。そしてタイヤキが作られて、私たちはもでたく「タイヤキを食べる」ことができるわけだ。
このたとえ話をExcel VBAに置き換えてみる。まず、クラスモジュールにクラス(タイヤキの器)を作成する。そして、標準モジュール(器)上でNewキーワード(材料)を使ってクラスを変数に代入すると、その変数はクラスの設計図通りのオブジェクト(タイヤキ)となる。すると、VBAはそのオブジェクトを操作したり、オブジェクトに対して発生したイベントを処理できるようになるわけだ。
実例を見ていただく前に、もうひとつ、「インスタンス」という概念を覚えておこう。タイヤキの型があれば、同じタイヤキをいくつも作ることができる。同様に、クラスがあれば、そこからは同じオブジェクトをいくつも作ることが可能なのだ。このように、ひとつのクラスからいくつでも複製されるオブジェクトのことを「インスタンス」と呼ぶ
◆クラスからインスタンスが生成される様子
さて、概念論はこのくらいにして、実際にクラスからインスタンスが生成される様子を、ある実験を通して見てみることにしよう。リスト1をご覧いただきたい。
Public x As Integer Public y As Integer Sub MainProc1() Dim i As Integer x = 0 y = 0 ' ひとつ目の変数の加算処理 For i = 1 To 10 NumAdd11 i Next ' 2つ目の変数の加算処理 For i = 11 To 20 NumAdd12 i Next MsgBox "ひとつ目の変数の加算処理:" & x & _ vbCrLf & _ "2つ目の変数の加算処理:" & y End Sub Sub NumAdd11(n1 As Integer) x = x + n1 End Sub Sub NumAdd12(n2 As Integer) y = y + n2 End Sub
リスト1は標準モジュールに作成したもので、変数「x」に1から10の数を加算し、変数「y」に11から20の数を加算している。リスト1の「MainProc1」プロシージャの実行結果が図1だが、この処理事態には別段特徴はない。ひとつのプロシージャ内で、異なる加算処理を2回行うため、変数も「x」「y」の2つを用意している。ただそれだけのことである。
図1:リスト1の実行結果
次に、クラスを使ってリスト1を書き換えてみることにしよう。クラスを使うためには、当然クラスモジュールが必要である。そこでまず、クラスモジュールを挿入していただきたい。ここでは、モジュール名は初期値の「Class1」のままにするが、この「Class1」がオブジェクトのひな型の名前、つまりクラス名となるのだ。
では、このひな型を先に完成させてしまおう。今から作るのは、「次々に数を加算される変数」というひな型である。そこで、クラスモジュールをリスト2のように、変数(x)を宣言して、さらにその変数に数を加算する処理(NumAdd2)を書く。
' クラスモジュールに作成 Public x As Integer Sub NumAdd2(n As Integer) x = x + n End Sub
リスト2では、リスト1とは違って、変数を一つしか用意していない点に注意していただきたい。この後すぐわかるが、変数がひとつしかなくても、クラスを使うとリスト1同様に複数の加算処理が実行できるのだ。
さて、これでタイヤキの型はできた。次は、型に流し込む材料を用意しなければならない。前述のとおり、材料を入れる器に相当する標準モジュールである。ここで標準モジュールをひとつ作成する。
標準モジュールを挿入したら、リスト3のコードを書いていただきたい。そして、1ステップずつ丁寧に処理を追いながら、クラスからインスタンスが生成されている様子を確認してほしい。
' 標準モジュールに作成 Sub MainProc2() Dim i As Integer ' 2つめのインスタンスの作成 Dim myClass1 As New Class1 ' *****A Dim myClass2 As New Class1 ' *****B ' ひとつ目のインスタンスの実行 For i = 1 To 10 myClass1.NumAdd2 i ' ******C Next ' 2つ目のインスタンスの実行 For i = 11 To 20 myClass2.NumAdd2 i ' ******D Next MsgBox "ひとと目のインスタンスの変数の合計:" _ & myClass1.x & vbCrLf & _ "2つ目のインスタンスの変数の合計:" & myClass2.x ' インスタンスの破棄 Set myClass1 = Nothing Set myClass2 = Nothing End Sub図2:リスト3の実行結果
***Aのステートメントで、「Class1」というクラスから、「myClass1」というインスタンスを生成している。ここで使われているNewキーワードが、クラスに流し込む材料に相当することは先に述べたとおりである。これで、VBAは「myClass1」を自由に操作できるようになった。
また、***Bのステートメントでも、Newキーワードを使って「Calss1」というクラスから、「myClass2」という別のインスタンスを生成している。タイヤキの型から2つのタイヤキを作るがごとく、「Class1」というクラスから2つのインスタンスを生成しているわけだ。
そして、***Cでは、
myClass1.NumAdd2 i
というステーメントを実行している。「myClass1」は、前述のとおり「Class1」から生成されたインスタンス(オブジェクト)である。オブジェクトだから、当然プロパティやメソッドが内在している。ここでは、クラスモジュールに作成したSubプロシージャ「NumAdd2」が、結果的にメソッドとして機能している。つまり、私たちは無意識のうちに、クラスモジュール内にメソッドも作成していたわけだ。
***Cのステートメントを復習すると、「myClass1」というオブジェクトの「NumAdd2」メソッドを「引数にiを指定して実行している」ということになる。
ActiveWorkbook.Close True
というExcel VBAのステートメントと、まったく同じ構文で構成されたステートメントであることがおわかりいただけるであろう。
***Dのステートメントの場合、今度は「myClass2」というインスタンスで、別の加算処理を実行している。
さて、ここで再度クラスモジュールに戻ってみよう。先に述べたとおり、クラスモジュールの変数は「x」のひとつしか作成していない。にも関わらず、標準モジュールからは「NumAdd2」をメソッドとして2回実行している。これでは、変数「x」の値は、1回目の加算処理と2回目の加算処理の合計になっていまわないだろうか。
答えは"ノー"である。標準モジュールの「MainProc2」プロシージャの実行結果(図3)を見ればわかるとおり、2回の加算処理は互いに干渉し合うことなく、並列で行われている。
さて、この理由を迷わず答えることができれば、クラスとインスタンスの概念は卒業である。では、この話の締めくくりに入らせていただこう。
再三述べているように、クラスからはインスタンスをいくつも作成することができる。つまり、タイヤキの型からタイヤキを2つ作ったときに、ひとつ食べても、もうひとつまでは消えてなくなってしまうことはない。これと全く同じ理屈で、リスト2・3の例では、「myClass1」と「myClass2」というインスタンスが、それぞれ別個に「x」という変数jを内在しているわけだ。
これでおわかりいただけたであろうか。なお、皆さんの中には、この場合の「x」がオブジェクトのプロパティの役割を果たしていることに気が付いた方もおられるだろう。そう、クラスモジュールに作成したSubプロシージャは最終的にインスタンスのメソッドとなるが、クラスモジュールで宣言した変数はインスタンスのプロパティ(データ)となるのだ。