获取在自定义用户控件中声明的事件的所有事件处理程序

时间:2019-06-26 01:30:31

标签: .net vb.net reflection delegates event-handling

我正在尝试编写一个通用函数,该函数给出对控件/组件的引用以及在其类上声明的事件的名称,它应该能够(通过 Reflection )进行检索当前为指定事件名称注册的所有事件处理程序。

我遇到的第一个也是主要的问题(已解决,因此您可以忽略此段),是我在StackOverflow中找到的所有解决方案(大多是用C#编写)的意义受到限制,作者只能寻找System.Windows.Forms.Control类中的事件字段声明,因此,例如,当尝试检索System.Windows.Forms.ToolStripMenuItem.MouseEnter事件的事件处理程序时,该事件将失败(因为在{{1}中声明了事件字段}类),也没有考虑System.Windows.Forms.ToolStripItem类的事件字段命名,该事件字段带有下划线。 因此,我涵盖了所有这些内容,目前,我的解决方案适用于(或我认为它适用于)从System.Windows.Forms.Form继承的任何类。

我现在遇到的唯一问题是当我声明自定义类型(继承自 Control / UserControl / Component / < strong> Form 等类),然后将该类型传递给函数。在这种情况下,我会得到一个空引用异常。不知道我在这里做错了什么...

System.ComponentModel.Component

此行发生异常:

Public Shared Function GetEventHandlers(component As IComponent, eventName As String) As IReadOnlyCollection(Of [Delegate])

    Dim componentType As Type
    Dim declaringType As Type ' The type on which the event is declared.
    Dim eventInfo As EventInfo
    Dim eventField As FieldInfo = Nothing
    Dim eventFieldValue As Object
    Dim eventsProp As PropertyInfo
    Dim eventsPropValue As EventHandlerList
    Dim eventDelegate As [Delegate]
    Dim invocationList As [Delegate]()

    ' Possible namings for an event field.
    Dim eventFieldNames As String() =
            {
                $"Event{eventName}",            ' Fields declared in 'System.Windows.Forms.Control' class.
                $"EVENT_{eventName.ToUpper()}", ' Fields declared in 'System.Windows.Forms.Form' class.
                $"{eventName}Event"             ' Fields auto-generated.
            }

    Const bindingFlagsEventInfo As BindingFlags =
              BindingFlags.ExactBinding Or
              BindingFlags.Instance Or
              BindingFlags.NonPublic Or
              BindingFlags.Public Or
              BindingFlags.Static

    Const bindingFlagsEventField As BindingFlags =
              BindingFlags.DeclaredOnly Or
              BindingFlags.ExactBinding Or
              BindingFlags.IgnoreCase Or
              BindingFlags.Instance Or
              BindingFlags.NonPublic Or
              BindingFlags.Static

    Const bindingFlagsEventsProp As BindingFlags =
              BindingFlags.DeclaredOnly Or
              BindingFlags.ExactBinding Or
              BindingFlags.Instance Or
              BindingFlags.NonPublic

    Const bindingFlagsEventsPropValue As BindingFlags =
              BindingFlags.Default

    componentType = component.GetType()
    eventInfo = componentType.GetEvent(eventName, bindingFlagsEventInfo)
    If (eventInfo Is Nothing) Then
        Throw New ArgumentException($"Event with name '{eventName}' not found in type '{componentType.FullName}'.", NameOf(eventName))
    End If

    declaringType = eventInfo.DeclaringType

    For Each name As String In eventFieldNames
        eventField = declaringType.GetField(name, bindingFlagsEventField)
        If (eventField IsNot Nothing) Then
            Exit For
        End If
    Next name

    If (eventField Is Nothing) Then
        Throw New ArgumentException($"Field with name 'Event{eventName}', 'EVENT_{eventName.ToUpper()}' or '{eventName}Event' not found in type '{declaringType.FullName}'.", NameOf(eventName))
    End If

#If DEBUG Then
    Debug.WriteLine($"Field with name '{eventField.Name}' found in type '{declaringType.FullName}'")
#End If

    eventFieldValue = eventField.GetValue(component)
    eventsProp = GetType(Component).GetProperty("Events", bindingFlagsEventsProp, Type.DefaultBinder, GetType(EventHandlerList), Type.EmptyTypes, Nothing)
    eventsPropValue = DirectCast(eventsProp.GetValue(component, bindingFlagsEventsPropValue, Type.DefaultBinder, Nothing, CultureInfo.InvariantCulture), EventHandlerList)
    eventDelegate = eventsPropValue.Item(eventFieldValue)
    invocationList = eventDelegate.GetInvocationList()

    If (invocationList Is Nothing) Then ' There is no event-handler registered for the specified event.
        Return Enumerable.Empty(Of [Delegate]).ToList()
    End If

    Return invocationList

End Function

因为invocationList = eventDelegate.GetInvocationList() 为空。


要测试异常,可以以此类为例:

eventDelegate

以及这样的用法示例:

Public Class TestUserControl : Inherits UserControl

    Event TestEvent As EventHandler(Of EventArgs)

    Overridable Sub OnTestEvent(e As EventArgs)
        If (Me.TestEventEvent IsNot Nothing) Then
            RaiseEvent TestEvent(Me, e)
        End If
    End Sub

End Class

不知道这是否是与绑定标志有关的问题,或者事件字段的命名...但是在与任何内置控件/组件尝试相同时,我没有此空引用对象问题显示事件的类,而不是Dim ctrl As New TestUserControl() AddHandler ctrl.TestEvent, Sub() Debug.WriteLine("Hello World!") End Sub Dim handlers As IReadOnlyCollection(Of [Delegate]) = GetEventHandlers(ctrl, NameOf(TestUserControl.TestEvent)) For Each handler As [Delegate] In handlers Console.WriteLine($"Method Name: {handler.Method.Name}") Next 类。

我做错了什么?如何解决?请注意,此功能应该仍然是通用的。

1 个答案:

答案 0 :(得分:2)

感谢@ Hans Passant 在主要问题的评论中提出的建议,该方法可以正常工作:

toStr5 = foldMap show

我们还可以为 EventInfo 类型定义下一个方法扩展,以充当EventInfo.RemoveEventHandler(Object, Delegate)的方法重载:

Public Function GetEventHandlers(component As IComponent, eventName As String) As IReadOnlyCollection(Of [Delegate])

    Dim componentType As Type = component.GetType()

    ' Find event declaration in the source type.
    Dim eventInfo As EventInfo = componentType.GetEvent(eventName, BindingFlags.ExactBinding Or BindingFlags.Instance Or BindingFlags.NonPublic Or BindingFlags.Public Or BindingFlags.Static)
    If (eventInfo Is Nothing) Then
        Throw New ArgumentException($"Event with name '{eventName}' not found in type '{componentType.FullName}'.", NameOf(eventName))
    End If

    ' The type on which the event is declared.
    Dim declaringType As Type = eventInfo.DeclaringType

    ' Find event-field declaration in the declaring type.
    Dim eventField As FieldInfo = Nothing

    ' Possible namings for an event field.
    Dim eventFieldNames As String() = {
        $"Event{eventName}",            ' Fields declared in 'System.Windows.Forms.Control' class.
        $"EVENT_{eventName.ToUpper()}", ' Fields declared in 'System.Windows.Forms.Form' class.
        $"{eventName}Event"             ' Fields (auto-generated) declared in other classes.
    }

    For Each name As String In eventFieldNames
        eventField = declaringType.GetField(name, BindingFlags.DeclaredOnly Or BindingFlags.ExactBinding Or BindingFlags.IgnoreCase Or BindingFlags.Instance Or BindingFlags.NonPublic Or BindingFlags.Static)
        If (eventField IsNot Nothing) Then
            Exit For
        End If
    Next name

    If (eventField Is Nothing) Then
        Throw New ArgumentException($"Field with name '{String.Join("' or '", eventFieldNames)}' not found in declaring type '{declaringType.FullName}'.", NameOf(eventName))
    End If

#If DEBUG Then
    Debug.WriteLine($"Field with name '{eventField.Name}' found in declaring type '{declaringType.FullName}'")
#End If

    Dim eventFieldValue As object = eventField.GetValue(component)
    If TypeOf eventFieldValue Is MulticastDelegate
        ' See @Hans Passant comment:
        ' https://stackoverflow.com/questions/56763972/get-all-the-event-handlers-of-a-event-declared-in-a-custom-user-control?noredirect=1#comment100177090_56763972

        Return DirectCast(eventFieldValue, MulticastDelegate).GetInvocationList()
    End If

    Dim eventsProp As PropertyInfo = GetType(Component).GetProperty("Events", BindingFlags.DeclaredOnly Or BindingFlags.ExactBinding Or BindingFlags.Instance Or BindingFlags.NonPublic, Type.DefaultBinder, GetType(EventHandlerList), Type.EmptyTypes, Nothing)
    Dim eventsPropValue As EventHandlerList = DirectCast(eventsProp.GetValue(component, BindingFlags.Default, Type.DefaultBinder, Nothing, CultureInfo.InvariantCulture), EventHandlerList)
    Dim eventDelegate As [Delegate] = eventsPropValue.Item(eventFieldValue)
    Dim invocationList As [Delegate]() = eventDelegate?.GetInvocationList()

    If (invocationList Is Nothing) Then ' There is no event-handler registered for the specified event.
       Return Enumerable.Empty(Of [Delegate]).ToList()
    End If

    Return invocationList

End Function