W świecie rozwoju oprogramowania wzorce projektowe odgrywają kluczową rolę w tworzeniu aplikacji, które są zarówno skalowalne, jak i łatwe w utrzymaniu. MVVM (Model-View-ViewModel) to jeden z takich wzorców, szczególnie popularny w aplikacjach z graficznym interfejsem użytkownika (GUI), zwłaszcza w technologii Windows Presentation Foundation (WPF).
W tym artykule omówimy szczegółowo, na czym polega MVVM, jak go zaimplementować w aplikacjach WPF oraz zaproponujemy strukturę folderów dla projektu C# WPF zgodnego z tym wzorcem.
Czym jest MVVM?
MVVM (Model-View-ViewModel) to wzorzec projektowy, który służy do oddzielenia logiki biznesowej i logiki prezentacji od interfejsu użytkownika. Głównym celem jest zwiększenie modularności, reużywalności kodu oraz ułatwienie testowania aplikacji.
Wzorzec MVVM został wprowadzony przez Johna Gossa w Microsoft w celu usprawnienia tworzenia interfejsów użytkownika w WPF. Bazuje on na wcześniejszych wzorcach, takich jak MVC (Model-View-Controller) i MVP (Model-View-Presenter), dostosowując je do specyfiki WPF i mechanizmu data binding.
Komponenty MVVM
Model
Model reprezentuje dane aplikacji oraz jej logikę biznesową. Jest niezależny od interfejsu użytkownika i nie zawiera żadnych informacji o prezentacji danych.
Funkcje
- Przechowywanie danych aplikacji.
- Implementacja logiki biznesowej.
- Obsługa operacji na bazach danych, usługach sieciowych itp.
public class Produkt
{
public int Id { get; set; }
public string Nazwa { get; set; }
public decimal Cena { get; set; }
}View
View to warstwa odpowiedzialna za prezentację danych użytkownikowi. W WPF są to pliki XAML definiujące interfejs użytkownika.
Funkcje
- Wyświetlanie danych dostarczonych przez
ViewModel. - Obsługa interakcji użytkownika (kliknięcia, wpisywanie tekstu).
<Window x:Class="Aplikacja.Views.ProduktView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
Title="Produkt">
<Grid>
<TextBox Text="{Binding Nazwa}" />
<TextBox Text="{Binding Cena}" />
<Button Content="Zapisz" Command="{Binding ZapiszCommand}" />
</Grid>
</Window>ViewModel
ViewModel działa jako pośrednik między Model a View. Zawiera logikę prezentacji i wykorzystuje mechanizmy data binding do komunikacji z widokiem.
Funkcje
- Udostępnianie danych z
ModeldoView. - Obsługa logiki prezentacji (np. walidacja danych).
- Implementacja komend (ICommand) do obsługi zdarzeń.
public class ProduktViewModel : INotifyPropertyChanged
{
private Produkt _produkt;
public string Nazwa
{
get { return _produkt.Nazwa; }
set
{
_produkt.Nazwa = value;
OnPropertyChanged(nameof(Nazwa));
}
}
public decimal Cena
{
get { return _produkt.Cena; }
set
{
_produkt.Cena = value;
OnPropertyChanged(nameof(Cena));
}
}
public ICommand ZapiszCommand { get; }
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string nazwaWłasności)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nazwaWłasności));
}
public ProduktViewModel()
{
_produkt = new Produkt();
ZapiszCommand = new RelayCommand(Zapisz);
}
private void Zapisz(object obj)
{
// Logika zapisu produktu
}
}MVVM w WPF
Data Binding
Data binding to mechanizm, który automatycznie synchronizuje dane między View i ViewModel. Umożliwia to oddzielenie logiki prezentacji od interfejsu użytkownika.
<TextBox Text="{Binding Nazwa, UpdateSourceTrigger=PropertyChanged}" />Komendy (ICommand)
ICommand to interfejs w .NET, który służy do obsługi poleceń, czyli akcji, które można wywołać w aplikacji. Jest on szeroko wykorzystywany w wzorcu MVVM (Model-View-ViewModel), szczególnie w aplikacjach WPF, Xamarin, czy MAUI.
Komendy pozwalają na obsługę zdarzeń bezpośrednio w ViewModel, zamiast w kodzie za strony View.
public class RelayCommand : ICommand
{
private Action<object> _execute;
private Predicate<object> _canExecute;
public event EventHandler CanExecuteChanged;
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);
}
}Powiadomienia (INotifyPropertyChanged)
INotifyPropertyChanged to interfejs w .NET, który umożliwia powiadamianie o zmianach właściwości w obiektach, co jest szczególnie przydatne w aplikacjach wykorzystujących wzorce MVVM (Model-View-ViewModel), takich jak WPF czy Xamarin.
Interfejs INotifyPropertyChanged umożliwia powiadamianie View o zmianach w ViewModel.
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string nazwaWłasności)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nazwaWłasności));
}Implementacja MVVM w WPF
- Utwórz Model: Definiuj klasy reprezentujące dane aplikacji.
- Utwórz ViewModel: Implementuj logikę prezentacji, komendy oraz powiadomienia o zmianach.
- Utwórz View: Zdefiniuj interfejs użytkownika w XAML, wiążąc kontrolki z właściwościami
ViewModel. - Ustaw DataContext: W
ViewustawDataContextna instancję odpowiedniegoViewModel.
public partial class ProduktView : Window
{
public ProduktView()
{
InitializeComponent();
this.DataContext = new ProduktViewModel();
}
}Struktura Folderów dla Projektu C# WPF
Aby utrzymać porządek i czytelność w projekcie, warto zorganizować pliki w logiczne foldery. Poniżej proponowana struktura:
/Aplikacja |-- /Models | |-- Produkt.cs | |-- Klient.cs | |-- /ViewModels | |-- ProduktViewModel.cs | |-- KlientViewModel.cs | |-- /Views | |-- ProduktView.xaml | |-- ProduktView.xaml.cs | |-- KlientView.xaml | |-- KlientView.xaml.cs | |-- /Commands | |-- RelayCommand.cs | |-- /Services | |-- IDataService.cs | |-- DataService.cs | |-- /Resources | |-- Styles.xaml | |-- /App.xaml |-- /MainWindow.xaml |-- /MainWindow.xaml.cs
Opis Folderów
- Models: Zawiera klasy modeli danych.
- ViewModels: Zawiera klasy
ViewModel. - Views: Zawiera pliki XAML oraz ich code-behind.
- Commands: Zawiera klasy implementujące interfejs
ICommand. - Services: Zawiera klasy usług, np. do komunikacji z bazą danych.
- Resources: Zawiera zasoby wspólne dla całej aplikacji, takie jak style.
MVVM w słowach
Zalety Stosowania MVVM
- Rozdzielenie logiki: Ułatwia utrzymanie i rozwój aplikacji.
- Testowalność:
ViewModeliModelmożna testować niezależnie od interfejsu użytkownika. - Reużywalność kodu: Logika może być wykorzystywana w różnych widokach.
- Czysty kod: Mniej kodu w code-behind
View, więcej w testowalnymViewModel.
Wady i Wyzwania
- Złożoność: Może być nadmiernie skomplikowany dla prostych aplikacji.
- Krzywa uczenia się: Wymaga zrozumienia mechanizmów WPF, takich jak data binding i komendy.
- Debugowanie: Trudniejsze w przypadku błędów w wiązaniach danych.
Najlepsze Praktyki
- Używaj Frameworków MVVM: Takich jak Prism czy MVVM Light, aby ułatwić implementację.
- Unikaj kodu w code-behind: Staraj się umieszczać całą logikę w
ViewModel. - Implementuj interfejsy powiadamiające:
INotifyPropertyChanged,INotifyCollectionChanged. - Stosuj Dependency Injection: Ułatwia testowanie i zarządzanie zależnościami.
Narzędzia Wspierające MVVM
- Prism: Framework ułatwiający implementację wzorca MVVM w WPF.
- MVVM Light Toolkit: Lekki framework dostarczający podstawowych funkcji MVVM.
- ReactiveUI: Umożliwia programowanie reaktywne w aplikacjach WPF.
Podsumowanie
Wzorzec MVVM jest potężnym narzędziem w tworzeniu aplikacji WPF, pozwalającym na czyste oddzielenie logiki biznesowej od interfejsu użytkownika. Poprzez zrozumienie i właściwe zastosowanie jego zasad, możemy tworzyć aplikacje, które są bardziej skalowalne, łatwiejsze w utrzymaniu i testowaniu.
Bibliografia
- Dokumentacja Microsoft: Wzorzec MVVM
- John Gossman, „Introduction to Model/View/ViewModel pattern for building WPF apps”, 2005
Obraz Artur Shamsutdinov z Pixabay






