Comment supprimer tous les gestionnaires d'événements d'un événement

367

Pour créer un nouveau gestionnaire d'événements sur un contrôle, vous pouvez le faire

c.Click += new EventHandler(mainFormButton_Click);

ou ca

c.Click += mainFormButton_Click;

et pour supprimer un gestionnaire d'événements, vous pouvez le faire

c.Click -= mainFormButton_Click;

Mais comment supprimer tous les gestionnaires d'événements d'un événement?

Carrick
la source
10
Si quelqu'un est venu ici à la recherche d'une solution WPF, vous voudrez peut-être regarder cette réponse .
Douglas
1
Pouvez-vous pas simplement définir c.Click = null?
alexania
C'est une de ces choses que je trouve ridiculement trop compliquées. Une Clearméthode simple était apparemment trop d'effort
Zimano

Réponses:

167

J'ai trouvé une solution sur les forums MSDN . L'exemple de code ci-dessous supprimera tous les Clickévénements de 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]);
        }
    }
}
xsl
la source
Si button1 est défini sur null, tous les gestionnaires d'événements sont-ils attachés à button1.Click correctement supprimés?
Damien
3
Corrigez-moi si je me trompe, mais la première ligne ne devrait-elle pas RemoveClickEventcommencer par FieldInfo f1 = typeof(Button):? J'obtiens null GetFieldsi j'utilise Control.
Protector one
2
Cela ne semble pas fonctionner pour ToolStripButtons. J'ai remplacé Button dans RemoveClickEvent par ToolStripButton, mais les événements sont toujours en place après avoir appelé RemoveClickEvent. Quelqu'un at-il une solution à ce problème?
Skalli
1
le lien ci-dessus dans MSDN suggère également d'essayer myButton.Click + = null; si vous souhaitez supprimer tous les délégués (pas pour Click, mais pour d'autres événements ..)
hello_earth
1
@hello_earth Ne semble pas fonctionnerObservableCollection.CollectionChanged += null;
Mike de Klerk
146

Vous les gars rendez cette façon trop dure pour vous-mêmes. C'est aussi simple que ça:

void OnFormClosing(object sender, FormClosingEventArgs e)
{
    foreach(Delegate d in FindClicked.GetInvocationList())
    {
        FindClicked -= (FindClickedHandler)d;
    }
}
Stephen Punak
la source
58
Cela ne fonctionnerait que si vous êtes propriétaire de l'événement. Essayez de le faire sur un contrôle.
Delyan
227
... et si vous êtes propriétaire de l'événement, vous pouvez simplement écrire FindClicked = null;ce qui est plutôt simple.
Jon Skeet
79
Qu'est-ce que FindClicked?
Levitikon
3
Cela ne fonctionne pas pour les événements Kinect - le kinect.ColorFrameReady -= MyEventHanderfait, mais il n'existe aucune GetInvocationList()méthode sur les instances Kinect pour itérer sur leurs délégués.
Brent Faust
GetInvocationListpas trouvé.
Joke Huang
75

De la suppression de tous les gestionnaires d'événements :

Directement non, en grande partie parce que vous ne pouvez pas simplement définir l'événement sur null.

Indirectement, vous pouvez rendre l'événement réel privé et créer autour de lui une propriété qui suit tous les délégués qui lui sont ajoutés / soustraits.

Prenez ce qui suit:

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();
}
Jorge Ferreira
la source
4
Je pensais que l'OP faisait référence à des contrôles généraux .net .. dans lesquels ce type de wrapping peut ne pas être possible.
Gishu
4
vous pouvez dériver le contrôle, alors ce serait
Tom Fobear
Cela conduit également à maintenir deux listes, voir stackoverflow.com/questions/91778/… pour la réinitialisation ou stackoverflow.com/questions/91778/… pour accéder à la liste.
TN.
63

La réponse acceptée n'est pas complète. Cela ne fonctionne pas pour les événements déclarés comme {add; retirer;}

Voici le code de travail:

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;
}
LionSoft
la source
4
CETTE version fonctionnait pour moi. La version acceptée n'a pas fonctionné. +1 pour cela.
Meister Schnitzel
1
N'a pas fonctionné pour les événements WPF jusqu'à ce que je l'utilise BindingFlags.Publiclors du premier GetFieldappel.
Lennart
40

Il ne fait aucun mal de supprimer un gestionnaire d'événements inexistant. Donc, si vous savez quels gestionnaires il peut y avoir, vous pouvez simplement les supprimer tous. Je viens d'avoir un cas similaire. Cela peut aider dans certains cas.

Comme:

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

// Remove handlers...
c.Click -= DoesSomething;
c.Click -= DoesSomethingElse;
Peter Mortensen
la source
16

J'utilise en fait cette méthode et cela fonctionne parfaitement. J'ai été «inspiré» par le code écrit par Aeonhack ici .

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

Le champ MyEventEvent est masqué, mais il existe.

Débogage, vous pouvez voir comment d.targetl'objet gère réellement l'événement et d.methodsa méthode. Vous n'avez qu'à le supprimer.

Cela fonctionne très bien. Plus d'objets non en cours de GC à cause des gestionnaires d'événements.

Villa Ivan Ferrer
la source
3
Veuillez ne pas écrire les réponses dans d'autres langues.
Hille
10

Je détestais toutes les solutions complètes présentées ici, j'ai fait un mélange et testé maintenant, travaillé pour n'importe quel gestionnaire d'événements:

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

Facile! Merci pour Stephen Punak.

Je l'ai utilisé car j'utilise une méthode locale générique pour supprimer les délégués et la méthode locale a été appelée après différents cas, lorsque différents délégués sont définis.

Vinicius Schneider
la source
4

Si vous devez vraiment le faire ... cela prendra de la réflexion et un certain temps pour le faire. Les gestionnaires d'événements sont gérés dans une mappe d'événement à délégué à l'intérieur d'un contrôle. Vous auriez besoin de

  • Réfléchissez et obtenez cette carte dans l'instance de contrôle.
  • Itérer pour chaque événement, obtenir le délégué
    • chaque délégué à son tour pourrait être une série enchaînée de gestionnaires d'événements. Appelez donc obControl.RemoveHandler (événement, gestionnaire)

Bref, beaucoup de travail. C'est possible en théorie ... Je n'ai jamais essayé quelque chose comme ça.

Voyez si vous pouvez avoir un meilleur contrôle / discipline sur la phase d'abonnement-désabonnement pour le contrôle.

Gishu
la source
3

Stephen a raison. C'est très facile:

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;
        }
    }
}
mmike
la source
38
Dieu, le compilateur devrait interdire ce genre de noms de variables. graphs_must_be_redrawn en français.
gracchus
4
Traduction du français foreach (EventHandler<MyCompletedArgs> handler in CompletionCompleted.GetInvocationList()) { CompletionCompleted -= handler; }
Anton K
La traduction en anglais par @AntonK fonctionne bien. N'oubliez pas de vérifier null sur le gestionnaire de propriétés.
Brett
2

Je viens de trouver Comment suspendre des événements lors de la définition d'une propriété d'un contrôle WinForms . Il supprimera tous les événements d'un contrôle:

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

    }
}
SwDevMan81
la source
1
Cela a été très utile, mais il y a une chose qui doit être modifiée: dans Resume (), vous ajoutez les gestionnaires dans l'ordre inverse (je suppose que c'est un copier / coller de Suppress, où vous voulez travailler en arrière afin comme pour ne pas jouer avec une collection que vous parcourez). Certains codes comptent sur les gestionnaires qui tirent dans un ordre donné, donc il ne faut pas jouer avec ça.
Michael
1

Sensationnel. J'ai trouvé cette solution, mais rien n'a fonctionné comme je le voulais. Mais c'est tellement bon:

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);
}
Sergio Cabral
la source
C'est certainement plus net que la réponse précédente. Fait-il exactement la même chose? Il semble que ce soit le cas, mais il me manque peut-être quelque chose. De plus, pourquoi avez-vous besoin de créer un nouvel objet alors que tout ce que vous voulez est un EventHandlerList? N'y a-t-il pas de c-tor accessible pour EventHandlerList, de sorte que l'on ne peut en obtenir qu'un qui a été construit en interne pour un composant?
Michael
1

Cette page m'a beaucoup aidé. Le code que j'ai obtenu ici était destiné à supprimer un événement de clic d'un bouton. Je dois supprimer les événements de double-clic de certains panneaux et cliquer sur les événements de certains boutons. J'ai donc fait une extension de contrôle, qui supprimera tous les gestionnaires d'événements pour un certain événement.

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

Maintenant, l'utilisation de cette extension. Si vous devez supprimer les événements de clic d'un bouton,

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

Si vous devez supprimer des événements de double-clic d'un panneau,

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

Je ne suis pas un expert en C #, donc s'il y a des bugs s'il vous plaît pardonnez-moi et veuillez me le faire savoir.

Anoop Muraleedharan
la source
1
La méthode d'extension .CastTo <> () où se trouve-t-elle exactement?
IbrarMumtaz
Vous pouvez simplement écrire le vôtre: public statique T CastTo <T> (cet objet objectToCast) {return (T) objectToCast; }
KingOfHypocrites
0

Parfois, nous devons travailler avec des contrôles ThirdParty et nous devons créer ces solutions maladroites. Basé dans la réponse @Anoop Muraleedharan, j'ai créé cette solution avec le type d'inférence et le support 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]);
    }

Et vous pouvez l'utiliser comme ça

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

    var button = new Button();
    button.RemoveControlEvents("EventClick");
Jhonattan
la source
0

J'ai trouvé une autre solution de travail par Douglas .

Cette méthode supprime tous les gestionnaires d'événements qui sont définis sur un événement de routage spécifique sur un élément.
Utilisez-le comme

Remove_RoutedEventHandlers(myImage, Image.MouseLeftButtonDownEvent);

Code complet:

/// <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);
}
Hille
la source
0

supprime tous les gestionnaires du bouton: 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();
    }
}
Anatoliy
la source
-1

Eh bien, voici une autre solution pour supprimer un événement associé (si vous avez déjà une méthode pour gérer les événements pour le contrôle):

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);
suso
la source
Vous pouvez simplement faire ceci.button1.MouseDown - = Delegate.CreateDelegate (typeof (EventHandler), ceci, "button1_MouseDownClicked"). Donc, cela n'aidera pas à résoudre la question de savoir comment trouver le délégué à supprimer, surtout s'il était en ligne.
Softlion
-1

Ce n'est pas une réponse au PO, mais j'ai pensé que je posterais ceci ici au cas où cela pourrait aider les autres.

  /// <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);
  }
RenniePet
la source
-3

J'ai trouvé cette réponse et elle correspondait presque à mes besoins. Merci à SwDevMan81 pour la classe. Je l'ai modifié pour permettre la suppression et la reprise de méthodes individuelles, et j'ai pensé le publier ici.

// 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);
                    }
                }
            }
        }
    } 
}
Francine
la source
8
Il s'agit d'une solution compliquée et ne doit jamais être utilisée dans un logiciel de qualité industrielle. La meilleure approche est comme mentionné: Gérez bien votre abonnement et votre désabonnement à un événement et vous ne rencontrerez jamais de tels problèmes.
Tri Q Tran
Je suis d'accord que nous ne devrions pas utiliser la réflexion pour déconnecter les événements, et l'abonnement et le désabonnement aux événements doivent être gérés par l'application. Je pense que la question en débat devrait être utilisée au moment du DEBUG, pour savoir si nous lâchons quelque chose. C'est un must sur les applications héritées que vous refactorez.
Tiago Freitas Leal