このドキュメントは下書き&校正/監修 用にアップロードしたものです。角田(2014/8/5)
This document has been uploaded for drafting & proofreading / supervision. (Japanese / Aug. 5, 2014)
正式ドキュメントは [ AddinBox / 擬似からの脱却§12 ] として下記リンクとなります。
http://addinbox.sakura.ne.jp/Bpca_Common.htm#C2CP ( 汎用クラス clsBpca ページ内 )
( English official website : http://addinbox.sakura.ne.jp/Breakthrough_P-Ctrl_Arrays_Eng_ref.htm#C2CP )
正式ドキュメントのリリース後に削除予定でしたが、Google 検索に載っていて、訪問される方が
居るようなので、このまま残しています。是非、正式ドキュメントの方もご覧ください。
(2014/8/5 22:30 Abyss さんの指摘を基に修正)
(2014/8/6 16:10 yayadon さんの指摘を基に修正)
(2014/8/7 15:10 Invoke メソッドと、その周辺の解説を追加)
※ Ver 2.0 より、Enter/Exit/BeforeUpdate/AfterUpdate が利用可能になりました。
通常、VBAのクラスモジュールでは、MsForms コントロールの Enter/Exit/BeforeUpdate/AfterUpdate の
イベントを扱う事ができません。 今回、API ( ConnectToConnectionPoint ) を利用する事で、イベントを受け
取れるようになりました。 ( 後述 )
:
:
「後述」の部分が下記です。
API : ConnectToConnectionPoint によるイベント処理の構築
通常のイベント処理のプログラミングとの比較をしつつ説明します。
〜 VBAにおける通常のイベント処理のプログラミング 〜
(a) イベントソース(UserFormに配置されたコントロール、WithEvents 付きのオブジェク
ト変数)に対するイベントハンドラ(Enter , Click 等のイベントプロシジャー)を、下記
の命名ルールに基づく名前のプロシジャーとして用意します。
Sub イベントソース名 + アンダースコア + イベント名 ( 引数1, 引数2, …… )
UserForm上のコマンドボタン( CommandButton1 )のClick イベントの場合
Private Sub CommandButton1_Click ( )
UserForm上のテキストボックス( TextBox1 )の Exit イベントの場合
Private Sub TextBox1_Exit ( ByVal Cancel As MSForms.ReturnBoolean )
clsTimer というクラスの TimeOut という名前のイベントの場合
Private WithEvents TimerCtrl As clsTimer
Private Sub TimerCtrl_TimeOut ( 引数 )
(b) そのプロシジャーの中に、そのイベントで実行する処理を記述する。
(c) 「イベントの発生」 と 「イベントハンドラの実行」の間を繋ぐのはVBAシステムが
担ってくれます(VBAは、イベントハンドラの命名ルールを基にして両者を紐付け
します。)
(d) ひとつのクラスモジュール(または、UserFormモジュール)にイベントソースとなる
コントロールオブジェクトは幾つあっても構わない(イベントハンドラが「イベントソー
ス名+イベント名」なので互いに区別できる)。
〜 API : ConnectToConnectionPoint によるイベント処理のプログラミング 〜
先ずは、ConnectToConnectionPoint によるイベントコーディングを見て貰いましょう。
(ConnectToConnectionPoint の使い方を見る為だけですので、単純にしています)
UserForm に テキストボックスを4個(TextBox1 〜 TextBox4)用意します。クラスモジュール(clsC2CP)内で、
Enter, Exit, BeforeUpdate, AfterUpdate イベントを受けてDebug.Print によりイミディエートウィンドウに実行
状況を出力します。 単純化する為に、イベント通知をUserFormに上げる処理は組み込んでいません。
【 UserForm1 モジュール 】
' UserFormにイベントを通知する処理を組み込んでいない(WithEvents は不要)ので、
' クラスオブジェクトを配列で定義しています。
Private aryTextBox( 1 To 4 ) As clsC2CP
Private Sub UserForm_Initialize( )
Dim i As Integer
For i = 1 To 4
Set aryTextBox( i ) = New clsC2CP
aryTextBox( i ).Item = Me.Controls( "TextBox" & i )
aryTextBox( i ).Index = i
Next i
End Sub
Private Sub UserForm_Terminate( )
Dim i As Integer
For i = 1 To 4
aryTextBox( i ).Clear
Next i
Erase aryTextBox
End Sub
' ConnectToConnectionPoint の動作確認をするだけなので、
' UserForm にイベントを通知する処理は省いています。
' (Debug.Print はクラスモジュール内で行なっています)
【 clsC2CP クラスモジュール 】
(ConnectToConnectionPoint の動作例ですので、 それを略してクラスモジュールの名前を C2CP としています)
赤字は エクスポートファイル上で書き込む(コードウィンドウ上には表示されない)。
' API 定義 [ ConnectToConnectionPoint ]
Private Type GUID
Data1 As Long
Data2 As Integer
Data3 As Integer
Data4( 0 To 7 ) As Byte
End Type
#If VBA7 And Win64 Then
Private Declare PtrSafe Function ConnectToConnectionPoint Lib "shlwapi" Alias "#168" _
(ByVal punk As stdole.IUnknown, ByRef riidEvent As GUID, _
ByVal fConnect As Long, ByVal punkTarget As stdole.IUnknown, _
ByRef pdwCookie As Long, Optional ByVal ppcpOut As LongPtr) As Long
#Else
Private Declare Function ConnectToConnectionPoint Lib "shlwapi" Alias "#168" _
(ByVal punk As stdole.IUnknown, ByRef riidEvent As GUID, _
ByVal fConnect As Long, ByVal punkTarget As stdole.IUnknown, _
ByRef pdwCookie As Long, Optional ByVal ppcpOut As Long) As Long
#End If
Private Cookie As Long 'pdwCookie of ConnectToConnectionPoint
'-----------------------------------------------------------
Private MyCtrl As Object 'Event Source
Private MyIndex As Integer
Private Sub ConnectEvent( ByVal Connect As Boolean )
Dim IID_IDispatch As GUID
' GUID {00020400-0000-0000-C000000000000046}
With IID_IDispatch
.Data1 = &H20400
.Data4( 0 ) = &HC0
.Data4( 7 ) = &H46
End With
Call ConnectToConnectionPoint _
( Me, IID_IDispatch, Connect, MyCtrl, Cookie, 0& )
End Sub
Public Property Let Index( NewIndex As Integer )
MyIndex = NewIndex
End Property
Public Property Let Item( NewCtrl As Object )
Set MyCtrl = NewCtrl
Call ConnectEvent( True ) 'Start Connect Event
End Property
Public Sub Clear( )
If ( Cookie <> 0 ) Then
Call ConnectEvent( False ) 'Finish Connect Event
End If
Set MyCtrl = Nothing
End Sub
'=== Event Handler ===
' イベントソースから呼び出されるプロシジャーなので 【 Public 】にします。
' List of VB_UserMemId
' Enter &H80018202 = -2147384830
' Exit &H80018203 = -2147384829
' BeforeUpdate &H80018201 = -2147384831
' AfterUpdate &H80018200 = -2147384832
Public Sub HookEnter( )
Attribute HookEnter.VB_UserMemId = -2147384830
Debug.Print MyCtrl.Name & "(" & MyIndex & ") [Enter] Value=" & MyCtrl.Value
End Sub
Public Sub HookExit( ByVal Cancel As MSForms.ReturnBoolean )
Attribute HookExit.VB_UserMemId = -2147384829
Debug.Print MyCtrl.Name & "(" & MyIndex & ") [Exit] Value=" & MyCtrl.Value
End Sub
Public Sub HookBeforeUpdate( ByVal Cancel As MSForms.ReturnBoolean )
Attribute HookBeforeUpdate.VB_UserMemId = -2147384831
Debug.Print MyCtrl.Name & "(" & MyIndex & ") [BeforeUpdate] Value=" & MyCtrl.Value
End Sub
Public Sub HookAfterUpdate( )
Attribute HookAfterUpdate.VB_UserMemId = -2147384832
Debug.Print MyCtrl.Name & "(" & MyIndex & ") [AfterUpdate] Value=" & MyCtrl.Value
End Sub
【 実行結果 : イミディエートウィンドウ 】
TextBox1(1) [Enter] Value=
TextBox1(1) [BeforeUpdate] Value=1
TextBox1(1) [AfterUpdate] Value=1
TextBox1(1) [Exit] Value=1
TextBox2(2) [Enter] Value=
TextBox2(2) [BeforeUpdate] Value=aa
TextBox2(2) [AfterUpdate] Value=aa
TextBox2(2) [Exit] Value=aa
TextBox3(3) [Enter] Value=
TextBox3(3) [BeforeUpdate] Value=3456
TextBox3(3) [AfterUpdate] Value=3456
TextBox3(3) [Exit] Value=3456
TextBox4(4) [Enter] Value=
TextBox4(4) [BeforeUpdate] Value=qwer
TextBox4(4) [AfterUpdate] Value=qwer
TextBox4(4) [Exit] Value=qwer
TextBox1(1) [Enter] Value=1
TextBox1(1) [Exit] Value=1
(A) イベントハンドラ(イベントプロシジャー)の名前は任意です。イベントソースの名前を
付する必要はありません。
※ 接頭辞としてイベントソースの名前を付したとしても、その事自体には何の意味もありません。
それは、あくまでも、プログラムの可読性の向上に寄与するだけです。
(B) そのプロシジャーの中に、そのイベントで実行する処理を記述するという点は同じです。
(C) 「イベントの発生」 と 「イベントハンドラの実行」の間を繋ぐ役割りを
API の ConnectToConnectionPoint と Attribute ステートメント
が担います。
(i) ConnectToConnectionPoint は、『イベントソースが発信するイベントメッセージ』が、
「どのオブジェクトで受信される」のかを、イベントソースに知らせ(、登録し)ます。
Call ConnectToConnectionPoint _
( Me, IID_IDispatch, Connect, MyCtrl, Cookie, 0& )
I I
+-----------------------------+
MyCtrl (イベントソース) のイベントメッセージを
Me (例では clsC2CPクラスオブジェクト) で受け取ります、という宣言。
※ イベントメッセージを受信するオブジェクトは、Sink (シンク)オブジェクト または イベントシンク 等と呼ば
れます(次々と流れ出る水(イベントメッセージ)を受け止める流し台(イベントシンク)という意味です)。
※ 実際には、
・ イベントメッセージを受け取り
・ そのメッセージに対応するイベントハンドラーを探し出して
・ 見つかったイベントハンドラーに処理を引き継ぐ
という役目を Invoke メソッド というものが担っています(VBAユーザーからは一切見えない部分です。
マクロにもその姿はありません。詳細はMSDNライブラリ等を閲覧して下さい)
※ VBAのObject 型(および クラスモジュール) には、この Invoke メソッド の機能が備わって いますので、
ConnectToConnectionPoint の引数として指定する事ができます。
(ii) Attribute ステートメントは、イベントの種別を表す値をプロシジャーに付与します(上記
サンプルマクロ参照)。その付与によって、そのプロシジャーは指定した種別のイベントを
受信するイベントハンドラー(イベントプロシジャー)として宣言されます。
※ Attribute ステートメントは、イベントハンドラ (イベントプロシジャー) の Sub ステートメントの直下に記述します。
(コードウィンドウからは書き込めません。エクスポートファ イル上で書き込み、その後インポートします)。
(iii) イベントソースが発信する各種のイベントメッセージにも、各々そのイベントを表す値が含
まれています。上記の Invoke メソッドが、メッセージのイベント種別と同じ値を付与されて
いるプロシジャーを探し出して(この為にAttributeステートメントによってイベント種別を設
定しています)、そのプロシジャーに処理を引き継ぎます。
ConnectToConnectionPoint を使ったイベント処理では、このようにしてイベントソース と
イベントハンドラーの間を繋いでいます。
(D) Attribute ステートメントには「受信するイベントの種別」しか定義されていないので、
ConnectToConnectionPoint で扱えるイベントソースとなるコントロールオブジェクトは、
ひとつのクラスモジュール(または、UserFormモジュール)に只ひとつです。
このように、ConnectToConnectionPointによるイベントコーディングでは
-- 通常のイベントコーディング --
クラスモジュール内に2つのイベントソースがあっても
各々に対応したイベントハンドラを記述できる
Private WithEvents TBox1 As MsForms.TextBox
Private WithEvents TBox2 As MsForms.TextBox
Private Sub TBox1_Enter ( )
End Sub
Private Sub TBox2_Enter ( )
End Sub
-- ConnectToConnectionPointによるイベントコーディング --
クラスモジュール内に2つのイベントソースがあると……
Private TBox3 As Object
Private Cookie3 As Long
Private TBox4 As Object
Private Cookie4 As Long
ConnectToConnectionPoint _
Me, IID_IDispatch, Connect, TBox3, Cookie3, 0&
ConnectToConnectionPoint _
Me, IID_IDispatch, Connect, TBox4, Cookie4, 0&
↑ ここまでは個々で記述が出来たとしても……
↓ Attribute にはイベントの種別しか指定しないので
イベントハンドラを2つ記述しても、どちらがどちらに対応するのか区別できない
(TBox3 のイベントハンドラーは HookEnterA ? それとも HookEnterB ? )
Public Sub HookEnterA ( )
Attribute HookEnterA.VB_UserMemId = -2147384830
End Sub
Public Sub HookEnterB ( )
Attribute HookEnterB.VB_UserMemId = -2147384830
End Sub
『イベントソースはひとつだけ』となります。
そもそも、ConnectToConnectionPointでは、最初から、
イベントソースひとつに、クラスひとつ
という設計にします。
実を言えば、clsBpca でサポートしているイベントは、ConnenctToConnectionPoint だけで全て
対応できます(単に、Change, Click 等に対応するイベントコードを指定すれば良いだけです)。
しかし、一般のVBAユーザーも clsBpca クラスのマクロコードを閲覧するという前提に立てば、
全てがConnectToConnectionPoint で作られているのは好ましい状況ではないと考えます。
(ここで、このように解説していても、一般のVBAユーザーには理解は難しいでしょう)
その為、clsBpca では、敢えて、通常のイベントプログラミングが出来るイベント(Enter, Exit,
BeforeUpdate, AfterUpdate 以外)については、通常のイベントプログラミングでコーディング
しています。