Lekcja 12. Nawigacja II — Shell, trasy i przekazywanie parametrów
Trudny / egzaminacyjnyPo co się tego uczymy?
Shell to dziś rekomendowany, nowoczesny standard nawigacji w .NET MAUI — działa jak mapa całej aplikacji z zarejestrowanymi trasami (adresami), do których przechodzisz PO NAZWIE, a nie przez tworzenie obiektów stron ręcznie. Dodatkowo daje "za darmo" menu boczne i dolne zakładki. W większych aplikacjach (więcej niż 2-3 ekrany) Shell jest zdecydowanie wygodniejszy niż NavigationPage z poprzedniej lekcji.
Teoria
Rejestracja tras w AppShell
W AppShell.xaml.cs rejestrujesz każdą stronę (poza startową) pod unikalną nazwą — zwykle nazwą jej klasy przez nameof:
Routing.RegisterRoute(nameof(SecondPage), typeof(SecondPage));
Od tego momentu możesz przechodzić do tej strony PO NAZWIE, z dowolnego miejsca aplikacji:
await Shell.Current.GoToAsync(nameof(SecondPage));
Powrót do poprzedniego ekranu w Shell wygląda inaczej niż PopAsync — używa się specjalnej "kropki-kropki": await Shell.Current.GoToAsync("..");
Przekazywanie parametrów przez Shell
Zamiast konstruktora, dane przekazujesz w SŁOWNIKU dołączonym do wywołania GoToAsync:
await Shell.Current.GoToAsync(nameof(SecondPage),
new Dictionary<string, object> { ["Imie"] = "Ania" });
Odbiór po stronie docelowej ma DWA sposoby:
| Sposób | Jak działa |
|---|---|
[QueryProperty] |
Atrybut nad klasą strony, np. [QueryProperty(nameof(Imie), "Imie")] — Shell automatycznie wstrzykuje wartość parametru "Imie" do właściwości Imie. Najprostszy sposób dla pojedynczych, prostych parametrów. |
IQueryAttributable |
Strona implementuje interfejs z metodą ApplyQueryAttributes(IDictionary<string, object> query), w której ręcznie odczytujesz wszystkie parametry ze słownika naraz. Wygodniejsze, gdy przekazujesz kilka parametrów albo cały obiekt. |
Żeby wartość wyświetlona przez {Binding Imie} w XAML odświeżyła się po ustawieniu właściwości, klasa strony (albo jej model) musi implementować INotifyPropertyChanged i wywoływać zdarzenie PropertyChanged przy każdej zmianie — do tego mechanizmu wrócimy szerzej w lekcji o modelach danych.
Kiedy wybrać Shell zamiast NavigationPage?
- Gdy planujesz menu boczne (Flyout) albo dolne zakładki (Tabs);
- Gdy chcesz proste, czytelne trasy tekstowe (przydatne też przy tzw. deep linkach);
- Gdy aplikacja ma więcej niż 2-3 ekrany i ręczne zarządzanie stosem stron zaczyna być niewygodne.
Dla bardzo prostych, dwu-trzy-ekranowych aplikacji (typowych na start w tym dziale) NavigationPage z poprzedniej lekcji jest równie poprawny i czasem szybszy do napisania.
Schemat
Nawigacja Shell - rejestracja i przejscie:
AppShell.xaml.cs:
Routing.RegisterRoute(nameof(SecondPage), typeof(SecondPage));
|
v
gdziekolwiek w aplikacji:
await Shell.Current.GoToAsync(nameof(SecondPage),
new Dictionary<string,object> { ["Imie"] = "Ania" });
|
v
SecondPage odbiera parametr:
[QueryProperty(nameof(Imie), "Imie")] <- najprostszy sposob
albo IQueryAttributable <- gdy parametrow jest wiecej
powrot: await Shell.Current.GoToAsync("..");
Przykład z życia
Aplikacja sklepu internetowego z dolnymi zakładkami "Produkty", "Koszyk", "Profil" — dokładnie to, co Shell daje "z pudełka". Kliknięcie produktu na liście i przejście do jego szczegółów z przekazaniem ID produktu w parametrze trasy to typowe zastosowanie GoToAsync z parametrami.
AppShell.xaml
<!-- AppShell.xaml -->
<Shell xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:NawigacjaShellApp"
x:Class="NawigacjaShellApp.AppShell">
<!-- Startowa pozycja -->
<ShellContent Title="Start" ContentTemplate="{DataTemplate local:MainPage}" />
</Shell>
AppShell.xaml.cs + MainPage/SecondPage
// AppShell.xaml.cs - rejestracja trasy
namespace NawigacjaShellApp;
public partial class AppShell : Shell
{
public AppShell()
{
InitializeComponent();
Routing.RegisterRoute(nameof(SecondPage), typeof(SecondPage));
}
}
// MainPage.xaml.cs - przejscie z parametrem
namespace NawigacjaShellApp;
public partial class MainPage : ContentPage
{
public MainPage() => InitializeComponent();
private async void OnGoClicked(object sender, EventArgs e)
{
var imie = PoleImie.Text ?? "";
await Shell.Current.GoToAsync(nameof(SecondPage),
new Dictionary<string, object> { ["Imie"] = imie });
}
}
// SecondPage.xaml.cs - odbior parametru przez QueryProperty
namespace NawigacjaShellApp;
[QueryProperty(nameof(Imie), "Imie")]
public partial class SecondPage : ContentPage
{
private string imie = "";
public string Imie
{
get => imie;
set { imie = value; OnPropertyChanged(); }
}
public SecondPage()
{
InitializeComponent();
BindingContext = this;
}
private async void OnBackClicked(object sender, EventArgs e)
{
await Shell.Current.GoToAsync("..");
}
}
Komentarz i wyjaśnienie kodu
AppShell.xaml definiuje TYLKO stronę startową jako ShellContent — SecondPage nie musi tam być wymieniona, bo jest zarejestrowana PROGRAMOWO jako "trasa" w konstruktorze AppShell.xaml.cs przez Routing.RegisterRoute.
MainPage wywołuje Shell.Current.GoToAsync z nazwą trasy i słownikiem parametrów — dokładnie tak, jak przekazuje się parametry w adresie URL strony internetowej.
SecondPage ma atrybut [QueryProperty(nameof(Imie), "Imie")] nad definicją klasy — mówi to Shellowi: "gdy dostaniesz parametr o kluczu 'Imie', wstrzyknij go do właściwości Imie". Właściwość Imie wywołuje OnPropertyChanged() przy każdej zmianie (dziedziczone z ContentPage, która implementuje INotifyPropertyChanged), dzięki czemu {Binding Imie} w XAML od razu pokazuje nową wartość. Konstruktor ustawia BindingContext = this — strona "wiąże się sama ze sobą", żeby binding w XAML miał dostęp do jej własnych właściwości.
Ćwiczenie samodzielne
Utwórz projekt z Shell, zarejestruj trasę do SecondPage, i sprawdź przekazywanie parametru "Imie" dokładnie jak w przykładzie. Następnie zmień odbiór na IQueryAttributable zamiast [QueryProperty] i porównaj oba podejścia.
Zadania do pracy własnej
Dodaj drugi parametr 'Wiek' (typu int) przekazywany razem z 'Imie' i wyświetl oba w SecondPage.
Zaimplementuj odbiór parametrów przez IQueryAttributable zamiast [QueryProperty] w SecondPage — porównaj, który sposób wydaje Ci się czytelniejszy przy dwóch parametrach naraz.
Zbuduj aplikację 'Katalog produktów' z Shell: ListaProduktowPage z zaszytą na sztywno listą 5 produktów (nazwa + cena jako para w prostej klasie Produkt) wyświetloną w CollectionView (poznanej wcześniej — na razie wystarczy ItemsSource z listy obiektów), gdzie kliknięcie produktu (SelectionChanged) przechodzi do SzczegolyProduktuPage z przekazaniem nazwy i ceny jako dwóch osobnych parametrów Shell, a strona szczegółów ma przycisk 'Wróć do listy' (GoToAsync('..')).
Typowe błędy
Brak Routing.RegisterRoute dla strony docelowej — próba GoToAsync(nameof(StronaX)) bez wcześniejszej rejestracji trasy rzuci wyjątek "nie znaleziono trasy".
Zapominanie o OnPropertyChanged w setterze właściwości QueryProperty — bez tego binding w XAML nie odświeży się, mimo że wartość właściwości faktycznie się zmieniła "pod spodem".
Mylenie GoToAsync("..") z Navigation.PopAsync() — to DWA różne systemy nawigacji (Shell kontra klasyczny NavigationPage) i nie należy ich mieszać w jednej aplikacji bez dobrego powodu.
Próba przekazania bardzo złożonego obiektu przez słownik GoToAsync między różnymi uruchomieniami aplikacji — słownik parametrów działa dobrze w ramach jednej sesji aplikacji, ale nie nadaje się do trwałego zapisywania stanu (do tego służą Preferences albo baza danych, omówione w dalszych lekcjach).
Nawiązanie do egzaminu zawodowego
To realizacja INF.04.6.2 w zakresie nowoczesnej nawigacji aplikacji mobilnej. Shell jest dziś standardem zalecanym przez Microsoft dla nowych projektów .NET MAUI — znajomość rejestrowania tras i przekazywania parametrów jest przydatna zarówno w zadaniach egzaminacyjnych, jak i w prawdziwych projektach.