从自定义集合类中的对象引发事件

时间:2013-10-09 19:08:10

标签: vba events

如果对象包含在集合中,该对象是否仍然可以将事件引发到父类?

显然,你可以告诉子类对父类的引用,然后在子类中的父类中调用一个公共方法,但这会导致循环引用,据我所知它会使它成为垃圾收集器永远不会摆脱任何一个对象。

详细说明: 我有两个类,一个是名为clsPerson的人,另一个是名为clsPeople的自定义集合类。 clsPerson有一个名为Selected的公共布尔属性。如果选择了更改,我会调用一个事件SelectedChange。那时,我需要在clsPeople做点什么。如何在自定义集合类clsPeople中捕获事件?可以从People的范围之外更改person类,否则我会查看另一个解决方案。

<<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

1 个答案:

答案 0 :(得分:10)

您可以轻松地从集合中的某个类中引发事件,问题是另一个类没有直接方法从同一个类的多个接收事件。

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的任何变量会自动注册它,以便该变量的事件处理程序接收它的事件。 不幸的是,变量一次只能容纳一个对象,因此该变量中的任何先前对象也会自动取消注册。

对此的解决方案(虽然仍然避免了COM中的引用循环问题)是为此使用共享委托。

所以你创建了这样一个类:

<<Class clsPersonsDelegate>>

Public Event SelectedChange

Public Sub Raise_SelectedChange
    RaiseEvent SelectedChange
End Sub

现在不是提升自己的事件或者都调用它们的父(创建引用循环),而是让它们在委托类的单个实例中调用SelectedChange sub。并且您有父/集合类从此单个委托对象接收事件。

详情

根据您使用这种方法的方式,有很多技术细节需要解决,但主要有:

  1. 不要让子对象(Person)创建委托。让父/容器对象(People)创建单个委托,然后在将它们添加到集合时将其传递给每个子节点。然后,子进程将其分配给本地对象变量,该变量的方法随后可以调用。

  2. 通常,您需要知道您的集合的哪个成员引发了该事件,因此请向委托Sub和Event添加类型为clsPerson的参数。然后,当调用委托Sub时,Person对象应该通过此参数传递对自身的引用,并且委托也应该通过Event将它传递给父对象。只要委托不保存它的本地副本,这不会导致引用循环问题。

  3. 如果您希望父级接收更多事件,只需将更多Subs和更多匹配的事件添加到同一个委托类。


  4. 响应对“让父/容器对象(People)创建单个委托的更具体示例的请求,然后将它们传递给每个子项,因为它们被添加到集合中。

    这是我们的委托课程。请注意,我已将调用子对象的参数添加到方法和事件中。

    <<Class clsPersonsDelegate>>
    
    Public Event SelectedChange(obj As clsPerson)
    
    Public Sub Raise_SelectedChange(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.SelectedChange(Me)
    End Property
    
    Public Property Get Selected as boolean
        Selected = pSelected
    End Property
    

    这是我们的父/自定义集合类(People)。我已将委托添加为可变的WithEvents对象(它应该与集合同时创建)。我还添加了一个示例Add方法,该方法显示在将其添加(或创建)到集合时设置子对象委托属性。从集合中删除时,您还应该有一个相应的Set item.colDelegate = Nothing

    <<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
        ' Do Stuff'
    End Sub
    
    ' Add an item to our collection '
    Public Sub Add(ExistingItem As clsPerson)
        Set ExistingItem.colDelegate = colDelegate
        colPeople.Add ExistingItem
    
        ' ... '
    End Sub