21

I have a button control, and I'd need to remove all the event handlers attached to its Click event.

How would that be possible?

Button button = GetButton();
button.Click.RemoveAllEventHandlers();
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
The Light
  • 26,341
  • 62
  • 176
  • 258
  • Can you replace Button with MyButtonClass ? – H H Jul 26 '11 at 09:47
  • I think the easier way would be to disable the button – V4Vendetta Jul 26 '11 at 09:48
  • I'd like to add only one event handler afterwards so disabling wouldn't help. – The Light Jul 26 '11 at 09:52
  • possible duplicate of [How to remove all event handlers from a control](http://stackoverflow.com/questions/91778/how-to-remove-all-event-handlers-from-a-control) – nawfal Jul 22 '13 at 12:32
  • 1
    I know this is a pretty old question, but I think you should probably change the selected answer to the reflection option given by Douglas instead. "You can't" is a pretty poor answer, especially when there's a perfectly good example showing you can now. – tpartee May 25 '16 at 21:58

6 Answers6

41

Note: Since the question on which I posted my original answer was closed as a duplicate of this question, I'm cross-posting an improved version of my answer here. This answer only applies to WPF. It will not work on Windows Forms or any other UI framework.

The below is a helpful utility method for removing all event handlers subscribed to a routed event on a given element. You can trivially convert this to an extension method if you like.

/// <summary>
/// Removes all event handlers subscribed to the specified routed event from the specified element.
/// </summary>
/// <param name="element">The UI element on which the routed event is defined.</param>
/// <param name="routedEvent">The routed event for which to remove the event handlers.</param>
public static void RemoveRoutedEventHandlers(UIElement element, RoutedEvent routedEvent)
{
    // Get the EventHandlersStore instance which holds event handlers for the specified element.
    // The EventHandlersStore class is declared as internal.
    var eventHandlersStoreProperty = typeof(UIElement).GetProperty(
        "EventHandlersStore", BindingFlags.Instance | BindingFlags.NonPublic);
    object eventHandlersStore = eventHandlersStoreProperty.GetValue(element, null);

    // If no event handlers are subscribed, eventHandlersStore will be null.
    // Credit: https://stackoverflow.com/a/16392387/1149773
    if (eventHandlersStore == null)
        return;

    // Invoke the GetRoutedEventHandlers method on the EventHandlersStore instance 
    // for getting an array of the subscribed event handlers.
    var getRoutedEventHandlers = eventHandlersStore.GetType().GetMethod(
        "GetRoutedEventHandlers", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
    var routedEventHandlers = (RoutedEventHandlerInfo[])getRoutedEventHandlers.Invoke(
        eventHandlersStore, new object[] { routedEvent });

    // Iteratively remove all routed event handlers from the element.
    foreach (var routedEventHandler in routedEventHandlers)
        element.RemoveHandler(routedEvent, routedEventHandler.Handler);
}

You could then easily call this utility method for your button's Click event:

RemoveRoutedEventHandlers(button, Button.ClickEvent);

Edit: I've copied the bug fix implemented by corona, which stops the method from throwing a NullReferenceException when no event handlers are subscribed. Credit (and upvotes) should go to their answer.

Community
  • 1
  • 1
Douglas
  • 53,759
  • 13
  • 140
  • 188
15

Just wanted to expand on Douglas' routine slightly, which I liked very much. I found I needed to add the extra null check to eventHandlersStore to handle any cases where the element passed didn't have any events attached yet.

/// <summary>
/// Removes all event handlers subscribed to the specified routed event from the specified element.
/// </summary>
/// <param name="element">The UI element on which the routed event is defined.</param>
/// <param name="routedEvent">The routed event for which to remove the event handlers.</param>
public static void RemoveRoutedEventHandlers(UIElement element, RoutedEvent routedEvent)
{
    // Get the EventHandlersStore instance which holds event handlers for the specified element.
    // The EventHandlersStore class is declared as internal.
    var eventHandlersStoreProperty = typeof(UIElement).GetProperty(
        "EventHandlersStore", BindingFlags.Instance | BindingFlags.NonPublic);
    object eventHandlersStore = eventHandlersStoreProperty.GetValue(element, null);

    if (eventHandlersStore == null) return;

    // Invoke the GetRoutedEventHandlers method on the EventHandlersStore instance 
    // for getting an array of the subscribed event handlers.
    var getRoutedEventHandlers = eventHandlersStore.GetType().GetMethod(
        "GetRoutedEventHandlers", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
    var routedEventHandlers = (RoutedEventHandlerInfo[])getRoutedEventHandlers.Invoke(
        eventHandlersStore, new object[] { routedEvent });

    // Iteratively remove all routed event handlers from the element.
    foreach (var routedEventHandler in routedEventHandlers)
        element.RemoveHandler(routedEvent, routedEventHandler.Handler);
}
corona
  • 159
  • 1
  • 2
10

You can't, basically - at least not without reflection and a lot of grubbiness.

Events are strictly "subscribe, unsubscribe" - you can't unsubscribe someone else's handler, any more than you can change someone else's reference to an object.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • 1
    that's exactly what I want; unsubscribing someone else's handler! I'm using a custom button from a thirdparty and would like to remove the author's click event handler... – The Light Jul 26 '11 at 09:54
  • @William: You can't, basically - not without relying on implementation details and reflection, unless the custom button exposes such behaviour. The encapsulation of events is that handlers don't interfere with each other. – Jon Skeet Jul 26 '11 at 09:55
  • 4
    @William: 1) You're using the control in a way for which it was not designed, and could thus cause problems very easily. 2) Your workaround could well break in a future version. 3) It won't work in low-trust environments. Basically, how fragile do you want your code to be? – Jon Skeet Jul 26 '11 at 10:14
  • good points; thinking about design not just implementation. I've created my own custom button instead and copied what I needed (icon, etc) from the original. – The Light Jul 26 '11 at 10:27
5

I found this answer here on StackOverflow:

How to remove all event handlers from a control

private void RemoveClickEvent(Button b)
{
    FieldInfo f1 = typeof(Control).GetField("EventClick", 
        BindingFlags.Static | BindingFlags.NonPublic);
    object obj = f1.GetValue(b);
    PropertyInfo pi = b.GetType().GetProperty("Events",  
        BindingFlags.NonPublic | BindingFlags.Instance);
    EventHandlerList list = (EventHandlerList)pi.GetValue(b, null);
    list.RemoveHandler(obj, list[obj]);
}

Which the origional poster found here:

Community
  • 1
  • 1
Jamie Dixon
  • 53,019
  • 19
  • 125
  • 162
  • 2
    That's implementation-specific though - the answer appeared in 2008, I wouldn't even like to say whether it will work on .NET 4. It's a really bad idea to rely on things like this. – Jon Skeet Jul 26 '11 at 09:45
  • 2
    thanks but this line always returns null: typeof(Control).GetField("EventClick", BindingFlags.Static | BindingFlags.NonPublic); – The Light Jul 26 '11 at 10:03
  • I implemented a similar solution that works using a couple more internal methods of the EventHandlerList object. See answer http://stackoverflow.com/questions/11031149/solution-to-remove-event-handler-dynamically-using-reflection-is-there-a-bett – Dinis Cruz Jun 14 '12 at 10:27
0

I had the null error issue with the code Jamie Dixon posted to take in to account not having a Click event.

private void RemoveClickEvent(Control control)
{
    // chenged "FieldInfo f1 = typeof(Control)" to "var f1 = b.GetType()". By changing to 
    // the type of the  passed in control we can use this for any control with a click event.
    // using var allows for null checking and lowering the chance of exceptions.

    var fi = control.GetType().GetField("EventClick", BindingFlags.Static | BindingFlags.NonPublic);
    if (fi != null)
    {
        object obj = fi.GetValue(control);
        PropertyInfo pi = control.GetType().GetProperty("Events", BindingFlags.NonPublic | BindingFlags.Instance);
        EventHandlerList list = (EventHandlerList)pi.GetValue(control, null);
        list.RemoveHandler(obj, list[obj]);
    }

}

Then a small change and it should be for any event.

private void RemoveClickEvent(Control control, string theEvent)
{
    // chenged "FieldInfo f1 = typeof(Control)" to "var f1 = b.GetType()". By changing to 
    // the type of the  passed in control we can use this for any control with a click event.
    // using var allows for null checking and lowering the chance of exceptions.

    var fi = control.GetType().GetField(theEvent, BindingFlags.Static | BindingFlags.NonPublic);
    if (fi != null)
    {
        object obj = fi.GetValue(control);
        PropertyInfo pi = control.GetType().GetProperty("Events", BindingFlags.NonPublic | BindingFlags.Instance);
        EventHandlerList list = (EventHandlerList)pi.GetValue(control, null);
        list.RemoveHandler(obj, list[obj]);
    }

}

I imagine this could be made better but it works for my current need. Hope this is useful for someone.

0

I was working on WinForms project where I had to remove the click-EventHandler from a ToolStripMenuItem and replace it with my own Handler. (I had to modify the action taken when a contextMenu Item was clicked)

for me the code from user2113340 did not work. I had to modify it like this to work with ToolStripMenuItem:

    private void RemoveClickEvent(ToolStripMenuItem control)
    {
        FieldInfo eventClick = typeof(Control).GetField("EventClick", BindingFlags.NonPublic | BindingFlags.Static);
        PropertyInfo eventsProp = typeof(Component).GetProperty("Events", BindingFlags.NonPublic | BindingFlags.Instance);
        EventHandlerList events = (EventHandlerList)eventsProp.GetValue(control, null);
        FieldInfo headInfo = events.GetType().GetField("head", BindingFlags.NonPublic | BindingFlags.Instance);
        object head = headInfo.GetValue(events);
        FieldInfo keyType = head.GetType().GetField("key", BindingFlags.NonPublic | BindingFlags.Instance);
        object key = keyType.GetValue(head);
        Delegate d1 = events[key];
        events.RemoveHandler(key, d1);
    }
Markus1980Wien
  • 471
  • 1
  • 5
  • 15