如何检查已分配的事件?

3

我的代码如下所示。

Control[] FoundControls = null;
FoundControls = MyFunctionToFilter(TF, c => c.Name != null && c.Name.StartsWith("grid"));
var eventinfo = FoundControls[0].GetType().GetEvents();

然而,eventinfo 给出了属于网格的所有控件列表。而主类中只定义了两个事件,即 KeyDown 和 Validating。
我该如何获取这些分配的事件列表,即 Keydown 和 Validating?

@Jacob 是的... 因为这是在主类中声明的唯二事件。 - Rohit
@Kyle 是 WinForms 吗? - Sergey Berezovskiy
请解释您真正的问题。为什么您想知道可用的事件?为什么不只订阅您感兴趣的事件呢?这就像打电话给公交公司询问他们的巴士是否使用柴油发动机,而实际上您只是想从A点到B点。 - CodeCaster
好的...我猜那会有所帮助 - Rohit
@lazyberezovsky 是的,只针对Datagridview。 - Rohit
显示剩余5条评论
4个回答

5

Windows Forms (WinForms) 拥有一个针对组件(包括 DataGridView)的事件模型。一些事件从 Control 继承而来(例如 FontChangedForeColorChanged 等),但是所有特定于组件的事件都存储在单个 EventHandlerList 对象中,该对象继承自 Component(顺便说一下,控件事件也存储在那里,参见答案末尾的更新)。这里有一个受保护的 Events 属性:

protected EventHandlerList Events
{
    get
    {
        if (this.events == null)            
            this.events = new EventHandlerList(this);            
        return this.events;
    }
}

以下是为 DataGridView 事件添加事件处理程序的方法:

public event DataGridViewCellEventHandler CellValueChanged
{
    add { Events.AddHandler(EVENT_DATAGRIDVIEWCELLVALUECHANGED, value); }
    remove { Events.RemoveHandler(EVENT_DATAGRIDVIEWCELLVALUECHANGED, value); }
}

如您所见,将委托(value)以某个键值传递给 EventHandlerList。所有事件处理程序都会按照键值存储在其中。您可以将 EventHandlerList 视为具有对象键和委托值的字典。因此,以下是通过反射获取组件事件的方法。第一步是获取这些键。正如您已经注意到,它们被命名为 EVENT_XXX:
private static readonly object EVENT_DATAGRIDVIEWCELLVALUECHANGED;
private static readonly object EVENT_DATAGRIDVIEWCELLMOUSEUP;
// etc.

所以我们开始吧:

var keys = typeof(DataGridView) // You can use `GetType()` of component object.
   .GetFields(BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.FlattenHierarchy)
   .Where(f => f.Name.StartsWith("EVENT_"));

接下来,我们需要我们的EventHandlerList:
var events = typeof(DataGridView) // or GetType()
          .GetProperty("Events", BindingFlags.Instance | BindingFlags.NonPublic);
// Could be null, check that
EventHandlerList handlers = events.GetValue(grid) as EventHandlerList;

最后一步是获取已附加处理程序的键列表:

var result = keys.Where(f => handlers[f.GetValue(null)] != null)
                 .ToList();

那将给你钥匙。如果你需要代表,那么只需在处理程序列表中查找它们。
更新:从Control继承的事件也存储在EventHandlerList中,但出于某种未知原因,它们的键具有不同的名称,如EventForeColor。您可以使用上述相同的方法获取这些键,并检查是否附加了处理程序。

2
根据此问题中的评论显示,这是一个与Windows Forms相关的问题,几乎不可能通过迭代列表来确定分配的事件处理程序,反射也不行。以下文本摘自类似问题的Hans Passant的答案
“Windows Forms采取了强有力的措施来防止这种做法。大多数控件将事件处理程序引用存储在一个需要秘密cookie的列表中。Cookie值是动态创建的,你不能提前猜到它。反射是一个后门,你必须知道cookie变量名。例如,Control.Click事件的cookie名称为“EventClick”,可以在Reference Source或Reflector中查看。
知道cookie名称可以帮助您获取分配的事件,但我不确定是否有这样的列表,您可以迭代其中的所有名称。请参见这个其他答案,它演示了如何从已知cookie名称的一个控件中获取事件。
在这里,您可以找到另一个例子(仍然使用已知事件名称)。

2
@TwoMore更新的答案也是不正确的。你不能只获取字段,因为在winform中使用了EventHandlerList。为什么人们还要投票支持错误的答案呢? - Sergey Berezovskiy
1
这是一个WinForm问题吗? - CloudyMarble
1
@TwoMore 是的,请查看问题的评论。 - Sergey Berezovskiy
1
@TwoMore 在你提到的例子中,“MyEvent”事件是预先知道的......而在我的情况下,我们正在尝试找出声明的事件处理程序是哪些......在这种情况下,它是(Keydown和validating),但可能还有更多......所以我不认为这个解决方案有帮助。 - Rohit
1
@Kyle,还有另一个问题,请看我的先前评论。 - Sergey Berezovskiy
显示剩余2条评论

0

你不能使用反射来查看处理程序列表吗?

这是一个简单的控制台应用程序,用于查看串口实例事件上挂钩的处理程序:

using System;
using System.IO.Ports;
using System.Reflection;

class Program
{
    static void OnErrorReceived(object sender, SerialErrorReceivedEventArgs e){}

    static void Main(string[] args)
    {
        var serialPort = new SerialPort();

        // Add a handler so we actually get something out.
        serialPort.ErrorReceived += OnErrorReceived;  

        foreach (var eventInfo in serialPort.GetType().GetEvents())
        {
            var field = serialPort.GetType().GetField(eventInfo.Name, BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.GetField);
            if (field != null)
            {
                var backingDelegate = (Delegate)field.GetValue(serialPort);
                if (backingDelegate != null)
                {
                    var subscribedDelegates = backingDelegate.GetInvocationList();

                    foreach (var subscribedDelegate in subscribedDelegates)
                    {
                        Console.WriteLine(subscribedDelegate.Method.Name + " is hooked on " + eventInfo.Name);
                    }
                }          
            }                     
        }
    }
}

-1
根据Kyle的评论:

@Jacob 是的...因为这是在主类中声明的唯一两个事件 - Kyle

事件仅包含KeyDown和Validating事件。

        Control a = new TextBox();
        var events = a.GetType().GetEvents().Where(eventInfo => eventInfo != null && (eventInfo.Name == "KeyDown" || eventInfo.Name == "Validating"));

+events.ToList()[0] {System.Windows.Forms.KeyEventHandler KeyDown} +events.ToList()[1] {System.ComponentModel.CancelEventHandler Validating}

我没有给你的回答点踩,但它并不是正确的答案。OP想要定义哪些事件有处理程序分配。 - Sergey Berezovskiy
@lazyberezovsky:看了这个链接后,我终于明白了问题所在:https://dev59.com/rFDTa4cB1Zd3GeqPNOhD#3856171 。谢谢! - jacob aloysious

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接