Привязка POCO-объектов. Интерфейс INotifyPropertyChanged

Последнее обновление: 29.01.2023

Подобно тому, как идет привязка к встроенным элементам управления, можно привязывать к любому объекту. Например, пусть у нас есть класс Person:

public class Person
{
    public string Name { get; set; } = "";
    public int Age { get; set; }
}

Создадим объект и выполним к нему привязку:

namespace MetanitApp;

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();

        Person person = new Person { Name = "Tom", Age = 18 };

        TextBox textBox1 = new TextBox();
        textBox1.Location = new Point(12, 12);
        textBox1.Size = new Size(250, 27);
        Controls.Add(textBox1);

        Label label1 = new Label();
        label1.Location = new Point(12, 60);
        label1.AutoSize = true;
        Controls.Add(label1);

        label1.DataBindings.Add(new Binding("Text", person, "Name"));
        textBox1.DataBindings.Add(new Binding("Text", person, "Name"));
    }
}

Здесь метка и текстовое поле ввода привязаны к свойству "Name" объекта person:

Привязка к сложным объектам в Windows Forms и C#

Однако здесь есть один недостаток - если мы изменим текст в текстовом поле, то значение привязанного свойства Name объекта Person сразу не изменится. Измения произойдут лишь когда, мы перейдем с текстового поля на другой элемент. Для автоматического изменения нам надо с помощью свойства DataSourceUpdateMode установить в качестве редима обновления источника данных значение DataSourceUpdateMode.OnPropertyChanged:

namespace MetanitApp;

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();

        Person person = new Person { Name = "Tom", Age = 18 };

        TextBox textBox1 = new TextBox();
        textBox1.Location = new Point(12, 12);
        textBox1.Size = new Size(250, 27);
        Controls.Add(textBox1);

        Label label1 = new Label();
        label1.Location = new Point(12, 60);
        label1.AutoSize = true;
        Controls.Add(label1);
        // установка через конструктор - пятый параметр
        label1.DataBindings.Add(new Binding("Text", person, "Name", false, DataSourceUpdateMode.OnPropertyChanged ));
        // установка через свойство DataSourceUpdateMode
        Binding binding = new Binding("Text", person, "Name", false, DataSourceUpdateMode.OnPropertyChanged);
        binding.DataSourceUpdateMode = DataSourceUpdateMode.OnPropertyChanged;
        textBox1.DataBindings.Add(binding);
    }
}

Установить режим обновления можно через пятый параметр конструктора, либо через свойство DataSourceUpdateMode. В итоге при изменения свойства текстового поля автоматически сразу будет изменяться и значение привязанного свойства и привязанного к нему текста метки/

Двусторонняя привязка и DataSourceUpdateMode.OnPropertyChanged в Windows Forms и C#

Подобным образом можно установить привязку и ко второму свойству - Age:

namespace MetanitApp;

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();

        Person person = new Person { Name = "Tom", Age = 18 };

        TextBox textBox1 = new TextBox();
        textBox1.Location = new Point(12, 12);
        textBox1.Size = new Size(250, 27);
        Controls.Add(textBox1);

        NumericUpDown numericUpDown1 = new NumericUpDown();
        numericUpDown1.Location= new Point(12, 50);
        numericUpDown1.Size = new Size(250, 27);
        Controls.Add(numericUpDown1);

        Label label1 = new Label();
        label1.Location = new Point(12, 90);
        label1.AutoSize = true;
        Controls.Add(label1);

        Label label2 = new Label();
        label2.Location = new Point(12, 120);
        label2.AutoSize = true;
        Controls.Add(label2);

        label1.DataBindings.Add(new Binding("Text", person, "Name", false, DataSourceUpdateMode.OnPropertyChanged));
        label2.DataBindings.Add(new Binding("Text", person, "Age", false, DataSourceUpdateMode.OnPropertyChanged));
        textBox1.DataBindings.Add(new Binding("Text", person, "Name", false,DataSourceUpdateMode.OnPropertyChanged));
        numericUpDown1.DataBindings.Add(new Binding("Value", person, "Age", false, DataSourceUpdateMode.OnPropertyChanged));
    }
}

Здесь для изменения возраста пользователя определен элемент NumericUpDown, у которого свойство Value привязано к свойству Age объекта Person. И также к этому свойству привязана метка для вывода его значения на форму.

Двусторонняя привязка к сложным объектам в Windows Forms и C#

Интерфейс INotifyPropertyChanged

Однако в примере выше мы все равно можем столкнуться в проблемой обновления данных. Рассмотрим следующий пример:

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
        Person person = new Person { Name = "Tom", Age = 18 };

        Label label1 = new Label();
        label1.Location = new Point(12, 10);
        label1.AutoSize = true;
        Controls.Add(label1);

        Label label2 = new Label();
        label2.Location = new Point(12, 40);
        label2.AutoSize = true;
        Controls.Add(label2);

        Button button = new Button();
        button.Location = new Point(12, 80);
        button.Text = "Change";
        button.AutoSize = true;
        Controls.Add(button);
        // изменяем свойство Name
        button.Click += (o, e) => person.Name = "admin";

        label1.DataBindings.Add(new Binding("Text", person, "Name", false, DataSourceUpdateMode.OnPropertyChanged));
        label2.DataBindings.Add(new Binding("Text", person, "Age", false, DataSourceUpdateMode.OnPropertyChanged));
    }
}

Для демонстрации проблемы здесь определена кнопка, по нажатию на которую изменяется свойство Name объекта person:

Изменение привязанных свойств в Windows Forms и C#

Однако сколько бы не нажимали, метка Label, которая привязана к свойству Name, не отобразит изменения и будет отображать старое значение свойства. И чтобы уведомить цель привязки об изменении значений источник привязки должен реализовать интерфейс INotifyPropertyChanged. Для этого изменим класс Person следующим образом:

public class Person : INotifyPropertyChanged
{
    string name = "";
    int age;
    public event PropertyChangedEventHandler? PropertyChanged;
    public string Name
    {
        get => name;
        set
        {
            if (name != value)
            {
                name = value;
                OnPropertyChanged();
            }
        }
    }
    public int Age
    {
        get => age;
        set
        {
            if (age != value)
            {
                age = value;
                OnPropertyChanged();
            }
        }
    }
    public void OnPropertyChanged([CallerMemberName] string prop = "")
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));
    }
}

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

Для каждого свойства создается поле, которое собственно хранит значение. Если требуется установить новое значение для свойства, то при его установке вызываем метод OnPropertyChanged, в который передается название свойства. Но благодаря применению к параметру атрибута [CallerMemberName], мы можем не передавать в вызов метода название свойства - оно определяется автоматически исходя из того, в каком именно свойстве вызывается этот метод. В этом методе собственно и вызывается событие PropertyChanged, в которое передается информация об измененном свойстве.

Теперь если мы запустим программу (при этом код формы изменять не надо), то при нажатию на кнопку привязанная метка сразу отобразит изменения:

Изменение привязанных свойств и интерфейс INotifyPropertyChanged в Windows Forms и C#
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850