Differentiating between events raised by user interaction and my own code

雨燕双飞 提交于 2019-12-18 15:36:08

问题


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.


回答1:


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 (suspendLatch.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);
        }
    }
}



回答2:


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().




回答3:


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



回答4:


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.




回答5:


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.




回答6:


I have finally found a solution to avoid the uncessary event from being fired too many time.

I use a counter and I only hook/unhook the events I want to mask once when it is not needed, and when it is needed again.

The example below shows how I hide the CellValueChanged event of a datagrid.

EventMask valueChangedEventMask;

// In the class constructor
valueChangedEventMask = new EventMask(
    () => { dgv.CellValueChanged += new DataGridViewCellEventHandler(dgv_CellValueChanged); },
    () => { dgv.CellValueChanged -= new DataGridViewCellEventHandler(dgv_CellValueChanged); }
);

// Use push to hide the event and pop to make it available again. The operation can be nested or be used in the event itself.
void changeCellOperation()
{
    valueChangedEventMask.Push();

    ...
    cell.Value = myNewCellValue
    ...

    valueChangedEventMask.Pop();
}

// The class
public class EventMask
{
    Action hook;
    Action unHook;

    int count = 0;

    public EventMask(Action hook, Action unHook)
    {
        this.hook = hook;
        this.unHook = unHook;
    }

    public void Push()
    {
        count++;
        if (count == 1)
            unHook();
    }

    public void Pop()
    {
        count--;
        if (count == 0)
            hook();
    }
}


来源:https://stackoverflow.com/questions/729897/differentiating-between-events-raised-by-user-interaction-and-my-own-code

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!