Что такое паттерн MVVM в C# и зачем он нужен в WPF?
Все WPF-проекты пишут на MVVM. Я понимаю буквы (Model-View-ViewModel), но не понимаю, что куда класть и зачем эта сложность вместо обычного кода в обработчиках кнопок. Объясните паттерн MVVM на простом примере.
2 ответа
MVVM (Model-View-ViewModel) — архитектурный паттерн, который разделяет приложение на три слоя:
- Model — данные и бизнес-логика (классы сущностей, работа с БД, API);
- View — интерфейс (XAML-разметка, без логики);
- ViewModel — посредник: хранит состояние для View, команды, и общается с Model.
Связь View ↔ ViewModel идёт через Binding, а не через прямые вызовы. View не знает о ViewModel напрямую — она просто привязана к её свойствам.
Пример. ViewModel:
public class MainViewModel : INotifyPropertyChanged
{
private string _name = "";
public string Name
{
get => _name;
set { _name = value; OnPropertyChanged(); }
}
public string Greeting => string.IsNullOrEmpty(Name)
? "Введите имя"
: $"Привет, {Name}!";
public event PropertyChangedEventHandler? PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string? prop = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));
if (prop == nameof(Name)) OnPropertyChanged(nameof(Greeting));
}
}
View (XAML) привязывается к ViewModel:
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<StackPanel Margin="10">
<TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}"/>
<TextBlock Text="{Binding Greeting}" FontSize="20"/>
</StackPanel>
Зачем это нужно:
- Тестируемость — ViewModel это обычный C#-класс без UI, его легко покрыть юнит-тестами;
- Разделение труда — дизайнер правит XAML, программист — ViewModel;
- Чистота — нет «спагетти» из обработчиков, где логика прибита к кнопкам;
- Переиспользование — одну ViewModel можно показать в разных View.
Да, для крошечной утилиты MVVM избыточен. Но на проекте от пары экранов он быстро окупается. Для команд (вместо кликов в код-бихайнде) используют ICommand / RelayCommand.
Дополню про команды, без которых MVVM не полон. Чтобы кнопка не вызывала обработчик в код-бихайнде, а дёргала метод ViewModel, используют ICommand:
public class RelayCommand : ICommand
{
private readonly Action _execute;
private readonly Func<bool>? _canExecute;
public RelayCommand(Action execute, Func<bool>? canExecute = null)
{ _execute = execute; _canExecute = canExecute; }
public bool CanExecute(object? p) => _canExecute?.Invoke() ?? true;
public void Execute(object? p) => _execute();
public event EventHandler? CanExecuteChanged;
}
И привязка в XAML:
<Button Content="Сохранить" Command="{Binding SaveCommand}"/>
Так во View вообще не остаётся Click-обработчиков — вся логика в ViewModel. На практике берут готовый RelayCommand из библиотек вроде CommunityToolkit.Mvvm и не пишут руками.