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

Posted

技术标签:

【中文标题】从自定义集合类中的对象引发事件【英文标题】:Raising event from object in custom collection class 【发布时间】:2013-10-09 19:08:09 【问题描述】:

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

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

详情: 我有两个类,一个是名为 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

【问题讨论】:

【参考方案1】:

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

您的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 子。你让父/集合类从这个单一的委托对象接收事件。

详情

根据您如何使用这种方法,有很多技术细节需要针对各种情况制定,但以下是主要的:

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

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

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


示例实现

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

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

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

【讨论】:

这段代码对我非常有帮助。它帮助我在 VBA 中一起使用接口和事件。谢谢您的发布!付诸实践后,对它进行了一些小的更正: 1) 文本Raise_ 可以从clsPersonsDelegate 类中的方法名称中删除。 2) clsPeople 上的 colDelegate_SelectedChange 方法需要更改为接受 clsPerson 类型的参数。此外,对于尝试此代码的其他人,clsPeople 将需要 Set colPeople = New CollectionSet colDelegate = New clsPersonsDelegate 的初始化方法。 @BarrettNashville 谢谢!是的,Raise_ 前缀不是必需的,我只是将其用作风格问题:我喜欢保持名称不同,以便我始终知道所指的是哪个。 同上 BarrettNashville 的 cmets,对我也非常有帮助。谢谢。 非常感谢!

以上是关于从自定义集合类中的对象引发事件的主要内容,如果未能解决你的问题,请参考以下文章

如何从自定义弹出菜单扩展中的选择事件中获取所选对象值?

Vue:从自定义组件派生的自定义组件中的 v-model 和输入事件

如何从自定义类中检索 NSArray

如何从自定义视图的类中更改文本视图

Flex 4 从自定义组件调度自定义事件(为啥 flex 将自定义事件转换为 mouseevent)

从自定义组件调度自定义事件