Custom EventをCallbackに変える
Custom EventをCallbackに変える
クラスモジュールを用いて作ったオブジェクトには、イベントを持たせることができる。
今回は、このCustom Eventと同様のことを、別のやり方でやってみる。
Custom Eventの場合は、Event
、RaiseEvent
、WithEvents
という三つのキーワードを用いましたが、今度はインターフェース用のクラスモジュールとImplements
キーワードを用います。
『VBA Developer's Handbook Second Edition』で紹介されていたテクニックです。いつもありがとうございます。
目次
こんなことができます
たとえば、
Private Sub testDekosuke01() Dim ds As Dekosuke Set ds = New Dekosuke Set ds.Callback = New ImmWndCallback Call ds.Say("ち~んw") ds.Name = "諸葛亮" Call ds.Say("他にすることはないのですか?") End Sub
こんなコードを実行するだけで、
こんな動作をさせる裏で、イミディエイトに
こんな風に出力させることができる。
上掲コード中、Dekosuke
クラスのインスタンスds
がSay
メソッドを実行したときに、あたかもイベントが発生したかのように、イミディエイトに文字列が出力されたのである。
もちろん、Custom Eventを使わずに、である。
前述のように、代わりにIDekosukeCallback
というインターフェース用クラスモジュールと、Implements
キーワード、そして、イベントを受け取ってイミディエイトに出力するためのImmWndCallback
クラスモジュールを使う。
準備
Dekosukeクラスを作る
まずは、Dekosuke
クラスを作る。
なるべく単純にするために、インスタンス個別の名前を表すName
プロパティ(read/write)と、引数で与えられた文字列をメッセージボックスで表示するSay(Message)
だけを持つ簡単なクラスにする。
リスト1 クラスモジュールDekosuke
'### オブジェクト名はDekosuke ###' Option Explicit Private m_Name As String Public Property Let Name(ByVal ArgString As String) m_Name = ArgString End Property Public Property Get Name() As String Name = m_Name End Property Public Sub Say(ByVal Message As String) Dim tmp As String tmp = m_Name & "曰く、「" & Message & "」" Call MsgBox(tmp) End Sub Private Sub Class_Initialize() m_Name = "名無しさん" End Sub
見ての通り、シンプルきわまりない。
IDekosukeCallbackインターフェースを作る
次に、イベントリスナに持たせるためのインターフェースIDekosukeCallback
。
リスト2 クラスモジュールIDekosukeCallback
Option Explicit Public Sub AfterSaid(ByVal Message As String, _ Optional ByVal Speaker As String) End Sub
これだけ。とりあえずメソッド一つだけにした。
Dekosuke
オブジェクトのSay
メソッド実行後に実行することを想定している。
DekosukeクラスにCallbackプロパティを生やす
問題はここから。
上掲リスト1のDekosuke
クラスのコードには、特にイベントを発火させるポイントはない。
そこで、このDekosuke
クラスにCallback
というプロパティを生やす。
すると、リスト1は、次のようになる。
リスト1-2 クラスモジュールDekosuke
'### オブジェクト名はDekosuke ###' Option Explicit Private m_Name As String Public Callback As IDekosukeCallback '……(*)' Public Property Let Name(ByVal ArgString As String) m_Name = ArgString End Property Public Property Get Name() As String Name = m_Name End Property Public Sub Say(ByVal Message As String) Dim tmp As String tmp = m_Name & "曰く、「" & Message & "」" Call MsgBox(tmp) End Sub Private Sub Class_Initialize() m_Name = "名無しさん" End Sub
(*)
の部分を追加。これでCallback
というプロパティを追加したことになる。
このCallback
プロパティ、IDekosukeCallback
型にしたところがミソ。
こうすることで、IDekosukeCallback
インターフェースをImplements
したクラスモジュールのインスタンスなら、何でもこのCallback
プロパティに突っ込めるようになったのである!
ImmWndCallbackクラスを作る
さあ、いよいよ、Dekosuke
クラスのCallback
プロパティに突っ込むためのクラスを作成する。
別の言い方をすれば、Dekosuke
オブジェクトのSay
メソッド実行に反応してイミディエイトに文字列を書き出すためのクラスである。
リスト3 クラスモジュールImmWndCallback
Option Explicit Implements IDekosukeCallback Private Sub IDekosukeCallback_AfterSaid(Byval Speaker As String, _ ByVal Message As String) If Speaker = "" Then Speaker = "名無しさん" Dim tmp As String tmp = "「" & Message & "」by " & Speaker Debug.Print tmp End Sub
IDekosukeCallback
インターフェースによって規定されたAfterSaid
メソッドを実装。
引数Message
とSpeaker
で受けとった文字列を組み合わせて、イミディエイトに出力するようにしている。
もちろん、これだけではだめ。
これに伴ってDekosuke
クラスのSay
メソッド側を変更せねばならない。
リスト1-3 クラスモジュールDekosuke
Public Sub Say(ByVal Message As String) Dim tmp As String tmp = m_Name & "曰く、「" & Message & "」" Call MsgBox(tmp) If Not Me.Callback Is Nothing Then '……(**)' Call Me.Callback.AfterSaid(Message, m_Name) End If End Sub
Say
メソッドのメインの処理(メッセージボックスの表示)の後、AfterSaid
メソッドを実行できるように、(**)
からの3行
If Not Me.Callback Is Nothing Then Call Me.Callback.AfterSaid(Message, m_Name) End If
がそれ。
Callback
プロパティにイベントリスナ用のクラスのインスタンスが突っ込まれていなかったら、Callback
プロパティはNothing
なので、何もしない。
Callback
プロパティにイベントリスナ用クラスのインスタンスが突っ込まれていたら、(それはIDekosukeCallback
型である以上、必ずAfterSaid
メソッドを持っているはずなので)ただAfterSaid
メソッドを実行する、という仕掛け。
これで、まるでSay
メソッドの実行を検知してAfterSaid
メソッドが実行されたかのような結果が得られるのである!
使ってみる
最初に示したのと同じだが、次のコードでDekosuke
クラスを使ってみる。
リスト4 標準モジュール
Private Sub testDekosuke01() Dim ds As Dekosuke Set ds = New Dekosuke Set ds.Callback = New ImmWndCallback '……(***)' Call ds.Say("ち~んw") ds.Name = "諸葛亮" Call ds.Say("他にすることはないのですか?") End Sub
ポイントは、(***)
のところ。
Dekosuke
クラスのインスタンスds
を得た後、即座にそのCallback
プロパティにImmWndCallback
クラスのインスタンスを突っ込んでいる。
少々ひつこいが、ImmWndCallback
クラスには、IDekosukeCallback
インターフェースがImplements
されているので、IDekosukeCallback
型のプロパティCallback
に突っ込めるのである。
これで、以後ds.Say
メソッドが実行されるたびにAfterSaid
メソッドが呼ばれることになる。
したがって、実行すると、
画面上はこうなって、
イミディエイトは
こうなる。
おわりに
VBAの特性上、やたら数多くのクラスモジュールを使うことになってしまうが、イベントを次々に連鎖させるような処理をするなら、これも一つの面白いやり方なのではないかと思いました。