2

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
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
ElektroStudios
  • 19,105
  • 33
  • 200
  • 417
  • Have you tried `Button1.Click` (without the event word)? – Daniel Möller Jun 09 '13 at 17:59
  • @Daniel of course, If I try that then it says: "public event Click is an event and cannot be called directly", I can't find the delegate for that event to check it. – ElektroStudios Jun 09 '13 at 18:39
  • You can use the voodoo magic [here](http://stackoverflow.com/questions/293007/is-it-possible-to-steal-an-event-handler-from-one-control-and-give-it-to-anoth/293031#293031) to get a list of handlers for an event. – Idle_Mind Jun 09 '13 at 19:04
  • 1
    @Idle_Mind I can't translate hard C# codes as that and also sure the online translators will fail to translate any of the Hans Passant codes (too Pro codes for translator engines xD) ...If you can translate it to VB I will appreciate it, anyways I will try to translate it, thanks... – ElektroStudios Jun 09 '13 at 19:08
  • The blog post *[How to obtain the invocation list of any event.](http://bobpowell.net/eventsubscribers.aspx)* may help. – Peter Mortensen Oct 05 '13 at 12:02

4 Answers4

2

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
Idle_Mind
  • 38,363
  • 3
  • 29
  • 40
  • The code is great but I think something goed wrong when trying to understand me, what I precise is to check if a method/procedure/funcion is handling an event, but what your code does is to check if a control is handling an event for any Sub?, please see my updated question, thanks. PS: Hans passant code is doing the same? – ElektroStudios Jun 10 '13 at 15:59
  • Hmmm...they are all doing different things. My modification simply reports back whether an event has any handlers attached to it or not. @Hans is moving the list of handlers from one button to another. You want to know if a **specific method** is attached to an event. I'm not sure that is possible...will have to investigate further. – Idle_Mind Jun 10 '13 at 16:17
1

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.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
ains
  • 1,436
  • 4
  • 18
  • 33
  • the class is throwing two exceptions: at "Dim method = eventInfo..." (addmethod is not a member of reflection), and at "Dim list = listInfo..." (overload resolution 'cause don't accept this number of arguments) – ElektroStudios Jun 22 '13 at 19:58
  • thankyou for your knowledge is good to learn all the things you've said but anyways your code can't use it 'cause I want to know if a "sub" is handling an event from a control (As you can see in my las update I use "3" args instead "2" like in your HasHandlers func) – ElektroStudios Jun 22 '13 at 19:59
0

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
varocarbas
  • 12,354
  • 4
  • 26
  • 37
  • You are looking just for the method associated with a given event, but I think that this is an even better approach to the problem: it searches through all the events in the given control. – varocarbas Jun 14 '13 at 11:04
  • Thankyou so much but is not doing exactly what I need, but almost all the job is done now thanks for your code, please can you see my update? Maybe you can help me to improve more you're code to do what I need and to resolve the little problem I have, thanks and sorry 'cause my english. – ElektroStudios Jun 14 '13 at 13:43
  • I'm using string type for the second argument (the event name) and maybe you know how to do it in a better way, also like I've said I need help to recognise one of the events. – ElektroStudios Jun 14 '13 at 13:45
  • No problem. First of all, bear in mind that you should remove the for loop and replace evnt.Name with eventname. Regarding the "FontChanged" I have done some testing and seems to happen with quite a few events; GetType(Control).GetField("Event" & evnt.Name, BindingFlags.NonPublic Or BindingFlags.Static) works fine with some events but not with all. I don't understand exactly how it works (just converted it from the Hans Passant's code), I guess that some of its parameters have to be tuned depending on the give event. I will look at it and come back to you. – varocarbas Jun 14 '13 at 14:09
  • thanks for your time, I will wait for your edit, also I'm on the search too about this – ElektroStudios Jun 14 '13 at 14:17
  • I have tried quite a few things and done a pretty relevant research, but got a result which wasn't what I had in mind: apparently, the taken approach (starting from Hans Passant) is wrong as explained in this post: http://bytes.com/topic/c-sharp/answers/235546-getfileld-fails-reflectionpermission-problem as far as it cannot account for all the possible controls. I have spent too much time today on this; will put together all the collected ideas and come up with a different approach during the weekend. – varocarbas Jun 14 '13 at 16:23
0

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:

  • The limitation of the original approach (from Hans Passant) is relying on FieldInfo by assuming that every event has an associated Field, what does not seem to be the case.
  • On the other hand, 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)).
  • By assuming that the two aforementioned points are right (not too much information on internet), the events shouldn’t be got as 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 (?!).
  • I have found what seem to be potential alternatives: either extracting the information from 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.

varocarbas
  • 12,354
  • 4
  • 26
  • 37