12

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

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

详细信息:我有两个类,一个是名为 clsPerson 的人,第二个是一个名为 clsPeople 的自定义集合类。clsPerson 有一个名为 Selected 的公共布尔属性。如果 selected 被更改,我将调用一个事件 SelectedChange。那时,我需要在 clsPeople 中做一些事情。如何在自定义集合类 clsPeople 中捕获事件?可以从 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中都会自动注册它,以便该变量的事件处理程序接收它的事件。 不幸的是,一个变量一次只能保存一个对象,因此该变量中的任何先前对象也会自动取消注册。

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

所以你做了一个这样的类:

<<Class clsPersonsDelegate>>

Public Event SelectedChange

Public Sub Raise_SelectedChange
    RaiseEvent SelectedChange
End Sub

现在不是引发他们自己的事件或都调用他们的父事件(进行引用循环),而是让他们都SelectedChange在委托类的单个实例中调用子。你让父/集合类从这个单一的委托对象接收事件。

细节

对于各种情况,有很多技术细节需要解决,具体取决于您如何使用这种方法,但以下是主要的:

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

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

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


示例实现

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

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

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

这是我们的父/自定义集合类(人员)。我已将委托添加为对象 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 回答