.NET MAUI Shell navigáció: Útmutató az útvonalkezeléstől a deep linkingig

Teljes útmutató a .NET MAUI Shell navigációhoz: útvonalak regisztrálása, GoToAsync, paraméterátadás, navigációs események kezelése, MVVM integráció és .NET 10 újdonságok kódpéldákkal.

.NET MAUI Shell navigáció: Teljes útmutató 2026

Bevezetés

A navigáció minden mobil alkalmazás gerincét képezi — ezt gondolom nem kell senkinek bizonygatni. Akár egy egyszerű kétoldalas alkalmazást készítesz, akár egy összetett, több tucatnyi képernyőből álló enterprise rendszert, a felhasználók zökkenőmentes navigációs élménye alapvetően meghatározza az alkalmazásod sikerét. A .NET MAUI Shell pontosan erre a kihívásra kínál átfogó megoldást: egy URI-alapú, deklaratív navigációs rendszert, amely egyszerűsíti az oldalak közötti átmenetet, támogatja a paraméterátadást, és natívan integrálja a flyout menüket, alsó füleket és felső lapozókat.

A .NET 10 megjelenésével a Shell számos finomhangolást kapott. A navigációs sáv láthatósági animációjának vezérlésétől kezdve a MessagingCenter végleges eltávolításáig és a WeakReferenceMessenger ajánlásáig — elég széles a változások palettája. Ebben az útmutatóban végigmegyünk a Shell navigáció minden fontosabb aspektusán: az útvonalak regisztrálásától a paraméterátadás különböző módjain át egészen a navigációs események kezeléséig és az MVVM mintával való integrációig.

Ha az előző cikkünket olvastad a CollectionView-ról, már ismered a .NET MAUI UI-rendszerének alapjait. Most a navigációs réteget vesszük górcső alá — azt a réteget, amely összeköti ezeket a felületi elemeket.

A Shell alapjai: struktúra és útvonalak

A Shell egy konténer-vezérlőelem, amely az alkalmazásod teljes navigációs struktúráját definiálja. Az AppShell.xaml fájlban deklaratívan leírhatod, hogy milyen oldalak, fülek és flyout menüpontok alkotják az alkalmazásod. Őszintén szólva, ha egyszer megszokod ezt a megközelítést, nehéz visszamenni a manuális navigációs kezeléshez.

A Shell vizuális hierarchia

A Shell hierarchia három fő elemből áll: FlyoutItem (vagy TabBar), Tab és ShellContent. Minden egyes elem rendelkezhet egy Route tulajdonsággal, amely egyedi azonosítót ad az adott navigációs célpontnak.

<Shell xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
       xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
       xmlns:views="clr-namespace:MyApp.Views"
       x:Class="MyApp.AppShell"
       FlyoutBehavior="Flyout">

    <!-- Főoldal flyout menüpontként -->
    <FlyoutItem Title="Főoldal"
                Icon="home.png"
                Route="fooldal">
        <ShellContent ContentTemplate="{DataTemplate views:FooldalPage}"
                      Route="fooldal-tartalom" />
    </FlyoutItem>

    <!-- Termékek fülekkel -->
    <FlyoutItem Title="Termékek"
                Icon="products.png"
                Route="termekek">
        <Tab Title="Kategóriák" Route="kategoriak">
            <ShellContent ContentTemplate="{DataTemplate views:KategoriakPage}"
                          Route="kategoriak-lista" />
        </Tab>
        <Tab Title="Kedvencek" Route="kedvencek">
            <ShellContent ContentTemplate="{DataTemplate views:KedvencekPage}"
                          Route="kedvencek-lista" />
        </Tab>
    </FlyoutItem>

    <!-- Beállítások -->
    <FlyoutItem Title="Beállítások"
                Icon="settings.png"
                Route="beallitasok">
        <ShellContent ContentTemplate="{DataTemplate views:BeallitasokPage}"
                      Route="beallitasok-tartalom" />
    </FlyoutItem>

</Shell>

Ezzel a konfigurációval az alábbi útvonal-hierarchia jön létre:

fooldal
  fooldal-tartalom
termekek
  kategoriak
    kategoriak-lista
  kedvencek
    kedvencek-lista
beallitasok
  beallitasok-tartalom

Fontos tudni: ha nem adsz meg explicit Route értéket, a rendszer automatikusan generál egyet, de ez nem garantáltan konzisztens az alkalmazás különböző futtatásai között. Szóval mindig érdemes explicit útvonalakat definiálni — ezzel sok fejfájástól kíméled meg magad.

Globális útvonalak regisztrálása

Azokat az oldalakat, amelyek nem részei a Shell vizuális hierarchiájának (tipikusan részlet- vagy szerkesztő oldalak), globális útvonalként kell regisztrálni. Ezt a Routing.RegisterRoute metódussal tehetjük meg, jellemzően az AppShell konstruktorában:

public partial class AppShell : Shell
{
    public AppShell()
    {
        InitializeComponent();

        // Globális útvonalak regisztrálása
        Routing.RegisterRoute("termek-reszletek", typeof(TermekReszletekPage));
        Routing.RegisterRoute("termek-szerkesztes", typeof(TermekSzerkesztesPage));
        Routing.RegisterRoute("rendeles-osszegzes", typeof(RendelesOsszegzesPage));
        Routing.RegisterRoute("profil", typeof(ProfilPage));
    }
}

A globális útvonalak az alkalmazás bármely pontjáról elérhetők relatív URI-val. Egy dolgot viszont ne felejts: duplikált útvonalak ArgumentException kivételt váltanak ki az alkalmazás indításakor, ami azért elég kellemetlen hiba lehet élesben.

Kontextuális útvonalak

A .NET MAUI Shell támogatja a kontextuális navigációt is. Ez azt jelenti, hogy ugyanaz az útvonalnév más-más oldalra mutathat attól függően, honnan navigálunk. Nézzünk egy példát:

// Kontextuális útvonalak regisztrálása
Routing.RegisterRoute("kategoriak/reszletek", typeof(KategoriaReszletekPage));
Routing.RegisterRoute("kedvencek/reszletek", typeof(KedvencReszletekPage));

Ebben az esetben ha a Kategóriák oldalról navigálunk a reszletek útvonalra, a KategoriaReszletekPage jelenik meg. Ha viszont a Kedvencek oldalról tesszük ugyanezt, a KedvencReszletekPage töltődik be. Saját tapasztalatom szerint ez rendkívül hasznos, ha hasonló struktúrájú, de eltérő tartalmú oldalakkal dolgozol — elég sokat tud egyszerűsíteni a kódon.

Navigáció a GoToAsync metódussal

Na, akkor nézzük a lényeget. A Shell navigáció központi eleme a GoToAsync metódus, amelyet a Shell.Current objektumon keresztül hívhatunk meg. Ez a metódus egy Task-ot ad vissza, amely a navigációs animáció befejezésekor teljesül.

Abszolút útvonalak

Az abszolút útvonalak a // előtaggal kezdődnek és a Shell vizuális hierarchia gyökerétől indulnak:

// Navigáció a Kedvencek fülre
await Shell.Current.GoToAsync("//termekek/kedvencek");

// Navigáció a Beállítások oldalra
await Shell.Current.GoToAsync("//beallitasok");

Fontos: abszolút útvonalak csak a Shell vizuális hierarchiában definiált oldalakhoz használhatók. A Routing.RegisterRoute metódussal regisztrált globális útvonalakhoz nem működnek — ez egy gyakori buktató, amin sokan meglepődnek először.

Relatív útvonalak

A relatív útvonalak az aktuális pozícióhoz képest navigálnak, és elsősorban a globális útvonalakhoz használatosak:

// Navigáció egy részletoldalra (globális útvonal)
await Shell.Current.GoToAsync("termek-reszletek");

// Navigáció kontextuális útvonalra
await Shell.Current.GoToAsync("reszletek");

Visszafelé navigáció

A visszafelé navigáció a .. szintaxissal valósítható meg. Ez a navigációs veremből eltávolítja az aktuális oldalt:

// Egyszerű visszalépés
await Shell.Current.GoToAsync("..");

// Visszalépés és navigáció egy másik útvonalra
await Shell.Current.GoToAsync("../masik-oldal");

// Több szint visszalépése
await Shell.Current.GoToAsync("../../harmadik-oldal");

// Visszalépés paraméterrel
await Shell.Current.GoToAsync($"..?eredmeny={mentettAdat}");

A visszafelé navigáció különösen hasznos, amikor egy részletoldalról szeretnénk visszatérni a lista nézethez, vagy amikor egy szerkesztő oldalról visszalépünk az eredménnyel. Egyszerű és intuitív — pont, ahogy lennie kell.

Paraméterátadás navigáció során

Az alkalmazások szinte mindig igénylik, hogy adatokat adjunk át az oldalak között navigáció során. A .NET MAUI Shell háromféle megközelítést kínál erre, és érdemes mindegyiket ismerni, mert más-más helyzetben lesz ideális.

Sztring-alapú query paraméterek

A legegyszerűbb módszer primitív adatok átadására az URI query paramétereinek használata:

// Egyetlen paraméter átadása
await Shell.Current.GoToAsync($"termek-reszletek?termekId={kivalasztottTermek.Id}");

// Több paraméter átadása
await Shell.Current.GoToAsync(
    $"termek-reszletek?termekId={termek.Id}&kategoriaId={kategoria.Id}&megpipirazva=true");

Objektum-alapú paraméterek (többszöri használat)

Ha komplex objektumokat szeretnénk átadni, használhatjuk az IDictionary<string, object> overloadot. Fontos tudni, hogy ezek a paraméterek a memóriában maradnak, amíg az oldal a navigációs veremben van:

// Komplex objektum átadása
var navigaciosParameterek = new Dictionary<string, object>
{
    { "KivalasztottTermek", kivalasztottTermek },
    { "KosarElemek", kosarLista }
};

await Shell.Current.GoToAsync("rendeles-osszegzes", navigaciosParameterek);

Figyelmeztetés: Mivel az objektum-alapú paraméterek a memóriában maradnak az oldal teljes élettartama alatt, visszanavigáláskor is újra megkapja az oldal ugyanazokat a paramétereket. Ha ez nem kívánatos (és gyakran nem az), használj egyszeri paramétereket.

Egyszeri használatú paraméterek (ShellNavigationQueryParameters)

A .NET MAUI bevezette a ShellNavigationQueryParameters osztályt, amely automatikusan törli a paramétereket a navigáció befejezése után. Ez gyakorlatilag a legtöbb esetben az, amit használni szeretnél.

// Egyszeri használatú paraméterek
var egyszerParameterek = new ShellNavigationQueryParameters
{
    { "RendelesAdatok", ujRendeles },
    { "FizetesiMod", fizetesiMod }
};

await Shell.Current.GoToAsync("rendeles-osszegzes", egyszerParameterek);
// A paraméterek automatikusan törlődnek a navigáció után

Ez a megközelítés ideális, amikor nem szeretnénk, hogy visszanavigáláskor az oldal újra megkapja az adatokat — például egy fizetési folyamatnál, ahol az dupla rendeléshez vezethetne.

Paraméterek fogadása: IQueryAttributable interfész

A paraméterek fogadásának ajánlott módja az IQueryAttributable interfész implementálása. Ez trim-biztos és NativeAOT-kompatibilis, ellentétben a régebbi QueryPropertyAttribute-tal:

public class TermekReszletekViewModel : IQueryAttributable, INotifyPropertyChanged
{
    private Termek _termek;
    private bool _pipirazva;

    public Termek Termek
    {
        get => _termek;
        set
        {
            _termek = value;
            OnPropertyChanged();
        }
    }

    public bool Pipirazva
    {
        get => _pipirazva;
        set
        {
            _pipirazva = value;
            OnPropertyChanged();
        }
    }

    public void ApplyQueryAttributes(IDictionary<string, object> query)
    {
        // Objektum-alapú paraméter fogadása
        if (query.TryGetValue("KivalasztottTermek", out var termekObj)
            && termekObj is Termek termek)
        {
            Termek = termek;
        }

        // Sztring-alapú paraméter fogadása és URL-dekódolás
        if (query.TryGetValue("termekId", out var termekIdObj))
        {
            var termekId = HttpUtility.UrlDecode(termekIdObj?.ToString());
            // Termék betöltése az ID alapján
            BetoltTermeketAsync(termekId);
        }

        // Bool paraméter kezelése
        if (query.TryGetValue("megpipirazva", out var pipObj))
        {
            Pipirazva = bool.TryParse(pipObj?.ToString(), out var eredmeny) && eredmeny;
        }
    }

    private async void BetoltTermeketAsync(string termekId)
    {
        // Aszinkron adatlekérés
        Termek = await _termekService.GetTermekByIdAsync(termekId);
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

Egy apró, de fontos részlet: a sztring-alapú query paraméterek értékeit az IQueryAttributable interfészen keresztül fogadva nem dekódolja automatikusan a rendszer. Ezért mindig használd a HttpUtility.UrlDecode metódust — ezt könnyű elfelejteni, és furcsa bugokhoz vezethet.

Navigációs események és halasztás

A Shell két fontos eseményt biztosít a navigáció életciklusának kezelésére: a Navigating és a Navigated eseményeket. Ezek lehetővé teszik, hogy beavatkozzunk a navigációs folyamatba — például megakadályozzuk a mentetlen adatok elvesztését.

A Navigating esemény

A Navigating esemény még a navigáció végrehajtása előtt következik be. A ShellNavigatingEventArgs tartalmazza az aktuális és a céloldal URI-ját, a navigáció típusát, és lehetőséget ad a navigáció megszakítására:

public partial class AppShell : Shell
{
    public AppShell()
    {
        InitializeComponent();
        Navigating += OnShellNavigating;
    }

    private void OnShellNavigating(object sender, ShellNavigatingEventArgs e)
    {
        // A navigáció forrásának és céljának naplózása
        System.Diagnostics.Debug.WriteLine(
            $"Navigáció: {e.Current?.Location} -> {e.Target?.Location} ({e.Source})");

        // Visszanavigáció megakadályozása bizonyos esetekben
        if (e.Source == ShellNavigationSource.Pop && !MentesElvegezve)
        {
            e.Cancel();
            // Felhasználó értesítése
            MentesElottiFigyelmeztetesAsync();
        }
    }
}

Navigáció halasztása (deferral)

Ha a navigáció megszakításáról aszinkron műveletek alapján szeretnénk dönteni (mondjuk felhasználói megerősítést kérünk), a deferral mechanizmust használhatjuk. Ez az egyik legeleánsabb megoldás, amit a Shell kínál:

public partial class AppShell : Shell
{
    protected override async void OnNavigating(ShellNavigatingEventArgs args)
    {
        base.OnNavigating(args);

        // Csak visszanavigáláskor kérdezünk
        if (args.Source != ShellNavigationSource.Pop)
            return;

        // Halasztási token kérése
        ShellNavigatingDeferral token = args.GetDeferral();

        try
        {
            // Felhasználói megerősítés kérése
            var valasz = await DisplayAlertAsync(
                "Megerősítés",
                "Biztosan el szeretnéd hagyni ezt az oldalt? A nem mentett módosítások elvesznek.",
                "Igen",
                "Mégse");

            if (!valasz)
            {
                args.Cancel();
            }
        }
        finally
        {
            // A token befejezése kötelező!
            token.Complete();
        }
    }
}

Figyelem: Ha egy halasztás van folyamatban és közben egy másik navigáció indul, InvalidOperationException keletkezik. Ezért mindig gondoskodj a token megfelelő befejezéséről — a try-finally blokk itt nem opcionális, hanem kötelező.

A Navigated esemény

A Navigated esemény a navigáció befejezése után következik be. Hasznos például analitikai adatok gyűjtéséhez:

public partial class AppShell : Shell
{
    public AppShell()
    {
        InitializeComponent();
        Navigated += OnShellNavigated;
    }

    private void OnShellNavigated(object sender, ShellNavigatedEventArgs e)
    {
        // Oldalmegtekintés naplózása analitikai célból
        AnalitikaiService.OldalMegtekintes(
            e.Current?.Location?.ToString(),
            e.Previous?.Location?.ToString());
    }
}

MVVM integráció és dependency injection

A modern .NET MAUI alkalmazásokban a navigáció szorosan összefonódik az MVVM mintával és a dependency injection rendszerrel. Ezt a részt érdemes különös figyelemmel olvasni, mert a .NET 10-ben a MessagingCenter végleges eltávolítása után a CommunityToolkit.Mvvm csomag WeakReferenceMessenger-e lett az ajánlott üzenetküldési megoldás.

Oldalak és ViewModel-ek regisztrálása a DI konténerben

A .NET MAUI beépített támogatást nyújt a dependency injection számára a MauiProgram.cs fájlban:

public static class MauiProgram
{
    public static MauiApp CreateMauiApp()
    {
        var builder = MauiApp.CreateBuilder();
        builder
            .UseMauiApp<App>()
            .ConfigureFonts(fonts =>
            {
                fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
            });

        // Szolgáltatások regisztrálása
        builder.Services.AddSingleton<ITermekService, TermekService>();
        builder.Services.AddSingleton<INavigaciosService, NavigaciosService>();
        builder.Services.AddSingleton<IKosarService, KosarService>();

        // Oldalak regisztrálása (transient, mert minden navigációkor új példány kell)
        builder.Services.AddTransient<FooldalPage>();
        builder.Services.AddTransient<TermekReszletekPage>();
        builder.Services.AddTransient<RendelesOsszegzesPage>();
        builder.Services.AddTransient<KosarPage>();

        // ViewModel-ek regisztrálása
        builder.Services.AddTransient<FooldalViewModel>();
        builder.Services.AddTransient<TermekReszletekViewModel>();
        builder.Services.AddTransient<RendelesOsszegzesViewModel>();
        builder.Services.AddTransient<KosarViewModel>();

        return builder.Build();
    }
}

Navigációs szolgáltatás készítése

Az MVVM minta egyik legfontosabb előnye, hogy a ViewModel-ek nem függenek közvetlenül a felhasználói felülettől. Ehhez érdemes egy navigációs szolgáltatást készíteni, ami elrejti a Shell-specifikus részleteket:

public interface INavigaciosService
{
    Task NavigaljRa(string utvonal);
    Task NavigaljRa(string utvonal, IDictionary<string, object> parameterek);
    Task NavigaljVissza();
    Task NavigaljVisszaEredmennyel(IDictionary<string, object> parameterek);
}

public class NavigaciosService : INavigaciosService
{
    public async Task NavigaljRa(string utvonal)
    {
        await Shell.Current.GoToAsync(utvonal);
    }

    public async Task NavigaljRa(string utvonal, IDictionary<string, object> parameterek)
    {
        await Shell.Current.GoToAsync(utvonal, parameterek);
    }

    public async Task NavigaljVissza()
    {
        await Shell.Current.GoToAsync("..");
    }

    public async Task NavigaljVisszaEredmennyel(IDictionary<string, object> parameterek)
    {
        await Shell.Current.GoToAsync("..", parameterek);
    }
}

ViewModel navigáció a CommunityToolkit.Mvvm csomaggal

A CommunityToolkit.Mvvm csomag jelentősen egyszerűsíti a ViewModel-ek kódját a source generátorok segítségével. Komolyan, ha még nem használod, nézz rá — rengeteg boilerplate kódtól szabadulsz meg:

using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;

public partial class FooldalViewModel : ObservableObject, IQueryAttributable
{
    private readonly INavigaciosService _navigaciosService;
    private readonly ITermekService _termekService;

    [ObservableProperty]
    private ObservableCollection<Termek> _termekek = new();

    [ObservableProperty]
    private bool _betoltes;

    public FooldalViewModel(
        INavigaciosService navigaciosService,
        ITermekService termekService)
    {
        _navigaciosService = navigaciosService;
        _termekService = termekService;
    }

    [RelayCommand]
    private async Task BetoltTermekeketAsync()
    {
        Betoltes = true;
        try
        {
            var termekLista = await _termekService.GetOsszesTermekAsync();
            Termekek = new ObservableCollection<Termek>(termekLista);
        }
        finally
        {
            Betoltes = false;
        }
    }

    [RelayCommand]
    private async Task TermekKivalasztva(Termek termek)
    {
        if (termek == null) return;

        var parameterek = new ShellNavigationQueryParameters
        {
            { "KivalasztottTermek", termek }
        };

        await _navigaciosService.NavigaljRa("termek-reszletek", parameterek);
    }

    public void ApplyQueryAttributes(IDictionary<string, object> query)
    {
        // Visszanavigáláskor kapott eredmény kezelése
        if (query.TryGetValue("frissitendo", out var frissitObj)
            && frissitObj is bool frissit && frissit)
        {
            BetoltTermekeketAsync();
        }
    }
}

Vissza gomb testreszabása

A Shell lehetőséget ad a vissza gomb viselkedésének és megjelenésének teljes testreszabására a BackButtonBehavior csatolt tulajdonságon keresztül. Ha valaha is próbáltad már testre szabni a back buttont Xamarin.Forms-ban, tudod, hogy ez nem mindig volt ilyen egyszerű.

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MyApp.Views.TermekSzerkesztesPage">

    <Shell.BackButtonBehavior>
        <BackButtonBehavior
            Command="{Binding VisszaGombCommand}"
            CommandParameter="{Binding MentettE}"
            IconOverride="custom_back.png"
            TextOverride="Vissza"
            IsVisible="{Binding VisszaGombLatszik}" />
    </Shell.BackButtonBehavior>

    <!-- Oldal tartalma -->
    <VerticalStackLayout Padding="16" Spacing="12">
        <Label Text="Termék szerkesztése"
               FontSize="24"
               FontAttributes="Bold" />
        <Entry Text="{Binding TermekNev}"
               Placeholder="Termék neve" />
        <Editor Text="{Binding TermekLeiras}"
                Placeholder="Termék leírása"
                HeightRequest="120" />
        <Button Text="Mentés"
                Command="{Binding MentesCommand}" />
    </VerticalStackLayout>

</ContentPage>

A ViewModel-ben a vissza gomb parancsát így kezelhetjük:

[RelayCommand]
private async Task VisszaGomb(bool mentettE)
{
    if (VanMentetlenValtoztatas && !mentettE)
    {
        var valasz = await Shell.Current.DisplayAlertAsync(
            "Figyelmeztetés",
            "Vannak mentetlen módosítások. Biztosan kilépsz?",
            "Igen",
            "Mégse");

        if (!valasz) return;
    }

    await Shell.Current.GoToAsync("..");
}

Modális navigáció és .NET 10 újdonságok

A Shell alapértelmezetten verem-alapú (push/pop) navigációt használ, de szükség esetén modális navigáció is megvalósítható a Navigation tulajdonságon keresztül.

Klasszikus modális megjelenítés

// Modális oldal megnyitása
await Navigation.PushModalAsync(new BejelentkezesPage());

// Modális oldal bezárása
await Navigation.PopModalAsync();

.NET 10 újdonság: popover modális iOS-en és Mac Catalyst-en

A .NET 10 bevezette a modális oldalak popover-ként történő megjelenítésének lehetőségét iOS és Mac Catalyst platformokon. Ez különösen iPaden és Mac-en hasznos, ahol a lebegő ablakok sokkal természetesebb felhasználói élményt nyújtanak, mint egy teljes képernyős modális.

using Microsoft.Maui.Controls.PlatformConfiguration;
using Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;

public partial class SzuroPopoverPage : ContentPage
{
    public SzuroPopoverPage(View forrasElem, Rectangle terulet)
    {
        InitializeComponent();
        On<iOS>().SetModalPopoverView(forrasElem);
        On<iOS>().SetModalPopoverRect(terulet);
        On<iOS>().SetModalPresentationStyle(UIModalPresentationStyle.Popover);
    }
}

// Használat egy gombból:
private async void SzuroGomb_Clicked(object sender, EventArgs e)
{
    var popover = new SzuroPopoverPage(sender as View, Rectangle.Empty);
    await Navigation.PushModalAsync(popover);
}

Navigációs sáv animáció vezérlése (.NET 10)

A .NET 10-ben új lehetőségként jelent meg a navigációs sáv láthatósági animációjának szabályozása:

<!-- Navigációs sáv animáció kikapcsolása -->
<Shell NavBarVisibilityAnimationEnabled="False"
       ...>
    <!-- Shell tartalom -->
</Shell>

Ez hasznos, ha az animáció zavaró hatású bizonyos oldaltípusoknál, vagy ha teljesítményoptimalizálásra van szükség. Egyébként a legtöbb esetben az alapértelmezett animáció tökéletesen megfelel.

Gyakorlati példa: komplett navigációs folyamat

Rendben, nézzünk meg egy teljes, valószerű navigációs folyamatot egy webáruház alkalmazásban. Ez a példa az eddig tanultakat fogja össze egyetlen koherens rendszerben — szóval ha eddig csak darabokban láttad a képet, itt áll össze az egész.

Az AppShell definiálása

<Shell xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
       xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
       xmlns:views="clr-namespace:WebAruhaz.Views"
       x:Class="WebAruhaz.AppShell"
       FlyoutBehavior="Disabled">

    <TabBar Route="main">
        <Tab Title="Bolt" Icon="shop.png" Route="bolt">
            <ShellContent ContentTemplate="{DataTemplate views:BoltPage}" />
        </Tab>
        <Tab Title="Kosár" Icon="cart.png" Route="kosar">
            <ShellContent ContentTemplate="{DataTemplate views:KosarPage}" />
        </Tab>
        <Tab Title="Fiók" Icon="account.png" Route="fiok">
            <ShellContent ContentTemplate="{DataTemplate views:FiokPage}" />
        </Tab>
    </TabBar>

</Shell>

Globális útvonalak és ViewModel-ek

// AppShell.xaml.cs
public partial class AppShell : Shell
{
    public AppShell()
    {
        InitializeComponent();

        Routing.RegisterRoute("termek", typeof(TermekPage));
        Routing.RegisterRoute("fizetes", typeof(FizetesPage));
        Routing.RegisterRoute("rendeles-keszult", typeof(RendelesKeszultPage));
    }
}

// A Bolt oldal ViewModel-je
public partial class BoltViewModel : ObservableObject
{
    private readonly ITermekService _termekService;

    [ObservableProperty]
    private ObservableCollection<Termek> _termekek = new();

    public BoltViewModel(ITermekService termekService)
    {
        _termekService = termekService;
    }

    [RelayCommand]
    private async Task MegnyitTermek(Termek termek)
    {
        await Shell.Current.GoToAsync("termek", new ShellNavigationQueryParameters
        {
            { "termek", termek }
        });
    }
}

// A Termék részletek ViewModel-je
public partial class TermekViewModel : ObservableObject, IQueryAttributable
{
    private readonly IKosarService _kosarService;

    [ObservableProperty]
    private Termek _termek;

    [ObservableProperty]
    private int _mennyiseg = 1;

    public TermekViewModel(IKosarService kosarService)
    {
        _kosarService = kosarService;
    }

    public void ApplyQueryAttributes(IDictionary<string, object> query)
    {
        if (query.TryGetValue("termek", out var obj) && obj is Termek t)
        {
            Termek = t;
        }
    }

    [RelayCommand]
    private async Task KosarbaTesz()
    {
        await _kosarService.HozzaadasAsync(Termek, Mennyiseg);
        await Shell.Current.DisplayAlertAsync(
            "Siker",
            $"{Termek.Nev} hozzáadva a kosárhoz!",
            "OK");
    }

    [RelayCommand]
    private async Task AzonalMegvesz()
    {
        await _kosarService.HozzaadasAsync(Termek, Mennyiseg);
        await Shell.Current.GoToAsync("//kosar");
    }
}

Hibakeresés és gyakori problémák

A Shell navigáció során számos gyakori probléma felmerülhet. Lássuk a legfontosabbakat — ezek azok, amikbe szinte mindenki belefut előbb-utóbb.

1. Duplikált útvonalak

Ha két azonos nevű útvonalat regisztrálsz, az alkalmazás ArgumentException kivétellel áll le induláskor. Mindig győződj meg az útvonalnevek egyediségéről:

// HIBÁS: duplikált útvonalnév
Routing.RegisterRoute("reszletek", typeof(TermekReszletekPage));
Routing.RegisterRoute("reszletek", typeof(RendelesReszletekPage)); // ArgumentException!

// HELYES: egyedi útvonalnevek
Routing.RegisterRoute("termek-reszletek", typeof(TermekReszletekPage));
Routing.RegisterRoute("rendeles-reszletek", typeof(RendelesReszletekPage));

2. Nem létező útvonalak

Nem létező útvonalra navigálás szintén ArgumentException kivételt eredményez. Érdemes konstansokat vagy egy statikus osztályt használni az útvonalak kezelésére — ezzel futásidejű hibákat alakíthatsz át fordítási idejű hibákká:

public static class Utvonalak
{
    public const string TermekReszletek = "termek-reszletek";
    public const string RendelesOsszegzes = "rendeles-osszegzes";
    public const string Profil = "profil";
    public const string Fizetes = "fizetes";
}

// Használat
await Shell.Current.GoToAsync(Utvonalak.TermekReszletek);

3. QueryPropertyAttribute és NativeAOT

A .NET 10-ben fontos figyelmeztetés: a QueryPropertyAttribute nem trim-biztos és nem használható NativeAOT-tal. Ha a jövőre is gondolsz, mindig az IQueryAttributable interfészt használd:

// NE HASZNÁLD NativeAOT esetén:
[QueryProperty(nameof(TermekId), "termekId")]
public partial class ReszletekPage : ContentPage { }

// HASZNÁLD HELYETTE:
public class ReszletekViewModel : IQueryAttributable
{
    public void ApplyQueryAttributes(IDictionary<string, object> query)
    {
        // Trim-biztos paraméter-kezelés
    }
}

4. Navigációs verem állapotának ellenőrzése

A Shell aktuális állapotát a Shell.Current.CurrentState tulajdonságon keresztül ellenőrizheted. Hibakeresésnél ez az egyik leghasznosabb eszköz:

// Aktuális útvonal lekérdezése
var aktualisUtvonal = Shell.Current.CurrentState.Location.ToString();
System.Diagnostics.Debug.WriteLine($"Aktuális útvonal: {aktualisUtvonal}");

// A Tab navigációs vermének vizsgálata
var aktualisTab = Shell.Current.CurrentItem?.CurrentItem as Tab;
if (aktualisTab != null)
{
    var verem = aktualisTab.GetNavigationStack();
    System.Diagnostics.Debug.WriteLine($"Verem mérete: {verem.Count}");
    foreach (var oldal in verem)
    {
        System.Diagnostics.Debug.WriteLine($"  - {oldal.GetType().Name}");
    }
}

.NET 10 Shell-specifikus változások összefoglalása

A .NET 10 több fontos változást hozott a Shell navigáció és az általános alkalmazásarchitektúra területén. Íme a legfontosabbak egy helyen:

  • NavBarVisibilityAnimationEnabled: Új tulajdonság a navigációs sáv megjelenési/eltűnési animációjának vezérlésére.
  • MessagingCenter eltávolítása: A .NET 10-ben a MessagingCenter belső (internal) lett. Helyette a CommunityToolkit.Mvvm csomag WeakReferenceMessenger-ét kell használni.
  • DisplayAlertAsync és DisplayActionSheetAsync: A korábbi DisplayAlert és DisplayActionSheet metódusok elavulttá váltak — az Async utótagú változatokat használd.
  • XAML Source Generator: Az új XAML forrásgenerátor fordítási időben erősen típusos kódot hoz létre, ami gyorsabb futást és jobb IntelliSense-támogatást eredményez.
  • Globális XML névterek: A GlobalXmlns.cs fájl segítségével megszabadulhatsz a XAML fájlok tetején lévő rengeteg xmlns: deklarációtól.
  • iOS popover modális: Modális oldalak mostantól popoverként jeleníthetők meg iPaden és Mac-en.
  • SafeAreaEdges: Finomhangolt safe area kezelés a SafeAreaEdges enum segítségével.
  • Aspire integráció: Új projektsablon a .NET Aspire szolgáltatás-alapértelmezések integrálásához MAUI alkalmazásokba.

Teljesítmény-tippek a navigációhoz

A navigáció optimalizálása kulcsfontosságú a jó felhasználói élményhez. Íme néhány gyakorlati tanács, amiket érdemes szem előtt tartani:

  • Lazy loading: Használd a ContentTemplate tulajdonságot a ShellContent-ben ahelyett, hogy közvetlenül adnád meg a Content-et. Így az oldalak csak akkor töltődnek be, amikor először navigálsz hozzájuk.
  • ShellNavigationQueryParameters: Egyszeri adatátadásnál mindig ezt használd a sima Dictionary helyett, hogy elkerüld a memóriaszivárgást.
  • Transient oldalak: A részletoldalakat mindig AddTransient-ként regisztráld a DI konténerben, hogy minden navigációkor friss példányt kapj.
  • Ne blokkolj a navigációs szálon: Az ApplyQueryAttributes metódusban kerüld a szinkron, hosszú futású műveleteket. Használj async void metódusokat az adatbetöltéshez (igen, tudom, általában kerülendő, de itt indokolt).
  • XAML Source Generator: Engedélyezd a .NET 10 XAML forrásgenerátort a gyorsabb indítási időért.
<!-- Projekt fájl (.csproj) kiegészítése a XAML Source Generator-hoz -->
<PropertyGroup>
  <MauiXamlInflator>SourceGen</MauiXamlInflator>
</PropertyGroup>

Összefoglalás

A .NET MAUI Shell navigáció egy sokoldalú, URI-alapú rendszer, amely jelentősen egyszerűsíti a mobil alkalmazások oldalak közötti átmeneteit. Ebben az útmutatóban áttekintettük a Shell hierarchia felépítését, az abszolút és relatív útvonalakat, a globális és kontextuális útvonalak regisztrálását, valamint a paraméterátadás három módszerét.

Megismertük a navigációs események kezelését, a halasztás mechanizmusát, a vissza gomb testreszabását és a modális navigáció lehetőségeit — beleértve a .NET 10 által bevezetett popover modálisokat is. Az MVVM integrációs fejezetben pedig láthattad, hogyan építhetsz tiszta, tesztelhető navigációs réteget dependency injection és a CommunityToolkit.Mvvm csomag segítségével.

A .NET 10 változásai (MessagingCenter eltávolítása, XAML Source Generator, globális XML névterek, SafeAreaEdges) együttesen egy érettebb, teljesítményorientáltabb keretrendszert eredményeznek. Ha eddig halogattad a migrációt, most tényleg érdemes lépni.

Következő lépésként próbáld ki a bemutatott mintákat a saját projektedben, és fokozatosan térj át az IQueryAttributable interfész használatára — különösen ha NativeAOT-t vagy full trimming-et tervezel. Hidd el, a befektetett idő megtérül.

A Szerzőről Sofia Marchetti

Sofia is a mobile platform engineer with eleven years across native iOS, Xamarin, and now .NET MAUI. She spent three years at Spotify in Stockholm working on internal tooling for the mobile build infrastructure, then joined a fintech in Milan where she leads the mobile foundations team responsible for a MAUI app that handles around 2 million monthly active users across iOS and Android. Most of what she writes about lives in the build and release layer: deterministic builds, fastlane integration for MAUI, code signing on macOS runners, MAUI .NET 9 upgrade postmortems, and benchmarking startup time on cheap Android hardware. She co-organizes the Milano .NET meetup and gave a talk at NDC Oslo 2025 on shrinking a MAUI Android APK from 84 MB to 31 MB without losing features.