シートモジュールへのインターフェース実装の代案

シートモジュールへのインターフェース実装の代案

ごく一部の(?)VBAerの間では、「シートモジュールにインターフェースをImplementsすると派手にバグる」というのは有名だと思う。「ごく一部で有名」であるということを「有名」と称するのかどうかはともかくとして。

参考

akashi-keirin.hatenablog.com

akashi-keirin.hatenablog.com

大規模な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というメソッドを搭載していく。

ちなみに、プロジェクト エクスプローラーはこんな状態。

f:id:akashi_keirin:20190310163714j:plain

四つのシートモジュールのオブジェクト名を、順にHoge01Hoge02Hoge03Hoge04に改めている。

スト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クラスのインスタンスHoge02Hoge03Hoge04をセットして、showA1Valueを呼び出している。

実行結果

四つのシートが、

f:id:akashi_keirin:20190310163717j:plain

f:id:akashi_keirin:20190310163721j:plain

f:id:akashi_keirin:20190310163727j:plain

f:id:akashi_keirin:20190310163733j:plain

この状態で実行。

f:id:akashi_keirin:20190310163736j:plain

まず、Call pSh1.showA1Valueが実行され、

f:id:akashi_keirin:20190310163744j:plain

Call MsgBox(pSh1.NormalSheet.Name)が実行され、

f:id:akashi_keirin:20190310163751j:plain

Call pSh2.showA1Valueが実行され、

f:id:akashi_keirin:20190310163755j:plain

Call pSh3.showA1Valueが実行され、

f:id:akashi_keirin:20190310163757j:plain

Call pSh1.showA1Valueが実行され、

Call pSh4.showA1Valueが実行されたところでエラーが出た。

四つ目のシート(Hoge04)には、showA1Valueが搭載されていないから、エラーになる。

おわりに

いちおう、意図どおりにはなったけれど、プロパティ・メソッド未搭載の場合に実行時エラーというのがイマイチだよなあ。