Различение событий, возникающих в результате взаимодействия с пользователем, и моего собственного кода - PullRequest
9 голосов
/ 08 апреля 2009

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

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

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

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

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

Ответы [ 6 ]

7 голосов
/ 08 апреля 2009

Я создал класс, который я назвал 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);
        }
    }
}
4 голосов
/ 08 апреля 2009

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

bool updatingCheckbox = false;

void updateCheckBox()
{
    updatingCheckBox = true;

    checkbox.Checked = true;

    updatingCheckBox = false;
}

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

[Редактировать: Размещение только кода не очень понятно]

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

3 голосов
/ 08 апреля 2009

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

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

private bool lockEvents;

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

    this.lockEvents = true;
    //Handle your event...
    this.lockEvents = false;
}
1 голос
/ 08 апреля 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;
          }
        }
    }
}

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

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

1 голос
/ 08 апреля 2009

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

0 голосов
/ 18 февраля 2013

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

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

В приведенном ниже примере показано, как скрыть событие 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();
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...