1.4 オブジェクトを作ってみよう

前節ではプロシージャを使って、一連の処理をまとめました。一歩進めて、今回はオブジェクト指向プログラミングにちょっとだけ踏み込んでみましょう。

オブジェクト指向プログラミングのカプセル化という特徴を活かすと、読みやすいコードを書き、プログラミングやテストを楽にできるようになると思います。

VBAでは本格的なオブジェクト指向プログラミングはできませんが、その分、初心者には理解しやすいと思います。プロシージャを進化させたという感じです。

本格的オブジェクト指向プログラミング理解の端緒たんしょとなれば幸いです。


1.4.1 クラスとオブジェクトについて

オブジェクトは、変数とその処理を一つにまとめたものと言っても間違いではないでしょう。

オブジェクトの中で扱う変数は、「適用範囲」を安易に広げないように、厳しく隠ぺいされ、呼び出し元との変数の受渡しは決まった方法でしかできません。

また、プロシージャの中で宣言した変数は、呼び出し元に処理が帰ると、領域もろともなくなってしまいましたが、オブジェクトの中で宣言した変数は、オブジェクトが存在する間、存在しています。

これがオブジェクト指向プログラミングの特徴の一つです。

実は、私たちは既に、最初にマクロの記録をして作ったMacro1()でオブジェクトを目にしています。ここで、Macro1()のソースをオブジェクトに注意して見てみましょう。

Sub Macro1()
'
' Macro1 Macro
'

'
    With Selection.Interior
        .Pattern = xlSolid
        .PatternColorIndex = xlAutomatic
        .Color = 65535
        .TintAndShade = 0
        .PatternTintAndShade = 0
    End With
End Sub
「With Selection.Interior」から「End With」が、オブジェクト「Selection.Interior」に対する処理です。「Selection.Interior」というのは、「選択されたセルの内部」というオブジェクトです。オブジェクト内の変数を変更しています。このマクロを記録したときに操作したのは「塗りつぶしの色」だけでしたが、「.Color」だけでなく、同時に「.Pattern」、「.PatternColorIndex」、「.TintAndShade」、「.PatternTintAndShade」も設定されています。このように外部から参照できるオブジェクト内の変数を「プロパティ」と呼びます。

塗りつぶしの色の設定を「With」を使わずに書けば、

        ' 「選択されたセルの内部」というオブジェクトの持つ「塗りつぶしの色」を黄色に変更しなさい
        Selection.Interior.Color = 65535
となり、これ以外の方法では塗りつぶしの色というデータを参照できないようになっています。

次のようにして、オブジェクト内から値を取出すこともできます。オブジェクト内のデータの内、呼び出し元から参照できるデータをVBAでは「プロパティ」と呼びます。

        ' オブジェクト「選択されたセルの内部」のプロパティ「塗りつぶしの色」を変数iroに取出しなさい
        iro = Selection.Interior.Color

ところでセルは無数にあり、そのインテリア(セルの内部)というオブジェクトも無数にあります。セル(A1)のインテリア、セル(A2)のインテリアという具合に、それぞれのインテリアをプロシージャで実現するには、呼び出し元でそれぞれのインテリアの状態を保存する変数も宣言しておかなければならないでしょう。

しかし、オブジェクトを使うと呼び出し元でそのような気を遣う必要がありません。

これを実現するために、クラスというオブジェクトの雛形ひながたを作っておき、それをインスタンス化することによって個々のオブジェクト(インスタンス)を作成するようになっています。
図1.4.1-1図1.4.1-1

右側のクラスが雛形となって、クラスをセル毎にインスタンス化したものがオブジェクトとなります。私たちは、このオブジェクトを今まで操作してきたわけですが、実際にどのようにコーディングしてあるのか見られません。コードを知らなくても、ヘルプで調べればオブジェクトを利用できますし、コードを壊すことも、オブジェクト内部の大事なデータを書き換えてしまうこともありません。

このようにカプセル化されたコードを見られれば良い勉強になるのですが、公開されていません。次の節では、自分で実際にクラスを作って理解を深めましょう。

1.4.2 クラスを作ってみよう

ここでは、信号機をシミュレーションするクラスを考えてみましょう。

簡単のため、この信号機は灯火を1つだけ持ち、色が青⇒黄⇒赤と3色に変化するものとします。灯火は、Excelのセルの塗りつぶしの色を使って表現します。

図1.4.2-1図1.4.2-1

さて、「信号」クラスは、どんなふうに実現するか具体的に考えてみましょう。

信号クラスは、色の状態というプロパティを持っています。また、信号を「初期化」する手続と「次点灯」という手続が必要になりそうです。手続はプロシージャのことですが、VBAでは、クラスの中のプロシージャは「メソッド」と呼びます。

「初期化」メソッドは、灯火の場所として1つのセルを指定します。初期化は、クラスをインスタンス化してオブジェクトを生成するときに行うのが普通ですが、VBAではインスタンス化するときに引数を渡せないので、別に「初期化」メソッドを作り、信号の場所(セル)を指定できるようにしました。

「次点灯」メソッドには、1回呼び出されるごとに、灯火の色、すなわちセルの塗りつぶしの色を青⇒黄⇒赤⇒青⇒……と順番に塗り替える働きをさせることにします。

1.4.2.1 クラスはクラスモジュールに作ります

エディタのメニュー[挿入]から[クラスモジュール]を選びます。
図1.4.2-2図1.4.2-2

「プロジェクト」エクスプローラに「クラスモジュール」というフォルダができて「Class1」というモジュールができます。
図1.4.2-3図1.4.2-3

「Class1」では何のモジュールか分かりにくいので名前を変更しましょう。「プロパティ」ウィンドウで「オブジェクト名」を変更してください。「プロジェクト」エクスプローラに反映されます。
「プロパティ」ウィンドウは、[表示]メニューから開けます。[F4]キーでも開けます。
図1.4.2-4図1.4.2-4

1.4.2.2 初期化プロシージャと終了プロシージャを用意します

まず、初期化プロシージャClass_Initialize()を作ります。初期化プロシージャは、名前の決まったプロシージャで、クラスからオブジェクトを作るときに1回だけ実行する手続きを記述します。必要なければ用意しなくてもかまいません。

オブジェクトプルダウンメニューから[Class]を選ぶと自動的に初期化プロシージャができます。中身は空っぽなので必要に応じて変数や手続きを記述します。
図1.4.2-5図1.4.2-5

同様に、終了プロシージャClass_Terminate()も作ります。クラスから作ったオブジェクトが消滅するときに最後に1回だけ実行する手続きを記述します。これも必要なければ用意しなくてもかまいません。

右側のプロシージャプルダウンリストから[Terminate]を選びます。中身が空っぽのプロシージャClass_Terminate()が自動的に追加されます。
図1.4.2-6図1.4.2-6

では、初期化プロシージャの中身を記述しましょう。信号の色を決める値を配列で定義しておきます。これはずっと変わらないデータなので初期値も与えておきましょう。定数で指定することもできるでしょう。

今、どの色が点灯しているか表す情報と、どの交差点の信号なのかを表す情報もここで作っておきましょう。モジュール内のどのプロシージャからも利用できるようにプロシージャの外に定義しておきます。

信号によって異なる値を持つ交差点の位置と、最初に点灯する色の値は、オブジェクトを作った後で設定します。Class_Initialize()には引数を渡せないからです。

終了プロシージャClass_Terminate()は、今のところ何も記述する必要はありません。

Option Explicit

Dim iro(2)  ' 信号の色を決めるデータ
Dim idx     ' 点灯している色を示す値
Dim cell_c  ' 信号の位置:列(column)
Dim cell_r  ' 信号の位置:行(row)

Private Sub Class_Initialize()
    
    iro(0) = 65280  ' 緑(あお)
    iro(1) = 65535  ' 黄
    iro(2) = 255    ' 赤

End Sub

Private Sub Class_Terminate()

End Sub

1.4.2.3 プロパティを作ろう

次にプロパティを作りましょう。プロパティは、呼び出し元と情報を交換する窓口になります。
VBエディタの[挿入]メニューからプロシージャの追加を選びます。
図1.4.2-7図1.4.2-7
[プロシージャの追加]ダイアログが表れるので、プロパティの名前を「color」と入力します。[種類]は、[Property プロシージャ]、[適用範囲]は、呼び出し元から参照できるように[Public プロシージャ]にしておきます。
図1.4.2-8図1.4.2-8
[OK]ボタンをクリックすると、GetプロシージャとLetプロシージャができます。

Getプロシージャは、呼び出し元から値を参照するときに使います。Functionプロシージャと同じように戻り値を返します。

Letプロシージャは、呼び出し元から値を書き込むときに使います。書込む値は、vNewValueという名前で渡されます。この名前は自由に変えられます。

プロパティ「color」のプロシージャは、次のように記述すると良いでしょう。

Public Property Get color() As Variant
    color = idx
End Property

Public Property Let color(ByVal vNewValue As Variant)
    idx = vNewValue
End Property
Letプロシージャの引数に自動的に入るByVal と As Variantは既定値なので、vNewValueと省略してもかまいません。ByValは、「値渡し」、As Variantは、データ型の宣言です。ヘルプで調べてみましょう。

1.4.2.4 メソッドを作ろう

次にメソッドを作りましょう。メソッドは、プロパティや内部変数の値を元に処理を行うプロシージャです。

先ず、インスタンス化したときに実行される初期化プロシージャで実行できなかった処理をメソッドとして作ってみます。

VBエディタの[挿入]メニューからプロシージャの追加を選びます。
図1.4.2-7図1.4.2-7
[プロシージャの追加]ダイアログが表れるので、プロパティの名前を「syokika」と入力します。[種類]は、値を返す必要がないので[Sub プロシージャ]を選びましょう。[適用範囲]は、プロパティと同じく、呼び出し元から参照できるように[Public プロシージャ]にしておきます。
図1.4.2-9図1.4.2-9
[OK]ボタンをクリックすると、syokikaプロシージャができます。

初期化に必要な引数は3つです。最初に点灯する色の値、セルの行、列です。「syokika」プロシージャは、次のように記述すると良いでしょう。 __(1)__ と __(2)__、 __(3)__ を埋めて完成してください。

Public Sub syokika(ix, c_ix, r_ix)
    
    idx = ix
    cell_c = c_ix
    cell_r = r_ix
    
    ' 指定セルを塗りつぶす
    Cells(__(1)__, __(2)__).Interior.color = __(3)__

End Sub

さて、次は次点灯メソッドを作りましょう。次点灯メソッドでは以下の処理を行います。

  1. 点灯している色を示す値を一つ進めます。
  2. 新しくなった色でセルを塗りつぶします。
点灯している色を示す値、セルの場所を示す行と列は、プロシージャの外で定義してあるので、オブジェクトが存在している間、その領域はメモリ上に残っており、前に呼び出された値が保存されています。

今の色が分かっているので、信号の次の色も分かります。引数は必要ありません。プロシージャの追加は、syokikaのときと同じです。以下のように記述すればよいでしょう。Modは余りを求める演算子です。詳細は、ヘルプを参照して __(1)__ と __(2)__ を埋めて完成してください。

Public Sub tugi()
    
    idx = idx + 1
    idx = __(1)__ Mod __(2)__	' 3で割った余りを求める
    
    ' 指定セルを塗りつぶす
    Cells(cell_r, cell_c).Interior.color = iro(idx)

End Sub

1.4.3 オブジェクトを作ってみよう

前節で作った信号クラスをインスタンス化してオブジェクトを動かしてみましょう。

2つの信号機を作りましょう。標準モジュールに以下のように作成します。

Sub singouki()

    Dim honmachi As singoClass
    Dim ginza  As singoClass
    
    Set honmachi = New singoClass
    Set ginza = New singoClass
    
End Sub
2つのDim文で本町(honmachi)と銀座(ginza)の信号オブジェクトを記憶する変数を定義しています。

次の2つのSet文で信号クラスをインスタンス化して信号オブジェクトを作り、変数に記憶しています。Set文を実行したときにClass_Initialize()プロシージャが実行されます。

標準モジュールにsingouki()プロシージャを作って動かしてみてください。キーを使い、デバッグモードで1ステップずつ動かしてみると分かりやすいでしょう。

Class_Initialize()プロシージャには引数が渡せないので、それぞれの信号機の特徴が作れません。syokikaメソッドで信号機を設置しましょう。標準モジュールのsingouki()プロシージャにそれぞれの信号機を初期化するメソッドを追加してください。初期化メソッドは以下のように呼び出します。

honmachi.syokika 0, 1, 1
ginza.syokika 1, 3, 1
honmachi.syokika 0, 1, 1でhonmachi信号機のsyokikaメソッドを呼び出しています。CALL文を使って、
call honmachi.syokika(0, 1, 1)
と書いても同じです。最初の数字が色を表します。1行1列目のセルに設置しています。ginza信号機も同じです。1行3列目のセルに設置しています。

これで信号機が2か所に設置できました。1行1列目のセルが緑(あお)に、1行3列目のセルが黄に塗りつぶされたと思います。

次は、信号の色を変えてみましょう。tugiメソッドを使います。引数はありません。

honmachi.tugi
ginza.tugi
これで、信号の色が次の色に変わる筈です。

これだけでは、面白くないので3秒ごとに変化するようにしてみましょう。3秒待つには以下のメソッドが用意されています。

Application.Wait (Now + TimeValue("0:00:03"))   ' 3秒待つ

以下は、3秒待つようにしたプロシージャsingouki()の作成例です。

Sub singouki()

    Dim honmachi As singoClass
    Dim ginza  As singoClass
    
    Set honmachi = New singoClass
    honmachi.syokika 0, 1, 1
    
    Set ginza = New singoClass
    ginza.syokika 1, 3, 1
    
    Dim ix
    For ix = 1 To 10
        Application.Wait (Now + TimeValue("0:00:03"))   ' 3秒待つ
        honmachi.tugi
        ginza.tugi
    Next
    
    MsgBox "信号機テスト終了"
    
End Sub

仕上げとして、30秒経ったらメッセージを表示して終わるように作ってください。参考までにsingoClassの完成例を以下に示しておきます。

Option Explicit

Dim iro(2)  ' 信号の色を決めるデータ
Dim idx     ' 点灯している色を示す値
Dim cell_r  ' 信号の位置:行(row)
Dim cell_c  ' 信号の位置:列(column)

Private Sub Class_Initialize()
    
    iro(0) = 65280  ' 緑(あお)
    iro(1) = 65535  ' 黄
    iro(2) = 255    ' 赤

End Sub

Private Sub Class_Terminate()

End Sub

Public Property Get color() As Variant
    color = idx
End Property

Public Property Let color(vNewValue)
    idx = vNewValue
End Property

Public Sub syokika(ix, c_ix, r_ix)
    
    idx = ix
    cell_c = c_ix
    cell_r = r_ix
    
    ' 指定セルを塗りつぶす
    Cells(cell_r, cell_c).Interior.color = iro(idx)

End Sub

Public Sub tugi()

    idx = idx + 1
    idx = idx Mod 3
    
    ' 指定セルを塗りつぶす
    Cells(cell_r, cell_c).Interior.color = iro(idx)

End Sub