Dzisiaj na warsztat bierzemy MVVM (Model-View-ViewModel) – jeden z najpopularniejszych wzorców projektowych, używany głównie w aplikacjach desktopowych (WPF), mobilnych (Xamarin, .NET MAUI), a nawet w niektórych webowych frameworkach Blazor (w pewnym zakresie). Skupimy się tutaj na przykładzie WPF w C#, żebyś zrozumiał(a) sedno MVVM i mógł szybko zacząć tworzyć własne aplikacje.
Czym jest MVVM?
MVVM to wzorzec projektowy (ang. Model-View-ViewModel), który ma na celu:
• Oddzielenie logiki biznesowej (Model) od warstwy prezentacji (View).
• Ułatwienie testowania i rozwoju aplikacji.
• Usprawnienie zarządzania stanem i danymi aplikacji w sposób bardziej strukturalny niż np. w prostym code-behind.
Kluczowe elementy MVVM:
1. Model – reprezentuje dane i logikę biznesową (np. obiekty z bazy danych, operacje, walidacje).
2. View – odpowiada za prezentację (np. okno WPF lub strona XAML w .NET MAUI).
3. ViewModel – "most" między View a Modelem, zarządza logiką prezentacji i zapewnia mechanizmy wiązania danych (binding).
W WPF MVVM wspierany jest przez:
• Data Binding – wiązania obiektów C# z elementami UI.
• INotifyPropertyChanged – interfejs informujący widok o zmianach w modelu.
• Commands – polecenia zamiast zdarzeń (Click itp.) w "code-behind".
Przykładowa aplikacja WPF z MVVM
Pokażę Ci prosty przykład aplikacji WPF, w której możemy edytować dane o osobach (imiona, nazwiska) i zobaczyć, jak działa natychmiastowe odświeżanie w widoku.
1. Model
W katalogu Models utwórzmy klasę Person.cs:
namespace MyMvvmApp.Models
{
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
}
Tu nie dzieje się wiele – mamy po prostu dane, czyli nasz "Model". Gdybyśmy korzystali z bazy danych czy API, to w tym miejscu moglibyśmy zaimplementować logikę dostępu do danych.
2. ViewModel
W katalogu ViewModels utwórzmy klasę MainViewModel.cs, która:
• Będzie przechowywać obiekt Person.
• Zaimplementuje interfejs INotifyPropertyChanged po to, by View (XAML) wiedział o wszelkich zmianach w danych.
• Zawiera proste polecenie (komendę) do np. "zaktualizowania" danych lub wyświetlenia komunikatu.
using MyMvvmApp.Models;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows.Input;
namespace MyMvvmApp.ViewModels
{
public class MainViewModel : INotifyPropertyChanged
{
private Person _currentPerson;
public Person CurrentPerson
{
get => _currentPerson;
set
{
_currentPerson = value;
OnPropertyChanged();
}
}
// Prostą komendę możemy zaimplementować np. w taki sposób:
public ICommand UpdateCommand { get; set; }
public MainViewModel()
{
// Inicjalizujemy dane
CurrentPerson = new Person
{
FirstName = "Jan",
LastName = "Kowalski"
};
// Przypisanie komendy do delegata
UpdateCommand = new RelayCommand(
execute: _ => UpdatePerson(),
canExecute: _ => true
);
}
private void UpdatePerson()
{
// Przykładowa logika, np. w realnej aplikacji zapiszemy do bazy itp.
// Tutaj wyświetlimy tylko w Debug output:
System.Diagnostics.Debug.WriteLine(
$"Zaktualizowano dane: {CurrentPerson.FirstName} {CurrentPerson.LastName}"
);
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
RelayCommand
W powyższym kodzie używamy klasy RelayCommand, którą musimy jeszcze zdefiniować. Dzięki niej szybko tworzymy komendy w MVVM. Dodaj do projektu plik RelayCommand.cs:
using System;
using System.Windows.Input;
namespace MyMvvmApp.ViewModels
{
public class RelayCommand : ICommand
{
private readonly Action<object> _execute;
private readonly Predicate<object> _canExecute;
public RelayCommand(Action<object> execute, Predicate<object> canExecute = null)
{
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return _canExecute == null || _canExecute(parameter);
}
public void Execute(object parameter)
{
_execute(parameter);
}
public event EventHandler CanExecuteChanged
{
add => CommandManager.RequerySuggested += value;
remove => CommandManager.RequerySuggested -= value;
}
}
}
Ta prosta implementacja ICommand pozwala nam tworzyć w ViewModel komendy, które można zbindować do przycisków w View bez pisania zdarzeń (Click) w code-behind.
3. View (XAML)
Zostajemy w domyślnym pliku MainWindow.xaml (lub tworzymy nowy plik XAML, który będzie naszym głównym widokiem). Najważniejsza jest deklaracja kontekstu danych (DataContext) – zrobimy to w konstruktorze code-behind (pokazując, że staramy się minimalizować logikę w pliku .xaml.cs).
MainWindow.xaml
<Window x:Class="MyMvvmApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:MyMvvmApp"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="MVVM Demo" Height="200" Width="300"
mc:Ignorable="d">
<Grid>
<StackPanel Margin="10">
<TextBlock Text="Imię:" FontWeight="Bold"/>
<TextBox Text="{Binding CurrentPerson.FirstName, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Text="Nazwisko:" FontWeight="Bold" Margin="0,10,0,0"/>
<TextBox Text="{Binding CurrentPerson.LastName, UpdateSourceTrigger=PropertyChanged}" />
<Button Content="Zaktualizuj"
Margin="0,10,0,0"
Command="{Binding UpdateCommand}" />
</StackPanel>
</Grid>
</Window>
W powyższym kodzie:
• TextBox ma przypisane Text="{Binding CurrentPerson.FirstName}". To znaczy, że gdy zmieni się właściwość CurrentPerson.FirstName we ViewModelu, to automatycznie zmieni się tekst w kontrolce (i odwrotnie, jeśli użytkownik coś wpisze, wartość w ViewModelu też się zmieni).
• Button jest powiązany przez Command="{Binding UpdateCommand}". Oznacza to, że kliknięcie w przycisk wywoła metodę UpdatePerson() we ViewModelu.
MainWindow.xaml.cs (code-behind)
using System.Windows;
using MyMvvmApp.ViewModels;
namespace MyMvvmApp
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
// Ustawiamy DataContext
this.DataContext = new MainViewModel();
}
}
}
W code-behind ustawiamy DataContext na nową instancję MainViewModel. Dzięki temu całe wiązanie danych w XAML będzie odnosić się do obiektów i właściwości w MainViewModel.
Jak to wszystko działa?
1. ViewModel – MainViewModel – posiada obiekt CurrentPerson i reaguje na zmiany właściwości dzięki INotifyPropertyChanged.
2. View – MainWindow z XAML – wiąże pola tekstowe do CurrentPerson.FirstName i CurrentPerson.LastName. Gdy użytkownik wprowadza dane, zmienia się obiekt w ViewModelu.
3. Command – RelayCommand – klasa implementująca ICommand, pozwala na powiązanie przycisku z metodą UpdatePerson() w ViewModelu, bez pisania zdarzeń w code-behind.
4. Minimalna logika w code-behind – jedyne, co robimy w pliku .xaml.cs, to ustawiamy DataContext (lub ewentualnie inne rzeczy niezbędne do startu aplikacji).
Rezultat? Czystszy kod, większa testowalność (możesz przetestować sam ViewModel bez interfejsu graficznego), a także klarowny podział obowiązków w aplikacji.
Kilka słów o testach jednostkowych
Dzięki MVVM możesz bez problemu napisać testy jednostkowe, np. do sprawdzenia, czy UpdatePerson() zmienia jakieś inne właściwości ViewModelu, lub czy CanExecute danej komendy zwraca oczekiwane wartości. Nie musisz do tego w ogóle uruchamiać interfejsu graficznego. To właśnie jest jedna z największych zalet MVVM – większa testowalność aplikacji.
Podsumowanie
Wzorzec MVVM to świetny sposób na oddzielenie warstwy prezentacji od logiki biznesowej. Dzięki temu:
• Łatwiej utrzymać i rozwijać aplikację.
• Kod staje się bardziej testowalny.
• Nasza aplikacja jest czytelna dla większego zespołu.
Jeśli chcesz poznać więcej szczegółów na temat tworzenia aplikacji w C#, .NET, WPF czy Blazor, a także opanować inne kluczowe zagadnienia, zapraszam Cię do mojego szkolenia online "Zostań Programistą .NET". Znajdziesz tam nie tylko omówienie fundamentów, ale również praktyczne projekty, dzięki którym nauczysz się efektywnie wykorzystywać możliwości platformy .NET.
Daj znać w komentarzu, czy udało Ci się uruchomić swoją pierwszą aplikację w MVVM!
To wszystkie na dzisiaj. Jeżeli taki artykuł Ci się spodobał, to koniecznie dołącz do mojej społeczności – darmowe zapisy, gdzie będziesz również miał dostęp do dodatkowych materiałów i przede wszystkim bonusów. Do zobaczenia w kolejnym artykule.