Excel VBAのインターフェースを活用して、マクロの進捗表示機能を再利用可能にする。

#はじめに

Excel VBAでインターフェースを活用して共通処理をまとめ、再利用できるようにしようとした時のメモです。

本記事のコードは長くなるので疑似コードです。
色々省略しています。

#環境
Microsoft Excel 2010

#読む人の前提
Excel VBAでクラスモジュールを使用したことがある人。
オブジェクト指向言語の経験者
　(私はC#を使っていますので、C#やVB.net、JAVAの感覚が近いかと。)

#参考

このサイトは非常に参考になりました。
>https://sites.google.com/site/compositiosystemae/home/vbaworld/upper/interface

#やりたかったこと

進捗表示のダイアログを表示して、データを処理するたびにバーが伸びていくマクロをいくつか作っていました。
行毎のデータ、シート毎、ファイル毎とまちまちだったので、インターフェースを使用して共通化しました。

#詳細

##そもそもインターフェースの使い方
VBAでは、クラスの継承はできませんが、インターフェースの実装はできます。
プロシージャの中身をすべて空にすることでインターフェースとなります。
インターフェースで定義されたプロシージャを実装する時は、実装クラス側で`Impements [インターフェース名]`で宣言し、各プロシージャは`[インターフェース名]_[プロシージャ名]`で定義します。
呼び出すときは、インターフェース型の変数に格納することで、呼び出しが可能です。

```ISample.cls
public sub Test
End sub
```

```A.cls
Implements ISample

public sub ISample_Test
    debug.print "A"
end sub
```
```B.cls
Implements ISample

public sub ISample_Test
    debug.print "B"
end sub
```

```Main.bas
dim aa as New A
dim bb as New B
dim sample as ISample

set sample = aa
sample.Test   '結果：A

set sample = bb
sample.Test   '結果：B
```
##共通化する前
`ProgressController`というクラスを定義し、`start`すると、中でループを回し、
`CancelForm`というフォームのProgressBarの値を1ずつ足しています。(`cancelForm.Imcrement`)



```ProgressController.cls
public sub Start
    CancelForm.show
    for r = 1 to end
        'r行目に対するなんらかの処理
        CancelForm.Increment
        DoEvents
    next
    unload CancelForm
end sub
```
上記の例は行ごとのデータ用ですが、このクラスが対象データ種ごとにありました。
##インターフェースを使用した共通化
###インターフェースを定義
行毎、シート毎、ファイル毎は気にせず、データを一つずつ列挙するためのIEnumeratorクラス(インターフェース)を定義します。
型名はC#ライクにIをプリフィックスとします。

```IEnumerator.cls
public sub Start()
end sub
```

###インターフェースからイベント発生
IEnumerator.Startで開始して、ひとつずつ列挙する時に`CancelForm.Increment`を呼びたいので、イベントを使用します。
インターフェースにイベントを定義できないので、イベントを発生させる別オブジェクト経由にします。

```EnumeratorEventObject.cls
Public Event OnAction(sender As IEnumerator)
Public Sub Raise(sender As IEnumerator)
    RaiseEvent OnAction(sender)
End Sub
```
```IEnumerator.cls
Public Property Get EventObject() As EnumeratorEventObject
End Property
public sub Start()
end sub
```

IEnumeratorの実装側で、1データ処理するたびに、`EventObject.Raise(Me)`を呼ぶと、イベントが発生します。

###イベントハンドラに処理を移す

ProgressControllerを下記のように変更します。

```ProgressController.cls
private withevents eventObject as EnumeratorEventObject

public sub Start(enumerator as IEnumerator)
    set eventObject = enumerator.EventObject
    cancelform.show
    '列挙開始
    enumerator.Start
    '全て列挙完了したらCancelFormを閉じる
    unload cancelForm
end sub

'データごとに呼び出されるイベントハンドラ
private sub eventObject_OnAction(sender as IEnumerator)
    cancelForm.Increment
    DoEvents
end sub
```

###実装例

```ExcelRowEnumerator.cls
Implements IEnumerator

public event Action(sender as ExcelRowEnumerator)
'対象シート
private m_Sheet as WorkSheet
public property set Target(value as WorkSheet)
    set m_Sheet = value
end property
'現在行
private m_CurrentRow as integer
public property get CurrentRow as integer
    CurrentRow = m_CurrentRow
end property


'初期化
Private Sub Class_Initialize()
    Set m_EventObject = New EnumeratorEventObject
End Sub

'列挙開始
public sub Start()
    dim i as integer

    for i = 1 to 100       '実際は、ここはプロパティ化する
        m_CurrentRow = i

        Action Me
        DoEvents
        m_EventObject.Raise Me
    next    
end sub

'以下、IEnumeratorの実装
Private Property Get IEnumerator_EventObject() As EnumeratorEventObject
    Set IEnumerator_EventObject = m_EventObject
End Property
Private Sub IEnumerator_Start()
    Me.Start
End Sub
```
```ToolMain.cls
'実際の行ごとの処理はExcelRowEnumeratorのイベントで処理する
private withevents rowEnumerator1 as ExcelRowEnumerator

public sub Main()
    set rowEnumerator1 = new ExcelRowEnumerator

    dim progressController as ProgressController
    set progressController = New ProgressController

    progressController.Start rowEnumerator1
end sub

private sub rowEnumerator1_Action(sender as ExcelRowEnumerator)
    'ここにアプリケーションとして、行ごとの処理を書く
end sub
```

例えば標準モジュールからの呼び出し

```Module1.bas
public sub Main1
    dim tool as New ToolMain
    tool.Main
end sub
```

プロシージャの呼び出しと完了を黒、イベントによる通知を赤の矢印で図に表してみました。
![ProgressController.png](https://qiita-image-store.s3.amazonaws.com/0/244907/3e7483cb-f12d-d6a8-2722-afe0d33ebf61.png)






#まとめ

インターフェースと移譲を利用して、オブジェクト指向の感覚で実装の共通化ができました。

Module1.basとToolMainだけ毎回作成して、後は再利用できます。