INotify Property Changed Интерфейс
Некоторые сведения относятся к предварительной версии продукта, в которую до выпуска могут быть внесены существенные изменения. Майкрософт не предоставляет никаких гарантий, явных или подразумеваемых, относительно приведенных здесь сведений.
Сообщает клиенту об изменении значения свойства.
Примеры
В следующем примере кода показано, как реализовать INotifyPropertyChanged интерфейс. При выполнении этого примера вы заметите, что привязанный DataGridView элемент управления отражает изменение в источнике данных, не требуя сброса привязки.
При использовании атрибута CallerMemberName в вызовах метода NotifyPropertyChanged нет необходимости указывать имя свойства в качестве строкового аргумента. Дополнительные сведения см. в разделе "Сведения о вызывающем объекте".
Замените код в form1 следующим кодом, а затем измените пространство имен на имя проекта. В качестве альтернативы при создании проекта можно присвоить проекту имя пространства имен, приведенное ниже.
Комментарии
Интерфейс INotifyPropertyChanged используется для уведомления клиентов, как правило, привязки клиентов о том, что значение свойства изменилось.
Например, рассмотрим Person объект со свойством с именем FirstName . Чтобы предоставить универсальное уведомление об изменении свойства, Person тип реализует INotifyPropertyChanged интерфейс и вызывает PropertyChanged событие при FirstName изменении.
Чтобы уведомление об изменениях произошло в привязке между привязанным клиентом и источником данных, ваш связанный тип должен иметь один из следующих типов:
Базовая реализация INotifyPropertyChanged
В случае с INPC в ViewModel часто существуют свойства, зависящие от других или вычисляемые на их основе. Для .net 4.0 ситуация с реализацией усложняется тем, что CallerMemberNameAttribute не поддерживается в этой версии (на самом деле поддерживается, если вы маг и кудесник).
Решение
Разбирая в очередной раз проект с десятками строк в package-файле, мне становится всё ближе концепция UNISTACK, когда комплексное хорошо интегрированное решение позволяет реализовывать типовые задачи в типовых сценариях и оставляет место для расширения под нужды пользователя. И одновременно с этим я вижу фатальные недостатки существующих решений — громоздкость и тяжеловесность. И, иногда, немодульность.
В предыдущей статье я обещал показать пример как раз такой интеграции — когда для любой запускаемой в обёртке асинхронной задачи блокируется UI и отображается BusyIndicator или его настраиваемый аналог. И я всё ещё обещаю показать этот пример. Так мы оборачиваем все вызовы WCF, но это можно использовать и для долгоиграющих вычислений, перегруппировки больших коллекций и подобных операций.
Одной из основ библиотеки Rikrop.Core.Wpf служит базовый класс объекта реализующего интерфейс INotifyProprtyChanged — ChangeNotifier, который предлагает своим наследникам следующий набор методов:
Здесь же сразу стоит указать интерфейсы ILinkedPropertyChanged и ILinkedObjectChanged:
Надуманный пример использования
Куда же без примера, который будут называть надуманным и нереалистичным? Посмотрим, как в разных сценариях пользоваться ChangeNotifier.
У нас есть устройство с N однотипных датчиков, которое отображает среднее значение со всех датчков. Каждый датчик отображает измеренное значение и отклонение от среднего. При изменении значения датчика мы должны вначале пересчитать среднее значение, а затем уже уведомить об изменении на самом датчике. При изменении среднего значения нам необходимо пересчитать отклонения от среднего для каждого из датчиков.
Интересующие нас строки кода:
Отдельно разберем каждую конструкцию и посмотрим на реализацию приведенных методов.
Реализация
SetProperty(ref _delta, value)
Этот код присваивает полю, переданному в первом параметре метода, значение из второго параметра, а так же уведомляет подписчиков об изменении свойства, имя которого передаётся третьим параметром. Если третий параметр не задан, используется имя вызывающего свойства.
NotifyPropertyChanged(() => AvgValue)
Все методы нотификации об изменении объектов, принимают ли они дерево выражений или строковое значение имени свойства, в конечном итоге вызывают следующий метод:
Каждый объект-наследник ChangeNotifier хранит коллекции связок «имя свойства» -> «набор слушателей уведомлений об изменении свойства»:
Таким образом, для каждого свойства в объекте-источнике хранится коллекция связанных объектов, свойств связанных объектов, об изменении которых необходимо уведомить подписчиков, и действия, которые необходимо выполнить до или после нотификации.
Стоит отметить, что при регистрации проверяется уникальность подписки. Если вы хоть раз пытались найти причину двухкратной нотификации об изменении объекта, то будете очень рады такой фиче.
AfterNotify(() => AvgValue).Execute(UpdateDelta)
BeforeNotify(sensor, s => s.Value).Notify(() => AvgValue)
BeforeNotify(() => Value).Notify(() => indicator.AvgValue);
Для добавления нового связанного объекта и действий над ним служит последовательность вызова методов AfterNotify/BeforeNotify класса ChangeNotifier и методов Notify/Execute классов-наследников ILinkedPropertyChanged. В качестве последних выступают вложенные по отношению к ChangeNotifier классы AfterLinkedPropertyChanged и BeforeLinkedPropertyChanged.
Для связывания используются методы RegisterBeforeLinkedPropertyListener/RegisterAfterLinkedPropertyListener класса ChangeNotifier:
Методы AfterNotify/BeforeNotify создают новые экземпляры «связывателей» для предоставления простого интерфейса связывания:
Из последнего листинга можно видеть, что связываемым объектом всегда выступает текущий объект, а в качестве исходного объекта может использоваться либо явно указанный экземпляр, либо полученный на основе разбора дерева выражения с помощью вспомогательного класса PropertyCallHelper. Зачастую исходный и связываемый объект совпадают.
Пожалуйста, не надо больше листингов
Ок. Ещё раз на пальцах. Объект ChangeNotifier содержит несколько коллекций, в которых хранятся данные о связанных с нотификацией свойства объектах, нотифицируемых свойствах этих объектов, а так же о действиях, которые должны быть вызваны до или после нотификации. Для предоставления простого интерфейса связывания объектов методы AfterNotify/BeforeNotify возвращают наследников ILinkedPropertyChanged, которые позволяют легко добавлять нужную информацию в коллекции. Методы ILinkedPropertyChanged возвращают исходный объект ILinkedPropertyChanged, что позволяет использовать цепочку вызовов для регистрации.
При нотификации об изменении свойства объект обращается к коллекциям связанных объектов и вызывает все необходимые зарегистрированные заранее действия.
ChangeNotifier предоставляет удобный интерфейс для изменения свойств объектов и нотификации об изменениях свойствах, который минимизирует затраты на разбор деревьев выражений. Все зависимости можно собрать в конструкторе.
Решение об использовании
Это не совсем статья про библиотеку, которой можно просто начать пользоваться. Я хотел показать внутреннюю реализацию одного из вариантов решения типовой для WPF в рамках MVVM задачи, простоту этого решения, простоту его использования, расширяемость. Без знания реализации гораздо проще неправильно применить используемый инструмент. Например, Microsoft Prism 4 позволял уведомлять об изменении свойств при помощи передачи дерева выражений, но в разборе участвовал только базовый сценарий «() => PropertName». Таким образом, если вычисляемое свойство находилось в другом классе, то не было никакой возможности уведомить об его изменении из исходного свойства. Что логично, но оставляет пространство для ошибки.
В исходной статье есть сравнение наиболее распространенных решений поставленной задачи, но этот обзор не будет полным без понимания внутренних механизмов работы этих решений. Гораздо проще начать пользоваться кодом, которому доверяешь как своему. Надеюсь, каждый, кто добрался до этой строки, сможет сделать базовую реализацию для автоматического обновления связанных свойств, а значит сможет и разобраться в чужой реализации.
Inotifypropertychanged c как использовать
В прошлой теме использовался объект Phone для привязки к текстовым блокам. Однако если мы изменим его, содержимое текстовых блоков не изменится. Например, добавим в окно приложения кнопку:
И в файле кода для этой кнопки определим обработчик, в котором будет меняться свойства ресурса:
Сколько бы мы не нажимали на кнопку, текстовые блоки, привязанные к ресурсу, не изменятся. Чтобы объект мог полноценно реализовать механизм привязки, нам надо реализовать в его классе интерфейс INotifyPropertyChanged. И для этого изменим класс Phone следующим образом:
Когда объект класса изменяет значение свойства, то он через событие PropertyChanged извещает систему об изменении свойства. А система обновляет все привязанные объекты.