12

オブジェクトがコレクションに含まれている場合、そのオブジェクトは引き続き親クラスにイベントを発生させることができますか?

明らかに、子クラスに親クラスへの参照を伝えてから、子クラス内の親クラス内でパブリック メソッドを呼び出すことができますが、それは循環参照になり、私が理解しているようにガベージ コレクターになります。どちらのオブジェクトも取り除かれません。

詳細: 2 つのクラスがあります。1 つは clsPerson という名前の人物で、もう 1 つは clsPeople という名前のカスタム コレクション クラスです。clsPerson には、Selected という名前のパブリック ブール プロパティがあります。選択が変更された場合は、イベント SelectedChange を呼び出します。その時点で、clsPeople で何かをする必要があります。カスタム コレクション クラス clsPeople でイベントをトラップするにはどうすればよいですか? person クラスは People の範囲外から変更できます。それ以外の場合は、別の解決策を検討します。

<<Class clsPerson>>
Private pSelected as boolean

Public Event SelectedChange()

Public Property Let Selected (newVal as boolean)
  pSelected = newVal
  RaiseEvent SelectedChange
End Property

Public Property Get Selected as boolean
  Selected = pSelected
End Property

<<Class clsPeople>>
Private colPeople as Collection

' Item set as default interface by editing vba source code files
Public Property Get Item(Index As Variant) As clsPerson
  Set Item = colPeople.Item(Index)
End Property

' New Enum set to -4 to enable for ... each to work
Public Property Get NewEnum() As IUnknown
  Set NewEnum = colPeople.[_NewEnum]
End Property

' If selected changes on a person, do something
Public Sub ???_SelectedChange
  ' Do Stuff
End Sub
4

1 に答える 1

17

コレクション内のクラスからイベントを簡単に発生させることができます。問題は、別のクラスが同じクラスの複数からイベントを受け取る直接的な方法がないことです。

clsPeople通常、イベントを受け取る方法は次のようになります。

Dim WithEvents aPerson As clsPerson

Public Sub AddPerson(p As clsPerson)
    Set aPerson = p    ' this automagically registers p to the aPerson event-handler `
End Sub

Public Sub aPerson_SelectedChange
    ...
End Sub

そのため、宣言された変数にオブジェクトを設定すると、オブジェクトがWithEvents自動的に登録され、その変数のイベント ハンドラーによってイベントが受信されます。 残念ながら、変数は一度に 1 つのオブジェクトしか保持できないため、その変数内の以前のオブジェクトも自動的に登録解除されます。

これに対する解決策 (COM での参照サイクルの問題を回避しながら) は、これに共有デリゲートを使用することです。

したがって、次のようなクラスを作成します。

<<Class clsPersonsDelegate>>

Public Event SelectedChange

Public Sub Raise_SelectedChange
    RaiseEvent SelectedChange
End Sub

独自のイベントを発生させたり、すべての親を呼び出す (参照サイクルを作成する) 代わりにSelectedChange、デリゲート クラスの 1 つのインスタンスですべてのサブを呼び出すようにします。そして、親/コレクション クラスがこの単一のデリゲート オブジェクトからイベントを受け取るようにします。

詳細

このアプローチをどのように使用するかに応じて、さまざまなケースで解決すべき技術的な詳細がたくさんありますが、主なものは次のとおりです。

  1. 子オブジェクト (Person) でデリゲートを作成しないでください。親/コンテナー オブジェクト (People) で単一のデリゲートを作成し、コレクションに追加されるときにそれを各子に渡します。子はそれをローカル オブジェクト変数に割り当て、後でそのメソッドを呼び出すことができます。

  2. 通常、コレクションのどのメンバーがイベントを発生させたかを知りたいので、型のパラメーターをclsPersonデリゲート Sub と Event に追加します。次に、デリゲート Sub が呼び出されると、Person オブジェクトはこのパラメーターを介してそれ自体への参照を渡す必要があり、デリゲートはそれを Event を介して親にも渡す必要があります。これは、デリゲートがそのローカル コピーを保存しない限り、参照サイクルの問題を引き起こしません。

  3. 親に受信させたいイベントが他にもある場合は、Subs と一致するイベントを同じデリゲート クラスに追加するだけです。


実装例

「親/コンテナー オブジェクト (People) に単一のデリゲートを作成させ、コレクションに追加されるたびにそれを各子に渡す」というより具体的な例の要求に応えます。

これがデリゲート クラスです。呼び出し元の子オブジェクトのパラメーターをメソッドとイベントに追加したことに注意してください。

<<Class clsPersonsDelegate>>

Public Event SelectedChange(obj As clsPerson)

Public Sub RaiseSelectedChange(obj As clsPerson)
    RaiseEvent SelectedChange(obj)
End Sub

これが子クラス (Person) です。元のイベントをデリゲートを保持するパブリック変数に置き換えました。また、RaiseEvent をそのイベントのデリゲートのメソッドへの呼び出しに置き換え、それ自体へのオブジェクト ポインターを渡します。

<<Class clsPerson>>
Private pSelected as boolean

'Public Event SelectedChange()'
' Instead of Raising an Event, we will use a delegate'
Public colDelegate As clsPersonsDelegate

Public Property Let Selected (newVal as boolean)
    pSelected = newVal
    'RaiseEvent SelectedChange'
    colDelegate.RaiseSelectedChange(Me)
End Property

Public Property Get Selected as boolean
    Selected = pSelected
End Property

そして、これが親/カスタム コレクション クラス (People) です。デリゲートをオブジェクト vairable WithEvents として追加しました (コレクションと同時に作成する必要があります)。また、子オブジェクトをコレクションに追加 (または作成) するときに子オブジェクトのデリゲート プロパティを設定する例の Add メソッドも追加しました。Set item.colDelegate = Nothingまた、コレクションから削除されたときにも対応する必要があります。

<<Class clsPeople>>
Private colPeople as Collection
Private WithEvents colDelegate as clsPersonsDelegate

Private Sub Class_Initialize()
    Set colPeople = New Collection
    Set colDelegate = New clsPersonsDelegate
End Sub

' Item set as default interface by editing vba source code files'
Public Property Get Item(Index As Variant) As clsPerson
    Set Item = colPeople.Item(Index)
End Property

' New Enum set to -4 to enable for ... each to work'
Public Property Get NewEnum() As IUnknown
    Set NewEnum = colPeople.[_NewEnum]
End Property

' If selected changes on any person in our collection, do something'
Public Sub colDelegate_SelectedChange(objPerson as clsPerson)
    ' Do Stuff with objPerson, (just don't make a permanent local copy)'
End Sub

' Add an item to our collection '
Public Sub Add(ExistingItem As clsPerson)
    Set ExistingItem.colDelegate = colDelegate
    colPeople.Add ExistingItem

    ' ... '
End Sub
于 2013-10-11T21:13:19.367 に答える