Custom Event 入門

Custom Eventに挑戦

存在は知っていたけど、実はやったことなかった。

VBA Developer's Handbook Second Edition』の説明が実にわかりやすかったので、ちょっとやってみようと思った。

まあ、単なる覚書です。

手順

今回は、三つのクラスモジュールを用いる。

  1. Ahoクラス
    アホを表すクラス(笑)。
  2. Ahoesクラス
    Ahoクラスのインスタンスの集合体を表すクラス。このクラスにイベントを装備する。
  3. AhoTestクラス
    Ahoesオブジェクトでイベントが起こったときに、そのイベント発生を検知して処理を行うクラス。

うーむ、わかりにくい。

もう少し詳しくやろうとしていることを述べる。

AhoesオブジェクトにAhoオブジェクトを追加するAddメソッドを持たせる。

AhoesオブジェクトでAddメソッドが実行されると、Addedイベントが起こるようにする。

AhoTestオブジェクトにAddedイベントを検知したときに実行されるイベントプロシージャを置く。

これで、AhoTestオブジェクト内の処理でAhoesクラスのAddメソッドを実行したときに、イベントプロシージャが実行されることになる。

うーむ、やっぱりわかりにくい。

Ahoクラスを作る

まずは、Ahoクラスを作る。

クラスモジュール Aho
Option Explicit

Private name_ As String

Public Property Get Name() As String
  Name = name_
End Property

Public Sub init(ByVal name__ As String)
  name_ = name__
End Sub

Public Sub introduceMyself()
  Dim msg As String
  msg = "アホ、アホ、アホの" & Me.Name & "~♪"
  Call MsgBox(msg)
End Sub

initメソッドで名前を設定する。

Nameプロパティを持ち、introduceMyselfメソッドで素敵な自己紹介をするクラス。

Ahoesクラスを作る

次に、先ほど作成したAhoクラスのインスタンスを大量に保持することのできるAhoesクラスを作成する。

クラスモジュール Ahoes
Option Explicit

'Attribute Item.VB_UserMemId = 0        設定済み'
'Attribute NewEnum.VB_UserMemId = -4    設定済み'

'Event declarations'
Public Event Added(ByVal AhoName As String)  '……(1)'

'Module level variables'
Private ahoes_ As Collection

'Properties'
Public Property Get Count() As Long
  Count = ahoes_.Count
End Property

'Constructor'
Private Sub Class_Initialize()
  Set ahoes_ = New Collection
End Sub

'Methods'
Public Function Item(ByVal Index As Variant) As Aho
  Set Item = ahoes_.Item(Index)
End Function

Public Sub Add(ByVal AhoName As String, _
      Optional ByVal Key As Variant, _
      Optional ByVal Before As Variant, _
      Optional ByVal After As Variant)
  Dim item_ As New Aho
  Call item_.init(AhoName)
  'Fire event'
  RaiseEvent Added(AhoName)  '……(2)'
  Call ahoes_.Add(item_, Key, Before, After)
End Sub

Public Sub Remove(ByVal Index As Variant)
  Call ahoes_.Remove(Index)
End Sub

Public Function NewEnum() As IUnknown
  Set NewEnum = ahoes_.[_NewEnum]
End Function

組み込みのCollectionクラスをラップしたクラスにしている。

Itemメソッドをデフォルトメンバにしたり、NewEnumメソッドを実装して内部Collectionahoes_の要素(Ahoオブジェクト)をFor Each ~ Nextで回せるようにしたりしているのだが、今回は関係ないのでパス。

ポイントは二つ。

まず宣言セクションにある、(1)の

Public Event Added(ByVal AhoName As String)

というやつ。

これがイベント宣言。「このクラスはこんなイベントを持っています」というもの。引数を持たせることができるので、イベントを検知して実行するプロシージャに引数を渡すことができる。

もう一つが、イベントを発生させたいAddイベント内にある(2)の

RaiseEvent Added(AhoName)

というやつ。

もう読んで字の如しで、この行が実行されると「イベント発生!」ということになる。『VBA Developer's Handbook Second Edition』では「Fire event」と説明されていた。

まさに、「発火」というイメージ。

AhoTestクラスを作る

さて、ここまででAddedイベントが起こるようにすることはできた。

Ahoesクラスのインスタンスを作成して、Addメソッドを実行すれば、めでたくAddedイベントは起こる。

しかし、このままではただイベントが起こるだけ。人知れずイベントが発生して終了し、見かけ上は何も起こらない。

イベントが起こったことを検知して、何らかの処理を行わねばならぬ。

そのために、イベントに対応して処理を行うクラスを作る。

それが、AhoTestクラスである。

クラスモジュール AhoTest
Option Explicit

Private WithEvents AhoesObject As Ahoes  '……(3)'

Public Property Get Ahoes() As Ahoes
  Set Ahoes = AhoesObject
End Property

Private Sub Class_Initialize()
  Set AhoesObject = New Ahoes
End Sub

Private Sub AhoesObject_Added( _
        ByVal AhoName As String)  '……(4)'
  Dim msg As String
  msg = "アホの" & AhoName & vbCrLf
  msg = msg & msg & msg & msg
  msg = msg & "アホの" & AhoName & "~~~~♪" & vbCrLf
  msg = msg & "おれぁ、そういう男よ。"
  Debug.Print msg
End Sub

ポイントは二つ。

まず、宣言セクションにある(3)の

Private WithEvents AhoesObject As Ahoes

こいつ。

Ahoesクラスのインスタンスを保持するためのモジュールレベル変数なんだが、WithEventsというキーワードが付いている。

こうすることで、AhoesObjectは、単なるAhoesクラスのインスタンスではなく、Ahoesオブジェクトで起こったイベントを検知することのできるちょっと特別なインスタンスになるのだ!

そして、(4)の

Private Sub AhoesObject_Added( _
        ByVal AhoName As String)
  Dim msg As String
  msg = "アホの" & AhoName & vbCrLf
  msg = msg & msg & msg & msg
  msg = msg & "アホの" & AhoName & "~~~~♪" & vbCrLf
  msg = msg & "おれぁ、そういう男よ。"
  Debug.Print msg
End Sub

こいつが、Addedイベントが検知されたときに実行されるプロシージャ。

AhoesObjectという変数にぶちこまれたAhoesオブジェクトでAddメソッドが実行されると、こいつが実行されるのだ。

使ってみる

次のコードで使ってみる。

リスト1
Private Sub test02()
  Dim ahoAho As AhoTest
  Set ahoAho = New AhoTest
  Call ahoAho.Ahoes.Add("坂田")
End Sub

AhoTestクラスのインスタンスを作成し、AhoTestクラスのAhoesプロパティでAhoesオブジェクトを取得してAddメソッドを実行する。

AhoTestクラス経由で[Ahoes].Addメソッドを実行することによって、イベントを検知してAhoesObject_Addedを実行させようという目論見。

うーむ、やっぱりわかりにくい。

よって、単純に実行結果をお目にかけるよりも、ステップ実行の様子をお目にかけた方がよかろう。

f:id:akashi_keirin:20200224074928g:plain

こんな感じ。

おわりに

標準モジュールでWithEventsキーワードを用いた変数宣言ができたら、もうちょっと簡単に説明ができるのだが、残念ながら標準モジュールでは不可。

VBA Developer's Handbook Second Edition』には、

You can only use WithEvents with variables declared at the module level and only within class modules. The reason for this is that VBA uses COM to supply your project with events, and COM requires that both event generators and event listeners be objects, thus the need for class modules.

と書いてある。

尤も、上記引用文中の"class modules"にはシートモジュールも含まれるので、今回の例の場合、別にAhoTestクラスなんぞ作らずとも、シートモジュールを使えば同じことができます。

スト2 シートモジュールに記述
Option Explicit

Private WithEvents AhoesObject As Ahoes

Private Sub test()
  Set AhoesObject = New Ahoes
  Call AhoesObject.Add("坂田")
End Sub

Private Sub AhoesObject_Added(ByVal AhoName As String)
  Dim msg As String
  msg = "アホの" & AhoName & vbCrLf
  msg = msg & msg & msg & msg
  msg = msg & "アホの" & AhoName & "~~~~♪" & vbCrLf
  msg = msg & "おれぁ、そういう男よ。"
  Debug.Print msg
End Sub

それにしても、わかりにくいなあ。

もっとわかりやすい説明が思いついたら、書き直そう。