📝 Практическое задание

Тема: OnPropertyChanged, реализация MVP и MVVM

Цель: научиться реализовывать паттерны MVP и MVVM, понять разницу и освоить механизм уведомлений OnPropertyChanged.

🛠️ Подготовка к работе

0
Создание проекта

Что нужно сделать:

  1. Запустить Visual Studio
  2. Создать новый проект: WPF Application (.NET 8.0 или .NET 9.0)
  3. Назвать проект: Lesson_MVP_MVVM
  4. Дождаться создания проекта

✅ Чек-лист:

  • В обозревателе решений есть MainWindow.xaml и MainWindow.xaml.cs
  • Проект компилируется (F5) и показывает пустое окно

🔥 Часть 1. Разминка

1
Разметка окна

Задача: Создай интерфейс программы.

Открой MainWindow.xaml и замени содержимое на код ниже:

<Window x:Class="Lesson_MVP_MVVM.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Практика MVP/MVVM" Height="350" Width="450">

    <StackPanel Margin="20">
        <Label Content="Введите имя:" FontWeight="Bold"/>
        <TextBox x:Name="InputTextBox" Margin="0,5" Padding="8"/>
        
        <Label Content="Отображаемое имя:" FontWeight="Bold" Margin="0,15,0,0"/>
        <TextBlock x:Name="DisplayTextBlock" Margin="0,5" Padding="8" 
                   Background="#F0F0F0" FontSize="14" Height="35"/>
        
        <Button x:Name="SetDefaultButton" Content="Установить 'Анна'" 
                Margin="0,20" Padding="10" Height="40"/>
    </StackPanel>
</Window>

✏️ Твоё задание:

  • Скопируй код в MainWindow.xaml
  • Запусти проект (F5) — должно открыться окно с полем ввода, текстом и кнопкой
  • Пока ничего не работает — это нормально

👑 Часть 2. Реализация MVP

MVP Без OnPropertyChanged. Presenter сам обновляет View.

2.1
Создание интерфейса IUserView

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

namespace Lesson_MVP_MVVM
{
    public interface IUserView
    {
        string InputName { get; set; }
        string DisplayName { get; set; }
    }
}

✏️ Задание: Создай файл и вставь код. Интерфейс описывает, что умеет View.

2.2
Создание модели UserModel

Где создать: Add → Class → UserModel.cs

namespace Lesson_MVP_MVVM
{
    public class UserModel
    {
        private string _name = "Иван";
        
        public string GetName() => _name;
        
        public void SetName(string newName)
        {
            if (!string.IsNullOrWhiteSpace(newName))
                _name = newName;
        }
    }
}

✏️ Задание: Создай модель. Она только хранит данные, ничего не знает о View.

2.3
Создание презентера UserPresenter

Где создать: Add → Class → UserPresenter.cs

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

        public UserPresenter(IUserView view)
        {
            _view = view;
            _model = new UserModel();
            _view.DisplayName = _model.GetName();
        }

        public void OnInputChanged()
        {
            _view.DisplayName = _view.InputName;
        }

        public void OnSetDefault()
        {
            _model.SetName("Анна");
            _view.InputName = "Анна";
            _view.DisplayName = "Анна";
        }
    }
}

✏️ Задание: Создай презентер. Он управляет логикой и обновляет View.

2.4
Подключение презентера к MainWindow

Открой MainWindow.xaml.cs и замени содержимое:

using System.Windows;

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

        public string InputName
        {
            get => InputTextBox.Text;
            set => InputTextBox.Text = value;
        }

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

        public MainWindow()
        {
            InitializeComponent();
            _presenter = new UserPresenter(this);
            
            InputTextBox.TextChanged += (s, e) => _presenter.OnInputChanged();
            SetDefaultButton.Click += (s, e) => _presenter.OnSetDefault();
        }
    }
}

✅ Проверь себя (MVP):

  • Запусти программу (F5)
  • Напечатай что-нибудь в поле ввода — текст снизу меняется?
  • Нажми кнопку — установится "Анна"?
  • Вопрос: Где в коде вызывается OnPropertyChanged? (Ответ: нигде — его нет, это MVP)

💡 Часть 3. Реализация MVVM

MVVM С OnPropertyChanged. ViewModel не знает о View, только уведомляет через событие.

3.1
Создание ViewModelBase

Создай новый проект (или очисти текущий от MVP-файлов).
Название: Lesson_MVVM
Или сделай копию проекта и удали IUserView, UserModel, UserPresenter.

Создай файл ViewModelBase.cs:

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

namespace Lesson_MVVM
{
    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. ВАЖНО Именно здесь живёт OnPropertyChanged.

3.2
Создание UserViewModel

Создай файл UserViewModel.cs:

namespace Lesson_MVVM
{
    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 = "Анна";
        }
    }
}

✏️ Задание: Создай ViewModel. Обрати внимание — нет никаких ссылок на View.

3.3
Разметка XAML с привязками (Binding)

Открой MainWindow.xaml и замени содержимое:

<Window x:Class="Lesson_MVVM.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MVVM практика" Height="350" Width="450">

    <StackPanel Margin="20">
        <Label Content="Введите имя:" FontWeight="Bold"/>
        <TextBox Text="{Binding InputName, UpdateSourceTrigger=PropertyChanged}" 
                 Margin="0,5" Padding="8"/>
        
        <Label Content="Отображаемое имя:" FontWeight="Bold" Margin="0,15,0,0"/>
        <TextBlock Text="{Binding DisplayName}" 
                   Margin="0,5" Padding="8" 
                   Background="#F0F0F0" FontSize="14" Height="35"/>
        
        <Button Content="Установить 'Анна'" 
                Click="SetDefaultButton_Click"
                Margin="0,20" Padding="10" Height="40"/>
    </StackPanel>
</Window>

✏️ Задание: Замени XAML. Обрати внимание на {Binding ...} — это привязки.

3.4
Подключение ViewModel к окну

Открой MainWindow.xaml.cs и замени содержимое:

using System.Windows;

namespace Lesson_MVVM
{
    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? (В SetProperty внутри ViewModelBase)

🔍 Часть 4. Сравнение MVP и MVVM

4
Заполни таблицу

Скопируй и заполни пропуски:

┌─────────────────────┬─────────────────────┬─────────────────────┐
│                     │        MVP          │        MVVM         │
├─────────────────────┼─────────────────────┼─────────────────────┤
│ Используется        │       ______         │       ______         │
│ OnPropertyChanged?  │                     │                     │
├─────────────────────┼─────────────────────┼─────────────────────┤
│ Кто обновляет View? │       ______         │   View сама через   │
│                     │                     │      Binding        │
├─────────────────────┼─────────────────────┼─────────────────────┤
│ Знает ли Presenter/ │       ______         │       НЕТ           │
│ ViewModel о View?   │                     │                     │
├─────────────────────┼─────────────────────┼─────────────────────┤
│ Где хранится        │  В IUserView и       │  В XAML через       │
│ логика обновления?  │  UserPresenter       │  {Binding}          │
└─────────────────────┴─────────────────────┴─────────────────────┘

❓ Вопрос для размышления: Почему в MVVM нельзя написать textBox.Text = "значение"?

Ответ: ___________________________________________

🏆 Часть 5. Самостоятельное задание

5
Добавь новое свойство "Возраст"

Задача: Расширь MVVM-проект — добавь поле для ввода возраста и его отображение.

✏️ Что нужно сделать:

  1. Добавить в UserViewModel свойства InputAge и DisplayAge
  2. Добавить в XAML два новых элемента: TextBox для ввода возраста и TextBlock для отображения
  3. Настроить привязки ({Binding InputAge} и {Binding DisplayAge})
  4. При изменении InputAge — автоматически обновлять DisplayAge
  5. Добавить в метод SetDefaultName() сброс возраста на 25

Критерии выполнения:

  • Возраст отображается в отдельном TextBlock
  • При печати возраста в поле — отображение меняется
  • После нажатия кнопки возраст становится 25
  • Код использует SetProperty и OnPropertyChanged

📝 Часть 6. Контрольные вопросы

Вопрос 1. Что произойдёт, если в MVVM-классе не вызывать OnPropertyChanged?

Ответ: ___________________________________________

Вопрос 2. Зачем нужен [CallerMemberName] в методе OnPropertyChanged?

Ответ: ___________________________________________

Вопрос 3. Какую роль играет DataContext в MVVM?

Ответ: ___________________________________________

Вопрос 4. Почему в MVP не нужен OnPropertyChanged?

Ответ: ___________________________________________

Вопрос 5. Что делает метод SetProperty в ViewModelBase?

Ответ: ___________________________________________

🏠 Домашнее задание

ДЗ
Список студентов (MVVM + коллекция)

Задача: Создать приложение для отображения списка студентов.

Требования:

  • Использовать MVVM и OnPropertyChanged
  • Модель: Student (Id, Name, Age, Group)
  • ViewModel: ObservableCollection<Student> Students
  • View: ListBox или DataGrid для отображения списка
  • Кнопка "Добавить" — открывает диалог или добавляет тестового студента
  • Кнопка "Удалить" — удаляет выбранного студента
  • Кнопка "Очистить" — очищает список

📌 Подсказка: Для коллекций используй ObservableCollection<T> — она сама уведомляет UI об изменениях.

⭐ Бонус (+1 балл): Добавить сохранение и загрузку списка в JSON-файл.


📊 Критерии оценки

Раздел Баллы
Часть 1-2 (MVP рабочий)3 балла
Часть 3-4 (MVVM рабочий)4 балла
Часть 5 (Самостоятельное задание)3 балла
Часть 6 (Контрольные вопросы)2 балла
Домашнее задание4 балла
Максимум16 баллов

Проходной балл: 10 баллов

📌 Сохрани этот HTML-файл — он понадобится на следующем занятии.
Вопросы по заданию можно задать преподавателю.