#はじめに
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だけ毎回作成して、後は再利用できます。