Worksheetを継承したクラスを作る(8)
ラップしたオブジェクトのイベントを検知する
めっちゃ久しぶりに
の続き。
ちょっと何言ってるかわからないかも知れないので説明する。
たとえば、Worksheet
オブジェクトをラップしたPoweredSheet
という自作クラスがあるとする。
当然、Worksheet
オブジェクトには数々のイベントがある。
PoweredSheet
にも同様のイベントを生やしたいのだ。
要するに、PoweredSheet
に包まれたWorksheet
オブジェクトで発火したイベントをPoweredSheet
側で検知したい、ということだ。
どうも、イベントについては頭がこんがらがって、うまく説明できる自信がない。(いつか、「Custom Event? ははは。簡単じゃねーか、あんなの!」と笑える日が来るのだろうか。)
自身の覚書のつもりで書き残しておく。
目次
イベントのしくみ
Custom Eventの作成に必要なのは、「Event
」、「RaiseEvent
」、「WithEvents
」の三つのキーワード。
手順としては、
- イベント名の宣言(
Event
キーワード) - イベント発火タイミングの設定(
RaiseEvent
キーワード) - イベントリスナ側にイベントプロシージャを作成(
WithEvents
キーワード)
という順になる。
イベント名の宣言
イベントを生やしたいオブジェクトモジュールの宣言セクション(プロシージャよりも上の部分)に、「Event
」キーワードを用いてイベント名を宣言する。
たとえば、Activate
という名前のイベントを宣言するときは、
Option Explicit Public Event Activate()
と書くだけ。
これで、既にイベントの作成自体はおしまい。
オブジェクト ブラウザーで見ると、
ちゃんとイベントが設定されている。
もちろん、これだけではイベントは発火しない。発火するタイミングがないのだから。
イベント発火タイミングの設定
イベントを発火させるタイミングを設定するには、「RaiseEvent
」キーワードを用いる。
イベントを発火させたい場所に、
RaiseEvent イベント名(引数リスト)
と書くだけ。
たとえば、次のようなshowMessage
というメソッドがあるとする。
Pubilc Sub showMessage(ByVal Message As String) Call MsgBox(Message) Message = Message & vbCrLf & "ち~んw" Call MsgBox(Message) End Sub
で、このプロシージャ内の1回目のMsgBox
実行直後に「Activate
」というイベントを発火させたいとする。
その場合は、
Pubilc Sub showMessage(ByVal Message As String) Call MsgBox(Message) RaiseEvent Activate Message = Message & vbCrLf & "ち~んw" Call MsgBox(Message) End Sub
と書けば良い。
これで、めでたくshowMessage
メソッド実行時の1回目のMsgBox
実行直後にActivate
イベントが発火する。
発火するだけだけど。
発火したイベントを拾う
イベントが発火しても、それを拾うプロシージャがなければ、無視されて終わり。
ちょうど、Worksheet
ではそれこそ四六時中イベントが発火しているにもかかわらず、Worksheet
モジュールにイベントプロシージャを置いていなかったら何も起こらないのと同じ。
イベントを拾う、すなわち、イベントプロシージャを作成するには、「WithEvents
」キーワードを使う。
ただし、
イベントを拾うことができるのは、オブジェクトモジュールに限られる
ということに注意が必要。
標準モジュールではイベントが拾えない。すなわち、「WithEvents
」が使えない。
とりあえず、手近なオブジェクトモジュールであるシートモジュールに書くことにする。
たとえば、シートモジュールの宣言セクションに、
Private WithEvents m_PoweredSheet As PoweredSheet
と書いてみる。
すると、VBEのオブジェクトリストに、
このようにm_PoweredSheet
が追加されているのがわかる。
こいつを選択してやると、
このように、今のところPoweredSheet
クラス唯一のイベントであるActivate
イベントのイベントプロシージャが挿入された。
このm_PoweredSheet_Activate
プロシージャ内にコードを書けば、m_PoweredSheet
にぶち込まれたPoweredSheet
オブジェクト内で発火したイベントを拾って、何らかの動作をさせることができる。
この説明では、シートモジュールによって、PoweredSheet
オブジェクトで発火したイベントを拾った。
このシートモジュールが、「イベントリスナ(Event listener)」である。
よそで起こったイベントを聞き取って反応するやつ、ぐらいの意味だろうか。
以上が、Custom Eventのしくみである。
PoweredSheetにイベント装着
イベントの宣言
これはアホみたいに簡単。
上の「イベント名の宣言」でも書いたように、PoweredSheet
クラスモジュールの宣言セクションに、
Public Event Activate()
と書くだけ。余裕。
イベント発火のタイミング設定
PoweredSheet
オブジェクトは、実在するWorksheet
オブジェクトをラップしているだけなので、PoweredSheet
オブジェクトのActivate
イベントは、実在のWorksheet
オブジェクトのActivate
イベントが発火したときに発火しないといけない。
一見ややこしそうに見えるけど、めっちゃ簡単。
要は、PoweredSheet
オブジェクトが、その中に隠し持っている実在のWorksheet
オブジェクトのイベントリスナになれば良い。
したがって、上の「発火したイベントを拾う」に書いたように、実在のWorksheet
オブジェクトをぶち込む変数「m_RealSh
」の宣言にWithEvents
を付けるだけ。
こうすることによって、
このように、m_RealSh_Activate
というイベントプロシージャを作ることができる。
このm_RealSh_Activate
は、m_RealSh
がActivate
されたときに発動する。
よって、このプロシージャ内でRaiseEvent
キーワードを用いれば、m_RealSh
のActivate
イベントを拾ってPoweredSheet
のActivate
イベントを発火させることができることになる。
つまり、
Private Sub m_RealSh_Activate() RaiseEvent Activate End Sub
こうするわけだ。
整理すると、
m_RealSh
にぶち込まれたWorksheet
でActivate
イベントが発火するPoweredSheet
オブジェクトがm_RealSh
のActivate
イベントを拾うm_RealSh_Activate
プロシージャ内でRaiseEvent Activate
が実行されるPoweredSheet
オブジェクトのActivate
イベントが発火する
このような次第。
イベントリスナをクラスモジュールで作成
クラスモジュールTestListener
を挿入し、次のコードを書く。
リスト1 クラスモジュールTestListener
Option Explicit Private WithEvents m_PoweredSheet As PoweredSheet 'Property' Public Property Get PoweredSheet() As PoweredSheet Set PoweredSheet = m_PoweredSheet End Property 'Constructor' Private Sub Class_Initialize() Set m_PoweredSheet = New PoweredSheet End Sub Public Sub init(ByVal WorksheetEntity As Worksheet) Call m_PoweredSheet.init(WorksheetEntity) End Sub
変数m_PoweredSheet
をWithEvents
キーワード付きで宣言しているので、このオブジェクトはPoweredSheet
オブジェクトのイベントを拾うことができるようになった。
準備は以上。後は実験あるのみ。
実験
次のコードで実験してみる。
ただし、このままではPoweredSheet
のActivate
イベントが無事に発動されたかどうかがわからないので、PoweredSheet
内でRaiseEvent
を実行した直後に、イミディエイトにメッセージを出力することとする。すなわち、PoweredSheet
クラスモジュール内のm_RealSh_Activate
プロシージャを次のようにする。
Private Sub m_RealSh_Activate() RaiseEvent Activate Debug.Print "Activate event has surely been raised..." End Sub
イミディエイトにメッセージが出力されていたら、RaiseEvent Activate
が実行された証となるはずだ。
リスト2 標準モジュール
Private Sub test01() Dim tl As TestListener Set tl = New TestListener Dim Sh As Worksheet Set Sh = PoweredSheetProject.Sh01 Call tl.init(Sh) Debug.Print "Sh02をActivateするよ。" Call PoweredSheetProject.Sh02.Activate Debug.Print "Sh02をActivateしたよ。" Debug.Print "Sh01をActivateするよ。" Call PoweredSheetProject.Sh01.Activate '……(*)' Debug.Print "Sh01をActivateしたよ。" End Sub
変数tl
にぶち込んだTestListener
オブジェクトの内部には、PoweredSheetProject.Sh01
が指し示すWorksheet
オブジェクトがぶち込まれている。
一旦PoweredSheetProject.Sh02
がActivate
され、その後PoweredSheetProject.Sh01
がActivate
されることになる。
(*)のところでイベントが発火するので、「Sh01をActivateするよ。」と「Sh01をActivateしたよ。」の間に、「Activate event has surely raised...」が出力されるはずだ。
上記リスト2を実行すると、
バッチリ。
おわりに
これで、PoweredSheetプロジェクトの完成にまた一歩近づいた。
過去記事
しかしまあ、Custom Eventの実装は、割と頭がこんがらがるなあ。