【问题标题】: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

【问题讨论】:

    标签: vba events


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

    详情

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

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

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

    3. 如果您希望父级接收更多事件,只需将更多 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,对我也非常有帮助。谢谢。
    • 非常感谢!
    猜你喜欢
    • 2011-03-25
    • 1970-01-01
    • 1970-01-01
    • 2015-10-05
    • 1970-01-01
    • 2014-05-28
    • 1970-01-01
    • 1970-01-01
    • 2017-05-14
    相关资源
    最近更新 更多