Xamarin.Forms Navigazione nella shell

Download Sample Scaricare l'esempio

Xamarin.Forms Shell include un'esperienza di navigazione basata su URI che usa le route per passare a qualsiasi pagina nell'applicazione, senza dover seguire una gerarchia di navigazione impostata. La shell offre anche la possibilità di spostarsi all'indietro senza dover visitare tutte le pagine dello stack di spostamento.

La Shell classe definisce le proprietà correlate allo spostamento seguenti:

Le proprietà BackButtonBehavior, CurrentItem e CurrentState sono supportate da oggetti BindableProperty e ciò significa che tali proprietà possono essere destinazioni di data binding.

Lo spostamento viene eseguito richiamando il metodo GoToAsync dalla classe Shell. Quando lo spostamento sta per essere eseguito, viene generato l'evento Navigating e l'evento Navigated viene generato al termine della navigazione.

Nota

Lo spostamento può comunque essere eseguito tra le pagine di un'applicazione Shell usando la proprietà Navigation . Per altre informazioni, vedere Navigazione gerarchica.

Route

La navigazione in un'applicazione shell avviene specificando un URI a cui passare. Gli URL di spostamento possono essere composti da tre componenti:

  • Una route, che definisce il percorso del contenuto esistente nell'ambito della gerarchia visiva di Shell.
  • Una pagina. Se le pagine non sono presenti nella gerarchia visiva della shell, è possibile eseguirne il push nello stack di navigazione da qualsiasi posizione all'interno di un'applicazione shell. Una pagina dei dettagli, ad esempio, non viene definita nella gerarchia visiva di Shell, ma può essere inserita nello stack di spostamento in base alle esigenze.
  • Uno o più parametri di query. I parametri di query possono essere passati alla pagina di destinazione durante lo spostamento.

Quando un URI di spostamento include tutti i componenti, la struttura è la seguente: //route/page?queryParameters

Registrare le route

Le route possono essere definite in FlyoutItemoggetti , TabBarTab, e ShellContent tramite le relative Route proprietà:

<Shell ...>
    <FlyoutItem ...
                Route="animals">
        <Tab ...
             Route="domestic">
            <ShellContent ...
                          Route="cats" />
            <ShellContent ...
                          Route="dogs" />
        </Tab>
        <ShellContent ...
                      Route="monkeys" />
        <ShellContent ...
                      Route="elephants" />  
        <ShellContent ...
                      Route="bears" />
    </FlyoutItem>
    <ShellContent ...
                  Route="about" />                  
    ...
</Shell>

Nota

A tutti gli elementi nella gerarchia della shell è associata una route. Se non si imposta una route, ne viene generata una in fase di esecuzione. Tuttavia, non c'è garanzia che le route generate siano coerenti tra sessioni diverse dell'applicazione.

L'esempio precedente crea la gerarchia di route seguente, che può essere usata nella struttura di spostamento a livello di codice:

animals
  domestic
    cats
    dogs
  monkeys
  elephants
  bears
about

Per passare all'oggetto ShellContent per la route dogs, l'URI della route assoluto è //animals/domestic/dogs. Analogamente, per passare all'oggetto ShellContent per la route about, l'URI della route assoluto è //about.

Avviso

Se viene rilevata una route duplicata, viene generata un'eccezione ArgumentException all'avvio dell'applicazione. Questa eccezione viene generata anche se due o più route allo stesso livello nella gerarchia condividono un nome di route.

Registrare le route della pagina dei dettagli

Nel costruttore della sottoclasse Shell o in qualsiasi altra posizione eseguita prima di richiamare una route, è possibile registrare in modo esplicito route aggiuntive per tutte le pagine di dettaglio non rappresentate nella gerarchia visiva di Shell. Questa operazione viene eseguita con il metodo Routing.RegisterRoute:

Routing.RegisterRoute("monkeydetails", typeof(MonkeyDetailPage));
Routing.RegisterRoute("beardetails", typeof(BearDetailPage));
Routing.RegisterRoute("catdetails", typeof(CatDetailPage));
Routing.RegisterRoute("dogdetails", typeof(DogDetailPage));
Routing.RegisterRoute("elephantdetails", typeof(ElephantDetailPage));

In questo esempio vengono registrate le pagine di dettaglio, non definite nella Shell sottoclasse, come route. È quindi possibile passare a queste pagine di dettaglio usando lo spostamento basato su URI, da qualsiasi posizione all'interno dell'applicazione. Le route per queste pagine sono dette route globali.

Avviso

Viene generata un'eccezione ArgumentException se il metodo Routing.RegisterRoute tenta di registrare la stessa route a due o più tipi diversi.

In alternativa, le pagine possono essere registrate in gerarchie di route diverse, se necessario:

Routing.RegisterRoute("monkeys/details", typeof(MonkeyDetailPage));
Routing.RegisterRoute("bears/details", typeof(BearDetailPage));
Routing.RegisterRoute("cats/details", typeof(CatDetailPage));
Routing.RegisterRoute("dogs/details", typeof(DogDetailPage));
Routing.RegisterRoute("elephants/details", typeof(ElephantDetailPage));

Questo esempio abilita la navigazione contestuale tra le pagine, con la quale se si passa alla route details dalla pagina relativa a monkeys viene visualizzato l'oggetto MonkeyDetailPage. Analogamente, se si passa alla route details dalla pagina relativa a elephants viene visualizzato l'oggetto ElephantDetailPage. Per altre informazioni, vedere Navigazione contestuale.

Nota

Per le pagine le cui route sono state registrate con il metodo Routing.RegisterRoute, è possibile annullare la registrazione con il metodo Routing.UnRegisterRoute, se necessario.

Eseguire la navigazione

Per eseguire la navigazione, è prima necessario ottenere un riferimento alla sottoclasse Shell. Questo riferimento può essere ottenuto eseguendo il cast della proprietà App.Current.MainPage in un oggetto Shell oppure tramite la proprietà Shell.Current. La navigazione può quindi essere eseguita chiamando il metodo GoToAsync nell'oggetto Shell. Questo metodo consente di passare a ShellNavigationState e restituisce un oggetto Task che verrà completato al termine dell'animazione relativa alla navigazione. L'oggetto ShellNavigationState viene costruito dal metodo GoToAsync, da un oggetto string o da un oggetto Uri e la sua proprietà Location è impostata sull'argomento string o Uri.

Importante

Quando si passa a una route dalla gerarchia visiva della shell, non viene creato uno stack di navigazione. Tuttavia, quando si passa a una pagina non presente nella gerarchia visiva della shell, lo stack di navigazione viene creato.

Lo stato di navigazione corrente dell'oggetto Shell può essere recuperato tramite la Shell.Current.CurrentState proprietà , che include l'URI della route visualizzata nella Location proprietà .

Route assolute

La navigazione può essere eseguita specificando un URI assoluto valido come argomento del metodo GoToAsync:

await Shell.Current.GoToAsync("//animals/monkeys");

Questo esempio consente di passare alla pagina per la route monkeys, con la route definita in un oggetto ShellContent. L'oggetto ShellContent che rappresenta la route monkeys è figlio di un oggetto FlyoutItem, la cui route è animals.

Route relative

La navigazione può essere eseguita anche specificando un URI relativo valido come argomento del metodo GoToAsync. Il sistema di routing cercherà di trovare una corrispondenza tra l'URI e un oggetto ShellContent. Pertanto, se tutte le route in un'applicazione sono univoche, la navigazione può essere eseguita specificando solo il nome di route univoco come URI relativo.

Sono supportati i formati di route relativi seguenti:

Formato Descrizione
route La gerarchia di route verrà cercata per la route specificata, verso l'alto dalla posizione corrente. Verrà eseguito il push della pagina corrispondente nello stack di navigazione.
/route La gerarchia di route verrà eseguita dalla route specificata, verso il basso dalla posizione corrente. Verrà eseguito il push della pagina corrispondente nello stack di navigazione.
//route La gerarchia di route verrà cercata per la route specificata, verso l'alto dalla posizione corrente. La pagina corrispondente sostituirà lo stack di navigazione.
///route La gerarchia di route verrà cercata per la route specificata, verso il basso dalla posizione corrente. La pagina corrispondente sostituirà lo stack di navigazione.

Nell'esempio seguente viene visualizzata la pagina per la monkeydetails route:

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

In questo esempio viene eseguita la ricerca della monkeyDetails route fino a quando non viene trovata la pagina corrispondente. Quando viene trovata la pagina, viene eseguito il push nello stack di navigazione.

Navigazione contestuale

Le route relative consentono la navigazione contestuale. Si consideri ad esempio la gerarchia di route seguente:

monkeys
  details
bears
  details

Quando è visualizzata la pagina registrata per la route monkeys, se si passa alla route details viene visualizzata la pagina registrata per la route monkeys/details. Analogamente, quando è visualizzata la pagina registrata per la route bears, se si passa alla route details viene visualizzata la pagina registrata per la route bears/details. Per informazioni su come registrare le route in questo esempio, vedere Registrare le route di pagine.

Spostamento indietro

Per eseguire lo spostamento indietro, è possibile specificare ".." come argomento al metodo GoToAsync:

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

La navigazione all'indietro con ".." può anche essere combinata con un itinerario:

await Shell.Current.GoToAsync("../route");

In questo esempio viene eseguita la navigazione all'indietro e quindi si passa alla route specificata.

Importante

Spostarsi all'indietro e in una route specificata è possibile solo se lo spostamento all'indietro si trova nella posizione corrente nella gerarchia di route per passare alla route specificata.

Analogamente, è possibile spostarsi più volte all'indietro e quindi passare a una route specificata:

await Shell.Current.GoToAsync("../../route");

In questo esempio la navigazione all'indietro viene eseguita due volte e quindi si passa alla route specificata.

Inoltre, i dati possono essere passati tramite proprietà di query durante lo spostamento all'indietro:

await Shell.Current.GoToAsync($"..?parameterToPassBack={parameterValueToPassBack}");

In questo esempio viene eseguita la navigazione all'indietro e il valore del parametro di query viene passato al parametro di query nella pagina precedente.

Nota

I parametri di query possono essere aggiunti a qualsiasi richiesta di navigazione all'indietro.

Per altre informazioni sul passaggio dei dati durante l'esplorazione, vedere Passare i dati.

Route non valide

I formati di route seguenti non sono validi:

Formato Spiegazione
//page o ///page Le route globali attualmente non possono essere l'unica pagina nello stack di navigazione. Il routing assoluto per le route globali non è quindi supportato.

L'uso di questi formati di route comporta la visualizzazione di un'eccezione Exception .

Avviso

Un tentativo di passare a una route non esistente causa la generazione di un'eccezione ArgumentException.

Debug della navigazione

Alcune classi della shell sono decorate con DebuggerDisplayAttribute, che specifica in che modo il debugger visualizza una classe o un campo. Ciò può essere utile per eseguire il debug delle richieste di navigazione visualizzando i dati correlati alla richiesta di navigazione. Lo screenshot seguente mostra ad esempio le proprietà CurrentItem e CurrentState dell'oggetto Shell.Current:

Screenshot of debugger

In questo esempio la proprietà CurrentItem, di tipo FlyoutItem, visualizza il titolo e la route dell'oggetto FlyoutItem. Analogamente, la proprietà CurrentState, di tipo ShellNavigationState, visualizza l'URI della route visualizzata all'interno dell'applicazione shell.

La Tab classe definisce una Stack proprietà di tipo IReadOnlyList<Page>, che rappresenta lo stack di navigazione corrente all'interno di Tab. La classe fornisce anche i metodi di navigazione sottoponibili a override seguenti:

  • GetNavigationStack, restituisce IReadOnlyList<Page>, lo stack di navigazione corrente.
  • OnInsertPageBefore, chiamato quando si chiama INavigation.InsertPageBefore.
  • OnPopAsync, restituisce Task<Page> e viene chiamato quando si chiama INavigation.PopAsync.
  • OnPopToRootAsync, restituisce Task e viene chiamato quando si chiama INavigation.OnPopToRootAsync.
  • OnPushAsync, restituisce Task e viene chiamato quando si chiama INavigation.PushAsync.
  • OnRemovePage, chiamato quando si chiama INavigation.RemovePage.

Nell'esempio seguente viene illustrato come eseguire l'override del OnRemovePage metodo :

public class MyTab : Tab
{
    protected override void OnRemovePage(Page page)
    {
        base.OnRemovePage(page);

        // Custom logic
    }
}

In questo esempio, MyTab gli oggetti devono essere utilizzati nella gerarchia visiva della shell anziché negli Tab oggetti .

La Shell classe definisce l'evento Navigating , che viene generato quando lo spostamento sta per essere eseguito, a causa della navigazione a livello di codice o dell'interazione dell'utente. L'oggetto ShellNavigatingEventArgs che accompagna l'evento Navigating fornisce le proprietà seguenti:

Proprietà Type Descrizione
Current ShellNavigationState URI della pagina corrente.
Source ShellNavigationSource Tipo di navigazione eseguita.
Target ShellNavigationState URI che rappresenta la destinazione della navigazione.
CanCancel bool Valore che indica se è possibile annullare la navigazione.
Cancelled bool Valore che indica se la navigazione è stata annullata.

Inoltre, la ShellNavigatingEventArgs classe fornisce un Cancel metodo che può essere usato per annullare la navigazione e un GetDeferral metodo che restituisce un ShellNavigatingDeferral token che può essere usato per completare la navigazione. Per altre informazioni sul rinvio dello spostamento, vedere Rinvio della navigazione.

La Shell classe definisce anche l'evento Navigated , che viene generato al termine della navigazione. L'oggetto ShellNavigatedEventArgs che accompagna l'evento Navigated fornisce le proprietà seguenti:

Proprietà Type Descrizione
Current ShellNavigationState URI della pagina corrente.
Previous ShellNavigationState URI della pagina precedente.
Source ShellNavigationSource Tipo di navigazione eseguita.

Importante

Il OnNavigating metodo viene chiamato quando viene generato l'evento Navigating . Analogamente, il OnNavigated metodo viene chiamato quando viene generato l'evento Navigated . Entrambi i metodi possono essere sottoposti a override nella Shell sottoclasse per intercettare le richieste di navigazione.

Le classi ShellNavigatedEventArgs e ShellNavigatingEventArgs hanno entrambe proprietà Source, di tipo ShellNavigationSource. Questa enumerazione fornisce i valori seguenti:

  • Unknown
  • Push
  • Pop
  • PopToRoot
  • Insert
  • Remove
  • ShellItemChanged
  • ShellSectionChanged
  • ShellContentChanged

Pertanto, la navigazione può essere intercettata in un OnNavigating override e le azioni possono essere eseguite in base all'origine di navigazione. Il codice seguente illustra ad esempio come annullare la navigazione indietro se i dati nella pagina non sono stati salvati:

protected override void OnNavigating(ShellNavigatingEventArgs args)
{
    base.OnNavigating(args);

    // Cancel any back navigation.
    if (args.Source == ShellNavigationSource.Pop)
    {
        args.Cancel();
    }
// }

Lo spostamento della shell può essere intercettato e completato o annullato in base alla scelta dell'utente. A tale scopo, è possibile eseguire l'override del OnNavigating metodo nella Shell sottoclasse e chiamando il GetDeferral metodo sull'oggetto ShellNavigatingEventArgs . Questo metodo restituisce un ShellNavigatingDeferral token con un Complete metodo, che può essere usato per completare la richiesta di navigazione:

public MyShell : Shell
{
    // ...
    protected override async void OnNavigating(ShellNavigatingEventArgs args)
    {
        base.OnNavigating(args);

        ShellNavigatingDeferral token = args.GetDeferral();

        var result = await DisplayActionSheet("Navigate?", "Cancel", "Yes", "No");
        if (result != "Yes")
        {
            args.Cancel();
        }
        token.Complete();
    }    
}

In questo esempio viene visualizzata una finestra delle azioni che invita l'utente a completare la richiesta di navigazione o annullarla. La navigazione viene annullata richiamando il Cancel metodo sull'oggetto ShellNavigatingEventArgs . La navigazione viene completata richiamando il Complete metodo sul ShellNavigatingDeferral token recuperato dal GetDeferral metodo sull'oggetto ShellNavigatingEventArgs .

Avviso

Il GoToAsync metodo genererà un'eccezione InvalidOperationException se un utente tenta di spostarsi mentre è presente un differire di navigazione in sospeso.

Passare dati

I dati possono essere passati come parametri di query quando si eseguono spostamenti a livello di codice basati su URI. Questa operazione viene ottenuta aggiungendo ? dopo una route, seguita da un ID parametro di query, =e un valore . Il codice seguente, ad esempio, viene eseguito nell'applicazione di esempio quando un utente seleziona un elefante in ElephantsPage:

async void OnCollectionViewSelectionChanged(object sender, SelectionChangedEventArgs e)
{
    string elephantName = (e.CurrentSelection.FirstOrDefault() as Animal).Name;
    await Shell.Current.GoToAsync($"elephantdetails?name={elephantName}");
}

Questo esempio di codice recupera l'elefante attualmente selezionato in CollectionView e passa alla route elephantdetails, passando elephantName come parametro di query.

Esistono due approcci per ricevere i dati di navigazione:

  1. La classe che rappresenta la pagina a cui si passa oppure la classe per la pagina può BindingContextessere decorata con un QueryPropertyAttribute oggetto per ogni parametro di query. Per altre informazioni, vedere Elaborare i dati di navigazione usando gli attributi delle proprietà di query.
  2. La classe che rappresenta la pagina a cui si passa o la classe per la pagina può BindingContextimplementare l'interfaccia IQueryAttributable . Per altre informazioni, vedere Elaborare i dati di navigazione usando un singolo metodo.

Elaborare i dati di navigazione usando gli attributi delle proprietà di query

I dati di navigazione possono essere ricevuti decorando la classe ricevente con un QueryPropertyAttribute per ogni parametro di query:

[QueryProperty(nameof(Name), "name")]
public partial class ElephantDetailPage : ContentPage
{
    public string Name
    {
        set
        {
            LoadAnimal(value);
        }
    }
    ...

    void LoadAnimal(string name)
    {
        try
        {
            Animal animal = ElephantData.Elephants.FirstOrDefault(a => a.Name == name);
            BindingContext = animal;
        }
        catch (Exception)
        {
            Console.WriteLine("Failed to load animal.");
        }
    }    
}

Il primo argomento per l'oggetto QueryPropertyAttribute specifica il nome della proprietà che riceverà i dati, con il secondo argomento che specifica l'ID parametro di query. Pertanto, QueryPropertyAttribute nell'esempio precedente viene specificato che la Name proprietà riceverà i dati passati nel name parametro di query dall'URI nella chiamata al GoToAsync metodo. Il Name setter della proprietà chiama il LoadAnimal metodo per recuperare l'oggetto Animal per namee lo imposta come della BindingContext pagina.

Nota

I valori dei parametri di query ricevuti tramite QueryPropertyAttribute vengono decodificati automaticamente tramite URL.

Elaborare i dati di navigazione usando un singolo metodo

I dati di navigazione possono essere ricevuti implementando l'interfaccia IQueryAttributable nella classe ricevente. L'interfaccia IQueryAttributable specifica che la classe di implementazione deve implementare il ApplyQueryAttributes metodo . Questo metodo ha un query argomento di tipo IDictionary<string, string>, che contiene tutti i dati passati durante la navigazione. Ogni chiave nel dizionario è un ID parametro di query, con il relativo valore come valore del parametro di query. Il vantaggio di usare questo approccio è che i dati di navigazione possono essere elaborati usando un unico metodo, che può essere utile quando si dispone di più elementi di dati di navigazione che richiedono l'elaborazione nel suo complesso.

L'esempio seguente mostra una classe del modello di visualizzazione che implementa l'interfaccia IQueryAttributable :

public class MonkeyDetailViewModel : IQueryAttributable, INotifyPropertyChanged
{
    public Animal Monkey { get; private set; }

    public void ApplyQueryAttributes(IDictionary<string, string> query)
    {
        // The query parameter requires URL decoding.
        string name = HttpUtility.UrlDecode(query["name"]);
        LoadAnimal(name);
    }

    void LoadAnimal(string name)
    {
        try
        {
            Monkey = MonkeyData.Monkeys.FirstOrDefault(a => a.Name == name);
            OnPropertyChanged("Monkey");
        }
        catch (Exception)
        {
            Console.WriteLine("Failed to load animal.");
        }
    }
    ...
}

In questo esempio, il ApplyQueryAttributes metodo recupera il valore del name parametro di query dall'URI nella chiamata al GoToAsync metodo. Viene quindi chiamato il LoadAnimal metodo per recuperare l'oggetto Animal , in cui il relativo set come valore della Monkey proprietà a cui sono associati i dati.

Importante

I valori dei parametri di query ricevuti tramite l'interfaccia IQueryAttributable non vengono decodificati automaticamente tramite URL.

Passare ed elaborare più parametri di query

È possibile passare più parametri di query collegandoli con &. Ad esempio, il codice seguente passa due elementi di dati:

async void OnCollectionViewSelectionChanged(object sender, SelectionChangedEventArgs e)
{
    string elephantName = (e.CurrentSelection.FirstOrDefault() as Animal).Name;
    string elephantLocation = (e.CurrentSelection.FirstOrDefault() as Animal).Location;
    await Shell.Current.GoToAsync($"elephantdetails?name={elephantName}&location={elephantLocation}");
}

Questo esempio di codice recupera l'elefante attualmente selezionato in CollectionViewe passa alla elephantdetails route, passando elephantName e elephantLocation come parametri di query.

Per ricevere più elementi di dati, la classe che rappresenta la pagina a cui si sposta o la classe per la pagina può BindingContextessere decorata con un QueryPropertyAttribute per ogni parametro di query:

[QueryProperty(nameof(Name), "name")]
[QueryProperty(nameof(Location), "location")]
public partial class ElephantDetailPage : ContentPage
{
    public string Name
    {
        set
        {
            // Custom logic
        }
    }

    public string Location
    {
        set
        {
            // Custom logic
        }
    }
    ...    
}

In questo esempio la classe è decorata con un QueryPropertyAttribute oggetto per ogni parametro di query. Il primo QueryPropertyAttribute specifica che la Name proprietà riceverà i dati passati nel name parametro di query, mentre il secondo QueryPropertyAttribute specifica che la Location proprietà riceverà i dati passati nel location parametro di query. In entrambi i casi, i valori dei parametri di query vengono specificati nell'URI nella chiamata al GoToAsync metodo.

In alternativa, i dati di navigazione possono essere elaborati da un singolo metodo implementando l'interfaccia IQueryAttributable nella classe che rappresenta la pagina a cui si passa o la classe per la pagina della BindingContextpagina :

public class ElephantDetailViewModel : IQueryAttributable, INotifyPropertyChanged
{
    public Animal Elephant { get; private set; }

    public void ApplyQueryAttributes(IDictionary<string, string> query)
    {
        string name = HttpUtility.UrlDecode(query["name"]);
        string location = HttpUtility.UrlDecode(query["location"]);
        ...        
    }
    ...
}

In questo esempio, il ApplyQueryAttributes metodo recupera il valore dei parametri di name query e location dall'URI nella chiamata al GoToAsync metodo.

Comportamento del pulsante Indietro

È possibile ridefinire l'aspetto e il comportamento del pulsante Indietro impostando la BackButtonBehavior proprietà associata su un BackButtonBehavior oggetto . La BackButtonBehavior classe definisce le proprietà seguenti:

  • Command, di tipo ICommand. Questo oggetto viene eseguito quando si preme sul pulsante Indietro.
  • CommandParameter, di tipo object, ovvero il parametro passato a Command.
  • IconOverride, di tipo ImageSource, ovvero l'icona usata per il pulsante Indietro.
  • IsEnabled, di tipo boolean, che indica se il pulsante Indietro è abilitato. Il valore predefinito è true.
  • TextOverride, di tipo string, ovvero il testo usato per il pulsante Indietro.

Tutte queste proprietà sono supportate da oggetti BindableProperty e ciò significa che tali proprietà possono essere destinazioni di data binding.

Il codice seguente mostra un esempio di ridefinizione dell'aspetto e del comportamento del pulsante Indietro:

<ContentPage ...>    
    <Shell.BackButtonBehavior>
        <BackButtonBehavior Command="{Binding BackCommand}"
                            IconOverride="back.png" />   
    </Shell.BackButtonBehavior>
    ...
</ContentPage>

Il codice C# equivalente è il seguente:

Shell.SetBackButtonBehavior(this, new BackButtonBehavior
{
    Command = new Command(() =>
    {
        ...
    }),
    IconOverride = "back.png"
});

La proprietà Command è impostata su un oggetto ICommand da eseguire quando si preme il pulsante Indietro, mentre la proprietà IconOverride è impostata sull'icona usata per il pulsante Indietro:

Screenshot of a Shell back button icon override, on iOS and Android