Aplikacje mobilne · Nawigacja
Nawigacja II: Shell, trasy i przekazywanie parametrów
Shell to dziś zalecany, nowoczesny sposób nawigacji w .NET MAUI. Działa jak mapa aplikacji z zarejestrowanymi trasami, do których przechodzisz po nazwie, a nie przez ręczne tworzenie stron. Daje też gotowe menu boczne i dolne zakładki.
- około 55 minut
- .NET MAUI · Shell · GoToAsync · QueryProperty
- INF.04.6.2
Po tej lekcji: zarejestrować trasę (Routing.RegisterRoute), przejść po nazwie (GoToAsync), przekazać parametry słownikiem i odebrać je przez [QueryProperty] lub IQueryAttributable.
Nawigacja z zakładkami i adresem strony
W większej aplikacji nie chcesz ręcznie obsługiwać każdego przycisku Wstecz. Shell opisuje mapę ekranów, a szczegóły otwierasz po zarejestrowanej trasie.
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 · Sprawdź w działaniu
Przejście po nazwie trasy
Przejdź do SecondPage z parametrem Imie i wróć przez ... Potem odznacz opcję rejestracji trasy i spróbuj przejść ponownie, aby zobaczyć błąd.
Najpierw przewidź, potem sprawdź: W modelu Shell przejdź do szczegółów i wróć. Porównaj adres trasy z obiektem strony w NavigationPage. Nie łącz w jednym korzeniu Shell i NavigationPage.
Shell przechodzi po nazwie, a nazwę trzeba wcześniej zarejestrować. Powrót to GoToAsync(".."), a nie PopAsync. 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 |
|---|---|
| AppShell.xaml | Wygląd strony lub kontrolki |
| AppShell.xaml — wariant zamienny | Fragment w istniejącym pliku |
| AppShell.xaml.cs | Logika i obsługa danych |
| MainPage.xaml.cs | Logika i obsługa danych |
| SecondPage.xaml.cs | Logika i obsługa danych |
| SzczegolyPage.xaml.cs | Logika i obsługa danych |
| KontaktPage.xaml | Wygląd strony lub kontrolki |
| MainPage.xaml | Wygląd strony lub kontrolki |
| SecondPage.xaml | Wygląd strony lub kontrolki |
| SzczegolyPage.xaml | Wygląd strony lub kontrolki |
| App.xaml.cs | Logika i obsługa danych |
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="Start aplikacji Shell" FontSize="24" />
<Label Text="Najpierw ekran, potem mapa przejść." />
</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 sklepu
Adres zamiast ręcznego tworzenia stron
Aplikacja sklepu z dolnymi zakładkami „Produkty”, „Koszyk” i „Profil” to dokładnie to, co Shell daje „z pudełka”. Kliknięcie produktu i przejście do jego szczegółów z przekazaniem identyfikatora to typowe GoToAsync z parametrem, tak jak adres URL ze stroną internetową.
**Kiedy wybrać Shell zamiast NavigationPage?** Gdy planujesz menu boczne (Flyout) albo dolne zakładki, gdy chcesz czytelne trasy tekstowe (też do deep linków) lub gdy aplikacja ma więcej niż 2–3 ekrany. Dla bardzo prostych aplikacji NavigationPage jest równie poprawny i szybszy do napisania.
7 · Porównanie
[QueryProperty] czy IQueryAttributable
| Sposób | Jak działa | Kiedy |
|---|---|---|
[QueryProperty] | Atrybut nad klasą wstrzykuje parametr do właściwości. | Jeden lub dwa proste parametry. |
IQueryAttributable | Metoda ApplyQueryAttributes czyta słownik. | Kilka parametrów lub cały obiekt. |
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
AppShell.xaml
Wariant A: podmień AppShell.xaml. TabBar tworzy dolne zakładki i wyłącza boczne menu Shell. Strony lokalne wskazuje ContentTemplate.
Wariant A: dolne zakładki. TabBar zawiera dwa Tab, a w nich ShellContent wskazujące strony przez DataTemplate. Dodaj stronę KontaktPage pokazaną niżej. TabBar wyłącza menu flyout: nie łącz obu wariantów bez zmiany struktury. Nie rejestruj ponownie globalnie tras tych samych stron, które są już w hierarchii.
<?xml version="1.0" encoding="utf-8" ?>
<Shell 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.AppShell">
<TabBar Route="glowne">
<Tab Title="Start" Route="start">
<ShellContent ContentTemplate="{DataTemplate local:MainPage}" Route="MainPage" />
</Tab>
<Tab Title="Kontakt" Route="kontakt">
<ShellContent ContentTemplate="{DataTemplate local:KontaktPage}" Route="KontaktPage" />
</Tab>
</TabBar>
</Shell>
<Shell ... x:Class="...AppShell">Korzeń aplikacji: zamiastNavigationPagestrukturę opisujeShell.<ShellContent ContentTemplate="{DataTemplate local:MainPage}" />Strona startowa.DataTemplatetworzy ją dopiero w chwili potrzeby.
Budowa krok po kroku
AppShell.xaml.cs
Zarejestruj trasy w konstruktorze po InitializeComponent. Nazwa trasy jest kluczem, a typeof wskazuje klasę, która ma zostać otwarta.
Każdą stronę poza startową rejestrujesz pod unikalną nazwą, zwykle przez nameof.
namespace MojaAplikacja;
public partial class AppShell : Shell
{
public AppShell()
{
InitializeComponent();
// Rejestracja trasy: od teraz do SecondPage można przejść po nazwie
Routing.RegisterRoute(nameof(SecondPage), typeof(SecondPage));
Routing.RegisterRoute(nameof(SzczegolyPage), typeof(SzczegolyPage));
}
}
Routing.RegisterRoute(nameof(SecondPage), typeof(SecondPage))Od tej chwili możesz przejść doSecondPagepo nazwie z każdego miejsca aplikacji.nameof(SecondPage)Nazwa trasy wynika z nazwy klasy, więc literówka zostanie wykryta przez kompilator.
Budowa krok po kroku
Widok MainPage
Dodaj lub otwórz MainPage.xaml i podmień jego zawartość. Najpierw odszukaj kontener, potem nazwane kontrolki oraz wskazane zdarzenia. Pozostaw zgodne x:Class i nazwę klasy w pliku .xaml.cs.
To widok do wcześniej omówionego pliku C# tej strony. Dodaj stronę przez szablon ContentPage (XAML), potem podmień obie części.
<?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="Przykład">
<VerticalStackLayout Padding="20" Spacing="12">
<Entry x:Name="PoleImie" Placeholder="Imię" />
<Button Text="Otwórz drugą stronę" Clicked="OnGoClicked" />
<Button Text="Szczegóły produktu" Clicked="OnDetailsClicked" />
</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
Otwórz MainPage.xaml.cs. Podmień wskazany plik po utworzeniu typów, do których się odwołuje. Objaśnienia pod kodem pokazują, które wartości przechodzą do następnego etapu.
Parametry przekazujesz w słowniku, tak jak w adresie URL strony.
namespace MojaAplikacja;
public partial class MainPage : ContentPage
{
public MainPage() => InitializeComponent();
private async void OnGoClicked(object sender, EventArgs e)
{
var imie = PoleImie.Text ?? "";
// Przejście po nazwie trasy, z parametrami w słowniku
await Shell.Current.GoToAsync(nameof(SecondPage),
new Dictionary<string, object> { ["Imie"] = imie });
}
private async void OnDetailsClicked(object sender, EventArgs e)
{
await Shell.Current.GoToAsync(nameof(SzczegolyPage),
new Dictionary<string, object> { ["Nazwa"] = "Notes", ["Cena"] = 12.50m });
}
}
Shell.Current.GoToAsync(nameof(SecondPage), słownik)Przechodzi po nazwie trasy i dołącza dane.new Dictionary<string, object> { ["Imie"] = imie }Klucz"Imie"ma odpowiadać nazwie parametru po stronie docelowej.Shell.CurrentBieżąca instancjaShell, dostępna z dowolnej strony.
Budowa krok po kroku
Strona użyta przez drugą zakładkę
Dodaj lub otwórz KontaktPage.xaml i podmień jego zawartość. Najpierw odszukaj kontener, potem nazwane kontrolki oraz wskazane zdarzenia. Pozostaw zgodne x:Class i nazwę klasy w pliku .xaml.cs.
Dodaj przez Dodaj → Nowy element → ContentPage (XAML). Zachowaj wygenerowany konstruktor wywołujący InitializeComponent().
<?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.KontaktPage" Title="Kontakt">
<VerticalStackLayout Padding="20" Spacing="12">
<Label Text="Kontakt do autora aplikacji" />
<Label Text="autor@example.com" />
</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
Widok SecondPage
Dodaj lub otwórz SecondPage.xaml i podmień jego zawartość. Najpierw odszukaj kontener, potem nazwane kontrolki oraz wskazane zdarzenia. Pozostaw zgodne x:Class i nazwę klasy w pliku .xaml.cs.
To widok do wcześniej omówionego pliku C# tej strony. Dodaj stronę przez szablon ContentPage (XAML), potem podmień obie części.
<?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.SecondPage" Title="Przykład">
<VerticalStackLayout Padding="20" Spacing="12">
<Label Text="{Binding Imie}" />
<Button Text="Wróć" Clicked="OnBackClicked" />
</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
SecondPage.xaml.cs
Otwórz SecondPage.xaml.cs. Podmień wskazany plik po utworzeniu typów, do których się odwołuje. Objaśnienia pod kodem pokazują, które wartości przechodzą do następnego etapu.
Atrybut nad klasą mówi Shellowi: „parametr Imie wstrzyknij do właściwości Imie”. To najprostszy sposób dla pojedynczych parametrów.
namespace MojaAplikacja;
// Shell wstrzyknie parametr „Imie” do właściwości Imie
[QueryProperty(nameof(Imie), "Imie")]
public partial class SecondPage : ContentPage
{
private string _imie = "";
public string Imie
{
get => _imie;
set
{
_imie = value;
OnPropertyChanged(); // powiadamia wiązanie, że wartość się zmieniła
}
}
public SecondPage()
{
InitializeComponent();
BindingContext = this;
}
private async void OnBackClicked(object sender, EventArgs e)
{
await Shell.Current.GoToAsync("..");
}
}
[QueryProperty(nameof(Imie), "Imie")]Pierwszy argument to nazwa właściwości, drugi to klucz parametru.OnPropertyChanged()w setterze Bez niego wiązanie{Binding Imie}nie odświeży się, mimo że wartość się zmieniła.BindingContext = thisStrona sama jest źródłem danych dla wiązań w XAML.GoToAsync("..")Powrót o jeden poziom w górę.
Budowa krok po kroku
Widok SzczegolyPage
Dodaj lub otwórz SzczegolyPage.xaml i podmień jego zawartość. Najpierw odszukaj kontener, potem nazwane kontrolki oraz wskazane zdarzenia. Pozostaw zgodne x:Class i nazwę klasy w pliku .xaml.cs.
To widok do wcześniej omówionego pliku C# tej strony. Dodaj stronę przez szablon ContentPage (XAML), potem podmień obie części.
<?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.SzczegolyPage" Title="Przykład">
<VerticalStackLayout Padding="20" Spacing="12">
<Label x:Name="EtykietaNazwa" />
<Label x:Name="EtykietaCena" />
<Button Text="Wróć" Clicked="OnBackClicked" />
</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
SzczegolyPage.xaml.cs
Parametry nawigacji odczytuje strona docelowa. QueryProperty ma ograniczenia przy pełnym trimming/NativeAOT — dla takich konfiguracji użyj IQueryAttributable, jak zaznaczono w materiale.
Wygodniejsze, gdy parametrów jest kilka albo przekazujesz cały obiekt: odczytujesz wszystkie naraz w jednej metodzie.
namespace MojaAplikacja;
// Drugi sposób odbioru parametrów: interfejs IQueryAttributable
public partial class SzczegolyPage : ContentPage, IQueryAttributable
{
public SzczegolyPage()
{
InitializeComponent();
}
public void ApplyQueryAttributes(IDictionary<string, object> query)
{
// Wszystkie parametry odczytujemy naraz ze słownika
if (query.TryGetValue("Nazwa", out var nazwa))
EtykietaNazwa.Text = nazwa?.ToString();
if (query.TryGetValue("Cena", out var cena))
EtykietaCena.Text = $"Cena: {cena} zł";
}
private async void OnBackClicked(object sender, EventArgs e)
{
await Shell.Current.GoToAsync("..");
}
}
IQueryAttributableInterfejs z jedną metodąApplyQueryAttributes.query.TryGetValue("Nazwa", out var nazwa)Bezpieczny odczyt: nie zgłasza błędu, gdy parametru brakuje.
Budowa krok po kroku
Uruchom Shell w App.xaml.cs
Okno tworzy bezpośrednio AppShell. Nie opakowuj Shell w NavigationPage.
W tej lekcji startem jest AppShell. Nie dodawaj NavigationPage z poprzedniej lekcji: to dwa różne warianty nawigacji.
namespace MojaAplikacja;
public partial class App : Application
{
public App() => InitializeComponent();
protected override Window CreateWindow(IActivationState? activationState)
=> new Window(new AppShell());
}
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
Wariant B zamiast TabBar: menu boczne
Wariant B wykonaj dopiero po działającym A. ZASTĄP cały AppShell.xaml, nie doklejaj drugiego Shell. FlyoutItem zawiera własne zakładki.
Podmień AppShell.xaml wariantem B, żeby porównać menu boczne oraz zakładki wewnątrz FlyoutItem. Nie doklejaj drugiego Shell do tego samego pliku. Oba warianty korzystają z tego samego AppShell.xaml.cs.
<Shell 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.AppShell" FlyoutBehavior="Flyout">
<FlyoutItem Title="Moje strony" Route="strony">
<Tab Title="Start" Route="start">
<ShellContent ContentTemplate="{DataTemplate local:MainPage}" Route="MainPage" />
</Tab>
<Tab Title="Kontakt" Route="kontakt">
<ShellContent ContentTemplate="{DataTemplate local:KontaktPage}" Route="KontaktPage" />
</Tab>
</FlyoutItem>
</Shell>
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().
KontaktPage — minimalny kod za widokiem
namespace MojaAplikacja;
public partial class KontaktPage : ContentPage
{
public KontaktPage() => 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.
8 · Czego unikać
Cztery błędy z Shell
Brak `Routing.RegisterRoute`
await Shell.Current.GoToAsync(nameof(SecondPage)); // trasa niezarejestrowanaWyjątek „nie znaleziono trasy”.
Routing.RegisterRoute(nameof(SecondPage), typeof(SecondPage)); // w AppShellShell przechodzi tylko do zarejestrowanych tras.
Brak `OnPropertyChanged` w setterze
public string Imie { get; set; }Wiązanie {Binding Imie} nie odświeża się po ustawieniu parametru.
set { _imie = value; OnPropertyChanged(); }Wiązanie musi dostać powiadomienie o zmianie.
Mieszanie `GoToAsync("..")` z `PopAsync`
await Navigation.PopAsync(); // w aplikacji z ShellDwa różne systemy nawigacji w jednej aplikacji prowadzą do niespójnego zachowania.
await Shell.Current.GoToAsync("..");W aplikacji z Shell konsekwentnie używaj GoToAsync.
Słownik parametrów jako trwały zapis
Przekazuję przez słownik dane, które mają przetrwać zamknięcie aplikacji.Po zamknięciu aplikacji dane znikają.
Preferences albo baza danychParametry trasy żyją tylko w jednej sesji.
Ćwiczenie z pomocą · samodzielna praca
6. Mała zmiana, którą zrobisz samodzielnie
Dodaj trasę do własnej strony PomocPage.
Dodaj trasę do własnej strony PomocPage. Dotychczasowe działanie ma pozostać poprawne.
? Potrzebujesz pomocy? Rozwiązuj krok po kroku.
Krok 1 — pokaż podpowiedź
Dodaj ContentPage (XAML) o nazwie PomocPage.
Krok 2 — pokaż podpowiedź
W konstruktorze AppShell zarejestruj unikalną trasę Routing.RegisterRoute.
Krok 3 — pokaż podpowiedź
Wywołaj GoToAsync z dokładnie tą nazwą trasy; literówka nie tworzy nowej strony.
Otwórz tylko tyle podpowiedzi, ile potrzebujesz. Po każdym kroku wróć do zadania i sprawdź wynik.
9 · Sprawdź się
Trasa, parametr czy odbiór?
Cztery sytuacje.
Podsumowanie
Co warto zapamiętać
- Shell przechodzi po nazwie trasy:
Routing.RegisterRoutewAppShell, potemShell.Current.GoToAsync(nameof(Strona)). - Powrót to
GoToAsync(".."). Nie mieszaj go zPopAsync. - Parametry przekazujesz słownikiem w
GoToAsync. - Odbiór:
[QueryProperty](proste) alboIQueryAttributable(kilka parametrów). - Setter właściwości woła
OnPropertyChanged(), aby wiązanie się odświeżyło.
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ę →