1 / 0

🧩 OnPropertyChanged

В MVP и MVVM — от создания проекта до работающего кода

Что ты научишься делать:

  • Создавать проект WPF в Visual Studio
  • Реализовывать MVP (без OnPropertyChanged)
  • Реализовывать MVVM (с OnPropertyChanged)
  • Понимать, куда вставлять каждый кусок кода

➡️ Нажимай стрелку вправо, чтобы начать

📁 Шаг 1: Создаём проект WPF

1. Запусти Visual Studio

2. Нажми «Создать новый проект»

3. В поиске напиши WPF

4. Выбери «WPF Application» (или «WPF приложение»)

5. Назови проект: DemoMVP_MVVM

6. Выбери папку (куда сохранить)

7. Нажми «Создать»

✅ Visual Studio создаст:

  • MainWindow.xaml — разметка окна
  • MainWindow.xaml.cs — код окна
  • App.xaml и App.xaml.cs — точка входа

📂 Шаг 2: Понимаем структуру проекта

В Обозревателе решений (Solution Explorer) — обычно справа или слева.

DemoMVP_MVVM/
├── Properties/
├── References/
├── App.xaml
├── App.xaml.cs
├── MainWindow.xaml      👈 ГЛАВНОЕ ОКНО (дизайн)
├── MainWindow.xaml.cs   👈 КОД ОКНА (логика, но мы её вынесем)

Что мы сделаем: Добавим новые файлы (классы) для MVP и MVVM.

Нажимай правой кнопкой на проект → Add → Class (Добавить → Класс).

🎯 Шаг 3: Что будет делать программа

Простое окно:

  • Поле ввода (TextBox) — пользователь печатает имя
  • Текст (TextBlock) — показывает это же имя
  • Кнопка — меняет имя на «Анна»

Задача: Когда данные меняются (печать в поле или нажатие кнопки), текст должен обновляться мгновенно.

Мы сделаем два варианта: MVP и MVVM.

🖼️ Шаг 4: Рисуем окно (XAML)

Куда вставлять? Открой MainWindow.xaml (двойной клик).

Замени ВСЁ содержимое на этот код:

<Window x:Class="DemoMVP_MVVM.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MVP и MVVM демо" Height="300" Width="400">

    <StackPanel Margin="20">
        <TextBox x:Name="InputTextBox" 
                 Margin="0,5" Padding="5" />
        
        <TextBlock x:Name="DisplayTextBlock" 
                   Margin="0,10" FontWeight="Bold" FontSize="16"/>
        
        <Button x:Name="ChangeButton" 
                Content="Установить 'Анна'" 
                Margin="0,15" Padding="10" Height="40"/>
    </StackPanel>
</Window>

✅ Теперь у нас есть окно с полем ввода, текстом и кнопкой. Но пока ничего не работает.

👑 MVP: Создаём файлы

Что нужно создать (3 файла):

  1. IUserView.cs — интерфейс (что умеет View)
  2. UserModel.cs — данные
  3. UserPresenter.cs — логика

Как создать каждый файл:

Правой кнопкой по проекту → Add → Class → введи имя → Enter

⚠️ Важно: Все файлы создаются в одном проекте, не создавай новые папки (пока).

📄 MVP: IUserView.cs

Куда вставлять? Открой только что созданный файл IUserView.cs и ЗАМЕНИ всё содержимое.

using System;

namespace DemoMVP_MVVM
{
    // Интерфейс — описывает, что View умеет делать
    public interface IUserView
    {
        string InputName { get; set; }      // что напечатал пользователь
        string DisplayName { get; set; }    // что показывать на экране
    }
}

💡 Зачем? Presenter будет работать с View через этот интерфейс, а не напрямую.

📄 MVP: UserModel.cs

Куда вставлять? Открой UserModel.cs и ЗАМЕНИ содержимое.

namespace DemoMVP_MVVM
{
    // Модель — хранит данные и бизнес-логику
    public class UserModel
    {
        private string _name = "Иван";
        
        public string GetName()
        {
            return _name;
        }
        
        public void SetName(string newName)
        {
            if (!string.IsNullOrWhiteSpace(newName))
                _name = newName;
        }
    }
}

💡 Модель ничего не знает ни о View, ни о Presenter'е. Просто данные.

📄 MVP: UserPresenter.cs

namespace DemoMVP_MVVM
{
    public class UserPresenter
    {
        private IUserView _view;
        private UserModel _model;

        public UserPresenter(IUserView view)
        {
            _view = view;
            _model = new UserModel();
            
            // Загружаем начальные данные в View
            _view.DisplayName = _model.GetName();
        }

        // Вызывается, когда пользователь печатает в поле
        public void OnInputChanged()
        {
            _view.DisplayName = _view.InputName;
        }

        // Вызывается, когда нажата кнопка
        public void OnSetDefault()
        {
            _model.SetName("Анна");
            _view.InputName = "Анна";
            _view.DisplayName = "Анна";
        }
    }
}

🔌 MVP: Подключаем Presenter к MainWindow.xaml.cs

Куда вставлять? Открой MainWindow.xaml.cs и ЗАМЕНИ содержимое.

using System.Windows;

namespace DemoMVP_MVVM
{
    public partial class MainWindow : Window, IUserView
    {
        private UserPresenter _presenter;

        // Реализация интерфейса IUserView
        public string InputName
        {
            get => InputTextBox.Text;
            set => InputTextBox.Text = value;
        }

        public string DisplayName
        {
            get => DisplayTextBlock.Text;
            set => DisplayTextBlock.Text = value;
        }

        public MainWindow()
        {
            InitializeComponent();
            
            // Создаём презентера и передаём ему View (this)
            _presenter = new UserPresenter(this);
            
            // Подписываемся на события
            InputTextBox.TextChanged += (s, e) => _presenter.OnInputChanged();
            ChangeButton.Click += (s, e) => _presenter.OnSetDefault();
        }
    }
}

✅ MVP готов! Запусти (F5). OnPropertyChanged НЕ ИСПОЛЬЗОВАЛСЯ.

🧹 MVVM: Начинаем с чистого листа

Вариант 1 (рекомендуется): Создай НОВЫЙ проект с именем DemoMVVM (повтори шаги из слайда 2).

Вариант 2: Удали старые файлы и оставь только MainWindow.xaml (разметка из слайда 5).

💡 MVVM требует OnPropertyChanged. Сейчас мы его реализуем.

📁 MVVM: Создаём файлы

Нужно создать 2 файла:

  1. ViewModelBase.cs — базовый класс с OnPropertyChanged
  2. UserViewModel.cs — ViewModel с данными

Как создать: Правой кнопкой по проекту → Add → Class → введи имя → Enter.

📄 MVVM: ViewModelBase.cs

Этот файл — главный. Именно здесь живёт OnPropertyChanged.

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

namespace DemoMVVM
{
    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;
        }
    }
}

⚠️ Без ViewModelBase MVVM работать не будет!

📄 MVVM: UserViewModel.cs

Класс с данными. Наследуется от ViewModelBase.

namespace DemoMVVM
{
    public class UserViewModel : ViewModelBase
    {
        private string _inputName = "";
        private string _displayName = "Иван";

        // Свойство для поля ввода
        public string InputName
        {
            get => _inputName;
            set
            {
                if (SetProperty(ref _inputName, value))
                {
                    // Когда изменился ввод, обновляем отображение
                    DisplayName = value;
                }
            }
        }

        // Свойство для отображения
        public string DisplayName
        {
            get => _displayName;
            set => SetProperty(ref _displayName, value);
        }

        // Команда для кнопки
        public void SetDefaultName()
        {
            InputName = "Анна";
            DisplayName = "Анна";
        }
    }
}

💡 SetProperty автоматически вызывает OnPropertyChanged. Кода стало меньше!

🖼️ MVVM: MainWindow.xaml (с привязками)

Открой MainWindow.xaml и ЗАМЕНИ на этот код:

<Window x:Class="DemoMVVM.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MVVM с OnPropertyChanged" Height="300" Width="400">

    <StackPanel Margin="20">
        <TextBox Text="{Binding InputName, UpdateSourceTrigger=PropertyChanged}" 
                 Margin="0,5" Padding="5"/>
        
        <TextBlock Text="{Binding DisplayName}" 
                   Margin="0,10" FontWeight="Bold" FontSize="16"/>
        
        <Button Content="Установить 'Анна'" 
                Click="SetDefaultButton_Click"
                Margin="0,15" Padding="10" Height="40"/>
    </StackPanel>
</Window>

🔗 Binding — это верёвочка между View и ViewModel.

UpdateSourceTrigger=PropertyChanged — обновлять при каждом нажатии клавиши.

🔌 MVVM: MainWindow.xaml.cs

Замени содержимое файла:

using System.Windows;

namespace DemoMVVM
{
    public partial class MainWindow : Window
    {
        private UserViewModel _viewModel;

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

        private void SetDefaultButton_Click(object sender, RoutedEventArgs e)
        {
            _viewModel.SetDefaultName();
        }
    }
}

✅ MVVM готов! Запусти (F5). При печати в поле — текст обновляется сам. OnPropertyChanged работает за кулисами.

🔍 Где именно OnPropertyChanged?

MVP

OnPropertyChanged: ❌ НЕТ

Presenter сам вызывает методы View.

_view.DisplayName = "Анна";

MVVM

OnPropertyChanged: ✅ ДА

Живёт в ViewModelBase.cs:

SetProperty(ref _displayName, value);
// внутри -> OnPropertyChanged()

В MVVM строка DisplayName = value вызывает SetProperty, который вызывает OnPropertyChanged, который говорит WPF: «Обнови экран!»

⚖️ Сравнение: как обновляется экран

MVP (без OnPropertyChanged)

// В MainWindow.xaml.cs
InputTextBox.TextChanged += (s, e) => 
    _presenter.OnInputChanged();

// В UserPresenter.cs
public void OnInputChanged()
{
    _view.DisplayName = _view.InputName;
}

// В MainWindow.xaml.cs (реализация)
public string DisplayName
{
    set => DisplayTextBlock.Text = value;
}

👉 Прямые вызовы. Много кода.

MVVM (с OnPropertyChanged)

// В MainWindow.xaml
<TextBox Text="{Binding InputName}" />
<TextBlock Text="{Binding DisplayName}" />

// В UserViewModel.cs
public string InputName
{
    set
    {
        SetProperty(ref _inputName, value);
        DisplayName = value; // OnPropertyChanged сам
    }
}

👉 Привязки. Меньше кода.

⚠️ Ошибки новичков в MVVM

  • Забыли : ViewModelBase → нет OnPropertyChanged
  • Забыли DataContext = _viewModel → привязки не знают, откуда брать данные
  • Опечатка в имени свойства в {Binding} → привязка не работает
  • Не вызвали SetProperty или OnPropertyChanged → экран не обновится

✅ Если экран не обновляется — проверь ViewModelBase и DataContext.

📋 Шпаргалка: какие файлы за что отвечают

MVP

  • IUserView.cs — интерфейс View
  • UserModel.cs — данные
  • UserPresenter.cs — логика
  • MainWindow.xaml.cs — реализует IUserView

MVVM

  • ViewModelBase.cs — OnPropertyChanged (один раз создаёшь)
  • UserViewModel.cs — данные + логика
  • MainWindow.xaml — привязки {Binding}
  • MainWindow.xaml.cs — только DataContext

🎯 Когда использовать MVP, а когда MVVM?

MVP

  • ✅ WinForms
  • ✅ Android (классический)
  • ✅ Когда нужен полный контроль
  • ✅ Маленькие проекты

MVVM

  • ✅ WPF, MAUI, Avalonia
  • ✅ Android + DataBinding
  • ✅ Когда важна автоматическая привязка
  • ✅ Большие проекты с XAML

Простое правило: Работаешь с XAML (WPF/MAUI) → используй MVVM и OnPropertyChanged. WinForms → MVP.

🏁 Итог

Что ты теперь умеешь:

  • Создавать проект WPF с нуля
  • Реализовывать MVP без OnPropertyChanged
  • Реализовывать MVVM с OnPropertyChanged
  • Понимать, куда вставлять каждый файл
  • Отвечать на вопрос «А где OnPropertyChanged?»

💾 Сохрани этот HTML-файл (Ctrl+S). Открывай, когда нужно вспомнить, как создать проект или что куда вставлять.

🎉 Теперь ты готов объяснить это студентам!