Навигация по оболочке Xamarin.Forms
Оболочка Xamarin.Forms предоставляет улучшенные возможности навигации по интерфейсу на основе URI, позволяя переходить на любую страницу в приложении без соблюдения строгой иерархии. Кроме того, есть возможность перемещаться в обратном направлении без обязательного посещений всех страниц в стеке навигации.
Класс Shell
определяет следующие свойства, связанные с навигацией:
BackButtonBehavior
с типомBackButtonBehavior
— присоединяемое свойство, которое определяет поведение кнопки "Назад";CurrentItem
с типомShellItem
— обозначает текущий выбранный элемент;CurrentPage
с типомPage
— обозначает текущую отображаемую страницу;CurrentState
с типомShellNavigationState
— текущее состояние навигации дляShell
;Current
с типомShell
—псевдонимApplication.Current.MainPage
с приведением типа.
Свойства BackButtonBehavior
, CurrentItem
и CurrentState
поддерживаются объектами BindableProperty
, то есть эти свойства можно указывать в качестве целевых для привязки данных.
Навигация выполняется путем вызова метода GoToAsync
из класса Shell
. Когда планируется действие навигации, срабатывает событие Navigating
, а после завершения навигации — событие Navigated
.
Примечание.
Навигацию по страницам в приложении оболочки можно по-прежнему выполнять с помощью свойства Navigation. Дополнительные сведения см. в статье об иерархической навигации.
Маршруты
Навигация в приложении оболочки выполняется путем указания URI для перехода. URI навигации могут содержать следующие три компонента:
- Маршрут, который определяет путь к содержимому в составе визуальной иерархии оболочки Shell.
- Страница. Страницы не отражены в визуальной иерархии оболочки и могут добавляться в стек навигации из любого места в приложении оболочки. Например, страница сведений отсутствует в визуальной иерархии оболочки Shell, но при необходимости может быть помещена в стек навигации.
- Один или несколько параметров запроса. Параметры запроса можно передать целевой странице при вызове метода навигации.
Если URI навигации содержит все три компонента, он имеет формат "//маршрут/страница?параметры".
Регистрация маршрутов
Маршруты можно определить для объектов FlyoutItem
, TabBar
, Tab
и ShellContent
с помощью свойств Route
:
<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>
Примечание.
С каждым элементом в иерархии оболочки сопоставляется маршрут. Если маршрут не задан, он создается во время выполнения. Но для автоматических маршрутов не гарантируется согласованность между сеансами приложения.
Этот пример создает описанную ниже иерархию маршрутов, которую можно использовать для программной навигации:
animals
domestic
cats
dogs
monkeys
elephants
bears
about
Чтобы перейти к объекту ShellContent
по маршруту dogs
, абсолютный URI должен выглядеть так: //animals/domestic/dogs
. Аналогичным образом для перехода к объекта ShellContent
по маршруту about
абсолютный URI должен выглядеть так: //about
.
Предупреждение
Если будет обнаружен дубликат маршрута, при запуске приложения создается исключение ArgumentException
. Это исключение также будет создаваться, если два или более маршрута на одном уровне иерархии совместно используют имя маршрута.
Регистрация маршрутов страниц сведений
В конструкторе подкласса Shell
или в любом другом расположении, которое выполняется перед вызовом маршрута, можно явным образом зарегистрировать дополнительные маршруты для любых страниц подробных сведений, которые отсутствуют в визуальной иерархии оболочки Shell. Это выполняется с помощью метода 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));
Этот пример регистрирует страницы сведений, для которых не определены маршруты в подклассе Shell
. К страницам сведений можно переходить по URI из любой точки в приложении. Маршруты для таких страниц называются глобальными маршрутами.
Предупреждение
Если метод Routing.RegisterRoute
пытается зарегистрировать один и тот же маршрут к двум или более разным типам, будет создано исключение ArgumentException
.
Также вы можете регистрировать страницы в другие иерархии маршрутов, если потребуется:
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));
Этот пример настраивает контекстную навигацию по страницам, где переход по маршруту details
со страницы для маршрута monkeys
отображает MonkeyDetailPage
. Аналогичным образом переход по маршруту details
со страницы для маршрута elephants
отображает ElephantDetailPage
. Дополнительные сведения см. в разделе Контекстная навигация.
Примечание.
Если для страницы зарегистрированы маршруты с помощью метода Routing.RegisterRoute
, вы можете отменить такую регистрацию с помощью метода Routing.UnRegisterRoute
, если потребуется.
Выполнение перемещения
Чтобы выполнить перемещение, нужно получить ссылку на подкласс Shell
. Чтобы получить эту ссылку, можно привести свойство App.Current.MainPage
к объекту Shell
или получить значение свойства Shell.Current
. После этого выполняется перемещение путем вызова метода GoToAsync
из объекта Shell
. Этот метод переходит к ShellNavigationState
и возвращает Task
, который завершается после завершения анимации для этого перехода. Объект ShellNavigationState
создается с помощью метода GoToAsync
из string
или Uri
и для его свойства Location
задается значение аргумента string
или Uri
.
Внимание
При переходе по маршруту, входящему в визуальную иерархию оболочки, стек навигации не создается. Но при переходе к странице, которая не входит в визуальную иерархию оболочки, создается стек навигации.
Текущее состояние навигации для объекта Shell
можно извлечь через свойство Shell.Current.CurrentState
, которое содержит URI отображаемого маршрута в свойстве Location
.
Абсолютные маршруты
Для навигации можно передать допустимый абсолютный URI в качестве аргумента в метод GoToAsync
:
await Shell.Current.GoToAsync("//animals/monkeys");
Этот пример выполняет переход на страницу с маршрутом monkeys
, который определен в объекте ShellContent
. Объект ShellContent
, который представляет маршрут monkeys
, является дочерним элементом объекта FlyoutItem
с маршрутом animals
.
Относительные маршруты
Для навигации можно также передать допустимый относительный URI в качестве аргумента в метод GoToAsync
. Система маршрутизации попытается сопоставить URI с объектом ShellContent
. Таким образом, если все маршруты в приложении уникальны, для навигации достаточно указать уникальное имя маршрута в качестве относительного URI.
Поддерживаются следующие форматы относительных маршрутов.
Формат | Description |
---|---|
маршрут | Поиск указанного маршрута выполняется в иерархии маршрутов вверх, начиная от текущей позиции. Страница совпадения будет отправлена в стек навигации. |
/маршрут | Поиск указанного маршрута выполняется в иерархии маршрутов вниз, начиная от текущей позиции. Страница совпадения будет отправлена в стек навигации. |
//маршрут | Поиск указанного маршрута выполняется в иерархии маршрутов вверх, начиная от текущей позиции. Страница совпадения заменит стек навигации. |
///маршрут | Поиск указанного маршрута выполняется в иерархии маршрутов вниз, начиная от текущей позиции. Страница совпадения заменит стек навигации. |
В приведенном ниже примере выполняется переход на страницу с маршрутом monkeydetails
.
await Shell.Current.GoToAsync("monkeydetails");
В этом примере поиск маршрута в иерархии monkeyDetails
выполняется вверх до тех пор, пока не будет найдена станица совпадения. Найденная страница отправляется в стек навигации.
Контекстная навигация
Относительные маршруты позволяют организовать контекстную навигацию. Для примера рассмотрим следующую иерархию маршрутов:
monkeys
details
bears
details
Когда отображается страница с зарегистрированным маршрутом monkeys
, переход по маршруту details
отображает страницу с зарегистрированным маршрутом monkeys/details
. Аналогичным образом, когда отображается страница с зарегистрированным маршрутом bears
, переход по маршруту details
отображает страницу с зарегистрированным маршрутом bears/details
. Сведения о том, как зарегистрировать маршруты для этого примера, см. в этой статье.
Обратная навигация
Обратную навигацию можно выполнить, указав аргумент ".." в вызове метода GoToAsync
:
await Shell.Current.GoToAsync("..");
Обратную навигацию с ".." можно также объединить с маршрутом:
await Shell.Current.GoToAsync("../route");
В этом примере выполняется обратная навигация, а затем осуществляется переход к указанному маршруту.
Внимание
Переход назад и по указанному маршруту возможен, только если при обратной навигации вы будете находиться в текущем местоположении в иерархии маршрутов для перехода по указанному маршруту.
Аналогичным образом можно перемещаться назад несколько раз, а затем переходить по указанному маршруту:
await Shell.Current.GoToAsync("../../route");
В этом примере обратная навигация выполняется дважды, а затем осуществляется переход к указанному маршруту.
Кроме того, при обратной навигации можно передавать данные в свойствах запроса:
await Shell.Current.GoToAsync($"..?parameterToPassBack={parameterValueToPassBack}");
В этом примере выполняется обратная навигация, и значение параметра запроса передается параметру запроса на предыдущей странице.
Примечание.
Параметры запроса можно добавить к любому запросу обратной навигации.
Дополнительные сведения о передаче данных при навигации см. в разделе Передача данных.
Недопустимые маршруты
Следующие форматы маршрутов считаются недопустимыми.
Формат | Описание |
---|---|
//страница или / / /страница | В настоящее время глобальные маршруты не могут быть единственной страницей в стеке навигации. Таким образом, абсолютная маршрутизация для глобальных маршрутов не поддерживается. |
Использование этих форматов маршрутов приводит к созданию исключения Exception
.
Предупреждение
Попытка перехода на несуществующий маршрут приводит к созданию исключения ArgumentException
.
Отладка навигации
Некоторые классы оболочки дополняются DebuggerDisplayAttribute
, который определяет отображение класса или поля в отладчике. Это помогает в отладке запросов навигации, отображая актуальные данные для запроса перехода. Например, на следующем снимке экрана показаны свойства CurrentItem
и CurrentState
объекта Shell.Current
:
В этом примере свойство CurrentItem
с типом FlyoutItem
отображает название и маршрут объекта FlyoutItem
. Аналогичным образом свойство CurrentState
с типом ShellNavigationState
отображает URI отображаемого маршрута в приложении оболочки.
Стек навигации
Класс Tab
определяет свойство Stack
с типом IReadOnlyList<Page>
, которое представляет текущий стек навигации в пределах Tab
. Этот класс также предоставляет следующие переопределяемые методы навигации:
GetNavigationStack
, возвращаетIReadOnlyList<Page>
текущий стек навигации.OnInsertPageBefore
, который вызывается при вызове методаINavigation.InsertPageBefore
.OnPopAsync
, который возвращаетTask<Page>
и вызывается при вызовеINavigation.PopAsync
.OnPopToRootAsync
, который возвращаетTask
и вызывается при вызовеINavigation.OnPopToRootAsync
.OnPushAsync
, который возвращаетTask
и вызывается при вызовеINavigation.PushAsync
.OnRemovePage
, который вызывается при вызове методаINavigation.RemovePage
.
В следующем примере показано переопределение метода OnRemovePage
:
public class MyTab : Tab
{
protected override void OnRemovePage(Page page)
{
base.OnRemovePage(page);
// Custom logic
}
}
В этом примере объекты MyTab
должны использоваться в визуальной иерархии оболочки, а не в объектах Tab
.
События навигации
Класс Shell
определяет событие Navigating
, которое возникает перед выполнением любого перехода — программного или вызванного действием пользователя. Объект ShellNavigatingEventArgs
, который прилагается к событию Navigating
, содержит следующие свойства:
Свойство | Type | Описание |
---|---|---|
Current |
ShellNavigationState |
URI текущей страницы. |
Source |
ShellNavigationSource |
Тип выполненного перехода. |
Target |
ShellNavigationState |
URI, представляющий целевой объект навигации. |
CanCancel |
bool |
Значение, указывающее, возможна ли отмена перехода. |
Cancelled |
bool |
Значение, указывающее, была ли навигация отменена. |
Кроме того, класс ShellNavigatingEventArgs
предоставляет метод Cancel
, позволяющий отменить переход, и метод GetDeferral
, возвращающий токен ShellNavigatingDeferral
, который можно использовать для завершения перехода. Дополнительные сведения: Отложенный переход.
Класс Shell
также определяет событие Navigated
, которое возникает при завершении навигации. Объект ShellNavigatedEventArgs
, который прилагается к событию Navigated
, содержит следующие свойства:
Свойство | Type | Описание |
---|---|---|
Current |
ShellNavigationState |
URI текущей страницы. |
Previous |
ShellNavigationState |
URI предыдущей страницы. |
Source |
ShellNavigationSource |
Тип выполненного перехода. |
Внимание
Метод OnNavigating
вызывается, когда генерируется событие Navigating
. Аналогично, метод OnNavigated
вызывается, когда генерируется событие Navigated
. Оба метода можно переопределить в подклассе Shell
для перехвата запросов навигации.
Классы ShellNavigatedEventArgs
и ShellNavigatingEventArgs
имеют свойства Source
с типом ShellNavigationSource
. Значения перечисления указаны ниже:
Unknown
Push
Pop
PopToRoot
Insert
Remove
ShellItemChanged
ShellSectionChanged
ShellContentChanged
Таким образом, переходы могут перехватываться в переопределении OnNavigating
, а действия могут выполняться на основе источника навигации. Например, следующий код позволяет отменить переход назад, если на странице есть несохраненные данные:
protected override void OnNavigating(ShellNavigatingEventArgs args)
{
base.OnNavigating(args);
// Cancel any back navigation.
if (args.Source == ShellNavigationSource.Pop)
{
args.Cancel();
}
// }
Отложенный переход
Переход в рамках оболочки можно перехватить, а затем завершить или отменить в зависимости от пользовательского выбора. Для этого можно переопределить метод OnNavigating
в подклассе Shell
и вызвать метод GetDeferral
для объекта ShellNavigatingEventArgs
. Этот метод возвращает токен ShellNavigatingDeferral
с методом Complete
, который можно использовать для завершения запроса о переходе:
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();
}
}
В этом примере отображается лист действий, приглашающий пользователя завершить запрос навигации или отменить его. Переход отменяется вызовом метода Cancel
для объекта ShellNavigatingEventArgs
. Переход завершается вызовом метода Complete
для токена ShellNavigatingDeferral
, полученного методом GetDeferral
, для объекта ShellNavigatingEventArgs
.
Предупреждение
Если пользователь пытается выполнить переход, когда имеется ожидающий отложенный переход, метод GoToAsync
выдает исключение InvalidOperationException
.
Передача данных
Данные можно передавать в параметрах запроса при выполнении программной навигации на основе URI. Это достигается путем добавления ?
после маршрута, за которым следует идентификатор параметра запроса, =
и значение. К примеру, следующий код выполняется в этом примере приложения, когда пользователь выбирает слона на странице ElephantsPage
:
async void OnCollectionViewSelectionChanged(object sender, SelectionChangedEventArgs e)
{
string elephantName = (e.CurrentSelection.FirstOrDefault() as Animal).Name;
await Shell.Current.GoToAsync($"elephantdetails?name={elephantName}");
}
Этот пример кода получает значения выбранного слона в параметре CollectionView
и переходит по маршруту elephantdetails
, передавая elephantName
как параметр запроса.
Существует два подхода к получению данных навигации.
- Класс той страницы, на которую осуществляется переход, или класс
BindingContext
этой страницы можно дополнить атрибутомQueryPropertyAttribute
для каждого параметра запроса. Дополнительные сведения об обработке данных навигации с помощью атрибутов свойств запроса см. в этом разделе. - Класс той страницы, на которую осуществляется переход, или класс
BindingContext
этой страницы может реализовать интерфейсIQueryAttributable
. Дополнительные сведения об обработке данных навигации с помощью одного метода см. в этом разделе.
Обработка данных навигации с помощью атрибутов свойств запроса
Данные навигации можно получить, дополнив принимающий класс атрибутом QueryPropertyAttribute
для каждого параметра запроса:
[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.");
}
}
}
Первый аргумент для QueryPropertyAttribute
свойства, который будет получать данные, с вторым аргументом, указывающим идентификатор параметра запроса. Поэтому в приведенном выше примере указывается, QueryPropertyAttribute
что Name
свойство получит данные, переданные в name
параметре запроса, из URI в вызове GoToAsync
метода. Метод задания свойств Name
вызывает метод LoadAnimal
для получения объекта Animal
для name
и задает его в качестве BindingContext
страницы.
Примечание.
Значения параметров запросов, полученные через QueryPropertyAttribute
, автоматически декодируются в URL-адресе.
Обработка данных навигации с помощью одного метода
Данные навигации можно получить, реализовав в принимающем классе интерфейс IQueryAttributable
. Интерфейс IQueryAttributable
указывает, что реализующий класс должен реализовать метод ApplyQueryAttributes
. У этого метода есть аргумент query
типа IDictionary<string, string>
, который содержит любые данные, передаваемые в процессе навигации. Каждый ключ в словаре — это идентификатор параметра запроса, значение которого является значением параметра запроса. Преимущество использования этого подхода заключается в том, что данные навигации можно обрабатывать с помощью одного метода. Это может быть полезно, если у вас есть несколько элементов данных навигации, требующих обработки в целом.
В следующем примере показан класс модели представления, реализующий интерфейс 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.");
}
}
...
}
В этом примере метод ApplyQueryAttributes
извлекает значение параметра запроса name
из URI в вызов метода GoToAsync
. Затем вызывается метод LoadAnimal
для извлечения объекта Animal
, где он устанавливается как значение свойства Monkey
, к которому привязаны данные.
Внимание
Значения параметров запросов, полученные через интерфейс IQueryAttributable
, не декодируются автоматически в URL-адресе.
Передача и обработка нескольких параметров запросов
Можно передавать несколько параметров запросов, соединяя их с помощью &
. Например, следующий код передает два элемента данных:
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}");
}
Этот пример кода получает значения выбранного слона в параметре CollectionView
и переходит по маршруту elephantdetails
, передавая elephantName
и elephantLocation
как параметры запроса.
Чтобы получать несколько элементов данных, класс той страницы, на которую осуществляется переход, или класс BindingContext
этой страницы можно дополнить атрибутом QueryPropertyAttribute
для каждого параметра запроса:
[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
}
}
...
}
В этом примере класс имеет атрибут QueryPropertyAttribute
для каждого параметра запроса. Первый атрибут QueryPropertyAttribute
указывает, что свойство Name
получит данные, переданные в параметре запроса name
, а второй атрибут QueryPropertyAttribute
указывает, что свойство Location
получит данные, переданные в параметре запроса location
. В обоих случаях значения параметров запросов указываются в URI в вызове метода GoToAsync
.
Кроме того, данные навигации можно обрабатывать одним методом, реализовав интерфейс IQueryAttributable
в классе, представляющем страницу, к которой осуществляется переход, или классе BindingContext
страницы:
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"]);
...
}
...
}
В этом примере метод ApplyQueryAttributes
извлекает значение параметров запроса name
и location
из URI в вызов метода GoToAsync
.
Действие кнопки "Назад"
Внешний вид и поведение кнопки "Назад" можно переопределить, присвоив присоединенное свойство BackButtonBehavior
объекту BackButtonBehavior
. Класс BackButtonBehavior
определяет следующие свойства:
Command
с типомICommand
, который выполняется при нажатии кнопки "Назад".CommandParameter
с типомobject
, который передается как параметр вCommand
.IconOverride
с типомImageSource
, который содержит значок для кнопки "Назад".IsEnabled
с типомboolean
, который указывает, доступна ли кнопка перехода назад. Значение по умолчанию —true
.TextOverride
с типомstring
, который содержит текст для кнопки "Назад".
Все эти свойства поддерживаются объектами BindableProperty
, то есть их можно указывать в качестве целевых для привязки данных.
В следующем коде показан пример переопределения внешнего вида и поведения кнопки "Назад":
<ContentPage ...>
<Shell.BackButtonBehavior>
<BackButtonBehavior Command="{Binding BackCommand}"
IconOverride="back.png" />
</Shell.BackButtonBehavior>
...
</ContentPage>
Эквивалентный код на C# выглядит так:
Shell.SetBackButtonBehavior(this, new BackButtonBehavior
{
Command = new Command(() =>
{
...
}),
IconOverride = "back.png"
});
Свойство Command
получает значение ICommand
для выполнения при нажатии кнопки "Назад", а в свойстве IconOverride
сохраняется значок, используемый для кнопки "Назад":