Reputation: 20474
For custom events I can check the handler like this:
If Object.EventNameEvent Is Nothing Then
MsgBox("Is not handling it.")
End If
...But how I can do the same, for example, to check a ".click" event of a button which is generated in the designer? This does not work:
If Button1.ClickEvent Is Nothing Then
MsgBox("Is not handling it.")
End If
UPDATE
Example of my requeriments:
MsgBox(HasAttachedHandler(MySub, Button1.Click)) ' Expected result: True
MsgBox(HasAttachedHandler(MyFunc, Button1.Click)) ' Expected result: False
Private Sub MySub() Handles Button1.Click, Button2.Click
' bla bla bla
End Sub
Private Function MyFunc() Handles Button2.Click
' bla bla bla
End Function
UPDATE 2:
I'm trying to use the @varocarbas solution, but is not doing exactly what I need, so I've tried to make the necessary modifications to get it work. The problem is the event "FontChaged" is not returning the desired result as you can see here:
Public Class Form1
Private WithEvents Button1 As New Button
Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
' This is working (Result True):
MsgBox(HasAttachedHandler(Button1, "Click", "Button1_Click")) ' Result: True
' This is not working (Result False):
MsgBox(HasAttachedHandler(Button1, "FontChanged", "Button1_Click")) ' Expected result: True
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles _
Button1.Click, _
Button1.MouseHover, _
Button1.GotFocus, _
Button1.Enter, _
Button1.FontChanged, _
Button1.AutoSizeChanged
End Sub
Private Function HasAttachedHandler(ByVal ctl As Control, ByVal eventname As String, ByVal targetMethod As String) As Boolean
For Each evnt In ctl.GetType().GetEvents()
' Get secret key for the current event:
Dim curEvent As Reflection.FieldInfo = GetType(Control).GetField("Event" & evnt.Name, Reflection.BindingFlags.NonPublic Or Reflection.BindingFlags.Static)
If (curEvent IsNot Nothing) Then
Dim secret As Object = curEvent.GetValue(Nothing)
' Retrieve the current event:
Dim eventsProp As Reflection.PropertyInfo = GetType(System.ComponentModel.Component).GetProperty("Events", Reflection.BindingFlags.NonPublic Or Reflection.BindingFlags.Instance)
Dim events As System.ComponentModel.EventHandlerList = DirectCast(eventsProp.GetValue(ctl, Nothing), System.ComponentModel.EventHandlerList)
If (Not IsNothing(events(secret))) AndAlso curEvent.Name.ToLower = "event" & eventname.ToLower Then
Dim handler As [Delegate] = events(secret)
Dim method As Reflection.MethodInfo = handler.Method
If (targetMethod = method.Name) Then Return True
End If
End If
Next
Return False
End Function
End Class
Upvotes: 2
Views: 295
Reputation: 1446
I'll present my unorthodox ways of dealing with this sort of things in .NET framework. From your question...
..."click" event of a button which is generated in the designer...
So I'm going to assume this only applies to only to subclasses of System.Windows.Forms.Control
. As many others have demonstrated, the Control
manages its event system a bit differently by using an EventHandlerList
. For example, with the KeyDown
event, it is implemented as follow...
Public Custom Event KeyDown As KeyEventHandler
AddHandler(ByVal value As KeyEventHandler)
MyBase.Events.AddHandler(Control.EventKeyDown, value)
End AddHandler
RemoveHandler(ByVal value As KeyEventHandler)
MyBase.Events.RemoveHandler(Control.EventKeyDown, value)
End RemoveHandler
End Event
Where EventKeyDown
is defined as a "key" object used later for retrieving the handlers
Private Shared ReadOnly EventKeyDown As Object
Okay, so far, so good! Idle_Mind and varocarbas both nailed the solution, but you mentioned the FontChanged
event does not seem to work with the proposed method. Why? Because both methods would assume there would be a EventFontChanged
field object, whereas in reality, it is defined as...
Private Shared ReadOnly EventFont As Object
Now it's clear that assuming...
GetField("Event" + eventName)
...would not work at all.
Since all events are defined in the Control
class, we can directly reflect it from there. In order to find the object key, we need to locate the "field reference" from the event handlers (refer to the KeyDown
example implementation above). We get the CIL byte codes, loop through it, until we find the opcode which loads a static field reference (ldsfld
). The operand of ldsfld
is a 32-bit integer that can be resolved into a type. The rest is straightforward as both Idle_Mind and varocarbas have already demonstrated.
Imports System.Reflection
Imports System.ComponentModel
Imports System.Reflection.Emit
Public Class Form1
Private Shared Sub OnFooEvent(sender As Object, e As EventArgs) Handles Foo.Click, _
Foo.MouseHover, _
Foo.GotFocus, _
Foo.Enter, _
Foo.FontChanged, _
Foo.AutoSizeChanged
End Sub
Private Sub OnWindowLoad(sender As Object, e As EventArgs) Handles MyBase.Load
MsgBox(HasHandlers(Foo, "AutoSizeChanged"))
End Sub
Private Function HasHandlers(ByVal instance As Object, ByVal arg As String) As Boolean
Dim baseType = GetType(Control)
Dim eventInfo = baseType.GetEvent(arg)
Dim method = eventInfo.AddMethod.GetMethodBody().GetILAsByteArray()
Dim id As Integer
For i As Integer = 0 To method.Length - 1
If method(i) <> OpCodes.Ldsfld.Value Then Continue For
i += 1
Dim operand(3) As Byte
Buffer.BlockCopy(method, i, operand, 0, 4)
id = BitConverter.ToInt32(operand, 0)
Exit For
Next
If id = 0 Then Return False
Dim key = baseType.Module.ResolveField(id).GetValue(instance)
Dim listInfo = GetType(Component).GetProperty("Events", BindingFlags.NonPublic Or BindingFlags.Instance)
Dim list = listInfo.GetValue(instance)
Return list(key) IsNot Nothing
End Function
End Class
This code is not safe, because we have yet to take into consideration of each byte code's operand size! Therefore, the loop might accidentally pick up non-opcode bytes and mistake it for the ldsfld
instruction.
Upvotes: 1
Reputation:
After doing some research, testing and having depleted my knowledge and the freely-available information on this front, I have to give up on the requested fixing. Here are my conclusions:
FieldInfo
by assuming that every event has an associated Field
, what does not seem to be the case. EventHandlerList
seems to do include all the events. Unfortunately, it is a special dictionary where the values can only be retrieved by relying on an Object
which defines the given Event
(in the (converted) Hans Passant code: Dim secret As Object = curEvent.GetValue(Nothing)
).FieldInfo
, but as EventInfo
(Dim curEvent As EventInfo = GetType(Control).GetEvent(evnt.Name)
). All the code would be fine with this change except for the calculation of the secret
variable and thus the required index for events
. Surprisingly, the solution for this problem is not too easy: apparently, only secret
can provide the right index (?!).EventHandlerList
without the secret object
(code on this line); or replacing secret object
/EventHandlerList
with other variables, like: PropertyDescriptorCollection
, EventDescriptorCollection
or MethodInfo
.It would be excellent if someone could correct/extend this post to help (me or others) to clear this issue out.
Upvotes: 0
Reputation:
Firstly, I want to highlight that the Hans Passant's code is brilliant and that with a so good starting point most of the work is done. I have taken part of the Idle_Mind code and done a bit of research and here you have what you are looking for:
Imports System.Reflection
Imports System.ComponentModel
Public Class Form1
Private WithEvents Button1 As New Button
Private Sub Button1_Click(sender As Object, e As System.EventArgs) Handles Button1.Click
End Sub
Private Function HasAttachedHandler2(ByVal ctl As Control, ByVal targetMethod As String) As Boolean
Dim allEvents0 = ctl.GetType().GetEvents()
For Each evnt In allEvents0
' Get secret key for the current event
Dim curEvent As FieldInfo = GetType(Control).GetField("Event" & evnt.Name, BindingFlags.NonPublic Or BindingFlags.Static)
If (curEvent IsNot Nothing) Then
Dim secret As Object = curEvent.GetValue(Nothing)
' Retrieve the current event
Dim eventsProp As PropertyInfo = GetType(Component).GetProperty("Events", BindingFlags.NonPublic Or BindingFlags.Instance)
Dim events As EventHandlerList = DirectCast(eventsProp.GetValue(ctl, Nothing), EventHandlerList)
If (Not IsNothing(events(secret))) Then
Dim handler As [Delegate] = events(secret)
Dim method As MethodInfo = handler.Method
If (targetMethod = method.Name) Then
Return True
End If
End If
End If
Next
End Function
Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
Dim isTheMethodUsed As Boolean = HasAttachedHandler2(Button1, "Button1_Click")
End Sub
End Class
Upvotes: 0
Reputation: 39142
Here's my attempt at a conversion and modification of the code posted by @HansPassant:
Imports System.Reflection
Imports System.ComponentModel
Public Class Form1
Private WithEvents btnA As New Button
Private WithEvents btnB As New Button
Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
btnA.AutoSize = True
btnA.Text = "Handler Attached"
btnA.Location = New Point(10, 10)
Me.Controls.Add(btnA)
btnB.AutoSize = True
btnB.Text = "No Handlers Attached"
Dim pt As Point = btnA.Location
pt.Offset(btnA.Width, 0)
btnB.Location = pt
Me.Controls.Add(btnB)
End Sub
Private Sub btnA_Click(sender As Object, e As System.EventArgs) Handles btnA.Click
Dim btnA_Handled As Boolean = HasAttachedHandler("Click", btnA)
Dim btnB_Handled As Boolean = HasAttachedHandler("Click", btnB)
Debug.Print("btnA_Handled = " & btnA_Handled)
Debug.Print("btnB_Handled = " & btnB_Handled)
End Sub
Private Function HasAttachedHandler(ByVal EventName As String, ByVal ctl As Control) As Boolean
' Get secret click event key
Dim eventClick As FieldInfo = GetType(Control).GetField("Event" & EventName, BindingFlags.NonPublic Or BindingFlags.Static)
Dim secret As Object = eventClick.GetValue(Nothing)
' Retrieve the click event
Dim eventsProp As PropertyInfo = GetType(Component).GetProperty("Events", BindingFlags.NonPublic Or BindingFlags.Instance)
Dim events As EventHandlerList = DirectCast(eventsProp.GetValue(ctl, Nothing), EventHandlerList)
Return Not IsNothing(events(secret))
End Function
End Class
Upvotes: 2