How to check which event are assigned?

2019-04-01 13:26发布

问题:

My code is as follows.

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

However, eventinfo gives me the list of all the controls which belongs to the grid. Whereas there are only two events that are defined that is KeyDown and Validating in the main class.

How can I get a list of these assigned events, that is, Keydown and Validating?

回答1:

Windows Forms (WinForms) has a tricky model of events for components (and DataGridView is a component). Some events are inherited from Control (like FontChanged, ForeColorChanged, etc.), but all specific to component events are stored in a single EventHandlerList object, which is inherited from Component (BTW, events from Control are also stored there, see the update at the end of the answer). There is a protected Events property for that:

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

And here is the way how event handlers are added for DataGridView events:

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

As you can see, delegate (value) is passed to EventHandlerList with some key value. All event handlers are stored there by key. You can think about EventHandlerList as a dictionary with objects as keys, and delegates as values. So, here is how you can get components' events with reflection. The first step is getting those keys. As you already noticed, they are named as EVENT_XXX:

private static readonly object EVENT_DATAGRIDVIEWCELLVALUECHANGED;
private static readonly object EVENT_DATAGRIDVIEWCELLMOUSEUP;
// etc.

So here we go:

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

Next, we need our 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;

And the last step, getting the list of keys, which have handlers attached:

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

That will give you the keys. If you need delegates, then simply look in the handlers list for them.

UPDATE: Events which inherited from Control are also stored in EventHandlerList, but for some unknown reason their keys have different names, like EventForeColor. You can use the same approach as above to get those keys and check if handlers are attached.



回答2:

According to the comments in this question showing that this is a Windows Forms related question it's almost not possible to determine assigned event handlers by iterating the list, not without reflection anyway.

The following text is from Hans Passant's answer on a similar question:

Windows Forms has strong counter-measures against doing this. Most controls store the event handler reference in a list that requires a secret 'cookie'. The cookie value is dynamically created, you cannot guess it up front. Reflection is a backdoor, you have to know the cookie variable name. The one for the Control.Click event is named "EventClick" for example, you can see this in the Reference Source or with Reflector.

Knowing the cookie name would help you get the assigned event, but I am not sure if there is such a list with all the names which you could iterate. See this other answer which demonstrates how to get an event from one control where the cookie name is known.

Here you can find another example (which still goes by using a known event name).



回答3:

Can't you use reflection to look the list of handlers?

Here is a simple console app looking at handlers hooked on the events of a serial port instance:

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);
                    }
                }          
            }                     
        }
    }
}


回答4:

Based on Kyle comment:

@Jacob Yes.... Because these are the only two events declared in the main class – Kyle

Events would contain KeyDown and Validating events only.

        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}