Object型変数を積極的に使う

Object型変数を積極的に使う

今まで、Object型の変数を積極的に使うことはなかった。

VBAで作成したツール類も、内輪で使うものばかりなので、CreateObjectを自分から使うことはまずなかった。参照設定してNewばっかりだった。

今回、Object型変数を使ったら便利だな、と思った場面があったので紹介する。

Sheet1オブジェクトにPropertyとメソッドを設定する

シートモジュールにPropertyを設定してみる。

今回は、シートの基準位置を示すセルを、シートオブジェクトのPropertyにする。

リスト1 Sheet1モジュール
Public Property Get BaseCell() As Range
  Set BaseCell = Me.Range("A1")
End Property

たったこれだけの単純なコード。

A1セルを、Sheet1オブジェクトの基準セルを表すBaseCellプロパティに設定している。

次に、Sheet1オブジェクトにメソッドを設定する。

スト2 Sheet1モジュール
Public Sub showBaseCellValue()
  Call MsgBox(Me.BaseCell.Value)
End Sub

ご覧のとおり、MsgBoxを使って、先ほど設定したBaseCellプロパティを参照して得られるRangeオブジェクト(すなわちSheet1オブジェクトのA1セル)の値を表示するだけのメソッド。

使ってみる

Sheet1を、

f:id:akashi_keirin:20181104084322j:plain

このようにしておいて、

次のコードで早速使ってみる。

リスト3 標準モジュール[ModuleMain]
Public Sub showBaseCellValueMain()
  Call Sheet1.showBaseCellValue
End Sub

Sheet1オブジェクトのshowBaseCellValueメソッドを呼び出す。

当然、

f:id:akashi_keirin:20181104084341j:plain

こうなる。

Sheet1オブジェクトのshowBaseCellValueメソッド内部でBaseCellValueプロパティを参照し、A1セルが返るので、A1セルのValueプロパティの値(すなわち「ち~んw」)がメッセージボックスに表示されたわけだ。

Sheet2オブジェクトにもPropertyとメソッドを設定する

同じように、Sheet2オブジェクトにも似たようなPropertyとメソッドを設定してみる。

ただし、今度は、

f:id:akashi_keirin:20181104084430j:plain

このように、基準セルをB2にする。

リスト4 Sheet2モジュール
Public Property Get BaseCell() As Range
  Set BaseCell = Me.Range("B2")
End Property

Public Sub showBaseCellValue()
  Call MsgBox(Me.BaseCell.Value)
End Sub

もはや多言は要すまい。BaseCellプロパティが指し示すセルの位置がB2に変わっただけだ。

使ってみる

今度は、上記のリスト3を次のように変更する。

リスト5 標準モジュール[ModuleMain]
Public Sub showBaseCellValueMain()
'  Sheet1.showBaseCellValue'
  Sheet2.showBaseCellValue
End Sub

Sheet1オブジェクトに対する操作の部分をコメントアウトして、Sheet2オブジェクトのshowBaseCellValueメソッドを呼ぶコードにした。

実行すると、

f:id:akashi_keirin:20181104084438j:plain

当然こうなる。何の不思議もない。

問題

似たようなシートがたくさんあるのだけれど、行き当たりばったりで行や列を追加してしまったせいで、基準位置となるセルがばらばら」みたいなときには、このようにSheetXオブジェクトのPropertyにしてしまう、というのは便利なテクニックだと思う。

ただし、このやり方だと早晩行き詰まることになる。

シートが増えるごとに、「似ているけれど少しづつ異なるコードが量産されてしまう」という問題である。

もちろん、量産する段階では、単純に「コピペして微修正」を繰り返すだけなので、たいした手間ではない。しかし、たとえば今回のshowBaseCellValueに機能を追加しようとしたらどういうことになるか。

もちろん、量産した分だけ手間がかかるのである!

ふつう、こういうときは、「共通部分の括り出し」ということをする。

メソッドの括り出しを試みる

とりあえず、標準モジュールに次のようなコードを書いて括り出してみる。

リスト6 標準モジュール[SheetOperator]

最近、標準モジュールに名前を付けて、メソッドの機能ごとに分ける、ということをしています。今回は、SheetXオブジェクトを操作するメソッドなので、モジュール名を「SheetOperator」にしました。命名方法については、現在試行錯誤中です。参考になるコーディング規約等ございましたら、ご教示ください。

Public Sub showBaseCellValue(ByVal targetSheet As Worksheet)
  Call targetSheet.showBaseCellValue
End Sub

引数でWorksheetオブジェクトを渡して、それぞれのshowBaseCellValueメソッドを実行する、という目論見。

しかしながら、この計画には、実はコード入力時点で既に暗雲が立ちこめている。

f:id:akashi_keirin:20181104084510j:plain

targetSheet.の段階で、入力候補にshowBaseCellValueが出てこないのである。

そして、その嫌な予感は、このメソッドを使ってみるとしっかり当たる。

使ってみる

リスト7 標準モジュール[ModuleMain]
Public Sub showBaseCellValueMain()
'  Sheet1.showBaseCellValue'
'  Sheet2.showBaseCellValue'
  Call SheetOperator.showBaseCellValue(Sheet1)
End Sub

Sheet1を引数として渡して、SheetOperatorモジュールのshowBaseCellValueメソッドを実行する。

すると、

f:id:akashi_keirin:20181104084526j:plain

あえなくコンパイル・エラー!

これは、少し考えれば当たり前の話で、

showBaseCellValueメソッド(BaseCellプロパティも)は、あくまでもSheet1オブジェクト、Sheet2オブジェクトのメソッド(プロパティ)なのであって、Worksheetオブジェクトのメソッド(プロパティ)ではない

ということなのである。

単純な理屈だけれど、案外見落としやすいポイントだと思う。

そこで、Object型の登場

要するに、SheetOperatorモジュールのshowBaseCellValueメソッドにSheet1オブジェクト、Sheet2オブジェクトが渡ればいいのである。

よって、リスト6を次のように修正する。

リスト8 標準モジュール[SheetOperator]
Public Sub showBaseCellValue(ByVal targetSheet As Object)
  Call targetSheet.showBaseCellValue
End Sub

Object型の変数に格納して渡すことで、行った先でもSheet1オブジェクト、Sheet2オブジェクトとして振る舞ってくれるはずだ。

使ってみる

次のコードで実験。

リスト9 標準モジュール[ModuleMain]
Public Sub showBaseCellValueMain()
'  Sheet1.showBaseCellValue'
'  Sheet2.showBaseCellValue'
  Call SheetOperator.showBaseCellValue(Sheet1)
  Call SheetOperator.showBaseCellValue(Sheet2)
End Sub

今度は、

f:id:akashi_keirin:20181104084537j:plain

f:id:akashi_keirin:20181104084551j:plain

うまくいった。

おわりに

Object型の変数を使うことによって、コードを一箇所に集約することができた。

もちろん、Object型の変数(引数)を使うと、コーディング中に入力候補が表示されなくて不便だが、その場合は、コーディング時には今回の場合だったら、ひとまずSheet1で書いておいて、書き終わって実行テストが終わってから一気にtargetSheetに置換する、というやり方をすればいいと思う。

参考

akashi-keirin.hatenablog.com