t-hom’s diary

主にVBAネタを扱っているブログ…とも言えなくなってきたこの頃。

VBA クラスライブラリをアドインにまとめてメインマクロをスッキリさせる方法

今回は公開されている既存のライブラリ「Ariawase」を使って解説したいと思う。

Ariawaseは次のページから入手できる。

github.com

ページを開くと右下のほうにZipでダウンロードできるボタンがあるのでこちらを入手する。
f:id:t-hom:20151001190327p:plain

zipを解凍するとbuild.batファイルがあるのでそれを使ってビルドするのだが、あらかじめExcelでマクロのセキュリティを次のように設定しておく必要がある。
f:id:t-hom:20151001190631p:plain

そしてbuild.batを実行すると、binフォルダが作成され、その中に「Ariawase.xlsm」が入っている。
Ariawaseは便利なライブラリなのだが、以下を見てのとおり、モジュール数が多いのが難点である。
f:id:t-hom:20151001190902p:plain

そもそも自由にモジュールを入れるフォルダを作れたら何も問題はないのだが、VBAでは残念なことに標準モジュール・クラスモジュールなどのデフォルトフォルダの配下をさらに整理することはできない。

そこで今回はこのAriawaseをアドイン化してメインマクロの入ったブックとは分離しようという試みである。

アドイン化自体はすこぶる簡単で、名前を付けて保存する際にファイルの種類でアドインを選ぶだけだ。
f:id:t-hom:20151001191418p:plain

そしてExcelのファイルタブ→オプション→アドイン→設定とたどり、追加したアドインを有効にする。
f:id:t-hom:20151001191608p:plain

次に別ファイルからこのアドインを参照設定する必要があるが、プロジェクト名がVBAProjectのままでは後でどれがAriawaseのアドインかわからなくなるのでプロパティからプロジェクト名をAriawaseに変えておく。
f:id:t-hom:20151001192140p:plain

そしてVBEでBook1に戻ってツールメニューから参照設定を行う。
f:id:t-hom:20151001192423p:plain

すると、プロジェクトエクスプローラーに次のように参照設定が表示される。
f:id:t-hom:20151001192519p:plain

さて、これでたとえばAriawaseのArrayExクラスを使ってみようと思う。

Book1に標準モジュールを追加し、次のようなコードを記入する。

Sub test()
    Dim AE As ArrayEx
End Sub

しかし実行してみると…

エラーがでる。
f:id:t-hom:20151001192834p:plain

クラスモジュールは通常Privateに設定されているため、他のブックから直接インスタンス化するすることはできない。
f:id:t-hom:20151001193013p:plain

しかし設定できるのはPrivateかPublicNotCreatableのみ。
f:id:t-hom:20151001193143p:plain

NotCreatableを選択すると、Newキーワードが使えないので結局インスタンス化できない。

しかしこれを回避する方法はある。
他のブックからインスタンスが作れないなら、Ariawaseアドイン内でインスタンスを生成して返す関数を用意すればよい。

Ariawaseアドイン側に標準モジュールを挿入し、次のコードを張り付ける。

Public Function NewArrayEx() As Object
    Set NewArrayEx = New ArrayEx
End Function

そしてBook1側では、次のように呼び出す。

Sub test()
    Dim AE As Object
    Set AE = Ariawase.NewArrayEx
End Sub

こうすると、ArrayEx自体がプライベートクラスであってもインスタンスを得ることができる。

しかしこのままではコーディング中にプロパティなどのヒントが出ないので使い勝手が悪い。

これは、インターフェースを使うことで回避できる。

まずAriawase側に新しいクラスモジュールを追加し、IArrayExとしておく。
IArrayExには次のコードを書いて、プロパティウインドウでPublicNotCreatableにしておく。

Option Explicit

Public Function AddVal(ByVal val As Variant)
End Function

Public Function AddObj(ByVal obj As Variant)
End Function

Public Function ToArray() As Variant
End Function

そして、元のArrayExクラスのOption Explictの下あたりに「Implements IArrayEx」と追記し、さらにPublic Funcitonの名前の先頭に、IArrayEx_を付与する。

改造後のコードはこちら。
著作権は、いげ太さんが保有します。再掲される場合はMITライセンスにしたがってください。)

'Copyright (c) 2011-2015 igeta
'''+----                                                                   --+
'''|                             Ariawase 0.6.0                              |
'''|                Ariawase is free library for VBA cowboys.                |
'''|          The Project Page: https://github.com/vbaidiot/Ariawase         |
'''+--                                                                   ----+
Option Explicit
Implements IArrayEx

Private xItems As Variant
Private xLength As Long
Private xIndex As Long

Private Sub Class_Initialize()
    xIndex = -1
    xLength = -1 + 32
    ReDim xItems(xLength - 1)
End Sub

Private Sub Extend()
    If xIndex < xLength Then GoTo Escape
    xLength = xLength + 1 'possible overflow (Err.Raise 6)
    xLength = -1 + xLength + xLength
    ReDim Preserve xItems(xLength - 1)
    
Escape:
End Sub

Public Function IArrayEx_AddVal(ByVal val As Variant)
    xIndex = xIndex + 1
    Extend
    Let xItems(xIndex) = val
End Function

Public Function IArrayEx_AddObj(ByVal obj As Variant)
    xIndex = xIndex + 1
    Extend
    Set xItems(xIndex) = obj
End Function

Public Function IArrayEx_ToArray() As Variant
    Dim arr As Variant: arr = xItems
    If xIndex > -1 Then
        ReDim Preserve arr(xIndex)
    Else
        arr = Array()
    End If
    IArrayEx_ToArray = arr
End Function


さて、これでアドイン側の準備はできた。

そしてBook1で次のコードを入力してみよう。

Sub test()
    Dim AE As IArrayEx
    Set AE = Ariawase.NewArrayEx
    AE.AddVal 1
    AE.AddVal 3
    AE.AddVal 5
    AE.AddVal 7
    AE.AddVal 9
    For Each x In AE.ToArray
        Debug.Print x
    Next
End Sub

すると、入力途中でプロパティなどの候補が表示されることに気付いたかもしれない。
f:id:t-hom:20151001200640p:plain

この場合のインターフェースは「取り決め・規約」といった意味を持つ。
インターフェースがなければ、Book1はAriawaseで生成されたブジェクトを汎用オブジェクト型として受け取ることになる。
その結果、どのようなメソッドやプロパティがあるのかVBコンパイラでは判断できない。

つまり、インターフェースが無ければ、コードの正しさは、プログラマーが保証しなければならない。
もしインターフェースがあれば、コードの正しさはコンパイラが保証してくれる。

プログラムが複雑になればなるほど、インターフェースの恩恵は大きい。

ということで、最後のほうはインターフェースの紹介に終わってしまったが、まとめると、ライブラリとメインコードを分離するとスッキリする、またVBプロジェクト間のオブジェクト受け渡しでインターフェースが活躍するという話である。

当ブログは、amazon.co.jpを宣伝しリンクすることによってサイトが紹介料を獲得できる手段を提供することを目的に設定されたアフィリエイト宣伝プログラムである、 Amazonアソシエイト・プログラムの参加者です。