🔧 OnPropertyChanged

Никакой магии. Только факты.

Сразу к делу: Если ты когда-нибудь сидел и думал:

👉 «Почему моя программа показывает старые данные, хотя я их поменял?»

👉 «Почему экран не обновляется после нажатия кнопки?»

👉 «Что за хрень, привязки есть, а работать не хотят?»

Ты пришёл по адресу. OnPropertyChanged — это ответ на все эти вопросы.

Дальше: Никакой воды. Только то, что реально нужно знать, чтобы разобраться.

❓ Зачем мне это?

Представь ситуацию (реальную):

Ты работаешь над программой:

  • Программа показывает список клиентов
  • Ты добавляешь нового клиента
  • Нажимаешь «Сохранить»
  • А список на экране НЕ ОБНОВЛЯЕТСЯ

👉 Клиент уже в базе. Данные изменились. А на экране — старьё.

👉 Пользователь в бешенстве. Ты тоже.

Так вот: OnPropertyChanged — это механизм, который говорит программе:

«Эй! Данные изменились! Обнови экран!»

✅ Если этого механизма нет — экран никогда не узнает об изменениях.

✅ Если он есть — всё работает автоматически.

🤔 А если я не хочу этим заниматься?

Тогда придётся писать руками:

  • После каждого изменения данных вручную дёргать элементы на экране
  • Писать textBoxName.Text = person.Name; 100500 раз
  • Ловить баги, когда забыл где-то обновить
  • Потратить часы на отладку

С OnPropertyChanged: один раз настроил — и забыл. Данные меняются — экран сам обновляется.

Это как разница между ручным переключением передач и автоматом.

⚙️ Как это работает (человеческим языком)

Есть три участника:

  1. Данные — например, имя человека
  2. Экран — поле, где это имя показывается
  3. Связка — которая соединяет данные и экран

Проблема: связка работает только ОДИН РАЗ — при запуске программы.

Потом она «засыпает» и не замечает, что данные поменялись.

OnPropertyChanged — это как пинок для связки:

«Проснись! Данные изменились! Бери новое значение и показывай!»

📺 Простая аналогия

Ты смотришь телевизор.

Ты нажал кнопку «включить» один раз — телевизор работает.

Телевизор НЕ СЛЕДИТ за кнопкой постоянно. Ему плевать, нажал ты ещё раз или нет.

👉 Чтобы телевизор что-то сделал (поменял громкость, канал) — ты должен снова нажать кнопку.

👉 Твой сигнал — это команда «сделай действие».

OnPropertyChanged — это та самая кнопка на пульте.

Ты изменил данные → ты нажал на кнопку → программа получила сигнал → экран обновился.

📍 Где это вообще используется?

Во всех программах, где экран и данные связаны «умной» связкой.

Такие фреймворки:

  • WPF — программы для Windows
  • MAUI — программы для Windows, Android, iPhone, Mac
  • Avalonia — программы даже для Linux
  • WinUI, UWP — современные приложения Microsoft

Что это за звери?

Это инструменты, с помощью которых рисуют окна, кнопки, поля ввода.

Они умеют связывать данные и экран. Но для обновлений им нужен OnPropertyChanged.

📄 Что такое XAML? (это просто)

Ты видел HTML? Это язык разметки для веб-страниц.

XAML — почти то же самое, но для оконных программ.

<Window>
    <TextBox Text="Привет" />
    <Button Content="Нажми меня" />
</Window>

В XAML ты описываешь, как выглядит окно: где поле ввода, где кнопка, где текст.

А потом говоришь: «Данные для этого текста бери из моего C# класса».

Эта фраза «бери оттуда» называется Binding (привязка).

🔗 Что такое Binding?

Binding = верёвочка между данными и экраном.

Ты говоришь: «Вот поле на экране. Вот переменная в коде. Свяжи их».

<TextBlock Text="{Binding UserName}" />

Проблема: эта верёвочка работает только один раз — при запуске.

Если ты поменял UserName в коде — экран НЕ УЗНАЕТ об этом.

Чтобы экран узнал — нужен звонок.

Этот звонок и есть OnPropertyChanged().

Ты говоришь: «Binding, проснись! UserName изменился! Обнови экран!»

🛠️ Как создать класс? (пошагово)

Если ты не знаешь, что такое класс:

Класс — это чертёж. По нему компьютер создаёт объекты.

Например, класс «Человек». А объект — «конкретный человек Иван».

Как создать класс в Visual Studio:

  1. Нажми правой кнопкой на проект
  2. Выбери Add → Class
  3. Введи имя (например, Person)
  4. Нажми Enter
// Вот так выглядит пустой класс
public class Person
{
    // сюда пишем свойства
}

✅ Теперь внутри этого класса можно создавать свойства (например, Name, Age).

✅ И именно в этих свойствах нужен OnPropertyChanged.

📦 Обычное свойство (НЕ работает с экраном)

private string _name;
public string Name
{
    get { return _name; }
    set { _name = value; }
}

Проблема: Ты меняешь Name, а экран продолжает показывать старое значение.

Потому что нет звонка (OnPropertyChanged).

Это работает, если ты просто хранишь данные. Но не работает, если данные показываются на экране и должны обновляться.

✅ Свойство, которое обновляет экран

private string _name;
public string Name
{
    get { return _name; }
    set 
    {
        if (_name != value)      // если реально изменилось
        {
            _name = value;
            OnPropertyChanged();  // ЗВОНОК! Обнови экран!
        }
    }
}

Что изменилось?

  • Добавилась проверка if (_name != value) — чтобы не дёргать попусту
  • Добавился вызов OnPropertyChanged() — уведомление для экрана

Результат: Когда ты меняешь Name — экран обновляется сам.

📜 Что такое INotifyPropertyChanged?

Это интерфейс. Звучит страшно, но на деле — просто разрешение.

Ты говоришь компьютеру: «Мой класс умеет звонить OnPropertyChanged».

using System.ComponentModel;

public class Person : INotifyPropertyChanged   // ← вот это разрешение
{
    public event PropertyChangedEventHandler? PropertyChanged;

    protected void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

Без : INotifyPropertyChanged у тебя даже не будет события PropertyChanged.

А без события — некому звонить.

📋 Полный класс Person (скопируй и вставь)

using System.ComponentModel;
using System.Runtime.CompilerServices;

public class Person : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler? PropertyChanged;

    protected void OnPropertyChanged([CallerMemberName] string? propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    private string _name = "";
    public string Name
    {
        get => _name;
        set
        {
            if (_name != value)
            {
                _name = value;
                OnPropertyChanged();
            }
        }
    }

    private int _age = 0;
    public int Age
    {
        get => _age;
        set
        {
            if (_age != value)
            {
                _age = value;
                OnPropertyChanged();
            }
        }
    }
}

✅ Этот класс уже умеет обновлять экран. Осталось привязать его к окну.

🔌 Как сказать окну «бери данные из этого класса»?

В коде окна (MainWindow.xaml.cs):

public partial class MainWindow : Window
{
    private Person _person = new Person { Name = "Анна", Age = 25 };

    public MainWindow()
    {
        InitializeComponent();
        this.DataContext = _person;   // 👈 ГЛАВНАЯ СТРОКА
    }
}

В разметке окна (MainWindow.xaml):

<Window ...>
    <StackPanel>
        <TextBox Text="{Binding Name}" />
        <TextBox Text="{Binding Age}" />
        <TextBlock Text="{Binding Name}" />
    </StackPanel>
</Window>

Всё! Теперь при изменении _person.Name экран обновится сам.

🖱️ Реальный пример: кнопка меняет данные

// XAML
<Button Content="Сменить имя" Click="Button_Click" />

// C# в окне
private void Button_Click(object sender, RoutedEventArgs e)
{
    _person.Name = "Мария";   // меняем данные
    _person.Age = 30;
    // OnPropertyChanged сработал внутри setter'а → экран обновился!
}

Без OnPropertyChanged: нажал кнопку → данные изменились → экран показывает старое имя.

С OnPropertyChanged: нажал кнопку → данные изменились → экран обновился мгновенно.

🎯 Где тебе реально пригодится OnPropertyChanged

  • Список клиентов/товаров — добавил новый, список обновился
  • Профиль пользователя — изменил имя, везде новое имя
  • Настройки программы — переключил тему, оформление поменялось
  • Чат — пришло новое сообщение, экран показал
  • Игры — изменилось здоровье игрока, полоска здоровья обновилась
  • Любое приложение с данными — без этого экран будет врать

💥 Последствия, если забыть про OnPropertyChanged

  • Пользователь меняет данные → нажимает «Сохранить» → а на экране старое
  • Пользователь думает: «Программа глючная, данные не сохранились»
  • Ты получаешь баг-репорты и злых клиентов
  • Ты начинаешь вручную обновлять каждый элемент на экране — код превращается в кашу
  • Ты тратишь дни на отладку

Решение: Потратить 5 минут, разобраться с OnPropertyChanged, и забыть о проблеме навсегда.

⚡ Как не писать одно и то же 100 раз

Вместо того чтобы в каждом свойстве писать if и OnPropertyChanged, делают один helper-метод:

protected bool SetProperty<T>(ref T field, T value, [CallerMemberName] string? propertyName = null)
{
    if (EqualityComparer<T>.Default.Equals(field, value))
        return false;
    field = value;
    OnPropertyChanged(propertyName);
    return true;
}

// Теперь свойство пишется в одну строку:
private string _name;
public string Name
{
    get => _name;
    set => SetProperty(ref _name, value);
}

✅ Меньше кода → меньше ошибок.

✅ Этот метод используют во всех серьёзных проектах.

🎁 Шаблон, который можно скопировать в любой проект

using System.ComponentModel;
using System.Runtime.CompilerServices;

public class ViewModelBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler? PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    protected bool SetProperty<T>(ref T field, T value, [CallerMemberName] string? propertyName = null)
    {
        if (EqualityComparer<T>.Default.Equals(field, value))
            return false;
        field = value;
        OnPropertyChanged(propertyName);
        return true;
    }
}

// Использование:
public class MyViewModel : ViewModelBase
{
    private string _title = "";
    public string Title
    {
        get => _title;
        set => SetProperty(ref _title, value);
    }
}

✅ Создаёшь этот класс один раз. Все остальные классы наследуешь от него.

✅ Во всех наследниках свойства пишутся через SetProperty — и экран обновляется.

⚠️ Ошибки, которые делают почти все

  • Забыли : INotifyPropertyChanged → класс не умеет звонить
  • Забыли вызвать OnPropertyChanged() в setter → данные изменились, а звонка нет
  • Не поставили проверку if (_field != value) → лишние звонки, программа тормозит
  • Не назначили DataContext в окне → окно не знает, откуда брать данные
  • Написали OnPropertyChanged("Name") с опечаткой → имя не совпало, обновления нет

✅ Используй [CallerMemberName] и SetProperty — эти ошибки исчезнут сами.

🔍 Как убедиться, что OnPropertyChanged срабатывает

Самый простой способ:

  1. Поставь точку остановки (breakpoint) внутри метода OnPropertyChanged
  2. Запусти программу (F5)
  3. Измени какое-нибудь свойство (например, через кнопку или ввод в поле)
  4. Если выполнение остановилось на breakpoint'е — значит, звонок работает
  5. Если нет — ищи, где забыл вызов

✅ Также можно добавить Console.WriteLine($"Property changed: {propertyName}"); и смотреть вывод.

🔗 Когда одно свойство зависит от других

Пример: FullName зависит от FirstName и LastName.

Если изменилось имя — должно обновиться и полное имя.

private string _firstName;
public string FirstName
{
    get => _firstName;
    set
    {
        if (SetProperty(ref _firstName, value))
            OnPropertyChanged(nameof(FullName));   // обновляем зависимое
    }
}

private string _lastName;
public string LastName
{
    get => _lastName;
    set
    {
        if (SetProperty(ref _lastName, value))
            OnPropertyChanged(nameof(FullName));
    }
}

public string FullName => $"{FirstName} {LastName}".Trim();

Важно: Если забыть вызвать OnPropertyChanged(nameof(FullName)), то при изменении имени экран не обновит полное имя.

🌍 Это не только в WPF/MAUI

Точно такой же принцип в других местах:

  • Blazor — StateHasChanged()
  • React (JS) — setState()
  • Angular — Change Detection
  • Vue.js — реактивные свойства
  • Avalonia, WinUI, Uno — тот же INotifyPropertyChanged

✅ Понимание OnPropertyChanged даёт тебе понимание того, как работают 90% современных UI-фреймворков.

✅ Разобрался здесь — везде будет легко.

📌 Итог (честно и без воды)

OnPropertyChanged — это:

  • Способ сказать программе: «Данные изменились, обнови экран»
  • Стандартный механизм в WPF, MAUI, Avalonia, WinUI
  • То, без чего привязки работают только один раз
  • То, что спасает от ручного обновления каждого элемента

Если ты понял это — ты понял 90% того, как работают современные приложения с данными.

Остальное — детали реализации.

🚀 Что делать сейчас?

  1. Сохрани этот HTML-файл (Ctrl+S)
  2. Открой Visual Studio
  3. Создай новый WPF или MAUI проект
  4. Скопируй класс ViewModelBase из слайда 19
  5. Создай свой класс, унаследуй от ViewModelBase
  6. Добавь свойства через SetProperty
  7. Привяжи через DataContext
  8. Убедись, что экран обновляется

Зачем? Чтобы закрепить на практике. Без практики теория забывается за 2 дня.

🤝 Если что-то пошло не так

Самые частые проблемы:

  • Экран не обновляется → проверь, что класс наследует INotifyPropertyChanged
  • Экран не обновляется → проверь, что в setter вызван OnPropertyChanged
  • Экран не обновляется → проверь, что DataContext назначен
  • Привязка вообще не работает → проверь, нет ли опечаток в имени свойства в XAML

✅ Поставь breakpoint в OnPropertyChanged. Если он срабатывает, но экран не обновляется — проблема в привязке, а не в OnPropertyChanged.

✅ Смотри в окно Output (Вывод) — там WPF пишет ошибки привязок.

🏁 Конец

🎉 Ты дочитал до конца.

Теперь ты знаешь о OnPropertyChanged больше, чем 80% начинающих.

Если остались вопросы — задавай их конкретно.

Сохрани этот файл. Открывай, когда забудешь.

👇 Ctrl+S прямо сейчас.