views:

884

answers:

5

The SelectedIndexChanged event gets fired in my application from a combo box when:

  1. the user chooses a different item in the combo box, or when:
  2. my own code updates the combo box's SelectedItem to reflect that the combo box is now displaying properties for a different object.

I am interested in the SelectedIndexChanged event for case 1, so that I can update the current object's properties. But in case 2, I do not want the event to fire, because the object's properties have not changed.

An example may help. Let's consider that I have a list box containing a list of people and I have a combo box representing the nationality of the currently selected person in the list. Case 1 could happen if Fred is currently selected in the list, and I use the combo box to change his nationality from English to Welsh. Case 2 could happen if I then select Bob, who is Scottish, in the list. Here, my list update event-handler code sees that Bob is now selected, and updates the combo box so that Scottish is now the selected item. This causes the combo box's SelectedIndexChanged event to be fired to set Bob's nationality to Scottish, even though it already is Scottish.

How can I update my combo box's SelectedItem property without causing the SelectedIndexChanged event to fire? One way would be to unregister the event handler, set SelectedItem, then re-register the event handler, but this seems tedious and error prone. There must be a better way.

+5  A: 

I created a class I called SuspendLatch. Offers on a better name are welcome, but it does what you need and you would use it like this:

void Method()
{
    using (suspendLatch.GetToken())
    {
        // Update selected index etc
    }
}

void listbox1_SelectedIndexChanged(object sender, EventArgs e)
{
    if (suspendEvents.HasOutstandingTokens)
    {
        return;
    }

    // Do some work
}

It's not pretty, but it does work, and unlike unregistering events or boolean flags, it supports nested operations a bit like TransactionScope. You keep taking tokens from the latch and it's only when the last token is disposed that the HasOutstandingTokens returns false. Nice and safe. Not threadsafe, though...

Here's the code for SuspendLatch:

public class SuspendLatch
{
    private IDictionary<Guid, SuspendLatchToken> tokens = new Dictionary<Guid, SuspendLatchToken>();

    public SuspendLatchToken GetToken()
    {
        SuspendLatchToken token = new SuspendLatchToken(this);
        tokens.Add(token.Key, token);
        return token;
    }

    public bool HasOutstandingTokens
    {
        get { return tokens.Count > 0; }
    }

    public void CancelToken(SuspendLatchToken token)
    {
        tokens.Remove(token.Key);
    }

    public class SuspendLatchToken : IDisposable
    {
        private bool disposed = false;
        private Guid key = Guid.NewGuid();
        private SuspendLatch parent;

        internal SuspendLatchToken(SuspendLatch parent)
        {
            this.parent = parent;
        }

        public Guid Key
        {
            get { return this.key; }
        }

        public override bool Equals(object obj)
        {
            SuspendLatchToken other = obj as SuspendLatchToken;

            if (other != null)
            {
                return Key.Equals(other.Key);
            }
            else
            {
                return false;
            }
        }

        public override int GetHashCode()
        {
            return Key.GetHashCode();
        }

        public override string ToString()
        {
            return Key.ToString();
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (!disposed)
            {
                if (disposing)
                {
                    // Dispose managed resources.
                    parent.CancelToken(this);
                }

                // There are no unmanaged resources to release, but
                // if we add them, they need to be released here.
            }
            disposed = true;

            // If it is available, make the call to the
            // base class's Dispose(Boolean) method
            //base.Dispose(disposing);
        }
    }
}
Neil Barnwell
Might me me. But is this not a bit overkill - "How can I update my combo box's SelectedItem property without causing the SelectedIndexChanged event to fire?" Please could I have feedback on my solution?
REA_ANDREW
It could be overkill. Setting flags or removing the eventhandler is a simpler solution, but I created my SuspendLatch when I realised I had multiple places where I wanted to suspend the SelectedIndexChanged event
Neil Barnwell
"Offers on a better name are welcome" -- public class SuspendThingerMajig?
Juliet
+3  A: 

I think the best way would be to use a flag variable:

bool updatingCheckbox = false;

void updateCheckBox()
{
    updatingCheckBox = true;

    checkbox.Checked = true;

    updatingCheckBox = false;
}

void checkbox_CheckedChanged( object sender, EventArgs e )
{
    if (!updatingCheckBox)
        PerformActions()
}

[Edit: Posting only the code is not really clear]

In this case, the event handler wouldn't perform its normal operations when the checkbox is changed through updateCheckBox().

Lennaert
This is the simpler version of my SuspendLatch (posted elsewhere on this question), and the method I used until I realised I had multiple places where I wanted to suspend the SelectedIndexChanged event.
Neil Barnwell
Indeed, upvoted your answer since I'm probably going to use that idea myself ;)
Lennaert
Glad it's of some use. Nice to have at least one nice idea everyone once in a while :)
Neil Barnwell
Does this still work if the event is handled _after_ updateCheckBox() has returned?
Kelly French
+1  A: 

I let the event fire. But, I set a flag before changing the index and flip it back after. In the event handler, I check if the flag is set and exit the handler if it is.

dotjoe
+1  A: 

I think your focus should be on the object and not on the event that's occuring.

Say for example you have the event

void combobox_Changed( object sender, EventArgs e )
{
    PerformActions()
}

and PerformActions did something to the effect of

void PerformActions()
{
    (listBox.SelectedItem as IPerson).Nationality = 
        (comboBox.SelectedItem as INationality)
}

then inside the Person you would expect to see something to the effect of

class Person: IPerson
{
    INationality Nationality
    {
        get { return m_nationality; }
        set 
        {
          if (m_nationality <> value)
          {
             m_nationality = value;
             this.IsDirty = true;
          }
        }
    }
}

the point here is that you let the object keep track of what is happening to itself, not the UI. This also lets you keep track of dirty flag tracking on your objects, which could be useful for persistence later on.

This also keeps your UI clean and keeps it from getting odd event registration code that will most likely be error prone.

Joseph
+1  A: 

I have always used a boolean flag variable to protect against unwanted event handlers. The TaskVision sample application taught me how to do this.

Your event handler code for all of your events will look like this:

private bool lockEvents;

protected void MyEventHandler(object sender, EventArgs e)
{
    if (this.lockEvents)
    {
        return;
    }

    this.lockEvents = true;
    //Handle your event...
    this.lockEvents = false;
}
Aaron Daniels