Jak usunąć wszystkie programy obsługi zdarzeń ze zdarzenia


366

Aby utworzyć nową procedurę obsługi zdarzeń w kontrolce, możesz to zrobić

c.Click += new EventHandler(mainFormButton_Click);

albo to

c.Click += mainFormButton_Click;

i aby usunąć moduł obsługi zdarzeń, możesz to zrobić

c.Click -= mainFormButton_Click;

Ale jak usunąć wszystkie moduły obsługi zdarzeń ze zdarzenia?


10
Jeśli ktoś przyszedł tutaj, szukając rozwiązania WPF, możesz rzucić okiem na tę odpowiedź .
Douglas,

1
Czy nie możesz po prostu ustawić c.Click = null?
Alexania

Jest to jedna z tych rzeczy, które uważam za absurdalnie skomplikowane. ClearNajwyraźniej prosta metoda była zbyt wielkim wysiłkiem
Zimano

Odpowiedzi:


167

Znalazłem rozwiązanie na forach MSDN . Przykładowy kod poniżej usunie wszystkie Clickzdarzenia z button1.

public partial class Form1 : Form
{
        public Form1()
        {
            InitializeComponent();

            button1.Click += button1_Click;
            button1.Click += button1_Click2;
            button2.Click += button2_Click;
        }

        private void button1_Click(object sender, EventArgs e)
        {
            MessageBox.Show("Hello");
        }

        private void button1_Click2(object sender, EventArgs e)
        {
            MessageBox.Show("World");
        }

        private void button2_Click(object sender, EventArgs e)
        {
            RemoveClickEvent(button1);
        }

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

Jeśli parametr button1 ma wartość null, wszystkie procedury obsługi zdarzeń są dołączone do button1. Czy kliknięcie jest poprawnie usunięte?
Damien

3
Popraw mnie jeśli się mylę, ale nie powinny w pierwszej linii od RemoveClickEventpoczątku z: FieldInfo f1 = typeof(Button)? Dostaję zero, GetFieldjeśli użyję Control.
Protektor jeden

2
To nie wydaje się działać dla ToolStripButtons. Zamieniłem Button w RemoveClickEvent na ToolStripButton, ale zdarzenia są nadal na miejscu po wywołaniu RemoveClickEvent. Czy ktoś ma rozwiązanie tego problemu?
Skalli

1
powyższy link w MSDN sugeruje również wypróbowanie myButton.Click + = null; jeśli chcesz usunąć wszystkich delegatów (nie dla Click, ale dla innych wydarzeń ..)
hello_earth

1
@hello_earth Wydaje się, że nie działaObservableCollection.CollectionChanged += null;
Mike de Klerk

146

Za bardzo utrudniacie sobie to. To takie proste:

void OnFormClosing(object sender, FormClosingEventArgs e)
{
    foreach(Delegate d in FindClicked.GetInvocationList())
    {
        FindClicked -= (FindClickedHandler)d;
    }
}

57
Działa to tylko wtedy, gdy jesteś właścicielem wydarzenia. Spróbuj zrobić to na kontrolerze.
Delyan

226
... a jeśli jesteś właścicielem wydarzenia, możesz po prostu pisać, FindClicked = null;co jest raczej prostsze.
Jon Skeet,

79
Co to jest FindClicked?
Levitikon

3
Nie działa to w przypadku zdarzeń Kinect - kinect.ColorFrameReady -= MyEventHanderdziała, ale nie ma GetInvocationList()metody instancji kinect umożliwiającej iterację nad ich delegatami.
Brent Faust,

GetInvocationListnie znaleziono.
Żart Huang

75

Od usunięcia wszystkich programów obsługi zdarzeń :

Bezpośrednio nie, w dużej części, ponieważ nie można po prostu ustawić zdarzenia na zero.

Pośrednio można ustawić rzeczywiste zdarzenie jako prywatne i utworzyć wokół niego właściwość, która śledzi wszystkich dodawanych / odejmowanych delegatów.

Wykonaj następujące czynności:

List<EventHandler> delegates = new List<EventHandler>();

private event EventHandler MyRealEvent;

public event EventHandler MyEvent
{
    add
    {
        MyRealEvent += value;
        delegates.Add(value);
    }

    remove
    {
        MyRealEvent -= value;
        delegates.Remove(value);
    }
}

public void RemoveAllEvents()
{
    foreach(EventHandler eh in delegates)
    {
        MyRealEvent -= eh;
    }
    delegates.Clear();
}

4
Myślałem, że OP odnosi się do ogólnych kontrolek .net .. w których tego rodzaju owijanie może być niemożliwe.
Gishu,

4
możesz przejąć kontrolę, a potem
Tom Fobear,

To także prowadzi do utrzymywania dwóch list, patrz stackoverflow.com/questions/91778/... do resetowania lub stackoverflow.com/questions/91778/... aby otworzyć listę.
TN.

63

Zaakceptowana odpowiedź nie jest pełna. Nie działa w przypadku zdarzeń zadeklarowanych jako {dodaj; usunąć;}

Oto działający kod:

public static void ClearEventInvocations(this object obj, string eventName)
{
    var fi = obj.GetType().GetEventField(eventName);
    if (fi == null) return;
    fi.SetValue(obj, null);
}

private static FieldInfo GetEventField(this Type type, string eventName)
{
    FieldInfo field = null;
    while (type != null)
    {
        /* Find events defined as field */
        field = type.GetField(eventName, BindingFlags.Static | BindingFlags.Instance | BindingFlags.NonPublic);
        if (field != null && (field.FieldType == typeof(MulticastDelegate) || field.FieldType.IsSubclassOf(typeof(MulticastDelegate))))
            break;

        /* Find events defined as property { add; remove; } */
        field = type.GetField("EVENT_" + eventName.ToUpper(), BindingFlags.Static | BindingFlags.Instance | BindingFlags.NonPublic);
        if (field != null)
            break;
        type = type.BaseType;
    }
    return field;
}

4
Ta wersja działała dla mnie. Zaakceptowana wersja nie działała. +1 za to.
Meister Schnitzel

1
Nie działał dla zdarzeń WPF, dopóki nie użyłem BindingFlags.Publicw pierwszym GetFieldwywołaniu.
Lennart

40

Usunięcie nieistniejącej procedury obsługi zdarzeń nie szkodzi. Więc jeśli wiesz, jakie mogą być programy obsługi, możesz po prostu usunąć je wszystkie. Właśnie miałem podobny przypadek. W niektórych przypadkach może to pomóc.

Lubić:

// Add handlers...
if (something)
{
    c.Click += DoesSomething;
}
else
{
    c.Click += DoesSomethingElse;
}

// Remove handlers...
c.Click -= DoesSomething;
c.Click -= DoesSomethingElse;

16

Właściwie używam tej metody i działa ona idealnie. Byłem „zainspirowany” kodem napisanym tutaj przez Aeonhacka .

Public Event MyEvent()
Protected Overrides Sub Dispose(ByVal disposing As Boolean)
    If MyEventEvent IsNot Nothing Then
        For Each d In MyEventEvent.GetInvocationList ' If this throws an exception, try using .ToArray
            RemoveHandler MyEvent, d
        Next
    End If
End Sub

Pole MyEventEvent jest ukryte, ale istnieje.

Debugowanie pozwala zobaczyć, jak d.targetobiekt faktycznie obsługuje zdarzenie, orazd.method jego metodę. Musisz go tylko usunąć.

Działa świetnie. Koniec z obiektami nie będącymi GC z powodu procedur obsługi zdarzeń.


2
Proszę nie pisać odpowiedzi w innych językach.
Hille

10

Nienawidziłem przedstawionych tutaj kompletnych rozwiązań, zrobiłem miks i przetestowałem teraz, pracowałem dla każdego modułu obsługi zdarzeń:

public class MyMain()
    public void MyMethod() {
        AnotherClass.TheEventHandler += DoSomeThing;
    }

    private void DoSomething(object sender, EventArgs e) {
        Debug.WriteLine("I did something");
        AnotherClass.ClearAllDelegatesOfTheEventHandler();
    }

}

public static class AnotherClass {

    public static event EventHandler TheEventHandler;

    public static void ClearAllDelegatesOfTheEventHandler() {

        foreach (Delegate d in TheEventHandler.GetInvocationList())
        {
            TheEventHandler -= (EventHandler)d;
        }
    }
}

Łatwy! Dzięki za Stephena Punaka.

Użyłem go, ponieważ używam ogólnej lokalnej metody usuwania delegatów, a lokalna metoda została wywołana po różnych przypadkach, gdy ustawiono różnych delegatów.


4

Jeśli naprawdę musisz to zrobić ... zajmie to refleksję i sporo czasu, aby to zrobić. Procedury obsługi zdarzeń są zarządzane w postaci mapy zdarzenia do delegowania wewnątrz kontrolki. Musisz

  • Odbij i uzyskaj tę mapę w instancji sterowania.
  • Iteruj dla każdego wydarzenia, zdobądź delegata
    • z kolei każdy delegat może być połączoną serią procedur obsługi zdarzeń. Wywołaj więc obControl.RemoveHandler (zdarzenie, moduł obsługi)

Krótko mówiąc, dużo pracy. Jest to teoretycznie możliwe ... Nigdy czegoś takiego nie próbowałem.

Sprawdź, czy możesz lepiej kontrolować / zdyscyplinować fazę subskrypcji i rezygnacji z kontroli.


3

Stephen ma rację. To bardzo łatwe:

public event EventHandler<Cles_graph_doivent_etre_redessines> les_graph_doivent_etre_redessines;
public void remove_event()
{
    if (this.les_graph_doivent_etre_redessines != null)
    {
        foreach (EventHandler<Cles_graph_doivent_etre_redessines> F_les_graph_doivent_etre_redessines in this.les_graph_doivent_etre_redessines.GetInvocationList())
        {
            this.les_graph_doivent_etre_redessines -= F_les_graph_doivent_etre_redessines;
        }
    }
}

38
Boże, kompilator powinien zabronić tego rodzaju nazw zmiennych. graphs_must_be_redrawn po francusku.
gracchus

4
Tłumaczenie z francuskiego foreach (EventHandler<MyCompletedArgs> handler in CompletionCompleted.GetInvocationList()) { CompletionCompleted -= handler; }
Anton K,

Tłumaczenie na angielski @AntonK działa dobrze. Pamiętaj, aby sprawdzić zerowanie w module obsługi nieruchomości.
Brett

2

Właśnie znalazłem Jak zawiesić zdarzenia podczas ustawiania właściwości kontrolki WinForm . Spowoduje to usunięcie wszystkich zdarzeń z kontrolki:

namespace CMessWin05
{
    public class EventSuppressor
    {
        Control _source;
        EventHandlerList _sourceEventHandlerList;
        FieldInfo _headFI;
        Dictionary<object, Delegate[]> _handlers;
        PropertyInfo _sourceEventsInfo;
        Type _eventHandlerListType;
        Type _sourceType;


        public EventSuppressor(Control control)
        {
            if (control == null)
                throw new ArgumentNullException("control", "An instance of a control must be provided.");

            _source = control;
            _sourceType = _source.GetType();
            _sourceEventsInfo = _sourceType.GetProperty("Events", BindingFlags.Instance | BindingFlags.NonPublic);
            _sourceEventHandlerList = (EventHandlerList)_sourceEventsInfo.GetValue(_source, null);
            _eventHandlerListType = _sourceEventHandlerList.GetType();
            _headFI = _eventHandlerListType.GetField("head", BindingFlags.Instance | BindingFlags.NonPublic);
        }

        private void BuildList()
        {
            _handlers = new Dictionary<object, Delegate[]>();
            object head = _headFI.GetValue(_sourceEventHandlerList);
            if (head != null)
            {
                Type listEntryType = head.GetType();
                FieldInfo delegateFI = listEntryType.GetField("handler", BindingFlags.Instance | BindingFlags.NonPublic);
                FieldInfo keyFI = listEntryType.GetField("key", BindingFlags.Instance | BindingFlags.NonPublic);
                FieldInfo nextFI = listEntryType.GetField("next", BindingFlags.Instance | BindingFlags.NonPublic);
                BuildListWalk(head, delegateFI, keyFI, nextFI);
            }
        }

        private void BuildListWalk(object entry, FieldInfo delegateFI, FieldInfo keyFI, FieldInfo nextFI)
        {
            if (entry != null)
            {
                Delegate dele = (Delegate)delegateFI.GetValue(entry);
                object key = keyFI.GetValue(entry);
                object next = nextFI.GetValue(entry);

                Delegate[] listeners = dele.GetInvocationList();
                if(listeners != null && listeners.Length > 0)
                    _handlers.Add(key, listeners);

                if (next != null)
                {
                    BuildListWalk(next, delegateFI, keyFI, nextFI);
                }
            }
        }

        public void Resume()
        {
            if (_handlers == null)
                throw new ApplicationException("Events have not been suppressed.");

            foreach (KeyValuePair<object, Delegate[]> pair in _handlers)
            {
                for (int x = 0; x < pair.Value.Length; x++)
                    _sourceEventHandlerList.AddHandler(pair.Key, pair.Value[x]);
            }

            _handlers = null;
        }

        public void Suppress()
        {
            if (_handlers != null)
                throw new ApplicationException("Events are already being suppressed.");

            BuildList();

            foreach (KeyValuePair<object, Delegate[]> pair in _handlers)
            {
                for (int x = pair.Value.Length - 1; x >= 0; x--)
                    _sourceEventHandlerList.RemoveHandler(pair.Key, pair.Value[x]);
            }
        }

    }
}

1
Było to bardzo pomocne, ale jest jedna rzecz, którą należy zmienić: W Resume () dodajesz procedury obsługi z powrotem w odwrotnej kolejności (zakładam, że to kopia / wklej z Suppress, gdzie chcesz pracować wstecz, więc jak nie zadzierać z kolekcją, nad którą iterujesz). Niektóre kody liczą na to, że moduły obsługi strzelają w określonej kolejności, więc nie należy z tym zadzierać.
Michael

1

Łał. Znalazłem to rozwiązanie, ale nic nie działało tak, jak chciałem. Ale to jest takie dobre:

EventHandlerList listaEventos;

private void btnDetach_Click(object sender, EventArgs e)
{
    listaEventos = DetachEvents(comboBox1);
}

private void btnAttach_Click(object sender, EventArgs e)
{
    AttachEvents(comboBox1, listaEventos);
}

public EventHandlerList DetachEvents(Component obj)
{
    object objNew = obj.GetType().GetConstructor(new Type[] { }).Invoke(new object[] { });
    PropertyInfo propEvents = obj.GetType().GetProperty("Events", BindingFlags.NonPublic | BindingFlags.Instance);

    EventHandlerList eventHandlerList_obj = (EventHandlerList)propEvents.GetValue(obj, null);
    EventHandlerList eventHandlerList_objNew = (EventHandlerList)propEvents.GetValue(objNew, null);

    eventHandlerList_objNew.AddHandlers(eventHandlerList_obj);
    eventHandlerList_obj.Dispose();

    return eventHandlerList_objNew;
}

public void AttachEvents(Component obj, EventHandlerList eventos)
{
    PropertyInfo propEvents = obj.GetType().GetProperty("Events", BindingFlags.NonPublic | BindingFlags.Instance);

    EventHandlerList eventHandlerList_obj = (EventHandlerList)propEvents.GetValue(obj, null);

    eventHandlerList_obj.AddHandlers(eventos);
}

To z pewnością jest ładniejsze niż poprzednia odpowiedź. Czy robi dokładnie to samo? Wygląda na to, że tak, ale być może czegoś mi brakuje. Ponadto, dlaczego musisz utworzyć nowy obiekt, gdy wszystko, czego potrzebujesz, to EventHandlerList? Czy dla EventHandlerList nie ma dostępnego c-tor, tak że można uzyskać tylko taki, który został zbudowany wewnętrznie dla komponentu?
Michael

1

Ta strona bardzo mi pomogła. Kod, który dostałem stąd, miał usunąć zdarzenie kliknięcia z przycisku. Muszę usunąć zdarzenia podwójnego kliknięcia z niektórych paneli i zdarzenia kliknięcia z niektórych przycisków. Zrobiłem więc rozszerzenie kontrolne, które usunie wszystkie programy obsługi zdarzeń dla określonego zdarzenia.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
using System.Reflection;
public static class EventExtension
{
    public static void RemoveEvents<T>(this T target, string eventName) where T:Control
    {
        if (ReferenceEquals(target, null)) throw new NullReferenceException("Argument \"target\" may not be null.");
        FieldInfo fieldInfo = typeof(Control).GetField(eventName, BindingFlags.Static | BindingFlags.NonPublic);
        if (ReferenceEquals(fieldInfo, null)) throw new ArgumentException(
            string.Concat("The control ", typeof(T).Name, " does not have a property with the name \"", eventName, "\""), nameof(eventName));
        object eventInstance = fieldInfo.GetValue(target);
        PropertyInfo propInfo = typeof(T).GetProperty("Events", BindingFlags.NonPublic | BindingFlags.Instance);
        EventHandlerList list = (EventHandlerList)propInfo.GetValue(target, null);
        list.RemoveHandler(eventInstance, list[eventInstance]);
    }
}

Teraz użycie tego rozszerzenia. Jeśli chcesz usunąć zdarzenia kliknięcia z przycisku,

Button button = new Button();
button.RemoveEvents(nameof(button.EventClick));

Jeśli chcesz usunąć zdarzenia podwójnego kliknięcia z panelu,

Panel panel = new Panel();
panel.RemoveEvents(nameof(panel.EventDoubleClick));

Nie jestem ekspertem w C #, więc jeśli są jakieś błędy, proszę wybacz mi i uprzejmie daj mi znać o tym.


1
Metoda rozszerzenia .CastTo <> () gdzie dokładnie została znaleziona?
IbrarMumtaz

Możesz po prostu napisać własny: public static T CastTo <T> (ten obiekt objectToCast) {return (T) objectToCast; }
KingOfHypocrites

0

Czasami musimy współpracować z kontrolkami ThirdParty i musimy zbudować te niezręczne rozwiązania. Na podstawie odpowiedzi @Anoop Muraleedharan stworzyłem to rozwiązanie z typem wnioskowania i obsługą ToolStripItem

    public static void RemoveItemEvents<T>(this T target, string eventName) 
        where T : ToolStripItem
    {            
        RemoveObjectEvents<T>(target, eventName);
    }

    public static void RemoveControlEvents<T>(this T target, string eventName)
        where T : Control
    {
        RemoveObjectEvents<T>(target, eventName);
    }

    private static void RemoveObjectEvents<T>(T target, string Event) where T : class
    {
        var typeOfT = typeof(T);
        var fieldInfo = typeOfT.BaseType.GetField(
            Event, BindingFlags.Static | BindingFlags.NonPublic);
        var provertyValue = fieldInfo.GetValue(target);
        var propertyInfo = typeOfT.GetProperty(
            "Events", BindingFlags.NonPublic | BindingFlags.Instance);
        var eventHandlerList = (EventHandlerList)propertyInfo.GetValue(target, null);
        eventHandlerList.RemoveHandler(provertyValue, eventHandlerList[provertyValue]);
    }

I możesz użyć tego w ten sposób

    var toolStripButton = new ToolStripButton();
    toolStripButton.RemoveItemEvents("EventClick");

    var button = new Button();
    button.RemoveControlEvents("EventClick");

0

Znalazłem inne działające rozwiązanie Douglasa .

Ta metoda usuwa wszystkie procedury obsługi zdarzeń, które są ustawione dla określonego zdarzenia routera w elemencie.
Użyj tego jak

Remove_RoutedEventHandlers(myImage, Image.MouseLeftButtonDownEvent);

Pełny kod:

/// <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="RoutetEvent_ToRemove">The routed event for which to remove the event handlers.</param>
public static void RemoveRoutedEventHandlers(UIElement UIElement_Target, RoutedEvent RoutetEvent_ToRemove)
{
    // Get the EventHandlersStore instance which holds event handlers for the specified element.
    // The EventHandlersStore class is declared as internal.
    PropertyInfo PropertyInfo_EventHandlersStore = typeof(UIElement).GetProperty(
        "EventHandlersStore", BindingFlags.Instance | BindingFlags.NonPublic);
    object oEventHandlersStore = PropertyInfo_EventHandlersStore.GetValue(UIElement_Target, null);

    // If there's no event handler subscribed, return
    if (oEventHandlersStore == null) return;

    // Invoke the GetRoutedEventHandlers method on the EventHandlersStore instance 
    // for getting an array of the subscribed event handlers.
    MethodInfo MethodInfo_RoutedEventHandlers = oEventHandlersStore.GetType().GetMethod(
        "GetRoutedEventHandlers", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
    RoutedEventHandlerInfo[] RoutedEventHandlerInfos = (RoutedEventHandlerInfo[])MethodInfo_RoutedEventHandlers.Invoke(
        oEventHandlersStore, new object[] { RoutetEvent_ToRemove });

    // Iteratively remove all routed event handlers from the element.
    foreach (RoutedEventHandlerInfo RoutedEventHandlerInfo_Tmp in RoutedEventHandlerInfos)
        UIElement_Target.RemoveHandler(RoutetEvent_ToRemove, RoutedEventHandlerInfo_Tmp.Handler);
}

0

usuwa wszystkie programy obsługi przycisku: save.RemoveEvents ();

public static class EventExtension
{
    public static void RemoveEvents<T>(this T target) where T : Control
    {
       var propInfo = typeof(T).GetProperty("Events", BindingFlags.NonPublic | BindingFlags.Instance);
        var list = (EventHandlerList)propInfo.GetValue(target, null);
        list.Dispose();
    }
}

-1

Cóż, tutaj jest inne rozwiązanie do usunięcia powiązanego zdarzenia (jeśli masz już metodę obsługi zdarzeń dla kontroli):

EventDescriptor ed = TypeDescriptor.GetEvents(this.button1).Find("MouseDown",true);            
Delegate delegate = Delegate.CreateDelegate(typeof(EventHandler), this, "button1_MouseDownClicked");
if(ed!=null) 
    ed.RemoveEventHandler(this.button1, delegate);

Możesz to zrobić po prostu. Przycisk1.MouseDown - = Delegate.CreateDelegate (typeof (EventHandler), to, „button1_MouseDownClicked”). Więc to nie pomoże rozwiązać pytania, w jaki sposób dowiedzieć się, którego delegata do usunięcia, zwłaszcza jeśli byli oni dołączeni.
Softlion

-1

To nie jest odpowiedź na PO, ale pomyślałem, że opublikuję to tutaj, na wypadek, gdyby mógł pomóc innym.

  /// <summary>
  /// Method to remove a (single) SocketAsyncEventArgs.Completed event handler. This is 
  /// partially based on information found here: http://stackoverflow.com/a/91853/253938
  /// 
  /// But note that this may not be a good idea, being very .Net implementation-dependent. Note 
  /// in particular use of "m_Completed" instead of "Completed".
  /// </summary>
  private static void RemoveCompletedEventHandler(SocketAsyncEventArgs eventArgs)
  {
     FieldInfo fieldInfo = typeof(SocketAsyncEventArgs).GetField("m_Completed", 
                                                BindingFlags.Instance | BindingFlags.NonPublic);
     eventArgs.Completed -= (EventHandler<SocketAsyncEventArgs>)fieldInfo.GetValue(eventArgs);
  }

-3

Znalazłem tę odpowiedź i prawie pasuje do moich potrzeb. Dzięki SwDevMan81 dla klasy. Zmodyfikowałem go, aby umożliwić tłumienie i wznawianie poszczególnych metod, i pomyślałem, że opublikuję go tutaj.

// This class allows you to selectively suppress event handlers for controls.  You instantiate
// the suppressor object with the control, and after that you can use it to suppress all events
// or a single event.  If you try to suppress an event which has already been suppressed
// it will be ignored.  Same with resuming; you can resume all events which were suppressed,
// or a single one.  If you try to resume an un-suppressed event handler, it will be ignored.

//cEventSuppressor _supButton1 = null;
//private cEventSuppressor SupButton1 {
//    get {
//        if (_supButton1 == null) {
//            _supButton1 = new cEventSuppressor(this.button1);
//        }
//        return _supButton1;
//    }
//}
//private void button1_Click(object sender, EventArgs e) {
//    MessageBox.Show("Clicked!");
//}

//private void button2_Click(object sender, EventArgs e) {
//    SupButton1.Suppress("button1_Click");
//}

//private void button3_Click(object sender, EventArgs e) {
//    SupButton1.Resume("button1_Click");
//}
using System;
using System.Collections.Generic;
using System.Text;

using System.Reflection;
using System.Windows.Forms;
using System.ComponentModel;

namespace Crystal.Utilities {
    public class cEventSuppressor {
        Control _source;
        EventHandlerList _sourceEventHandlerList;
        FieldInfo _headFI;
        Dictionary<object, Delegate[]> suppressedHandlers = new Dictionary<object, Delegate[]>();
        PropertyInfo _sourceEventsInfo;
        Type _eventHandlerListType;
        Type _sourceType;

        public cEventSuppressor(Control control) {
            if (control == null)
                throw new ArgumentNullException("control", "An instance of a control must be provided.");

            _source = control;
            _sourceType = _source.GetType();
            _sourceEventsInfo = _sourceType.GetProperty("Events", BindingFlags.Instance | BindingFlags.NonPublic);
            _sourceEventHandlerList = (EventHandlerList)_sourceEventsInfo.GetValue(_source, null);
            _eventHandlerListType = _sourceEventHandlerList.GetType();
            _headFI = _eventHandlerListType.GetField("head", BindingFlags.Instance | BindingFlags.NonPublic);
        }
        private Dictionary<object, Delegate[]> BuildList() {
            Dictionary<object, Delegate[]> retval = new Dictionary<object, Delegate[]>();
            object head = _headFI.GetValue(_sourceEventHandlerList);
            if (head != null) {
                Type listEntryType = head.GetType();
                FieldInfo delegateFI = listEntryType.GetField("handler", BindingFlags.Instance | BindingFlags.NonPublic);
                FieldInfo keyFI = listEntryType.GetField("key", BindingFlags.Instance | BindingFlags.NonPublic);
                FieldInfo nextFI = listEntryType.GetField("next", BindingFlags.Instance | BindingFlags.NonPublic);
                retval = BuildListWalk(retval, head, delegateFI, keyFI, nextFI);
            }
            return retval;
        }

        private Dictionary<object, Delegate[]> BuildListWalk(Dictionary<object, Delegate[]> dict,
                                    object entry, FieldInfo delegateFI, FieldInfo keyFI, FieldInfo nextFI) {
            if (entry != null) {
                Delegate dele = (Delegate)delegateFI.GetValue(entry);
                object key = keyFI.GetValue(entry);
                object next = nextFI.GetValue(entry);

                if (dele != null) {
                    Delegate[] listeners = dele.GetInvocationList();
                    if (listeners != null && listeners.Length > 0) {
                        dict.Add(key, listeners);
                    }
                }
                if (next != null) {
                    dict = BuildListWalk(dict, next, delegateFI, keyFI, nextFI);
                }
            }
            return dict;
        }
        public void Resume() {
        }
        public void Resume(string pMethodName) {
            //if (_handlers == null)
            //    throw new ApplicationException("Events have not been suppressed.");
            Dictionary<object, Delegate[]> toRemove = new Dictionary<object, Delegate[]>();

            // goes through all handlers which have been suppressed.  If we are resuming,
            // all handlers, or if we find the matching handler, add it back to the
            // control's event handlers
            foreach (KeyValuePair<object, Delegate[]> pair in suppressedHandlers) {

                for (int x = 0; x < pair.Value.Length; x++) {

                    string methodName = pair.Value[x].Method.Name;
                    if (pMethodName == null || methodName.Equals(pMethodName)) {
                        _sourceEventHandlerList.AddHandler(pair.Key, pair.Value[x]);
                        toRemove.Add(pair.Key, pair.Value);
                    }
                }
            }
            // remove all un-suppressed handlers from the list of suppressed handlers
            foreach (KeyValuePair<object, Delegate[]> pair in toRemove) {
                for (int x = 0; x < pair.Value.Length; x++) {
                    suppressedHandlers.Remove(pair.Key);
                }
            }
            //_handlers = null;
        }
        public void Suppress() {
            Suppress(null);
        }
        public void Suppress(string pMethodName) {
            //if (_handlers != null)
            //    throw new ApplicationException("Events are already being suppressed.");

            Dictionary<object, Delegate[]> dict = BuildList();

            foreach (KeyValuePair<object, Delegate[]> pair in dict) {
                for (int x = pair.Value.Length - 1; x >= 0; x--) {
                    //MethodInfo mi = pair.Value[x].Method;
                    //string s1 = mi.Name; // name of the method
                    //object o = pair.Value[x].Target;
                    // can use this to invoke method    pair.Value[x].DynamicInvoke
                    string methodName = pair.Value[x].Method.Name;

                    if (pMethodName == null || methodName.Equals(pMethodName)) {
                        _sourceEventHandlerList.RemoveHandler(pair.Key, pair.Value[x]);
                        suppressedHandlers.Add(pair.Key, pair.Value);
                    }
                }
            }
        }
    } 
}

8
To skomplikowane rozwiązanie i nigdy nie powinno być stosowane w oprogramowaniu przemysłowym. Najlepsze podejście jest takie, jak wspomniano: Zarządzaj dobrze swoją subskrypcją i anulowaniem subskrypcji, a nigdy nie napotkasz takich problemów.
Tri Q Tran

Zgadzam się, że nie powinniśmy używać refleksji do rozplątywania wydarzeń, a subskrypcja i wyrejestrowanie zdarzenia powinny być zarządzane przez aplikację. Uważam, że kwestia będąca przedmiotem debaty powinna być wykorzystana w czasie DEBUGI, aby dowiedzieć się, czy czegoś nie chcemy. Jest to konieczne w przypadku starszych aplikacji, które są refaktoryzowane.
Tiago Freitas Leal
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.