Различие между событиями, вызванными взаимодействием с пользователем, и моим собственным кодом

Событие SelectedIndexChanged запускается в моем приложении из поля со списком, когда:

  1. пользователь выбирает другой элемент в поле со списком или когда:
  2. мой собственный код обновляет SelectedItem поля со списком, чтобы отразить, что поле со списком теперь отображает свойства для другого объекта.

Меня интересует событие SelectedIndexChanged для случая 1, чтобы я мог обновить свойства текущего объекта. Но в случае 2 я не хочу, чтобы событие запускалось, потому что свойства объекта не изменились.

Пример может помочь. Предположим, у меня есть поле со списком, содержащее список людей, и поле со списком, представляющее национальность человека, выбранного в данный момент в списке. Случай 1 может произойти, если в данный момент в списке выбран Фред, и я использую поле со списком, чтобы изменить его национальность с английского на валлийский. Случай 2 может произойти, если я затем выберу Боба, который является шотландцем, в списке. Здесь мой код обработчика событий обновления списка видит, что теперь выбран Боб, и обновляет поле со списком, так что шотландский язык теперь является выбранным элементом. Это приводит к срабатыванию события SelectedIndexChanged поля со списком, чтобы установить шотландское гражданство Боба, хотя оно уже шотландское.

Как мне обновить свойство поля со списком SelectedItem, не вызывая срабатывания события SelectedIndexChanged? Один из способов - отменить регистрацию обработчика событий, установить SelectedItem, а затем повторно зарегистрировать обработчик событий, но это кажется утомительным и подвержены ошибкам. Должен быть способ получше.


person Community    schedule 08.04.2009    source источник


Ответы (6)


Я создал класс, который назвал SuspendLatch. Предложения о лучшем имени приветствуются, но оно делает то, что вам нужно, и вы можете использовать его так:

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

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

    // Do some work
}

Это некрасиво, но работает, и, в отличие от событий отмены регистрации или логических флагов, поддерживает вложенные операции, немного похожие на TransactionScope. Вы продолжаете брать токены из защелки, и только когда последний токен удален, HasOutstandingTokens возвращает false. Красиво и безопасно. Но не поточно ...

Вот код 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);
        }
    }
}
person Neil Barnwell    schedule 08.04.2009
comment
Могу я меня. Но разве это не перебор? Как я могу обновить свойство SelectedItem поля со списком, не вызывая срабатывания события SelectedIndexChanged? Могу я оставить отзыв о моем решении? - person REA_ANDREW; 08.04.2009
comment
Это могло быть излишним. Установка флагов или удаление обработчика событий - более простое решение, но я создал SuspendLatch, когда понял, что у меня есть несколько мест, где я хотел бы приостановить событие SelectedIndexChanged. - person Neil Barnwell; 08.04.2009
comment
Приветствуются предложения по лучшему имени - публичный класс SuspendThingerMajig? - person Juliet; 08.04.2009
comment
Ваша работа вдохновляет! Я тоже ответил и написал класс EventMask, который использует счетчик и методы Push () / Pop (). Он перехватывает / отключает событие, когда счетчик достигает 0 или выходит из него. Он может быть вложенным, и событие больше не запускается, когда оно не нужно. То, как мы можем использовать Using () {} в вашем, делает его особенно интересным. - person Larry; 21.02.2013
comment
Реализация IDisposable (и, следовательно, using) важна, потому что это означает, что если вы получаете исключение, защелка автоматически снимается. Рад, что вам это нравится. :) - person Neil Barnwell; 21.02.2013

Я думаю, что лучше всего было бы использовать переменную флага:

bool updatingCheckbox = false;

void updateCheckBox()
{
    updatingCheckBox = true;

    checkbox.Checked = true;

    updatingCheckBox = false;
}

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

[Изменить: публикация только кода не совсем понятна]

В этом случае обработчик событий не будет выполнять свои обычные операции при изменении флажка с помощью updateCheckBox ().

person Lennaert    schedule 08.04.2009
comment
Это более простая версия моей SuspendLatch (опубликованная в другом месте по этому вопросу) и метод, который я использовал, пока не понял, что у меня есть несколько мест, где я хотел бы приостановить событие SelectedIndexChanged. - person Neil Barnwell; 08.04.2009
comment
Действительно, поддержал ваш ответ, так как я, вероятно, сам воспользуюсь этой идеей;) - person Lennaert; 08.04.2009
comment
Рад, что это пригодилось. Приятно иметь хоть одну хорошую идею, каждый раз время от времени :) - person Neil Barnwell; 08.04.2009
comment
Это все еще работает, если событие обрабатывается после возврата updateCheckBox ()? - person Kelly S. French; 15.07.2010

Я всегда использовал логическую переменную флага для защиты от нежелательных обработчиков событий. Пример приложения TaskVision научил меня это делать.

Ваш код обработчика событий для всех ваших событий будет выглядеть так:

private bool lockEvents;

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

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

Я позволил событию загореться. Но я устанавливаю флаг перед изменением индекса и возвращаю его обратно после. В обработчике событий я проверяю, установлен ли флаг, и выхожу из обработчика, если он установлен.

person dotjoe    schedule 08.04.2009

Я думаю, вы должны сосредоточиться на объекте, а не на происходящем событии.

Скажем, например, у вас мероприятие

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

и PerformActions сделали что-то,

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

то внутри человека вы ожидаете увидеть что-то вроде

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

Дело в том, что вы позволяете объекту отслеживать, что происходит с самим собой, а не с пользовательским интерфейсом. Это также позволяет вам отслеживать отслеживание грязных флагов на ваших объектах, что может быть полезно для сохранения в дальнейшем.

Это также сохраняет ваш пользовательский интерфейс в чистоте и не дает ему получить странный код регистрации событий, который, скорее всего, будет подвержен ошибкам.

person Joseph    schedule 08.04.2009

Я наконец-то нашел решение, чтобы избежать слишком частого срабатывания ненужного события.

Я использую счетчик и подключаю / отключаю события, которые хочу замаскировать, только один раз, когда он не нужен, и когда он снова нужен.

В приведенном ниже примере показано, как я скрываю событие CellValueChanged таблицы данных.

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();
    }
}
person Larry    schedule 18.02.2013