Bit of background info first - I've been struggling with a problem for a while now. How could you have one single event handler to handle every control (or all of a particular type of control) on a form? I've never been able to find a complete solution online either. The two 'easy' methods have glaring issues. For sake of example, let's say we want every text box to change its border color when it gains focus. The code for changing one text box border on focus is easy:
Private Sub TextBox0_GotFocus()
TextBox0.BorderColor = vbBlue
End Sub
But doing this for every single text box causes a problem. The brute-force option would be to manually add a handler to every text box, and have them all call a master handler (http://sharetext.org/SWek). That creates a lot of duplicate code, fast. And it's time-consuming, boring, easy to forget to do... and as a programmer I'm too lazy to do it like this.
The other option is to set the handler for every text box to a constant expression in the form designer (eg =AnyTextBox_HandleGotFocus()
), but doing so makes you lose which text box actually got the focus:
Private Sub AnyTextBox_HandleGotFocus()
' ??? Which text box just fired this? I have no way of knowing!
End Sub
I'd need to loop through the form's entire control collection every single time this thing fires. I also lose the ability to grant specific behaviour to an individual control. Unless I whack a massive Switch
statement inside the loop or something. Shudder.
After a long time rolling it around in my head, I think I have a solution. It isn't perfect, but the caveats of this are much smaller than the caveats of the above.
First off, we want to build a collection of the controls we want to use. We can then loop through it and assign all of their event handlers to a single handler programatically. This is the only loop in this code.
Private iTextBoxes As Collection
Private Sub Form_Load()
BuildControlCollection Me, iTextBoxes, eTextBox
Dim lTextBox As TextBox
For Each lTextBox In iTextBoxes
lTextBox.OnGotFocus = "=AnyTextBox_GotFocus(" & lTextBox.Name & ")"
Next lTextBox
End Sub
Not relevant to the question, but BuildControlCollection
here turns iTextBox
into a collection of all the text boxes on the form. Don't worry about the details of my Hungarian notation either.
Next, let's add the 'master' event handler:
Public Function AnyTextBox_GotFocus(ByRef mpTextBox As TextBox)
mpTextBox.BorderColor = RGB(100, 150, 215)
FireControlSingleEvent mpTextBox, "GotFocus"
End Function
That FireControlSingleEvent
is the important part (the name is a bit awkward, suggestions welcome!) - this fires the control's 'unique' event handler, so I can add handling for a specific control.
Private Function FireControlSingleEvent(ByRef mpControl As Control, _
ByVal ipEventProcName As String)
Try:
On Error GoTo Catch
Dim iProcName As String
iProcName = Me.Controls(mpControl.Name).EventProcPrefix & "_" & ipEventProcName
Select Case ipEventProcName
Case "Click", "AfterUpdate", "Change", "GotFocus", "LostFocus", "Enter":
CallByName Me, iProcName, VbMethod
Case "KeyPress":
CallByName Me, iProcName, VbMethod, mLastKeyPressedAscii
Case Else:
Debug.Print "Multi event handling cannot support the " & ipEventProcName & " event."
End Select
GoTo Finally
Catch:
If Err.Number = 2465 Then
Debug.Print "Procedure " & Forms("Form1").Controls(mpControl.Name).EventProcPrefix & "_" & ipEventProcName & _
" does not exist or is private"
Else
Err.Raise Err.Number
End If
Finally:
On Error GoTo 0
End Function
This attempts to call a method named with the standard convention, according to the control and event procedure name it's given. For example, if it's handed TextBox0
and GotFocus
, it will attempt to call TextBox0_GotFocus()
. The error handling stops it crashing if TextBox0_GotFocus()
doesn't exist, removing the necessity for having empty handlers for every single control.
The Select
statement is there as I haven't worked out how to handle every single event yet - only events that don't need arguments work with this code, so things like mouse events are currently out.(KeyPress
is a special case - by turning Key Preview
on at the form level, I can capture the KeyAscii required into mLastKeyPressedAscii
in the form's Form_KeyPress
event, and pass that)
Known caveats
- As mentioned above, event handlers that take arguments - such as
MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single)
- aren't compatible without losing the data that would be passed to the parameters. - The
TextBox0_GotFocus()
-type handlers must be altered toPublic
rather thanPrivate
-CallByName
can't findPrivate
handlers.
Any suggestions or improvements on this code? There's a few bits I'm iffy about - always nervous about using errors as an intentional part of the code structure, for eg. Feedback welcome.
i
this variable shouldn't be changed once initially set ('immutable');m
this variable is expected to be changed ('mutable');p
parameter;l
index / object for a loop;e
Enum value. It's experimental, just been trying it on for size recently. \$\endgroup\$CallByName
- and now there's a whole new world of refactoring possibilities that have just appeared right before my eyes... I'm so upvoting this, the minute I get votes back (out of ammo right now..). \$\endgroup\$