如何删除“按钮”的“单击”事件的所有事件处理程序?

Posted

技术标签:

【中文标题】如何删除“按钮”的“单击”事件的所有事件处理程序?【英文标题】:How would it be possible to remove all event handlers of the 'Click' event of a 'Button'? 【发布时间】:2011-10-13 06:31:23 【问题描述】:

我有一个按钮控件,我需要删除所有附加到其Click event 的事件处理程序。

这怎么可能?

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

【问题讨论】:

你能用 MyButtonClass 替换 Button 吗? 我认为更简单的方法是禁用按钮 我想在之后只添加一个事件处理程序,因此禁用不会有帮助。 How to remove all event handlers from a control的可能重复 我知道这是一个很老的问题,但我认为您可能应该将所选答案更改为 Douglas 给出的反射选项。 “你不能”是一个非常糟糕的答案,尤其是当有一个非常好的例子表明你现在可以。 【参考方案1】:

我正在处理 WinForms 项目,我必须从 ToolStripMenuItem 中删除 click-EventHandler 并将其替换为我自己的处理程序。 (我必须修改单击 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);
    

【讨论】:

【参考方案2】:

注意:由于我发布原始答案的问题已关闭as a duplicate 这个问题,我在这里交叉发布我的答案的改进版本。 此答案仅适用于 WPF。它不适用于 Windows 窗体或任何其他 UI 框架。

以下是一个有用的实用方法,用于删除订阅给定元素上的路由事件的所有事件处理程序。如果您愿意,您可以轻松地将其转换为扩展方法。

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

编辑:我复制了bug fix implemented by corona,它会在没有订阅事件处理程序时阻止该方法抛出NullReferenceException。信用(和赞成)应该去他们的答案。

【讨论】:

是否可以在 Silverlight 中执行此操作? silverlight 没有 RoutedEventHandlerInfo。 ... 这可以扩展成扩展方法吗?那会很甜蜜...【参考方案3】:

只是想稍微扩展一下道格拉斯的常规,我非常喜欢。 我发现我需要向 eventHandlersStore 添加额外的 null 检查以处理传递的元素尚未附加任何事件的任何情况。

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

【讨论】:

【参考方案4】:

Jamie Dixon 发布的代码出现空错误问题,考虑到没有 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]);
    


我想这可以做得更好,但它可以满足我当前的需求。希望这对某人有用。

【讨论】:

【参考方案5】:

我在 *** 上找到了这个答案:

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

原始发帖人找到了here:

【讨论】:

这是特定于实现的——答案出现在 2008 年,我什至不想说它是否适用于 .NET 4。依赖这样的东西是一个非常糟糕的主意。 谢谢,但这一行总是返回 null:typeof(Control).GetField("EventClick", BindingFlags.Static | BindingFlags.NonPublic); 我实现了一个类似的解决方案,它使用 EventHandlerList 对象的更多内部方法工作。见答案***.com/questions/11031149/…【参考方案6】:

你不能,基本上 - 至少不能没有反思和很多肮脏。

事件严格来说是“订阅、取消订阅”——您不能取消订阅其他人的处理程序,就像您不能更改其他人对对象的引用一样。

【讨论】:

这正是我想要的;取消订阅别人的处理程序!我正在使用来自第三方的自定义按钮,并想删除作者的点击事件处理程序... @William:你不能,基本上 - 不能不依赖实现细节和反射,除非自定义按钮公开这种行为。事件的封装就是handler之间互不干扰。 @William: 1) 您正在以非设计方式使用控件,因此很容易导致问题。 2)您的解决方法很可能会在未来的版本中中断。 3)它不会在低信任环境中工作。基本上,你希望你的代码有多脆弱? 优点;考虑设计而不仅仅是实现。我创建了自己的自定义按钮,并从原始按钮中复制了我需要的内容(图标等)。

以上是关于如何删除“按钮”的“单击”事件的所有事件处理程序?的主要内容,如果未能解决你的问题,请参考以下文章

如何删除事件处理程序并将其重新附加到 c# 中的控件?

防止对话框在按钮的单击事件处理程序中关闭

如何在没有单独的单击事件处理程序的情况下处理多个表单提交 [关闭]

R Shiny - 使用jQuery和onclick事件处理程序删除UI

是否可以在 javascript 中删除给定元素的所有事件处理程序?

如何使用 delay() 清除事件调用集