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
Model
doView
. - 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
View
ustawDataContext
na 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ść:
ViewModel
iModel
moż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