2012-06-08 2 views
1

Я заметил небольшую проблему в WPF. Если вы используете двухсторонние свойства связывания в цепочке (A <-> B, B <-> C, C <-> D ...), все будет хорошо, пока вы не представите два или более конвертера.Ошибка связывания цепи

Теперь, с одним преобразователем, он все еще работает, но когда вы добавляете два преобразователя в цепочку (A <x> B, B <-> C, C <x> D ...), он может застревать в бесконечном цикле, если свойства меняются достаточно быстро.

Кажется, что WPF очень умный, чтобы избежать бесконечной рекурсии, но если изменения происходят слишком быстро, это препятствует тому, что блокирует вас, и уходит в бесконечность.

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

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

Это произошло только в том случае, если преобразователи были вызваны достаточно быстро. Поэтому я предполагаю, что переполнил некоторое значение, которое подсчитало стек рекурсии.

Кто-нибудь еще заметил что-либо в этом роде, и как вы можете избежать таких проблем, когда люди используют привязку к свойству «значение результата» вашего элемента управления?

+0

У вас есть небольшой пример compilbale и runnable, демонстрирующий это? –

+0

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

ответ

1

Если преобразователи не точны (например, есть числовые расчеты), то вам придется разорвать цепь таким образом, похожее на это ...

Для уведомляющих свойств

Вместо:

public double MyProperty 
{ 
    get 
    { 
     return _myProperty; 
    } 
    set 
    { 
     if (value != _myProperty) 
     { 
      _myProperty = value; 
      NotifyPropertyChanged("MyProperty"); 
     } 
    } 
} 

Использование:

public double MyProperty 
{ 
    get 
    { 
     return _myProperty; 
    } 
    set 
    { 
     if (Abs(value - _myProperty)/(Max(Abs(value), Abs(_myProperty)) + double.Epsilon) > MyEpsilon) 
     { 
      _myProperty = value; 
      NotifyPropertyChanged("MyProperty"); 
     } 
    } 
} 

Или аналогичное неточное сравнение, чтобы неточности не создавали бесконечный цикл.

Для свойств зависимостей

использования Register(String, Type, Type, PropertyMetadata, ValidateValueCallback) и PropertyMetadata(Object, PropertyChangedCallback, CoerceValueCallback) определить CoerceValueCallback, чтобы предотвратить небольшие изменения (с помощью того же алгоритма, что и выше).

+0

То, что я закончил делать, не использует привязки вообще, но уведомления и прерывает уведомления, когда меня изменяет настройка. Таким образом, привязка возникает только тогда, когда пользователь что-то меняет, потому что когда я что-то меняю, я ЗНАЮ, что они теперь синхронизированы. –

+0

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

+0

Итак, теперь привязка привязывается только к внешним краям (сами поля управления). –

Смежные вопросы