Перейти к основному содержимому

Практикум WPF — основы MVVM

Разработчику Архитектору

Практикум, шаг 2 из 6. Разделяем данные, логику представления и XAML. Теория паттернов в Особенности разработки десктопных приложений; локальный пример — Первая форма WPF — XAML, стили и шаблоны. Базовые окна без MVVM — Lab — WinForms и WPF.


Три роли MVVM

СлойTaskDeskЗапрещено
ModelTaskItem, статусы, валидация заголовкаСсылки на Button, MessageBox
ViewModelTaskListViewModel, списки, AddTaskCommand, вызов ITaskApiПрямой new HttpClient() в каждой команде без DI
ViewTaskListView.xaml, списки, поляРасчёт бизнес-правил в Click

View не знает о Model напрямую — только о свойствах ViewModel, удобных для отображения (DisplayStatus, IsBusy).


Model — данные и правила

Код ITЗагрузка примера кода…

Model живёт в TaskDesk.Core — его подключают и API, и клиент (DTO можно маппить отдельно).


ViewModel — INotifyPropertyChanged

UI обновляется, когда ViewModel сообщает об изменении свойства:

Код ITЗагрузка примера кода…

ObservableCollection<T> сам уведомляет список об добавлении и удалении — ListBox перерисовывается без ручного Items.Refresh().


Команды — ICommand вместо Click

Код ITЗагрузка примера кода…

В XAML:

<Button Content="Добавить задачу" Command="{Binding AddTaskCommand}"/>

Кнопка автоматически неактивна, когда CanExecute возвращает false (пустой заголовок).


CommunityToolkit.Mvvm — меньше boilerplate

Пакет CommunityToolkit.Mvvm (официальный, MIT) генерирует код через source generators:

dotnet add package CommunityToolkit.Mvvm

Код ITЗагрузка примера кода…

[ObservableProperty] создаёт свойство с уведомлениями; [RelayCommand] — асинхронную команду с CanExecute.


Асинхронность и IsBusy

Сетевые вызовы в TaskDesk не блокируют UI:

Код ITЗагрузка примера кода…

В View индикатор:

<ProgressBar IsIndeterminate="True" Height="4"
Visibility="{Binding IsBusy, Converter={StaticResource BoolToVis}}"/>

Сервисный слой — ITaskRepository

ViewModel не вызывает HTTP напрямую в финальной архитектуре — только интерфейс:

public interface ITaskRepository
{
Task<IReadOnlyList<TaskItem>> GetAllAsync(CancellationToken ct = default);
Task<TaskItem> CreateAsync(TaskItem task, CancellationToken ct = default);
Task UpdateAsync(TaskItem task, CancellationToken ct = default);
Task DeleteAsync(Guid id, CancellationToken ct = default);
}

Реализации:

  • InMemoryTaskRepository — для unit-тестов и офлайн-прототипа;
  • ApiTaskRepositoryHttpClient к TaskDesk.Api (шаг 4).

Так ViewModel тестируется с моком репозитория без окна и без сервера.


View — только привязки

Код ITЗагрузка примера кода…

DataContext задаёт Prism при навигации (шаг 4) или вручную в MainWindow.


Типичные ошибки MVVM

СимптомПричинаРешение
UI не обновляетсяНет INotifyPropertyChangedToolkit или ручные уведомления
Кнопка всегда сераяCanExecute не пересчитываетсяRaiseCanExecuteChanged при изменении полей
Дубликаты в спискеДобавление до ответа APIЖдать CreateAsync, один источник правды
Краш при стартеDataContext nullРегистрация VM в DI, проверка привязок в Output

Чек-лист шага 2

  • Model без ссылок на WPF-сборки.
  • ViewModel с командами и ObservableCollection.
  • View без обработчиков Click с бизнес-логикой.
  • Интерфейс репозитория для будущего API.

Дальше: Сервер на ASP.NET Core Web API.