このドキュメントは下書き&校正/監修 用にアップロードしたものです。角田(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 の ConnectToConnectionPointAttribute ステートメント
        が担います。

        (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モジュール)に只ひとつです。
 -- 通常のイベントコーディング --
 
 クラスモジュール内に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によるイベントコーディングでは
         『イベントソースはひとつだけ』となります。
        そもそも、ConnectToConnectionPointでは、最初から、
              イベントソースひとつに、クラスひとつ
        という設計にします。



    実を言えば、clsBpca でサポートしているイベントは、ConnenctToConnectionPoint だけで全て
    対応できます(単に、Change, Click 等に対応するイベントコードを指定すれば良いだけです)。
    しかし、一般のVBAユーザーも clsBpca クラスのマクロコードを閲覧するという前提に立てば、
    全てがConnectToConnectionPoint で作られているのは好ましい状況ではないと考えます。
    (ここで、このように解説していても、一般のVBAユーザーには理解は難しいでしょう)

    その為、clsBpca では、敢えて、通常のイベントプログラミングが出来るイベント(Enter, Exit,
    BeforeUpdate, AfterUpdate 以外)については、通常のイベントプログラミングでコーディング
    しています。