I am trying to write a clever little function here that will add a specified delegate as an event handler to all the controls in a collection for any dynamic event. What I'm trying to do is write this as a completely generic function so that I could possibly use it in various different projects (perhaps including it in some sort of tools library).
Basically I want to specify a group of controls, the delegate to handle the event, and the type of event to handle. The problem that I'm running up against is that I can't figure out how to dynamically specify the event at run time.
Here's my 'work-in-progress' sub:
Private Sub AddHandlerToControls(controlList As ControlCollection, eventToHandle As EventHandler, eventHandlerDelegate As Func(Of Object, EventArgs), Optional filterList As List(Of Type) = {})
For Each controlInList As Control In controlList
If controlInList.HasChildren Then
AddHandlerToControls(controlInList.Controls, controlInList.MouseEnter, eventHandlerDelegate, filterList)
End If
If filterList.Count > 0 Then
If filterList.Contains(controlInList.GetType) = False Then
Continue For
End If
End If
AddHandler controlInList.MouseEnter, eventHandlerDelegate
Next
End Sub
Ideally I would like to use the eventToHandle
parameter there at the end in the AddHandler
statement instead of specifically using controlInList.MouseEnter
. Like this:
AddHandler eventToHandle, eventHandlerDelegate
That way I could call this function dynamically in a form.load method, and call it sort of like how I did earlier in the sub where it's recursively calling itself for child controls. Somehow say "for this list of controls I would like to use this delegate as the 'MouseEnter' event handler". Like So:
AddHandlerToControls(Me.Controls, control.MouseEnter, MouseEnterHandlerDelegate, new List(Of Type) {TextBox, ComboBox})
This could just be wishfull thinking, I'm starting to think that this isn't quite possible at this level of 'genericness', but it's an interesting enough problem that I thought I should at least ask.
Edit for solution:
Jon Skeet's suggestion of using Reflection ended up working for me. Here's the final function:
Private Shared Sub AddHandlerToControls(controlList As Control.ControlCollection, eventToHandle As String, eventHandlerDelegate As MethodInfo, Optional filterList As List(Of Type) = Nothing)
For Each controlInList As Control In controlList
If controlInList.HasChildren Then
AddHandlerToControls(controlInList.Controls, eventToHandle, eventHandlerDelegate, filterList)
End If
If Not filterList Is Nothing Then
If filterList.Contains(controlInList.GetType) = False Then
Continue For
End If
End If
Dim dynamicEventInfo As EventInfo = controlInList.GetType.GetEvent(eventToHandle)
Dim handlerType As Type = dynamicEventInfo.EventHandlerType
Dim eventDelegate As [Delegate] = [Delegate].CreateDelegate(handlerType, eventHandlerDelegate)
dynamicEventInfo.AddEventHandler(controlInList, eventDelegate)
Next
End Sub
And how I call it and the delegate used:
AddHandlerToControls(Controls, "MouseClick", GetType(MainFrm).GetMethod("MouseClickEventDelegate"), New List(Of Type) From {GetType(TextBox), GetType(ComboBox)})
Shared Sub MouseClickEventDelegate(sender As Object, eventArgs As EventArgs)
sender.SelectAll()
End Sub
This allowed me to set all text boxes and combo boxes on my form (there's quite a few) to select all text when clicked into, in about 20 lines of code. The best part is that if I add any in the future, I won't have to worry about going back to add this handler, it'll be taken care of at run time. It may not be the cleanest solution, but it ended up working pretty well for me.
Two options:
Specify a "subscription delegate" via a lambda expression. I wouldn't like to guess at what this would look like in VB, but in C# it would be something like:
(control, handler) => control.MouseEnter += handler;
Then you just need to pass each control to the delegate.
Specify the event name as a string, and use reflection to fetch the event and subscribe (Type.GetEvent
then EventInfo.AddEventHandler
).