Raising event from object in custom collection class

后端 未结 1 1532
灰色年华
灰色年华 2020-12-31 11:47

If an object is contained within a collection, can that object still raise events to a parent class?

Clearly you could tell the child class a reference to the pare

相关标签:
1条回答
  • 2020-12-31 12:16

    You can easily raise an event from a class in a collection, the problem is that there's no direct way for another class to receive events from multiples of the same class.

    The way that your clsPeople would normally receive the event would be like this:

    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
    

    So setting an object into any variable declared WithEvents automatically registers it so that it's events will be received by that variable's event handlers. Unfortunately, a variable can only hold one object at a time, so any previous object in that variable also gets automatically de-registered.

    The solution to this (while still avoiding the problems of reference cycles in COM) is to use a shared delegate for this.

    So you make a class like this:

    <<Class clsPersonsDelegate>>
    
    Public Event SelectedChange
    
    Public Sub Raise_SelectedChange
        RaiseEvent SelectedChange
    End Sub
    

    Now instead of raising their own event or all calling their parent (making a reference cycle), you have them all call the SelectedChange sub in a single instance of the delegate class. And you have the parent/collection class receive events from this single delegate object.

    The Details

    There are a lot of technical details to work out for various cases, depending on how you may use this approach, but here are the main ones:

    1. Don't have the child objects (Person) create the delegate. Have the parent/container object (People) create the single delegate and then pass it to each child as they are added to the collection. The child would then assign it to a local object variable, whose methods it can then call later.

    2. Typically, you will want to know which member of your collection raised the event, so add a parameter of type clsPerson to the delegate Sub and the Event. Then when the delegate Sub is called, the Person object should pass a reference to itself through this parameter, and the delegate should also pass it along to the parent through the Event. This does not cause reference-cycle problems so long as the delegate does not save a local copy of it.

    3. If you have more events that you want the parent to receive, just add more Subs and more matching Events to the same delegate class.


    Responding to request for more concrete example of "Have the parent/container object (People) create the single delegate and then pass it to each child as they are added to the collection."

    Here's our delegate class. Notice that I've added the parameter for the calling child object to the method and the event.

    <<Class clsPersonsDelegate>>
    
    Public Event SelectedChange(obj As clsPerson)
    
    Public Sub Raise_SelectedChange(obj As clsPerson)
        RaiseEvent SelectedChange(obj)
    End Sub
    

    Here's our child class (Person). I have replaced the original event, with a public variable to hold the delegate. I have also replaced the RaiseEvent with a call to the delegate's method for that event, passing along an object pointer to itself.

    <<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.SelectedChange(Me)
    End Property
    
    Public Property Get Selected as boolean
        Selected = pSelected
    End Property
    

    And here's our parent/custom-collection class (People). I have added the delegate as an object vairable WithEvents (it should be created at the same time as the collection). I have also added an example Add method that shows setting the child objects delegate property when you add (or create) it to the collection. You should also have a corresponding Set item.colDelegate = Nothing when it is removed from the collection.

    <<Class clsPeople>>
    Private colPeople as Collection
    Private WithEvents colDelegate as clsPersonsDelegate
    
    ' 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 out 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
    
    0 讨论(0)
提交回复
热议问题