Перейти к содержимому

Inotifypropertychanged c как использовать

  • автор:

INotify Property Changed Интерфейс

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

Сообщает клиенту об изменении значения свойства.

public interface class INotifyPropertyChanged
public interface INotifyPropertyChanged
type INotifyPropertyChanged = interface
Public Interface INotifyPropertyChanged

Производный

Примеры

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

При использовании атрибута CallerMemberName в вызовах метода NotifyPropertyChanged нет необходимости указывать имя свойства в качестве строкового аргумента. Дополнительные сведения см. в разделе Сведения о вызывающем объекте.

Замените код в Form1 следующим кодом, а затем измените пространство имен на имя проекта. В качестве альтернативы можно присвоить проекту имя пространства имен ниже при его создании.

using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Runtime.CompilerServices; using System.Windows.Forms; // Either change the following namespace to the name of your project, // or name your project with the following name when you create it. namespace TestNotifyPropertyChangedCS < // This form demonstrates using a BindingSource to bind // a list to a DataGridView control. The list does not // raise change notifications. However the DemoCustomer type // in the list does. public partial class Form1 : Form < // This button causes the value of a list element to be changed. private Button changeItemBtn = new Button(); // This DataGridView control displays the contents of the list. private DataGridView customersDataGridView = new DataGridView(); // This BindingSource binds the list to the DataGridView control. private BindingSource customersBindingSource = new BindingSource(); public Form1() < InitializeComponent(); // Set up the "Change Item" button. this.changeItemBtn.Text = "Change Item"; this.changeItemBtn.Dock = DockStyle.Bottom; this.changeItemBtn.Click += new EventHandler(changeItemBtn_Click); this.Controls.Add(this.changeItemBtn); // Set up the DataGridView. customersDataGridView.Dock = DockStyle.Top; this.Controls.Add(customersDataGridView); this.Size = new Size(400, 200); >private void Form1_Load(object sender, EventArgs e) < // Create and populate the list of DemoCustomer objects // which will supply data to the DataGridView. BindingListcustomerList = new BindingList(); customerList.Add(DemoCustomer.CreateNewCustomer()); customerList.Add(DemoCustomer.CreateNewCustomer()); customerList.Add(DemoCustomer.CreateNewCustomer()); // Bind the list to the BindingSource. this.customersBindingSource.DataSource = customerList; // Attach the BindingSource to the DataGridView. this.customersDataGridView.DataSource = this.customersBindingSource; > // Change the value of the CompanyName property for the first // item in the list when the "Change Item" button is clicked. void changeItemBtn_Click(object sender, EventArgs e) < // Get a reference to the list from the BindingSource. BindingListcustomerList = this.customersBindingSource.DataSource as BindingList; // Change the value of the CompanyName property for the // first item in the list. customerList[0].CustomerName = "Tailspin Toys"; customerList[0].PhoneNumber = "(708)555-0150"; > > // This is a simple customer class that // implements the IPropertyChange interface. public class DemoCustomer : INotifyPropertyChanged < // These fields hold the values for the public properties. private Guid idValue = Guid.NewGuid(); private string customerNameValue = String.Empty; private string phoneNumberValue = String.Empty; public event PropertyChangedEventHandler PropertyChanged; // This method is called by the Set accessor of each property. // The CallerMemberName attribute that is applied to the optional propertyName // parameter causes the property name of the caller to be substituted as an argument. private void NotifyPropertyChanged([CallerMemberName] String propertyName = "") < PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); >// The constructor is private to enforce the factory pattern. private DemoCustomer() < customerNameValue = "Customer"; phoneNumberValue = "(312)555-0100"; >// This is the public factory method. public static DemoCustomer CreateNewCustomer() < return new DemoCustomer(); >// This property represents an ID, suitable // for use as a primary key in a database. public Guid ID < get < return this.idValue; >> public string CustomerName < get < return this.customerNameValue; >set < if (value != this.customerNameValue) < this.customerNameValue = value; NotifyPropertyChanged(); >> > public string PhoneNumber < get < return this.phoneNumberValue; >set < if (value != this.phoneNumberValue) < this.phoneNumberValue = value; NotifyPropertyChanged(); >> > > > 
Imports System Imports System.Collections.Generic Imports System.ComponentModel Imports System.Drawing Imports System.Runtime.CompilerServices Imports System.Windows.Forms ' This form demonstrates using a BindingSource to bind ' a list to a DataGridView control. The list does not ' raise change notifications. However the DemoCustomer type ' in the list does. Public Class Form1 Inherits System.Windows.Forms.Form ' This button causes the value of a list element to be changed. Private changeItemBtn As New Button() ' This DataGridView control displays the contents of the list. Private customersDataGridView As New DataGridView() ' This BindingSource binds the list to the DataGridView control. Private customersBindingSource As New BindingSource() Public Sub New() InitializeComponent() ' Set up the "Change Item" button. Me.changeItemBtn.Text = "Change Item" Me.changeItemBtn.Dock = DockStyle.Bottom AddHandler Me.changeItemBtn.Click, AddressOf changeItemBtn_Click Me.Controls.Add(Me.changeItemBtn) ' Set up the DataGridView. customersDataGridView.Dock = DockStyle.Top Me.Controls.Add(customersDataGridView) Me.Size = New Size(400, 200) End Sub Private Sub Form1_Load(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles Me.Load ' Create and populate the list of DemoCustomer objects ' which will supply data to the DataGridView. Dim customerList As New BindingList(Of DemoCustomer) customerList.Add(DemoCustomer.CreateNewCustomer()) customerList.Add(DemoCustomer.CreateNewCustomer()) customerList.Add(DemoCustomer.CreateNewCustomer()) ' Bind the list to the BindingSource. Me.customersBindingSource.DataSource = customerList ' Attach the BindingSource to the DataGridView. Me.customersDataGridView.DataSource = Me.customersBindingSource End Sub ' This event handler changes the value of the CompanyName ' property for the first item in the list. Private Sub changeItemBtn_Click(ByVal sender As Object, ByVal e As EventArgs) ' Get a reference to the list from the BindingSource. Dim customerList As BindingList(Of DemoCustomer) = _ CType(customersBindingSource.DataSource, BindingList(Of DemoCustomer)) ' Change the value of the CompanyName property for the ' first item in the list. customerList(0).CustomerName = "Tailspin Toys" customerList(0).PhoneNumber = "(708)555-0150" End Sub End Class ' This class implements a simple customer type ' that implements the IPropertyChange interface. Public Class DemoCustomer Implements INotifyPropertyChanged ' These fields hold the values for the public properties. Private idValue As Guid = Guid.NewGuid() Private customerNameValue As String = String.Empty Private phoneNumberValue As String = String.Empty Public Event PropertyChanged As PropertyChangedEventHandler _ Implements INotifyPropertyChanged.PropertyChanged ' This method is called by the Set accessor of each property. ' The CallerMemberName attribute that is applied to the optional propertyName ' parameter causes the property name of the caller to be substituted as an argument. Private Sub NotifyPropertyChanged( Optional ByVal propertyName As String = Nothing) RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName)) End Sub ' The constructor is private to enforce the factory pattern. Private Sub New() customerNameValue = "Customer" phoneNumberValue = "(312)555-0100" End Sub ' This is the public factory method. Public Shared Function CreateNewCustomer() As DemoCustomer Return New DemoCustomer() End Function ' This property represents an ID, suitable ' for use as a primary key in a database. Public ReadOnly Property ID() As Guid Get Return Me.idValue End Get End Property Public Property CustomerName() As String Get Return Me.customerNameValue End Get Set(ByVal value As String) If Not (value = customerNameValue) Then Me.customerNameValue = value NotifyPropertyChanged() End If End Set End Property Public Property PhoneNumber() As String Get Return Me.phoneNumberValue End Get Set(ByVal value As String) If Not (value = phoneNumberValue) Then Me.phoneNumberValue = value NotifyPropertyChanged() End If End Set End Property End Class 

Комментарии

Интерфейс INotifyPropertyChanged используется для уведомления клиентов(обычно привязывая клиентов) об изменении значения свойства.

Например, рассмотрим Person объект со свойством с именем FirstName . Чтобы предоставить универсальное уведомление об изменении свойства, Person тип реализует INotifyPropertyChanged интерфейс и вызывает PropertyChanged событие при FirstName изменении.

Чтобы уведомление об изменениях произошло в привязке между привязанным клиентом и источником данных, связанный тип должен иметь одно из следующих значений:

  • Реализуйте INotifyPropertyChanged интерфейс (предпочтительный).
  • Укажите событие изменения для каждого свойства привязанного типа.

Не используйте оба механизма сразу.

Как работает интерфейс INotifyPropertyChanged?

Собственно сабж: как изнутри работает данный интерфейс? Что происходит при вызове метода NotifyPropertyChanged(string propertyName) ? Как UI элемент узнает, что что-то произошло и нужно «перерисоваться»? Объясните пожалуйста. Чем подробнее — тем лучше)

Отслеживать
задан 29 сен 2015 в 21:30
475 4 4 серебряных знака 11 11 бронзовых знаков

1 ответ 1

Сортировка: Сброс на вариант по умолчанию

Если вы имплементируете интерфейс INotifyPropertyChanged , то вы должны определить event PropertyChanged , и отправлять его каждый раз, когда значение изменится.

Если у вас есть привязка к какому-то свойству, то Binding в числе прочего проверяет, реализует ли binding source (то есть, объект, к свойству которого происходит binding), этот интерфейс, и если да, то подписывается на этот самый event . По приходу event’а значение свойства перечитывается (через reflection).

Дополнение: А что происходит с ItemsSource ? А для него интересен интерфейс INotifyCollectionChanged , который сообщает о том, что в коллекции появились/исчезли элементы.

Если вы привязываете ItemsSource в ListView к коллекции, Binding заботится о том, чтобы ListView знал текущее значение самой коллекции. То есть, если вы коллекцию подмените на другую коллекцию, об этом сообщит Binding. А вот нотификация об изменении списка элементов коллекции происходит следующим образом.

Когда ListView видит, что объект коллекции поменялся (эту информацию поставляет Binding ), он самостоятельно проверяет наличие интерфейса INotifyCollectionChanged , и если коллекция его поддерживает, подписывается на событие CollectionChanged . Когда это событие приходит, в нём есть NotifyCollectionChangedEventArgs , который содержит информацию о том, что именно произошло, какие элементы добавились (и куда), а какие исчезли (и откуда). Имея эту информацию, ListView может перестроить список.

Заметьте, что обыкновенный List не реализует интерфейс INotifyCollectionChanged , поэтому если в привязке лежит List , ListView автоматически обновляться не будет. Обычно никто не реализует INotifyCollectionChanged самостоятельно, а пользуются готовыми коллекциями. Например, ObservableCollection .

[Unity+c#] INotifyPropertyChanged как лучше?

Вопросы:
1 — Если нужно оповещать изменение св-в FullAttack и AvgAttack, как это сделать красивее?
2 — Инициализация данного класса может:
2.1 — Вызвать многократно OnPropertyChanged(nameof(FullAttack)); OnPropertyChanged(nameof(AvgAttack)),
2.2 — Использовать другое св-во, которое еще не инициализировано. Инициализировать св-ва в определенном порядке.
Можно добавить две функции BeginInit и EndInit (соответственно перед инициализацией всех св-в и после), которые будут изменять флаг IsInitialized, в OnPropertyChanged добавить проверку на этот флаг. EndInit в конце будет вызывать OnPropertyChanged всех св-в через рефлексию. Может есть другой способ?
3 — Стоит ли тулить INotifyPropertyChanged, если не нужно отслеживать все св-ва(например, нужно только одно св-во).
3.1 — Можно прописать только в нужных св-ах (OnPropertyChanged)
3.2 — Лучше создать свое событие (по принципу PropertyChangedEventHandler), что бы не сбивать с толку.
3.3 — Не создавать лишние события, использовать стандартный и во всех св-ах всегда прописывать OnPropertyChanged() — да же, если не пригодится.

public StatClass : INotifyPropertyChanged < public event PropertyChangedEventHandler PropertyChanged; public int Attack < get < return attack; > set < attack = value; OnPropertyChanged( ); OnPropertyChanged( nameof( FullAttack)); OnPropertyChanged( nameof( AvgAttack));> > public int BonusAttack < get < return bonusAttack; > set < bonusAttack = value; OnPropertyChanged( ); OnPropertyChanged( nameof( FullAttack)); OnPropertyChanged( nameof( AvgAttack));> > public int FullAttack < get < return Attack + BonusAttack; > > public int AvgAttack < get < return ( Attack + BonusAttack) / 2; > > Private int attack = 0; Private int bonusAttack = 0; public void OnPropertyChanged( [CallerMemberName] string propertyName = null) < PropertyChanged?.Invoke( this, new PropertyChangedEventArgs( propertyName)); > >
  • patsanchik3
  • Постоялец

#1
11:26, 19 дек 2018

Быстрый старт с WPF. Часть 1. Привязка, INotifyPropertyChanged и MVVM

По разным причинам большинство из нас использует десктопные приложения, как минимум, браузер 🙂 А у некоторых из нас возникает необходимость в написании своих. В этой статье я хочу пробежаться по процессу разработки несложного десктопного приложения с использованием технологии Windows Presentation Foundation (WPF) и применением паттерна MVVM. Желающих продолжить чтение прошу под кат.

Думаю, необязательно говорить, что WPF — это разработка Microsoft 🙂 Технология эта предназначена для разработки десктопных приложений под Windows, начиная с Windows XP. Почему именно так? Это связано с тем, что WPF работает поверх платформы .NET, минимальные требования которой — Windows XP и новее. К сожалению, WPF не работает на других платформах, хотя есть шансы, что в ближайшее время это изменится: в стадии разработки находится WPF-based фреймворк Avalonia.

В чём особенность WPF?

Два основных отличия WPF от других средств построения десктопных приложений:

  • Язык разметки XAML, предназначенный для разметки самого интерфейса окна.
  • Рендеринг посредством DirectX, аппаратное ускорение графики.

Я не буду углубляться в подробности, т.к. это не совсем тема статьи. Если интересно, то гуглить XAML, WPF rendering, milcore.dll и DirectX 🙂

О чём эта статья?

Эта статья содержит пример приложения, построенного на технологии WPF:

Я постараюсь ориентировать материал статьи в практическую сторону в стиле «повторяй за мной» с пояснениями.

Что нам понадобится для повторения статьи?

Небольшой опыт разработки на C# 🙂 Как минимум, нужно хорошо понимать синтаксис языка. Также понадобится Windows-машина (в примерах будет Win 10) с установленной на ней Visual Studio (в примерах будет 2017, есть бесплатная Community версия). При установке VS необходимо будет включить поддержку десктопной разработки под платформу .NET

image

Так же в этом разделе я опишу создание проекта.

Запускаем VS, создаём новый проект, тип приложения выбираем WPF App (.NET Framework) (можно ввести в строке поиска справа вверху), называем как угодно.

image

После создания нового проекта откроется окно редактора интерфейса, у меня оно выглядит так

image

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

image

Перед тем, как начать

Элементы окна (их ещё называют контрОлами от слова Control) должны размещаться внутри контейнера или внутри другого элемента типа ContentControl. Контейнер — это специальный контрол, позволяющий разместить внутри себя несколько дочерних контролов и организовать их взаимное расположение. Примеры контейнеров:

  • Grid — позволяет организовать элементы по столбцам и строкам, ширина каждого столбца или строки настраивается индивидуально.
  • StackPanel — позволяет расположить дочерние элементы в одну строку или столбец.

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

MVVM и интерфейс INotifyPropertyChanged. Копия текста.

Итогом этого примера станет приложение с двумя контролами, в одном из которых можно редактировать текст, а в другом только просматривать. Изменения из одного в другой будут переходить синхронно без явного копирования текста с помощью привязки (binding).

Итак, у нас есть свежесозданный проект (я назвал его Ex1), перейдём в редактор разметки и первым делом заменим контейнер, указанный по умолчанию ( ) на . Этого контейнера будет достаточно, т.к. нам понадобится расположить всего лишь два контрола один над другим. Укажем явно, каким образом будут располагаться компоненты, добавив свойство Orientation=»Vertical». Добавим внутрь стек панели парочку элементов: поле для ввода текста и поле для отображения текста. Поскольку эти контролы не будут содержать вложенного кода, можно описать их самозакрывающимся тегом (см. код ниже). После всех вышеописанных процедур код описания контейнера и вложенных контролов должен принять такой вид:

Теперь сосредоточимся на цели этого примера. Мы хотим, чтобы при наборе текста в текстбоксе этот же текст синхронно отображался в текстблоке, избежав при этом явной операции копирования текста. Нам понадобится некая связующая сущность, и вот тут-то мы и подошли к такой штуке, как привязка (binding), о которой было сказано выше. Привязка в терминологии WPF — это механизм, позволяющий связывать некоторые свойства контролов с некоторыми свойствами объекта C#-класса и выполнять взаимное обновление этих свойств при изменении одной из частей связки (это может работать в одну, в другую или в обе стороны сразу). Для тех, кто знаком с Qt, можно провести аналогию слотов и сигналов. Чтобы не растягивать время, перейдём к коду.

Итак, для организации привязки нужны свойства контролов и некое свойство некоего C#-класса. Для начала разберёмся с XAML-кодом. Текст обоих контролов хранится в свойстве Text, поэтому добавим привязку для этих свойств. Делается это так:

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

Так что это за класс? Этот класс называется вьюмоделью (view model) и служит как раз связующим звеном между view (интерфейсом или его частями) и model (моделью, т.е. теми частями кода, которые отвечают за логику приложения. Это позволяет отделить (в какой-то степени) логику приложения от интерфейса (представления, view) и называется паттерном Model-View-ViewModel (MVVM). В рамках WPF этот класс также называется DataContext.

Однако, просто написать класс вьюмодели недостаточно. Нужно ещё как-то оповещать механизм привязки о том, что свойство вьюмодели или свойство вью изменилось. Для этого существует специальный интерфейс INotifyPropertyChanged, который содержит событие PropertyChanged. Реализуем этот интерфейс в рамках базового класса BaseViewModel. В дальнейшем все наши вьюмодели мы будем наследовать от этого базового класса, чтобы не дублировать реализацию интерфейса. Итак, добавим в проект каталог ViewModels, а в этот каталог добавим файл BaseViewModel.cs. Получим такую структуру проекта:

image

Код реализации базовой вьюмодели:

using System.ComponentModel; namespace Ex1.ViewModels < public class BaseViewModel : INotifyPropertyChanged < public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged(string propertyName = "") < PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); >> >

Создадим для нашего класса MainWindow свою вьюмодель, унаследовавшись от базовой. Для этого в том же каталоге ViewModels создадим файл MainWindowViewModel.cs, внутри которого будет такой код:

namespace Ex1.ViewModels < public class MainWindowViewModel : BaseViewModel < >>

Шикарно! Теперь нужно добавить в эту вьюмодель свойство, на которое будем биндить текст наших контролов. Поскольку это текст, тип этого свойства должен быть string:

public string SynchronizedText

В итоге получим такой код

namespace Ex1.ViewModels < public class MainWindowViewModel : BaseViewModel < public string SynchronizedText < get; set; >> >

Так, кажется, справились. Осталось забиндиться на это свойство из вьюхи и готово. Давайте сделаем это прямо сейчас:

Ништяк, запускаем проект, набираем текст в текстбокс иииии… ничего не происходит))) Ну, ничего страшного, на самом деле мы идём правильной дорогой, просто пока ещё не дошли до нужной точки.

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

Ладно, шутки в сторону. Мы забыли создать объект вьюмодели и кое-что ещё (об этом позже). Сам класс мы описали, но это ничего не значит, ведь у нас нет объектов этого класса. Ок, где нужно хранить ссылку на этот объект? Ближе к началу примера я упомянул некий DataContext, используемый в WPF. Так вот, у любой вью есть свойство DataContext, которому мы можем присвоить ссылку на нашу вьюмодель. Сделаем это. Для этого откроем файл MainWindow.xaml и нажмём F7, чтобы открыть код этой вьюхи. Он практически пустой, в нём есть только конструктор класса окна. Добавим в него создание нашей вьюмодели и поместим её в DataContext окна (не забываем добавить using с нужным неймспейсом):

public MainWindow()

Это было просто, но этого всё равно не хватает. По-прежнему при запуске приложения никакой синхронизации текста не происходит. Что ещё нужно сделать?

Нужно вызвать событие PropertyChanged при изменении свойства SynchronizedText и сообщить вьюхе о том, что она должна следить за этим событием. Итак, чтобы вызвать событие, модифицируем код вьюмодели:

public class MainWindowViewModel : BaseViewModel < private string _synchronizedText; public string SynchronizedText < get =>_synchronizedText; set < _synchronizedText = value; OnPropertyChanged(nameof(SynchronizedText)); >> >

Что мы тут сделали? Добавили скрытое поле для хранения текста, обернули его в уже существующее свойство, а при изменении этого свойства не только меняем скрытое поле, но и вызываем метод OnPropertyChanged, определённый в базовой вьюмодели и вызывающий событие PropertyChanged, объявленное в интерфейсе INotifyPropertyChanged, так же реализованное в базовой вьюмодели. Получается, что при каждом изменении текста возникает событие PropertyChanged, которому передаётся имя свойства вьюмодели, которое было изменено.

Ну, почти всё, финишная прямая! Осталось указать вьюхе, что оно должно слушать событие PropertyChanged:

Помимо того, что мы указали, по какому триггеру должно происходить обновление, мы так же указали, в какую сторону это обновление отслеживается: от вью к вьюмодели или наоборот. Поскольку в текстбоксе мы вводим текст, то нам интересны изменения только во вью, поэтому выбираем режим OneWayToSource. В случае с текстблоком всё ровно наоборот: нам интересны изменения во вьюмодели, чтобы отобразить их во вью, поэтому выбираем режим OneWay. Если бы нам нужно было, чтобы изменения отслеживались в обе стороны, можно было не указывать Mode вообще, либо указать TwoWay явно.

Итак, запускаем программу, набираем текст и voi-la! Текст синхронно меняется, и мы нигде ничего не копировали!

image

Спасибо за внимание, продолжение следует. Будем разбираться с DataTemplate и паттерном Command.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *