シートモジュールへのインターフェース実装の代案
シートモジュールへのインターフェース実装の代案
ごく一部の(?)VBAerの間では、「シートモジュールにインターフェースをImplementsすると派手にバグる」というのは有名だと思う。「ごく一部で有名」であるということを「有名」と称するのかどうかはともかくとして。
参考
大規模なExcelブックで生じがちなこと
Excelはいろいろな使い方ができる。本来の表計算から割と離れた用途で使われていることも多いと思う。うちの業界もその一つ。簡易なデータベース的な使い方が多い。人間のデータを元に名簿を作るとか。
そうなると、いきおい、〈一つのブックに大量のシート〉ということが生ずる。そして、ブックの中に、〈類似の特殊な役割を持った似たようなシート〉が複数生ずることも多い。
同じメンバーを、Aという観点で分類した名簿とBという観点で分類した名簿と……といった具合に。
名簿の形態そのものはよく似ているので、それぞれのシートモジュールに同じ名前のメソッド(やプロパティ)を持たせると便利。しかし、それぞれのシートモジュール内では、異なる処理をしなければならない。
本来ならば、こういうときこそインターフェースの出番なんだけれど、上述のように、シートモジュールでは事実上インターフェースは使えない……。
そこで、素人なりに対応を考えた。
お題
実現したい内容は次のとおり。
- 同じメソッド名でそれぞれのシートモジュールのメソッド・プロパティを呼べるようにする。
- シートモジュールに存在すべきメソッド・プロパティがなかったらエラーを吐く
とりあえず、これだけできれば、擬似的にインターフェース的なことが実現できると思った。
クラスモジュールを用いる
クラスモジュールを挿入して、オブジェクト名をPoweredSheet
にする。
ただのWorksheetにあれこれプロパティやらメソッドやらを追加搭載することになるので、PoweredSheet
。
まずはクラスモジュールのコード。
リスト1 クラスモジュール
'オブジェクト名は「PoweredSheet」' Option Explicit 'Constants' Private Enum ErrMsg emNotAvailable = 1 emNotWorksheet End Enum 'Class Variables' Private isAvailable As Boolean Private exSheet_ As Object 'Properties' Public Property Get ExSheet() As Object Set ExSheet = exSheet_ End Property Public Property Get NormalSheet() As Worksheet Set NormalSheet = exSheet_ End Property Public Property Get A1Value(ByVal index As Long) As Variant If Not isAvailable Then Call raiseError(emNotAvailable) A1Value = exSheet_.Range("A1").Value End Property 'Constructor' Private Sub Class_Initialize() isAvailable = False End Sub Public Function init( _ ByVal targetSheet As Object) As PoweredSheet If TypeName(targetSheet) <> "Worksheet" Then _ Call raiseError(emNotWorksheet) Dim ret As PoweredSheet Set exSheet_ = targetSheet isAvailable = True Set ret = Me Set init = ret End Function 'Methods' Public Sub showA1Value() If Not isAvailable Then Call raiseError(emNotAvailable) Call exSheet_.showA1Value End Sub Private Sub raiseError(ByVal causedBy As ErrMsg) Select Case causedBy Case emNotAvailable Call Err.Raise(10000 + causedBy, getErrorMsg(causedBy)) End Select End Sub Private Function getErrorMsg( _ ByVal causedBy As ErrMsg) As String Dim ret As String Select Case causedBy Case emNotAvailable ret = "使用可能な状態になっていない" Case emNotWorksheet ret = "引数がWorksheetではない" End Select getErrorMsg = ret End Function
エラー吐かせ用の列挙体とかプロシージャまで載っけたので、タテ長になっているのはご容赦を。
めんどくさいので、プロパティとメソッドのみ簡単に説明をば。
まずはExSheet
プロパティ。
これは、シートオブジェクトをそのまま返す。PoweredSheet
に含まれていないプロパティ・メソッドで、シートオブジェクト独自のプロパティ・メソッドを呼ぶときに使う。Object
型なので、当然入力補完は効かない。
次に、NormalSheet
プロパティ。こいつは、シートオブジェクトをWorksheet
型にキャストして返す。シートオブジェクトはObject
型で受け取っているので、ExSheet
プロパティだと入力補完が効かない。通常のWorksheet
オブジェクトのプロパティ・メソッドが使いたい場合は、このNormalSheet
プロパティを利用すれば良い。
あとは、showA1Value
メソッド。単に、シートのA1セルの値をメッセージボックスで表示するだけ。実験なのでこんなアホみたいなメソッドでご勘弁を。
各シートモジュールにメソッドを搭載
ここで、各シートモジュールにshowA1Value
というメソッドを搭載していく。
ちなみに、プロジェクト エクスプローラーはこんな状態。
四つのシートモジュールのオブジェクト名を、順にHoge01
、Hoge02
、Hoge03
、Hoge04
に改めている。
リスト2-1 シートモジュール
'オブジェクト名は「Hoge01」' Public Sub showA1Value() Call MsgBox(Me.Range("A1").Value) End Sub
コチラはシンプルに、シートのA1セルの値を単純にメッセージボックスで表示するだけ。
リスト2-2 シートモジュール
'オブジェクト名は「Hoge02」' Public Sub showA1Value() Dim tmp As String tmp = Me.Range("A1").Value Call MsgBox(tmp & " " & tmp) End Sub
コチラは、シートのA1セルの値を、半角スペースを間にかましてメッセージボックスで二つ表示する。
リスト2-3 シートモジュール
'オブジェクト名は「Hoge03」' Public Sub showA1Value() Dim tmp As String tmp = Me.Range("A1").Value Call MsgBox(tmp & vbCrLf & tmp & vbCrLf & tmp)
コチラは、シートのA1セルの値を、3行にわたって表示する。
同じshowA1Value
というメソッド名だが、挙動が少しづつ異なる。
で、四つ目のシート(オブジェクト名「Hoge04
」)には、showA1Value
を搭載し忘れている。
使ってみる
次のコードで実験。
リスト3 標準モジュール
Public Sub testPoweredSheet() Dim pSh1 As New PoweredSheet '……(1)' Set pSh1 = pSh1.init(Hoge01) Call pSh1.showA1Value Call MsgBox(pSh1.NormalSheet.Name) Dim pSh2 As New PoweredSheet '……(2)' Set pSh2 = pSh2.init(Hoge02) Call pSh2.showA1Value Dim pSh3 As New PoweredSheet '……(3)' Set pSh3 = pSh3.init(Hoge03) Call pSh3.showA1Value Dim pSh4 As New PoweredSheet '……(4)' Set pSh4 = pSh4.init(Hoge04) Call pSh3.showA1Value End Sub
(1)の
Dim pSh1 As New PoweredSheet Set pSh1 = pSh1.init(Hoge01) Call pSh1.showA1Value Call MsgBox(pSh1.NormalSheet.Name)
では、PoweredSheet
クラスのインスタンスにHoge01
オブジェクトをセットして使用。
showA1Value
メソッドを呼んで、その後、NormalSheet
プロパティでWorksheet
オブジェクトとしてのName
プロパティの値をメッセージボックスで表示させる。
(2)の
Dim pSh2 As New PoweredSheet Set pSh2 = pSh2.init(Hoge02) Call pSh2.showA1Value
(3)の
Dim pSh3 As New PoweredSheet Set pSh3 = pSh3.init(Hoge03) Call pSh3.showA1Value
(4)の
Dim pSh4 As New PoweredSheet Set pSh4 = pSh4.init(Hoge04) Call pSh4.showA1Value
は、それぞれPoweredSheet
クラスのインスタンスにHoge02
、Hoge03
、Hoge04
をセットして、showA1Value
を呼び出している。
実行結果
四つのシートが、
この状態で実行。
まず、Call pSh1.showA1Value
が実行され、
Call MsgBox(pSh1.NormalSheet.Name)
が実行され、
Call pSh2.showA1Value
が実行され、
Call pSh3.showA1Value
が実行され、
Call pSh1.showA1Value
が実行され、
Call pSh4.showA1Value
が実行されたところでエラーが出た。
四つ目のシート(Hoge04)には、showA1Value
が搭載されていないから、エラーになる。
おわりに
いちおう、意図どおりにはなったけれど、プロパティ・メソッド未搭載の場合に実行時エラーというのがイマイチだよなあ。