Как избежать рекурсивного запуска событий в WPF?

Я имея два WPF (из стандартного набора) виджетов A и B. Когда я изменяю какое-то свойство A, оно должно быть установлено на B, когда оно изменяется на B, оно должно быть установлено на A.

Теперь у меня есть эта уродливая рекурсия -> Я меняю А, поэтому код меняет B, но поскольку B изменяется, он меняет A, поэтому он меняет B ... У вас есть картинка.

Как избежать этой рекурсии наиболее «стандартным» способом? Наивное удаление и добавление обработчиков событий не работает, и проверка, является ли новое значение таким же, как старое значение, здесь не применима (из-за колебаний расчета - я не устанавливаю одно и то же значение в A и B, но преобразую) .

Предпосылки

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

  • . Я не писал эти виджеты, я просто обрабатывал события, вот и все
  • , несмотря на заголовок «рекурсивный запуск», обработчики вызываются последовательно, поэтому у вас есть последовательность entry-exit- запись-выход-запись-выход, а не запись-запись-запись-выход-выход-выход

    и последняя, ​​вероятно, наименее важная, но тем не менее

  • в этом конкретном случае у меня есть общий обработчик для A и B

A и B (в данном случае) являются наблюдателями прокрутки, и я стараюсь пропорционально поддерживать одинаковую позицию для них обоих. Проект (автор Карин Хубер) находится здесь: http://www.codeproject.com/KB/WPF/ScrollSynchronization.aspx

Инициирование событий

Идея блокирования событий настолько популярна, что я добавил последовательность запуска событий. :

  • Я изменяю A
  • Обработчик называется
  • Я отключаю обработчик A
  • Я изменяю B (это сохраняется, но не вызывается)
  • Теперь я включаю обработчик A
  • событие get из очереди
  • Обработчик B называется
  • Я отключаю обработчик B
  • Я изменяю A
  • ...

Как видите, это бесполезно.

7
задан greenoldman 26 August 2010 в 12:24
поделиться

4 ответа

Слегка хакерское решение состоит в том, чтобы установить период отключения, в течение которого вы игнорируете последующие срабатывания обработчика событий. В этом случае обязательно используйте DateTime.UtcNow, а не DateTime.Now, чтобы избежать граничного условия перехода на летнее время.

Если вы не хотите зависимости от времени (события могут законно быстро обновляться), вы можете использовать аналогичный тип блокировки, хотя это еще более хакерский:

        private int _handlerCounter;
        private void Handler()
        {
            //The logic of this handler will trigger an 'asynchronously reentrant' callback, so we ignore the next (and only the next) callback
            //Note that this breaks down if the callback is not triggered, so we need to make certain the reentrancy will occur
            //If we can't ensure that, we need to at least detect that it won't occur and manually decrement the counter
            if (Interlocked.CompareExchange(ref _handlerCounter, 0, 1) == 0)
            {
                //Call set on A/B, which triggers callback of this same handler for B/A
            }
            else
            {
                Interlocked.Decrement(ref _handlerCounter);
            }
        }
0
ответ дан 7 December 2019 в 12:12
поделиться

Вместо того, чтобы вызывать события, реорганизуйте свой код, чтобы обработчики событий для A и B вызывали другой метод для выполнения фактической работы.

private void EventHandlerA(object sender, EventArgs e)
{
    ChangeA();
    ChangeB();
}

private void EventHandlerB(object sender, EventArgs e)
{
    ChangeB();
    ChangeA();
}

Затем вы можете расширить/изменить эти методы, если вам нужно делать немного разные вещи при изменении A напрямую или через B.

UPDATE

Учитывая, что вы не можете изменить/у вас нет доступа к коду это не решение.

2
ответ дан 7 December 2019 в 12:12
поделиться

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

Однако могут быть ситуации, когда такие зависимости — единственный выход. В этом случае я бы предложил использовать частные флаги, указывающие, было ли изменение в B вызвано изменением в A. Что-то вроде этого (обновлено):

public class A
{
    private bool m_ignoreChangesInB = false;

    private void B_ChangeOccurred(object sender, EventArgs e)
    {
        if (!m_ignoreChangesInB)
        {
            // handle the changes...
        }
    }

    private void SomeMethodThatChangesB()
    {
        m_ignoreChangesInB = true;
        // perform changes in B...
        m_ignoreChangesInB = false;
    }
}

Тот же подход следует использовать в классе B. Однако этот подход не обрабатывает изменения из нескольких потоков. Если A или B могут быть изменены из нескольких потоков одновременно, вам придется использовать соответствующие методы, чтобы избежать потери изменений свойств.

3
ответ дан 7 December 2019 в 12:12
поделиться

Решение Криса, вероятно, является правильным, но иногда мы удаляем событие, вносим изменения и повторно добавляем обработчик событий:

Представьте себе DataContext с событием DataContextChanged:

DataContextChanged -= OnDataContextChanged;
DataContext = new object();
DataContextChanged += OnDataContextChanged;

Таким образом, вы будете точно знать, что ваш обработчик событий, так сказать, не сработает. Однако событие все еще срабатывает;).

0
ответ дан 7 December 2019 в 12:12
поделиться
Другие вопросы по тегам:

Похожие вопросы: