Язык программирования C#9 и платформа .NET5
Причина в том, что индексатор выполняется, только когда для свойства сгенерировано событие
. Как обсуждалось в разделе "Система уведомлений привязки WPF" ранее в главе, событиеPropertyChangedинициируется при изменении исходного значения свойства объекта, что происходит либо через код (вроде обработчика событияPropertyChangedдля кнопки Change Color), либо через взаимодействие с пользователем (синхронизируется с помощьюClick). При изменении цвета свойство Make не изменяется, а потому событиеUpdateSourceTriggerдля него не генерируется. Поскольку событие не генерируется, индексатор не вызывается и проверка достоверности для свойстваPropertyChangedне выполняется.MakeРешить проблему можно двумя путями. Первый предусматривает изменение объекта
, которое обеспечит обновление всех привязанных свойств, за счет передачи его конструктору значенияPropertyChangedEventArgsвместо имени поля. Как упоминалось ранее, это заставит механизм привязки обновить каждое свойство в данном экземпляре. Добавьте методstring.Emptyсо следующим кодом:OnPropertyChanged()protected virtual void OnPropertyChanged([CallerMemberName]string propertyName = ""){if (propertyName != nameof(IsChanged)){IsChanged = true;}//PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));PropertyChanged?.Invoke(this,new PropertyChangedEventArgs(string.Empty));}Теперь при выполнении того же самого теста текстовые поля Make и Color декорируются с помощью шаблона отображения ошибки, когда одно из них обновляется. Так почему бы ни генерировать событие всегда в такой манере? В значительной степени причиной является производительность. Вполне возможно, что обновление каждого свойства объекта приведет к снижению производительности. Разумеется, без тестирования об этом утверждать нельзя, и конкретные ситуации могут (и вероятно будут) варьироваться.
Другое решение предполагает генерацию события
для зависимого поля (полей), когда одно из полей изменяется. Недостаток такого приема в том, что вы (или другие разработчики, сопровождающие ваше приложение) должны знать о взаимосвязи между свойствамиPropertyChangedиMakeчерез код проверки достоверности.ColorИнтерфейс INotifyDataErrorInfo
Интерфейс
, появившийся в версии .NET 4.5, построен на основе интерфейсаINotifyDataErrorInfoи предлагает дополнительные возможности для проверки достоверности. Конечно, возросшая мощь сопровождается дополнительной работой! По разительному контрасту с предшествующими приемами проверки достоверности, которые вы видели до сих пор, свойство привязкиIDataErrorInfoимеет стандартное значениеValidatesOnNotifyDataErrors, поэтому добавлять его к операторам привязки не обязательно.trueИнтерфейс
чрезвычайно мал, но, как вскоре будет показано, для обеспечения своей эффективности требует написания порядочного объема связующего кода. Ниже приведено определение интерфейсаINotifyDataErrorInfo:INotifyDataErrorInfopublic interface INotifyDataErrorInfo{bool HasErrors { get; }event EventHandler<DataErrorsChangedEventArgs>ErrorsChanged;IEnumerable GetErrors(string propertyName);}Свойство
используется механизмом привязки для выяснения, есть ли какие-нибудь ошибки в любых свойствах экземпляра. Если методHasErrorsвызывается со значениемGetErrors()или пустой строкой в параметреnull, то он возвращает все ошибки, существующие в экземпляре. Если методу передан параметрpropertyName, тогда возвращаются только ошибки, относящиеся к конкретному свойству. СобытиеpropertyName(подобно событиямErrorsChangedиPropertyChanged) уведомляет механизм привязки о необходимости обновления пользовательского интерфейса для текущего списка ошибок.CollectionChangedРеализация поддерживающего кода
При реализации
большая часть кода обычно помещается в базовый класс модели, поэтому она пишется только один раз. Начните с заменыINotifyDataErrorInfoинтерфейсомIDataErrorInfoв файле классаINotifyDataErrorInfo(код дляCarPartial.csв классе можете оставить; вы обновите его позже).IDataErrorInfopublic partial class Car: INotifyDataErrorInfo, IDataErrorInfo{...public IEnumerable GetErrors(string propertyName){throw new NotImplementedException();}public bool HasErrors { get; }public eventEventHandler<DataErrorsChangedEventArgs> ErrorsChanged;}Добавьте закрытое поле типа
, которое будет хранить сведения о любых ошибках, сгруппированные по именам свойств. Понадобится также добавить операторDictionary<string,List<string>>для пространства именusing. Вот как выглядит код:System.Collections.Genericusing System.Collections.Generic;private readonly Dictionary<string,List<string>> _errors