none
How to suspend events when setting a property of a WinForms control

    Question

  • Hello,

    What I want to achieve is setting a control's property, but do not want the already registered events to be triggered. (controls such as DropDownButton, TextBox, ListView etc. Events like EnableChanged, TextChanged, CheckedChanged etc.)

    The reason for that is to initialize appearance without running any further logic in an event handler. After initialized the events shall be enabled again to handle user interaction properly.

    What I'm not searching for is:

    - to unregister events for the time of initializing the control properties

    - using a custom flag that would be checked within every event handler, as that must have been kept in mind for every new handler being written. Eventually, this is the best solution I thought of so far.

    - (obviously SuspendLayout is not what I'm searching for, too)

     

    Any ideas?

    Thanks

    • Moved by OmegaManMVP Friday, March 05, 2010 2:37 AM (From:Visual C# General)
    Tuesday, October 10, 2006 9:06 PM

Answers

  • What Jared is saying in this thread is correct, but apparently you are looking for more a turn key answer to fit your current design.  The class below allows you to effectively suppress all of the events, that are maintained in the EventHandlerList, for a control.  The class uses an unsavory approach of reflecting on private fields and members by name, but if you can live with that then it should do the trick.

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

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

    Wednesday, October 11, 2006 10:45 PM

All replies

  • You could just temporarily unhook the event handler in question, perform your change, and re-hook it.  It would be better if you could find a way to avoid having to do this altogether, but it's possible and not terribly difficult.

    For instance



    // Change a text box's Text property when the control has a TextChanged event handler
    // ... all other class code omitted...
    TextBox _textBox;
    private void InitializeComponent()
    {
        // ... control declarations etc. here...
        _textBox = new TextBox();
        _textBox.TextChanged += new System.EventHandler(_textBox_Changed);
    }
    protected void _textBox_Changed(object sender, EventArgs e)
    {
        // ...do something when the text changes...
    }
    private void SetMyTextBox(string someText)
    {
        // Temporarily unhook the event handler
        _textBox.TextChanged -= new System.EventHandler(_textBox_Changed);
        //... do whatever you want to do here without triggering the event
        // Reconnect the event handler
        _textBox.TextChanged += new System.EventHandler(_textBox_Changed);
    }

     

     

    Hope this helps.

    Tuesday, October 10, 2006 11:09 PM
  • Hello,

    thanks but that's the approach I would like to avoid. (btw. I didn't try, but don't believe the example will work at all, as by using the 'new' keyword you create a new instance of the event handler, thus cannot unhook the existing one using the '-' operator...what you would need to do is to store the delegate somewhere previously)

    i.e. not a solution.

     

    Wednesday, October 11, 2006 12:12 AM
  • Yeah, i'm interested in finding a better solution as well. It would be nice for there to be a suppress events property for a control.

     

    I use the above method to unhook events. I think the trick to using the above method to unhook events is to set all hooks in the onload events rather then the constructor. That way you can initialize and databind stuff before they start triggering events. I always put the hooks at the end of anything I need to do on the onload event.

    Then, you have to unhook an event on a specific property if you  want that behavior - and rehook it after your done :(

    Wednesday, October 11, 2006 12:25 AM
  • Ok, so anybody can say for sure, that we have only the following possibilities for a solution?

    a) Unhook event handlers before setting a control properties. I don't find this solution convenient nor sufficient as eventually outer code may hook handlers to a publicly exposed contol, a local class such as a Form may not be aware of that, so this extends to wider design considerations.

    b) Use a custom flag indicating whether or not to execute event handlers. This must be checked within every involved handler manually.

     

    By the framework supported solution as with (b) or a method pair aka SuspendLayout(), ResumeLayout(bool) designed to suspend all custom events would be what I'm looking for.

    ?

     

    Wednesday, October 11, 2006 7:09 PM
  • Yes, actually, the example for unhooking an event handler works as written.  I've used it on occassion.  Try it out.
    Wednesday, October 11, 2006 7:15 PM
  • I don't know why you want this behavior so badly (versus just finding a different way to design your solution), but since my first example was apparently not sufficient for your needs, here's another route you can take:

    If you have access to the source for the type raising the event(s), then you can put a utility method on it to return the delegate array (Delegate[]) representing the invocation list(s) for the event(s) you want to suppress (see MulticastDelegate.GetInvocationList() for more information).

    If you don't have access to the source (or just don't want to edit it) for the type exposing the event(s), you'll have to reflect to get access to the invocation list (field is _invocationList, I believe).

    Once you have the invocation list, you can keep the delegate instances in temporary storage while you unhook them from the event (EventInfo.RemoveEventHandler), do whatever it is that you're trying to hide from the handlers, then re-hook the events (EventInfo.AddEventHandler).

    In general, I would say this is a fairly bold and potentially very bad idea.  I have no clue what you're trying to achieve here but if, as you wrote, one of the potential problems with my first suggestion is that you don't know who might have hooked the event(s) you're about to quiesce, then it follows that you don't really know what functionality you're going to suppress by doing it.  It's roughly akin to hiding base class methods using the "new" keyword - you don't know what you don't know.

    Anyway...good luck with whatever it is you're trying to do.  I'm sure there's some practical use for this scheme.
    Wednesday, October 11, 2006 8:15 PM
  • What Jared is saying in this thread is correct, but apparently you are looking for more a turn key answer to fit your current design.  The class below allows you to effectively suppress all of the events, that are maintained in the EventHandlerList, for a control.  The class uses an unsavory approach of reflecting on private fields and members by name, but if you can live with that then it should do the trick.

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

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

    Wednesday, October 11, 2006 10:45 PM
  • Thanks for suggestions folks, I saved the long example for education, however do not wish to use reflection on such issue, one of the reasons may be that I can't be sure whatever system event could eventually be unhooked thus not properly executed...

    When there is no system support for temporarily suspending events :( I think I'm gonna use a custom flag, eventually place all event registration into some custom HookAll()/UnhookAll() methods, keeping event hooking centralized this way, reusable at the time the events need to be suspended (i.e. must not use the designer to setup handlers).

     Jared Wennstrom wrote:
    Yes, actually, the example for unhooking an event handler works as written.  I've used it on occassion.  Try it out.

    Ok, first I just thought you made a mistake, but it will be working if you say... (I assume that either using 'new' with a delegate works diffrently than by a common type or the EventHandler type overrides object.Equals()... however).

     

    Bye

     

     

    Monday, October 16, 2006 7:45 PM
  • Unfortunately, this code DOESN'T WORK AT ALL with CheckedListBox.

            private void button1_Click(object sender, EventArgs e)
            {
                EventSuppressor oES = new EventSuppressor(checkBox1);
                oES.Suppress();
                checkedListBox1.SetItemChecked(0, checkedListBox1.CheckedItems.Count == 0);
                oES.Resume();
            }
    
            private void checkedListBox1_ItemCheck(object sender, ItemCheckEventArgs e)
            {
                MessageBox.Show("checkedListBox1_ItemCheck");
            }
    

    In this case you'll see "checkedListBox1_ItemCheck" after clicking on big button.
    Friday, March 05, 2010 2:24 AM
  • Hi. I tested your code and work fine, but...

    if i call Supress method, Resume method and Supress method again, can´t work any more.

    I need to restart application.

    Can you help me with this?

    Thanks


    =

    Thursday, January 16, 2014 6:42 PM
  • You could just temporarily unhook the event handler in question, perform your change, and re-hook it.  It would be better if you could find a way to avoid having to do this altogether, but it's possible and not terribly difficult.

    For instance



    // Change a text box's Text property when the control has a TextChanged event handler
    // ... all other class code omitted...
    TextBox _textBox;
    private void InitializeComponent()
    {
        // ... control declarations etc. here...
        _textBox = new TextBox();
        _textBox.TextChanged += new System.EventHandler(_textBox_Changed);
    }
    protected void _textBox_Changed(object sender, EventArgs e)
    {
        // ...do something when the text changes...
    }
    private void SetMyTextBox(string someText)
    {
        // Temporarily unhook the event handler
        _textBox.TextChanged -= new System.EventHandler(_textBox_Changed);
        //... do whatever you want to do here without triggering the event
        // Reconnect the event handler
        _textBox.TextChanged += new System.EventHandler(_textBox_Changed);
    }

     

     

    Hope this helps.


    Thursday, January 16, 2014 7:24 PM