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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/19280524/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-11 23:44:29  来源:igfitidea点击:

Raising event from object in custom collection class

vbaevents

提问by lfrandom

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 parent class, and then call a public method within the parent class within the child class, however that would result in a circular reference, which as I understand it would make it so the garbage collector would not ever get rid of either object.

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

Details: I have two classes, one a person named clsPerson, and the second a custom collection class named clsPeople. clsPerson has a public boolean property named Selected. If selected is changed, I call an event SelectedChange. At that point, I need to do something in clsPeople. How can I trap the event in the custom collection class clsPeople? The person class can be changed from outside of the scope of People, otherwise I would look at another solution.

详细信息:我有两个类,一个是名为 clsPerson 的人,另一个是名为 clsPeople 的自定义集合类。clsPerson 有一个名为 Selected 的公共布尔属性。如果 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

回答by RBarryYoung

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 receiveevents from multiples of the same class.

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

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

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

So setting an object into any variable declared WithEventsautomatically 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.

因此,将对象设置为任何声明的变量会WithEvents自动注册它,以便该变量的事件处理程序接收它的事件。 不幸的是,一个变量一次只能保存一个对象,因此该变量中的任何先前对象也会自动取消注册。

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

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

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 SelectedChangesub in a single instance of the delegate class. And you have the parent/collection class receive events from this single delegate object.

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

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 whichmember of your collection raised the event, so add a parameter of type clsPersonto 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.

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

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

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



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.

这是我们的子类(人)。我已经用一个公共变量替换了原始事件来保存委托。我还通过调用该事件的委托方法替换了 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

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 = Nothingwhen it is removed from the collection.

这是我们的父/自定义集合类 (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(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