Aplikacje mobilne · Architektura
Oddzielamy logikę od interfejsu: droga do MVVM w MAUI
Dotąd cała logika siedziała w code-behind strony, wymieszana z obsługą przycisków. To działa w małych przykładach, ale w większym projekcie robi się bałagan. Wydzielisz logikę do osobnej klasy i zobaczysz, jak ta zasada rozwija się w MVVM, standardowy wzorzec .NET MAUI.
- około 50 minut
- .NET MAUI · klasa logiki · MVVM
- INF.04.6.2
Po tej lekcji: wydzielić logikę do klasy niezależnej od strony, zostawić w code-behind tylko „klej” oraz wyjaśnić role Model, View i ViewModel i działanie wiązania danych.
Ta sama funkcja bez zależności od przycisku
Jeżeli zasady programu są zamknięte w kliknięciu, trudno ich użyć na innym ekranie. Wyjmiemy formatowanie do klasy, a dopiero po działającej wersji pokażemy MVVM.
Najpierw uruchomisz mały ekran. Potem połączysz go z kodem i sprawdzisz konkretne wyniki. Dodatki wykonuj dopiero, gdy wersja podstawowa działa. Quiz i oceniane zadania pozostają w biurku po zalogowaniu.
Laboratorium · ćwiczenie w przeglądarce, do którego odsyłają rozgrzewki na końcu lekcji
1 · Zobacz przepływ
Trzy warstwy, trzy zadania
Wpisz dane (celowo z zapasowymi spacjami) i kliknij „Wyślij”. Zobacz, która warstwa kiedy pracuje. Spróbuj zostawić jedno pole puste.
Najpierw przewidź, potem sprawdź: W modelu warstw prześledź dane: pola → metoda logiki → wynik → etykieta. Wskaż, która warstwa powinna znać Entry, a która powinna przyjąć zwykły string.
Widok pokazuje, code-behind przekazuje, logika decyduje. Każdą z trzech części zmienisz osobno. Model pomaga zobaczyć mechanizm; właściwą aplikację uruchomisz z plików poniżej.
1. Przygotuj miejsce pracy
Co dodajesz, a co podmieniasz?
Stronę dodaj przez Projekt → Dodaj → Nowy element → .NET MAUI ContentPage (XAML). Powstaje para .xaml i .xaml.cs. Dla ContentView wybierz szablon ContentView; dla modeli i serwisów zwykłą klasę. Jeśli plik już istnieje, podmień wskazaną zawartość — nie twórz drugiej klasy o tej samej nazwie.
| Plik | Rola w ćwiczeniu |
|---|---|
| Logika/Formater.cs | Logika i obsługa danych |
| MainPage.xaml.cs | Logika i obsługa danych |
| MainPage.xaml | Wygląd strony lub kontrolki |
| FormularzViewModel.cs | Logika i obsługa danych |
| projekt.csproj (fragment) | Fragment w istniejącym pliku |
| MvvmPage.xaml | Wygląd strony lub kontrolki |
Zanim uruchomisz: trzy połączenia
x:Class="MojaAplikacja.NazwaStrony" w XAML musi zgadzać się z namespace MojaAplikacja i nazwą klasy C#. InitializeComponent() tworzy kontrolki opisane w XAML.
x:Name="PoleImie" daje kodowi C# dostęp przez PoleImie.Text. Z kolei Clicked="Zapisz_Clicked" wskazuje metodę o tej nazwie w pliku .xaml.cs. Sam napis przycisku w Text niczego nie podpina.
Gdy dodajesz XAML z obsługą zdarzeń, od razu uzupełnij też odpowiadający C#. Uruchamiaj dopiero po skończeniu oznaczonej pary plików; brakujący handler nie jest błędem środowiska.
2. Uruchom sam wygląd — jeszcze bez logiki
W świeżym projekcie zastąp MainPage.xaml poniższym ekranem. W MainPage.xaml.cs zachowaj konstruktor z InitializeComponent(); usuń szablonowy licznik i OnCounterClicked, bo nie ma ich już w XAML. Na tym etapie App i AppShell pozostają z szablonu. To pełny, tymczasowy XAML — później świadomie go podmienisz, nie dokleisz do drugiego ContentPage.
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MojaAplikacja.MainPage">
<VerticalStackLayout Padding="24" Spacing="14">
<Label Text="Oddzielamy logikę" FontSize="24" />
<Entry Placeholder="Imię" />
<Entry Placeholder="Nazwisko" />
</VerticalStackLayout>
</ContentPage>
- Pierwsza próba Uruchom. Zobaczysz kontrolki, ale przyciski bez Clicked jeszcze nic nie robią. Sprawdź rozmiar i kolejność elementów. Zatrzymaj aplikację przed następnym etapem.
Zacznijmy od telefonu
Gdy wszystko jest w jednej metodzie
Formularz „Imię + Nazwisko → Wyślij → wynik” na telefonie. Gdy cała logika łączenia napisów leży w metodzie kliknięcia, trudno znaleźć, gdzie zmienić regułę, a gdzie wygląd, i da się ją sprawdzić tylko po uruchomieniu aplikacji.
Zasada: **logika (obliczenia, reguły, formatowanie) żyje w osobnej klasie, która nie wie nic o stronach, kontrolkach ani DisplayAlert**. Przyjmuje dane i zwraca wynik.
5 · Krok dalej
MVVM: ta sama idea w pełnym wzorcu
MVVM to standardowy wzorzec w .NET MAUI (i WPF). Nie jest wymagany w pełnej formie na egzaminie, ale zasada oddzielenia logiki od interfejsu jest oceniana jako dobra praktyka.
Modeldane i reguły
Czyste dane i reguły biznesowe, bez wiedzy o stronie. Tu odpowiada mu klasa Formater.
Na przykład klasa Produkt z nazwą i ceną
Viewwygląd
Plik XAML (ContentPage) bez logiki.
Na przykład MvvmPage.xaml
ViewModelpośrednik
Udostępnia dane w formie gotowej do wyświetlenia (wiązanie danych, ustawiane jako BindingContext) i komendy zamiast Clicked.
Na przykład FormularzViewModel
3. Wskaż stronę startową ćwiczenia
Po dodaniu docelowych stron podmień App.xaml.cs na poniższy. App.xaml zostaje z szablonu. Od tej chwili startuje MainPage, a nie wcześniejszy tymczasowy MainPage z AppShell. Uzupełnij jej pliki w kolejnych etapach, zanim ponownie uruchomisz projekt.
namespace MojaAplikacja;
public partial class App : Application
{
public App() => InitializeComponent();
protected override Window CreateWindow(IActivationState? activationState)
=> new Window(new NavigationPage(new MainPage()));
}
CreateWindowTworzy okno aplikacji. NavigationPage jest kontenerem zwykłych stron i daje przycisk powrotu; nie łączymy go z Shell.new MainPage()Tworzy instancję docelowej strony. Aby testować drugi niezależny przykład, zatrzymaj program, zmień tylko ten typ i uruchom ponownie.
4. Połącz wygląd z działaniem
Teraz zbuduj docelowy przykład. Pełne pliki poniżej zastępują swoje wcześniejsze wersje. Przeczytaj opis nad kodem i objaśnienia pod nim; potem sprawdź przepływ danych, nie tylko brak czerwonych podkreśleń.
Gdy przykład ma kilka plików zależnych od siebie, najpierw dodaj wszystkie wskazane typy, a potem uruchom całość. Ustaw breakpoint w obsłudze głównego przycisku i prześledź jedną próbę.
Budowa krok po kroku
Formater.cs
Dodaj zwykłą klasę Formater w folderze Logika. Nie dziedziczy po ContentPage i nie zna żadnego Entry. Przyjmuje napisy, zwraca napis.
Klasa nie używa ContentPage, Entry ani DisplayAlert. Można ją przetestować osobno i użyć w innej aplikacji.
namespace MojaAplikacja.Logika;
public class Formater
{
public string PolaczImieNazwisko(string? imie, string? nazwisko)
{
if (string.IsNullOrWhiteSpace(imie) && string.IsNullOrWhiteSpace(nazwisko))
return "Proszę podać imię i nazwisko.";
if (string.IsNullOrWhiteSpace(imie))
return $"Nazwisko: {nazwisko}";
if (string.IsNullOrWhiteSpace(nazwisko))
return $"Imię: {imie}";
return $"{imie} {nazwisko}";
}
}
string? imieZnak?oznacza, że wartość może byćnull. Metoda to uwzględnia.string.IsNullOrWhiteSpaceWykrywanull, pusty napis i same spacje.- Trzy
ifireturnTrzy przypadki: oba pola puste, brak imienia, brak nazwiska. Pozostałe dane składamy w jeden napis.
Budowa krok po kroku
MainPage.xaml
Na początek użyj MainPage z Clicked. Imię i nazwisko mają własne nazwy, a wynik osobną etykietę.
Widok to opis wyglądu: dwa pola, przycisk i etykieta. Bez reguł działania.
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MojaAplikacja.MainPage" Title="Formularz">
<VerticalStackLayout Padding="20" Spacing="12">
<Entry x:Name="PoleImie" Placeholder="Imię" />
<Entry x:Name="PoleNazwisko" Placeholder="Nazwisko" />
<Button Text="Wyślij" Clicked="PrzyciskWyslij_Click" />
<Label x:Name="WynikLabel" Text="Tu pojawi się wynik" FontAttributes="Bold" />
</VerticalStackLayout>
</ContentPage>
- Przeczytaj powiązanie x:Name identyfikuje kontrolkę, a Binding odczytuje właściwość obiektu ustawionego jako BindingContext. To dwa różne mechanizmy.
Budowa krok po kroku
MainPage.xaml.cs
Handler jest łącznikiem: pobiera dane, wywołuje Formater i pokazuje wynik. Zasady dotyczące brakującego imienia są w Formater, nie w handlerze.
Metoda kliknięcia robi trzy rzeczy: odczytuje pola, woła logikę, pokazuje wynik. Obiekt logiki tworzysz raz, jako pole klasy.
using MojaAplikacja.Logika;
namespace MojaAplikacja;
public partial class MainPage : ContentPage
{
private readonly Formater _formater = new();
public MainPage()
{
InitializeComponent();
}
private void PrzyciskWyslij_Click(object sender, EventArgs e)
{
// Code-behind TYLKO odczytuje dane z UI i woła logikę
string? imie = PoleImie.Text;
string? nazwisko = PoleNazwisko.Text;
string wynik = _formater.PolaczImieNazwisko(imie, nazwisko);
WynikLabel.Text = wynik;
}
}
private readonly Formater _formater = new();Jeden obiekt na stronę zamiastnew Formater()przy każdym kliknięciu._formater.PolaczImieNazwisko(imie, nazwisko)Cała robota dzieje się w klasie logiki.WynikLabel.Text = wynikJedyne, co strona robi z wynikiem.
Zatrzymaj się przed kolejnym wariantem
Nie zastępuj jeszcze działającej wersji. Wykonaj tę próbę i dopiero potem przejdź do dodatku.
Dopiero teraz: drugi wariant lub rozszerzenie
To następny etap tej lekcji, nie warunek pierwszego uruchomienia. Porównaj jego rolę z działającą bazą. Jeśli brakuje czasu, wróć do niego po sprawdzeniu podstawowej wersji.
Dodatek do działającej wersji
Zależność w pliku projektu
To fragment do istniejącego projekt.csproj (fragment), nie cały plik. Zachowaj pozostałe ustawienia szablonu i dodaj podaną sekcję w odpowiednim miejscu.
Pakiet instalujesz przez Menedżera pakietów NuGet (wyszukaj CommunityToolkit.Mvvm), co dopisuje takie wpisy do .csproj.
<ItemGroup>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
</ItemGroup>
Dodatek do działającej wersji
FormularzViewModel.cs: to samo, bez code-behind
Dodatek MVVM po działającej wersji podstawowej. partial umożliwia generatorowi dodanie Imie/Nazwisko/Wynik i WyslijCommand z pól oraz metody oznaczonych atrybutami.
W MAUI pełne MVVM zwykle korzysta z biblioteki CommunityToolkit.Mvvm (pakiet NuGet od Microsoftu), która sama generuje powiadamianie o zmianach i komendy. Ręczne pisanie tego od zera jest rozwlekłe.
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using MojaAplikacja.Logika;
namespace MojaAplikacja;
// ObservableObject i atrybuty generują powiadamianie o zmianie (INotifyPropertyChanged) i komendy
public partial class FormularzViewModel : ObservableObject
{
private readonly Formater _formater = new();
[ObservableProperty]
private string imie = "";
[ObservableProperty]
private string nazwisko = "";
[ObservableProperty]
private string wynik = "Tu pojawi się wynik";
[RelayCommand]
private void Wyslij()
{
Wynik = _formater.PolaczImieNazwisko(Imie, Nazwisko);
}
}
ObservableObjectKlasa bazowa z gotowymINotifyPropertyChanged.[ObservableProperty] private string imieGenerator tworzy publiczną właściwośćImiez powiadamianiem o zmianie.[RelayCommand] private void Wyslij()Generator tworzy komendęWyslijCommand, którą wiążesz z przyciskiem.partial classWymagane, bo generator dopisuje drugą część klasy.
Dodatek do działającej wersji
MvvmPage.xaml: pola powiązane z właściwościami
Dodaj osobną MvvmPage z konstruktorem szablonu. BindingContext tworzy FormularzViewModel, a Command zastępuje handler Clicked.
Strona nie ma ani jednej metody obsługi zdarzeń. Pola są wiązane ({Binding}) z właściwościami ViewModelu, a przycisk z komendą.
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:MojaAplikacja"
x:Class="MojaAplikacja.MvvmPage"
x:DataType="local:FormularzViewModel"
Title="Formularz MVVM">
<ContentPage.BindingContext>
<local:FormularzViewModel />
</ContentPage.BindingContext>
<VerticalStackLayout Padding="20" Spacing="12">
<Entry Text="{Binding Imie}" Placeholder="Imię" />
<Entry Text="{Binding Nazwisko}" Placeholder="Nazwisko" />
<Button Text="Wyślij" Command="{Binding WyslijCommand}" />
<Label Text="{Binding Wynik}" FontAttributes="Bold" />
</VerticalStackLayout>
</ContentPage>
<ContentPage.BindingContext>Mówi stronie, który obiekt jest jej ViewModelem.x:DataType="local:FormularzViewModel"Włącza skompilowane wiązania: błędy w nazwach właściwości wykryje kompilator.{Binding Imie},{Binding WyslijCommand}Dwukierunkowe wiązanie pola i komenda przycisku.
Plik .xaml.cs też musi istnieć
Szablon strony lub ContentView generuje poniższe klasy. Nie dodawaj ich drugi raz, jeśli Visual Studio już je utworzyło. W każdej parze musi pozostać InitializeComponent().
MvvmPage — minimalny kod za widokiem
namespace MojaAplikacja;
public partial class MvvmPage : ContentPage
{
public MvvmPage() => InitializeComponent();
}
partialDruga część klasy powstaje z XAML. Nie twórz ręcznie pól kontrolek ani drugiej metody InitializeComponent.
5. Odbiór: czy naprawdę działa?
Wykonaj próby w swojej aplikacji, nie tylko w modelu na stronie. Zaznaczaj dopiero po sprawdzeniu wyniku. Funkcje urządzenia wymagają testu na właściwej platformie.
9 · Czego unikać
Trzy błędy z oddzielaniem logiki
Logika zna `DisplayAlert` lub kontrolki
public class Formater
{
public void Pokaz(Entry pole)
{
Application.Current.MainPage.DisplayAlertAsync("Witaj", pole.Text, "OK");
}
}Klasy nie da się przetestować ani użyć poza tą aplikacją.
public string Zloz(string imie) => "Witaj, " + imie;Logika przyjmuje dane i zwraca wynik. Pokazanie go to zadanie strony.
Gruby code-behind mimo klasy logiki
if (PoleImie.Text.Length > 0 && PoleNazwisko.Text.Length > 0)
WynikLabel.Text = _formater.Zloz(PoleImie.Text);
else
WynikLabel.Text = "Błąd";Reguła „oba pola muszą być wypełnione” nadal siedzi na stronie.
WynikLabel.Text = _formater.PolaczImieNazwisko(PoleImie.Text, PoleNazwisko.Text);Warunki i reguły należą do klasy logiki.
Nowy obiekt logiki przy każdym kliknięciu
var formater = new Formater(); // w metodzie obsługiNiepotrzebne tworzenie obiektu. Stan logiki zniknąłby po każdym kliknięciu.
private readonly Formater _formater = new();Obiekt logiki tworzysz raz, jako pole strony.
Ćwiczenie z pomocą · samodzielna praca
6. Mała zmiana, którą zrobisz samodzielnie
Dodaj do Formater metodę zwracającą inicjały.
Dodaj do Formater metodę zwracającą inicjały. Dotychczasowe działanie ma pozostać poprawne.
? Potrzebujesz pomocy? Rozwiązuj krok po kroku.
Krok 1 — pokaż podpowiedź
Metoda przyjmuje dwa napisy, nie Entry.
Krok 2 — pokaż podpowiedź
Sprawdź puste wartości przed pobraniem pierwszej litery.
Krok 3 — pokaż podpowiedź
Wywołaj ją z formularza i sprawdź przypadek z brakującym nazwiskiem.
Otwórz tylko tyle podpowiedzi, ile potrzebujesz. Po każdym kroku wróć do zadania i sprawdź wynik.
10 · Sprawdź się
Która warstwa za to odpowiada?
Cztery zadania. Wskaż warstwę.
Podsumowanie
Co warto zapamiętać
- Logika żyje w osobnej klasie, która nie zna stron, kontrolek ani
DisplayAlert. - Code-behind to cienki „klej”: odczytaj dane, wywołaj logikę, pokaż wynik. Obiekt logiki jest polem strony.
- MVVM = Model (dane i reguły) + View (XAML) + ViewModel (dane do wyświetlenia i komendy).
- W MAUI MVVM ułatwia pakiet CommunityToolkit.Mvvm (
ObservableObject,[ObservableProperty],[RelayCommand]). - Widok łączy się z ViewModelem przez
BindingContexti{Binding}.
Dalej na swoim koncie
Rozgrzewki, quiz i zadania są w biurku ucznia.
Teoria jest otwarta dla wszystkich. Żeby rozwiązywać zadania, zapisywać szkice i widzieć swój postęp, zaloguj się.
Zaloguj się →