This Stack Overflow answer solves the problem: How can a form receive events from multiple instances of a WithEvents class? The simple answer is to use RaiseEvent but the problem is that when you create a collection of WithEvents classes, the RaiseEvent only works for the last class instance added to the collection. This post shows you how to connect each WithEvents control to a RaiseEvent using a delegate class and receive the event in the form. For example, you could have changes to any one of a group of textboxes trigger a change in another form control.
(When I came across the Stack Overflow post above I read the explanation but not the code. I let it bounce around my brain for a couple of days and then got the main point that you can just funnel the WithEvents control to the delegate when the WithEvents control fires. After that, writing the code was easy.)
Review: Create a Textbox WithEvents Class
In past posts I’ve discussed UserForm code that creates a collection of WithEvents classes to capture changes to all the textboxes:
Public collControls As Collection
Private Sub UserForm_Activate()
Dim ctl As MSForms.Control
Set cDelegate = New clsDelegate
Set collControls = New Collection
For Each ctl In Me.Controls
If TypeOf ctl Is MSForms.TextBox Then
Set cEvents = New clsEvents
Set cEvents.cDelegate = cDelegate
Set cEvents.txtBox = ctl
collControls.Add cEvents
End If
Next ctl
End Sub
clsEvents looks like this:
Private Sub txtBox_Change()
debug.print "Textbox changed"
End Sub
That’s nice for creating a message, or opening a file, or some other action outside the form. But sometimes you want to receive the event in the form and use it as a trigger to change other form controls.
You can add an Event to clsEvents so that it can be raised in the txtBox_Change event and received by another class, such as a form, but again the problem is that only the last textbox added to collControls will raise the event. Previously, I’ve coded around that by passing a reference to the userform into clsEvents, and manipulated the form from the class. It works, but it’s dirty. Plus I think it creates a memory leak.
Modification to allow multiple RaiseEvents
The solution is to create another class, a delegate, that is also instantiated in the userform. The delegate has a single public control property that is set by any of the clsEvents instances, one at a time, as their txtBox_Change event fires. When the txtBox variable is passed to the cDelegate it raises its txtBox_Changed event, which is then received by the cDelegate_Change routine back in the form. This way there’s only one instance of cDelegate, but its txtBox variable refers to whichever clsEvents textbox has changed most recently.
Here’s the code for clsDelegate:
Public Sub PassControl(txtBox As MSForms.TextBox)
RaiseEvent txtBoxChanged(txtBox)
End Sub
So simple! And here’s the modified code for clsEvents:
Private m_cDelegate As clsDelegate
Public Property Set cDelegate(value As clsDelegate)
Set m_cDelegate = value
End Property
Private Sub txtBox_Change()
m_cDelegate.PassControl txtBox
End Sub
m_cDelegate holds the reference to the cDelegate instance that’s passed to clsEvents, so it can pass its txtBox instance to the delegate class. In the form code below you can see where cDelegate is instantiated and where that instance is passed to each cEvent instance:
Public WithEvents cDelegate As clsDelegate
Public collControls As Collection
Private Sub UserForm_Activate()
Dim ctl As MSForms.Control
Set cDelegate = New clsDelegate
Set collControls = New Collection
For Each ctl In Me.Controls
If TypeOf ctl Is MSForms.TextBox Then
Set cEvents = New clsEvents
Set cEvents.cDelegate = cDelegate
Set cEvents.txtBox = ctl
collControls.Add cEvents
End If
Next ctl
End Sub
Private Sub cDelegate_txtBoxChanged(txtBox As MSForms.TextBox)
Me.Label1.Caption = txtBox.Name & " text length is " & Len(txtBox.Text) & "." & vbCrLf & "Text is: " & vbCrLf & txtBox.Text
End Sub
The last sub in the code is the receiving event. It just updates information about whatever textbox was changed last.
Download!
Here’s a sample workbook with the form and code. Let me know what you think!