如何检查Sub是否正在处理非自定义事件?

时间:2013-06-09 17:55:09

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

对于自定义事件,我可以像这样检查处理程序:

 If Object.EventNameEvent Is Nothing Then
    MsgBox("Is not handling it.")
 End If

...但是我怎么能这样做,例如,检查设计器中生成的按钮的“.click”事件?这不起作用:

If Button1.ClickEvent Is Nothing Then
   MsgBox("Is not handling it.")
End If
  

更新

我的琐事示例:

    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
  

更新2:

我正在尝试使用@ varocarbas 解决方案,但并没有完全按照我的需要进行操作,因此我尝试进行必要的修改以使其正常工作。问题是事件“FontChaged”没有返回所需的结果,如下所示:

    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

4 个答案:

答案 0 :(得分:2)

这是我尝试转换和修改@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

答案 1 :(得分:1)

我将在.NET框架中介绍我处理这类事情的非正统方法。从你的问题......

  

...“点击”在设计师中生成的按钮事件......

所以我假设这只适用于System.Windows.Forms.Control的子类。正如许多其他人所证明的那样,使用Control EventHandlerList管理其事件系统的方式略有不同。例如,对于KeyDown事件,它实现如下...

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

其中EventKeyDown被定义为稍后用于检索处理程序的“关键”对象

Private Shared ReadOnly EventKeyDown As Object

好的,到目前为止,太棒了! Idle_Mind和varocarbas都指出了解决方案,但是你提到FontChanged事件似乎不适合所提出的方法。为什么?因为两种方法都假定会有一个EventFontChanged字段对象,而实际上它被定义为......

Private Shared ReadOnly EventFont As Object

现在很明显假设......

GetField("Event" + eventName)

......根本不起作用。

由于所有事件都在Control类中定义,我们可以直接从那里反映出来。为了找到对象键,我们需要从事件处理程序中找到“字段引用”(请参阅​​上面的KeyDown示例实现)。我们得到CIL字节代码,循环遍历它,直到我们找到加载静态字段引用(ldsfld)的操作码。 ldsfld的操作数是一个32位整数,可以解析为一个类型。剩下的很简单,因为Idle_Mind和varocarbas都已经证明了。

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

这段代码不安全,因为我们还没有考虑每个字节码的操作数大小!因此,循环可能会意外地拾取非操作码字节并将其误认为是ldsfld指令。

答案 2 :(得分:0)

首先,我想强调一下Hans Passant的代码非常出色,并且有一个很好的起点,大部分工作都已完成。我已经参与了Idle_Mind代码的一部分并做了一些研究,在这里你得到了你想要的东西:

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

答案 3 :(得分:0)

在做了一些研究,测试并耗尽了我的知识和这方面的免费信息后,我不得不放弃所要求的修复。以下是我的结论:

  • 原始方法的限制(来自Hans Passant)依赖于FieldInfo,假设每个事件都有关联的Fieldwhat does not seem to be the case
  • 另一方面,EventHandlerList似乎包括所有事件。不幸的是,它是一个特殊的字典,只能通过依赖定义给定Object的{​​{1}}来检索值(在(转换的)Hans Passant代码中:Event)。< / LI>
  • 假设上述两点是正确的(互联网上没有太多信息),事件不应该是Dim secret As Object = curEvent.GetValue(Nothing),而是FieldInfoEventInfo)。除了计算Dim curEvent As EventInfo = GetType(Control).GetEvent(evnt.Name)变量以及secret所需的索引之外,所有代码都可以正常使用此更改。令人惊讶的是,这个问题的解决方案并不容易:显然,只有events可以提供正确的索引(?!)。
  • 我找到了似乎是潜在的替代选择:要么从secret中提取信息而不使用EventHandlerListcode on this line);或将secret object / secret object替换为其他变量,例如:EventHandlerListPropertyDescriptorCollectionEventDescriptorCollection

如果有人可以纠正/扩展此帖子以帮助(我或其他人)清除此问题,那将是非常好的。