はじめに
Excel VBAでインターフェースを活用して共通処理をまとめ、再利用できるようにしようとした時のメモです。
本記事のコードは長くなるので疑似コードです。
色々省略しています。
環境
Microsoft Excel 2010
読む人の前提
Excel VBAでクラスモジュールを使用したことがある人。
オブジェクト指向言語の経験者
(私はC#を使っていますので、C#やVB.net、JAVAの感覚が近いかと。)
参考
このサイトは非常に参考になりました。
https://sites.google.com/site/compositiosystemae/home/vbaworld/upper/interface
やりたかったこと
進捗表示のダイアログを表示して、データを処理するたびにバーが伸びていくマクロをいくつか作っていました。
行毎のデータ、シート毎、ファイル毎とまちまちだったので、インターフェースを使用して共通化しました。
詳細
そもそもインターフェースの使い方
VBAでは、クラスの継承はできませんが、インターフェースの実装はできます。
プロシージャの中身をすべて空にすることでインターフェースとなります。
インターフェースで定義されたプロシージャを実装する時は、実装クラス側でImpements [インターフェース名]で宣言し、各プロシージャは[インターフェース名]_[プロシージャ名]で定義します。
呼び出すときは、インターフェース型の変数に格納することで、呼び出しが可能です。
public sub Test
End sub
Implements ISample
public sub ISample_Test
debug.print "A"
end sub
Implements ISample
public sub ISample_Test
debug.print "B"
end sub
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)
public sub Start
CancelForm.show
for r = 1 to end
'r行目に対するなんらかの処理
CancelForm.Increment
DoEvents
next
unload CancelForm
end sub
上記の例は行ごとのデータ用ですが、このクラスが対象データ種ごとにありました。
インターフェースを使用した共通化
インターフェースを定義
行毎、シート毎、ファイル毎は気にせず、データを一つずつ列挙するためのIEnumeratorクラス(インターフェース)を定義します。
型名はC#ライクにIをプリフィックスとします。
public sub Start()
end sub
インターフェースからイベント発生
IEnumerator.Startで開始して、ひとつずつ列挙する時にCancelForm.Incrementを呼びたいので、イベントを使用します。
インターフェースにイベントを定義できないので、イベントを発生させる別オブジェクト経由にします。
Public Event OnAction(sender As IEnumerator)
Public Sub Raise(sender As IEnumerator)
RaiseEvent OnAction(sender)
End Sub
Public Property Get EventObject() As EnumeratorEventObject
End Property
public sub Start()
end sub
IEnumeratorの実装側で、1データ処理するたびに、EventObject.Raise(Me)を呼ぶと、イベントが発生します。
イベントハンドラに処理を移す
ProgressControllerを下記のように変更します。
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
実装例
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
'実際の行ごとの処理は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
例えば標準モジュールからの呼び出し
public sub Main1
dim tool as New ToolMain
tool.Main
end sub
プロシージャの呼び出しと完了を黒、イベントによる通知を赤の矢印で図に表してみました。

まとめ
インターフェースと移譲を利用して、オブジェクト指向の感覚で実装の共通化ができました。
Module1.basとToolMainだけ毎回作成して、後は再利用できます。