Содержание
Для того, чтобы привязка данных работала в WPF главным условием является то, что цель привязки обязательно должна наследоваться от DependencyObject (иначе мы не сможем считывать и записывать значение в свойство зависимостей), а целевым свойством должно выступать свойство зависимостей (DependencyProperty). При этом, в качестве источника привязки может выступать кто угодно, но для того, чтобы источник привязки мог сообщать об изменении своих свойств, он должен реализовать интерфейс INotifyPropertyChanged.
Пример приложения
Создадим новое приложение WPF и добавим в него следующий класс:
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
Объекты этого класса будут выступать в качестве источника данных. Теперь изменим разметку XAML главного окна приложения следующим образом:
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
xmlns:components="clr-namespace:WpfApp1.Components"
Title="MainWindow" Height="450" Width="435">
<StackPanel>
<TextBox Margin="10" Text="{Binding Person.Name, RelativeSource={RelativeSource AncestorType=Window}}"/>
<TextBox Margin="10" Text="{Binding Person.Age, RelativeSource={RelativeSource AncestorType=Window}}"/>
<Button Content="Новый пользователь" Click="Button_Click"/>
</StackPanel>
</Window>
Здесь мы добавили два текстовых поля для вывода информации о пользователе и кнопку, клик по которой будет менять свойства объекта Person. Здесь мы установили относительную привязку к объекту в окне Window, поэтому изменим файл отдельного кода MainWindow.xaml.cs следующим образом:
using System.Windows;
namespace WpfApp1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public Person Person { get; set; } = new() { Name = "Вася Пупкин", Age = 25};
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
Person.Name = "Петя Петров";
Person.Age = 40;
}
}
}
Запустим приложение и проверим результат:
Сколько бы раз мы не кликали по кнопке — текстовые поля не поменяют свое значение, хотя сам источник привязки — меняется. Всё дело в том, что наш объект ни каким образом не сообщает о своем изменении. Чтобы он смог это сделать необходимо реализовать интерфейс INotifyPropertyChanged.
Интерфейс INotifyPropertyChanged
Интерфейс INotifyPropertyChanged предоставляет нам свойство
public event PropertyChangedEventHandler? PropertyChanged;
которое должно срабатывать каждый раз при изменении привязанных свойств источника. Чтобы это сделать — перепишем код класса Person следующим образом:
public class Person: INotifyPropertyChanged
{
private string _name;
public string Name {
get => _name;
set {
if (_name != value)
{
_name = value;
OnPropertyChanged();
}
}
}
private int _age;
public int Age {
get => _age;
set {
if (_age != value)
{
_age = value;
OnPropertyChanged();
}
}
}
public event PropertyChangedEventHandler? PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string prop = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));
}
}
Здесь мы добавили в класс метод OnPropertyChanged(), который и генерирует событие интерфейса. Обратите внимание на атрибут [CallerMemberName] у параметра этого метода. Атрибут CallerMemberName позволяет не указывать вручную имя измененного свойства. Теперь наши объекты типа Person смогут сообщать об изменении своих свойств. Проверим результат работы:
Как видите, теперь интерфейс реагирует на изменение свойств объекта Person.
Автоматизация рутинных задач для привязки данных
Может возникнуть вполне резонный вопрос: если источников привязки в приложении десять и у каждого объекта будет по несколько свойств, то получается, что для каждого привязываемого свойства мы должны будем организовывать работу как показано выше? При использовании только стандартных средств .NET/C# — да, нам придется каждый раз реализовывать интерфейс INotifyPropertyChanged. Однако, этот рутинный процесс можно автоматизировать с помощью пакета CommunityToolkit.Mvvm
Благодаря использованию этого пакета, мы можем автоматизировать выполнении рутинных задач по организации привязки данных в наших приложениях WPF. В качестве примера, продемонстрирую то, как бы выглядел наш класс Person при использовании CommunityToolkit.Mvvm
using CommunityToolkit.Mvvm.ComponentModel;
namespace WpfApp1
{
public partial class Person: ObservableObject
{
[ObservableProperty]
private string _name;
[ObservableProperty]
private int _age;
}
}
При этом, код XAML менять не нужно — обратите внимание на то, что наш класс стал частичным и наследуется от ObservableObject. Это позволяет CommunityToolkit создать необходимые свойства объекта и использовать их в разметке XAML.
Итого
В WPF мы можем привязываться к произвольным объектам. При этом, классы таких объектов-источников должны реализовать интерфейс INotifyPropertyChanged, чтобы иметь возможность сообщать об изменении своих свойств. В этой части мы рассмотрели то, как реализуется интерфейс INotifyPropertyChanged, а также то, как можно автоматизировать рутинные задачи по привязке данных с использованием пакета CommunityToolkit.Mvvm

