Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to obtain the invocation list of any event

How to get the delegate list form event of the control in WPF.

I have tried the following code but it will return the field info as null

TextBox cont = new TextBox();
cont.TextChanged += new TextChangedEventHandler(cont_TextChanged);
FieldInfo fi = cont.GetType().GetField("TextChanged", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.GetField | BindingFlags.Public | BindingFlags.Static);
Delegate del = (Delegate)fi.GetValue(cont);
like image 518
Ponraja Avatar asked Jan 09 '12 14:01

Ponraja


3 Answers

I dont know why the people say it's not possible:

Lets say you want to disable any event temporary, you can create a method like this:

static Delegate[] DisableEvents(this Control ctrl, string eventName)
{
        PropertyInfo propertyInfo = ctrl.GetType().GetProperty("Events", BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance);
        EventHandlerList eventHandlerList = propertyInfo.GetValue(ctrl, new object[] { }) as EventHandlerList;
        FieldInfo fieldInfo = typeof(Control).GetField("Event"+eventName, BindingFlags.NonPublic | BindingFlags.Static);

        object eventKey = fieldInfo.GetValue(ctrl);
        var eventHandler = eventHandlerList[eventKey] as Delegate;
        Delegate[] invocationList = eventHandler.GetInvocationList();
        foreach (EventHandler item in invocationList)
        {
            ctrl.GetType().GetEvent(eventName).RemoveEventHandler(ctrl, item);
        }
        return invocationList;

}|

You can call it like this:

var events = textbox1.DisableEvents("GotFocus")

If you want to add them again you just need to go through the events list.

like image 92
Kenneth J. Sanchez Venegas Avatar answered Sep 21 '22 18:09

Kenneth J. Sanchez Venegas


The approach described in the previous answer works perfectly. However, to use it, you need to be sure that the event is implemented not as a field, but as a property. At the same time, the reference to the delegate is stored in the internal object of the Dictionary class. You can read more here: How to: Use a Dictionary to Store Event Instances (C# Programming Guide)

like image 26
retrograde.su Avatar answered Sep 21 '22 18:09

retrograde.su


Reflection, as recommended by Kenneth J. Sanchez Venegas, is the only way to get an invocation list of event outside of a class. But when performance is important, we can make a helper with compiled lambdas like this:

public class EventHelper<TClass, TDelegate> where TDelegate : Delegate
{
    public EventHelper(string eventName)
    {
        var fieldInfo = typeof(TClass).GetField(eventName, BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance) ??
                throw new ArgumentException("Event was not found", nameof(eventName));

        var thisArg = Expression.Parameter(typeof(TClass));
        var body = Expression.Convert(Expression.Field(thisArg, fieldInfo), typeof(TDelegate));
        Get = Expression.Lambda<Func<TClass, TDelegate?>>(body, thisArg).Compile();
    }

    public Func<TClass, TDelegate?> Get { get; }

    public IEnumerable<TDelegate> GetInvocationList(TClass forInstance)
    {
        var eventDelegate = Get(forInstance);
        if (eventDelegate is null)
            yield break;

        foreach (var d in eventDelegate.GetInvocationList())
            yield return (TDelegate)d;
    }
}

Assuming we will have one instance of this helper and use it every time:

private static readonly EventHelper<JsonSerializer, EventHandler<ErrorEventArgs>> _errorHelper = new(nameof(JsonSerializer.Error));
...

JsonSerializerSettings settings = new();
foreach (var errorHandler in _errorHelper.GetInvocationList(serializer))
    settings.Error += errorHandler;

...

*I'm using this code to extract settings from Json.NET JsonSerializer so examples are taken from that context

**Code examples using C# 9.0 syntax features, tested on .NET 5 and .NET 6.

like image 39
Aleksej Solomatin Avatar answered Sep 20 '22 18:09

Aleksej Solomatin