Как можно было бы удалить все обработчики событий "Click" для "Button"?

У меня есть элемент управления кнопкой, и мне нужно удалить все обработчики событий, связанные с его событием Click.

Как это было бы возможно?

Button button = GetButton();
button.Click.RemoveAllEventHandlers();

5 ответов

Решение

Вы не можете, в основном - по крайней мере, не без размышлений и большого количества грязи.

События строго "подписываются, отписываются" - вы не можете отписаться от чьего-либо другого обработчика, равно как не можете изменить чужую ссылку на объект.

Примечание. Поскольку вопрос, на который я разместил свой исходный ответ, был закрыт как дубликат этого вопроса, я публикую здесь улучшенную версию своего ответа. Этот ответ относится только к WPF. Это не будет работать на Windows Forms или любой другой структуре пользовательского интерфейса.

Ниже приведен полезный служебный метод для удаления всех обработчиков событий, подписанных на перенаправленное событие в данном элементе. Вы можете легко преобразовать это в метод расширения, если хотите.

/// <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://stackru.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);
}

Затем вы можете легко вызвать этот метод утилиты для ваших кнопок Click событие:

RemoveRoutedEventHandlers(button, Button.ClickEvent);

Изменить: я скопировал исправление ошибки, реализованное Corona, который останавливает метод бросать NullReferenceException когда обработчики событий не подписаны. Кредит (и отзывы) должен идти на их ответ.

Просто хотел немного расширить рутину Дугласа, что мне очень понравилось. Я обнаружил, что мне нужно добавить дополнительную нулевую проверку в eventHandlersStore для обработки любых случаев, когда переданный элемент еще не имеет прикрепленных событий.

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

Я нашел этот ответ здесь, на Stackru:

Как удалить все обработчики событий из элемента управления

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

Какой оригинальный плакат нашел здесь:

У меня была нулевая ошибка с кодом, опубликованным Джейми Диксоном, чтобы учесть, что у него нет события Click.

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

}

Затем небольшое изменение, и оно должно быть для любого события.

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

}

Я думаю, что это можно сделать лучше, но это работает для моей текущей потребности. Надеюсь, это кому-нибудь пригодится.

Я работал над проектом WinForms, где мне пришлось удалить click-EventHandler из ToolStripMenuItem и заменить его своим собственным обработчиком. (Мне пришлось изменить действие, выполняемое при нажатии элемента contextMenu)

у меня не работал код от user2113340. Мне пришлось изменить его вот так, чтобы работать с 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);
    }
Другие вопросы по тегам