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

2023-12-20

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

我遇到的第一个也是主要问题(已解决,因此您可以忽略本段)是我在 StackOverflow 中找到的所有解决方案(主要是用 C# 编写的)都仅限于作者仅查找事件的含义 -中的字段声明System.Windows.Forms.Control类,因此会失败,例如在尝试检索事件处理程序时System.Windows.Forms.ToolStripMenuItem.MouseEnter事件(因为事件字段是在System.Windows.Forms.ToolStripItem类),并且也不考虑事件字段命名System.Windows.Forms.Form类,其中有一个下划线。 所以我涵盖了所有这些,目前我的解决方案适用于(或者我认为它适用于)继承自的任何类System.ComponentModel.Component.

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

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()

because 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

不确定这是否是与绑定标志相关的问题,或者可能是与事件字段命名相关的问题...但在尝试与公开的任何内置控件/组件类相同时,我没有这个空引用对象问题事件,而不是那个TestUserControl class.

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


感谢什么@汉斯·帕桑特在主要问题的评论中建议,这按预期工作:

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

我们还可以定义下一个方法扩展事件信息充当方法重载的类型EventInfo.RemoveEventHandler(对象,委托) https://learn.microsoft.com/en-us/dotnet/api/system.reflection.eventinfo.removeeventhandler?view=netframework-4.8:

''' ----------------------------------------------------------------------------------------------------
''' <summary>
''' Removes an event handler from an event source.
''' </summary>
''' ----------------------------------------------------------------------------------------------------
''' <example> This is a code example.
''' <code>
''' Public Class Form1
''' 
'''     Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Shown
'''         Dim target As Form = Me
'''         Dim eventInfo As EventInfo = target.GetType().GetEvent(NameOf(Form.Click))
'''         eventInfo.RemoveEventHandler(target, NameOf(Me.Form1_Click))
'''     End Sub
''' 
'''     Private Sub Form1_Click(sender As Object, e As EventArgs) Handles MyBase.Click
'''         MsgBox(MethodBase.GetCurrentMethod().Name)
'''     End Sub
''' 
''' End Class
''' </code>
''' </example>
''' ----------------------------------------------------------------------------------------------------
''' <param name="eventInfo">
''' The event information.
''' </param>
''' 
''' <param name="target">
''' The event source.
''' </param>
''' 
''' <param name="handlerName">
''' The name of the delegate to be disassociated from the events raised by <paramref name="target"/>.
''' <para></para>
''' Note that the name is case-sensitive.
''' </param>
''' ----------------------------------------------------------------------------------------------------
<Extension>
Public Sub RemoveEventHandler(eventInfo As EventInfo, target As IComponent, handlerName As String)

    If String.IsNullOrWhiteSpace(handlerName)
        Throw New ArgumentNullException(NameOf(handlerName))
    End If

    For each handler As [Delegate] in GetEventHandlers(target, eventInfo.Name)
        If handler.Method.Name.Equals(handlerName, StringComparison.Ordinal)
            eventInfo.RemoveEventHandler(target, handler)
            Exit Sub
        End If
    Next handler
    Throw New ArgumentException($"No delegate was found with the specified name: '{handlerName}'", NameOf(handlerName))

End Sub
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

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

随机推荐

  • 调用纯虚函数[重复]

    这个问题在这里已经有答案了 可能的重复 在构造函数中调用虚函数 https stackoverflow com questions 962132 calling virtual functions inside constructors 看
  • JAXB 是否存在内存利用率问题?

    我使用 JAXB 进行 xml 解析 是否存在任何性能或内存利用率问题 需要注意的一件事是JAXBContext newInstance 是一个非常慢的操作 这是发生大量反射和类生成的地方 导致 duffymo 提到的烫发空间问题 值得庆幸
  • 如果我仅使用 JSON.Net,我可以安全地删除 C# 模型类中指定后缀的字段和属性吗

    我有一个 C 应用程序 我有一个从 xsd 生成的类 该类如下所示 public class Transaction public bool amountSpecified get set public double amount get
  • Ocaml 中查找树深度的尾递归函数

    我有一个类型tree定义如下 type a tree Leaf of a Node of a a tree a tree 我有一个函数可以找到树的深度 如下所示 let rec depth function Leaf x gt 0 Node
  • 并行化 tf.data.Dataset.from_generator

    我有一个不平凡的输入管道from generator非常适合 dataset tf data Dataset from generator complex img label generator tf int32 tf string dat
  • 我可以在 Excel VBA 中捕获并模拟 KeyDown 事件吗?

    阿伦 辛格 Arun Singh 对类似问题给出了很好的答案 编辑单元格时按下按键时是否会触发任何事件 https stackoverflow com questions 11153995 is there any event that f
  • 自动居中 vim 搜索结果

    当我使用 vim 或 gvim 进行搜索时 光标在窗口内的最终定位有些随机 经常落在窗口的最后一行 或第一行 搜索突出显示有所帮助 但必须在屏幕上四处寻找才能找到光标仍然很麻烦 而且有点讽刺的是 在 vim 在一些兆字节长的日志文件中找到下
  • IE11 阻止 ActiveX 运行

    我们的网络浏览器插件在 IE9 和 IE10 中工作正常 但在 IE11 中该插件既不被识别为附加组件 也不被允许运行 就好像IE11不再支持ActiveX一样 当然有解决方法 但是我们需要改变什么 注意 这个问题是作为插件的开发者提出的
  • Phonegap 相机返回带有黑条的照片

    我正在使用 Phonegap 3 4 当我在 iPhone 上拍摄风景照片时 我得到的照片顶部和底部有黑条 这是我的相机配置选项 var cameraOptions correctOrientation true quality 90 de
  • 如何更改电子邮件的 html5 模式错误消息

    Html5 required 属性通常会添加错误消息 请填写此字段 并且可以使用以下代码轻松更改它 oninvalid setCustomValidity Custom text in another language oninput se
  • 如何将记录插入到sql server express数据库表中?

    我正在尝试将文本框值插入到名为的数据库表中site list The site list表包含两列id and site name id设置为自动递增 这是我正在尝试的代码 执行时没有错误 但数据未显示在表中 SqlConnection c
  • 如何验证 Google Cloud Endpoints 服务 URL 的所有权?

    我已经设置了 Google Cloud Endpoints 项目 并且可以调用 http https 请求 Endpoints 为我提供了可以使用的 MY API endpoints MY PROJECT cloud goog 域名 我正在
  • Spark Dataframe 在性能上比 Pandas Dataframe 有何优势? [关闭]

    Closed 这个问题需要多问focused help closed questions 目前不接受答案 谁能解释一下 Spark Dataframes 在执行时间方面比 Pandas Dataframes 更好 我正在处理中等数量的数据并
  • didAddAnnotationViews 不适用于 MKMapView

    我一直在研究 MKMapView 并尝试了解 MKMapViewDelegate 系统的工作原理 到目前为止 我没有运气在添加当前位置标记时调用 didAddAnnotationViews 我已将我的应用程序委托设置为实现 MKMapVie
  • 导入到 .pch 文件和 .h 文件顶部?

    我是 obj c 的新手 我注意到 pch 文件用于包含整个 x code 项目中的文件 但是相同的文件也包含在一些 h 文件的顶部 例如 import 什么是如果它已经包含在 pch 文件中 是否需要在 h 文件的顶部再次导入它 预编译头
  • 了解 Laravel 5.2 上的队列和调度程序

    我正在尝试如何在 Laravel 上编写作业 根据 Laravel 文档 我创建了一个使用 mail queue 发送电子邮件的简单作业 我还添加了一个 cron 作业 该作业每分钟调用一次调度程序 而调度程序又每 5 分钟运行一次该作业
  • 显示 Scala 表达式的推断类型

    如何查看 Scala 编译器为表达式等推断的类型 我有一些具有复杂类型推断和隐式转换的代码 仅通过阅读代码很难看出发生了什么 我尝试过添加 scalacOptions in Compile Xprint types 在build sbt中
  • 关于fork和execve系统调用

    据说fork系统调用创建调用进程的克隆 然后 通常 子进程发出execve系统调用来更改其映像并运行新进程 为什么要分两步走 顺便说一句 什么是execve代表 采取两步走的原因是灵活性 在这两个步骤之间 您可以修改新执行的程序将继承的子进
  • 我们如何更改标签中的行?

    我有一个标签 在设置文本时我想更改行 例如 String str first line n Secondline JLabel label setText str 我尝试了上面的代码 但它不起作用 如何更改字符串中的行 Swing 不使用经
  • 获取自定义用户控件中声明的事件的所有事件处理程序

    我正在尝试编写一个通用函数 给出对控件 组件的引用以及在其类上声明的事件的名称 它应该能够检索 通过反射 当前为指定事件名称注册的所有事件处理程序 我遇到的第一个也是主要问题 已解决 因此您可以忽略本段 是我在 StackOverflow