Когда происходит навигация на страницу с помощью вызова метода PushAsync()
или PushModalAsync()
, то возникает ряд действий:
У страницы, с которой осуществляется переход, вызывается переопределенная версия метод OnDisappearing()
(если данный метод в классе страницы переопределен)
У страницы, на которую осуществляется переход, вызывается переопределенная версия метод OnAppearing()
(если данный метод в классе страницы переопределен)
После этого завершается выполнение методов PushAsync/PushModalAsync
Вызов методов PopAsync()
и PopModalAsync()
фактически приводит к тем же самым действиям, только в обратную сторону:
У страницы, с которой осуществляется переход, вызывается переопределенная версия метод OnDisappearing()
(вызывается при скрытии страницы)
У страницы, на которую осуществляется переход, вызывается переопределенная версия метод OnAppearing()
(вызывается при отображении страницы)
После этого завершается выполнение методов PopAsync/PopModalAsync
Из этих правил есть одно исключение: на платформе Android у страницы, которая вызывает метод PushModalAsync
, не вызывается метод
OnDisappearing()
. И аналогично у страницы, которая вызвала метод PopModalAsync()
, не вызывается метод OnAppearing()
.
Для управления переходами интерфейс INavigation кроме вышеупомянутых методов также определяет два свойства:
NavigationStack: стек страниц, который содержит все немодальные обычные страницы
ModalStack: стек модальных страниц
Эти свойства представляют коллекцию IReadOnlyList<Page>
. Напрямую эти свойства доступны только для чтения, и мы можем влиять на них
только с помощью вышеуказанных методов. Так, метод PushAsync()
добавляет страницу в NavigationStack, а метод PopAsync()
, наоборот,
извлекает последнюю страницу из NavigationStack.
Аналогичным образом методы PushModalAsync()
и PopModalAsync()
изменяют содержимое в ModalStack.
Причем такое разделение на два стека имеет большое значение: мы можем перейти с обычной страницы на любую страницу, но с модальной мы можем перейти только на модальную страницу, или вернуться назад.
Для рассмотрения работы со стеками пусть в проекте будут три страницы - MainPage (главная) и дополнительные страницы Page2 и Page3:
Код главной страницы MainPage будет выглядеть следующим образом:
namespace HelloApp; public partial class MainPage : ContentPage { Label stackLabel; bool loaded = false; public MainPage() { //InitializeComponent(); Title = "Main Page"; Button forwardButton = new Button {Text = "Вперед" }; forwardButton.Clicked += GoToForward; stackLabel = new Label(); Content = new StackLayout { Children = { forwardButton, stackLabel } }; } protected override void OnAppearing() { base.OnAppearing(); if (loaded == false) { DisplayStack(); loaded = true; } } protected internal void DisplayStack() { if (Application.Current?.MainPage is NavigationPage navPage) { stackLabel.Text = ""; foreach (Page p in navPage.Navigation.NavigationStack) { stackLabel.Text = $"{p.Title}\n{stackLabel.Text}"; } } } // Переход вперед на Page2 private async void GoToForward(object? sender, EventArgs e) { Page2 page = new Page2(); await Navigation.PushAsync(page); page.DisplayStack(); } }
На этой странице определяется кнопка для перехода вперед к странице Page2 и метка, в которую выводится текущее содержимое из NavigationStack.
Прежде всего здесь надо отметить метод DisplayStack()
, который выводит все содержимое стека NavigationStack. Хотя в каждой странице мы
нам напрямую доступно свойство Navigation.NavigationStack
, однако это будет не общий стек, а стек, ассоциированный непосредственно с текущей страницей.
Более того на момент использования по умолчанию он будет иметь 0 элементов. И чтобы получить общий стек, нам надо обратиться к NavigationPage:
NavigationPage? navPage = Application.Current?.MainPage as NavigationPage; var stack = navPage?.Navigation.NavigationStack;
Чтобы отобразить этот стек в MainPage мы переопределяем метод OnAppearing(), который срабатывает после загрузки страницы, в том числе после перехода
на эту страницу. Причем здесь отображение стека срабатывает только один раз - при самой первой загрузке страницы. И чтобы этот момент отследить, применяется вспомогательная
переменная loaded
.
Третий момент - переход на страницу Page2 в обработчике кнопки. Здесь выполняется метод DisplayStack()
у Page2 после того, как отработает
метод await Navigation.PushAsync(page)
:
Page2 page = new Page2(); await Navigation.PushAsync(page); page.DisplayStack();
Не забудем установить в файле App.xaml.cs в качестве типа стартовой страницы NavigationPage:
namespace HelloApp; public partial class App : Application { public App() { InitializeComponent(); MainPage = new NavigationPage(new MainPage()); } }
Теперь определим страницу Page2:
namespace HelloApp; public class Page2 : ContentPage { Label stackLabel; public Page2() { Title = "Page 2"; Button forwardBtn = new Button { Text = "Вперед"}; forwardBtn.Clicked += GoToForward; Button backBtn = new Button { Text = "Назад" }; backBtn.Clicked += GoToBack; stackLabel = new Label(); Content = new StackLayout { Children = { forwardBtn, backBtn, stackLabel } }; } protected internal void DisplayStack() { if (Application.Current?.MainPage is NavigationPage navPage) { // выводим стек навигации stackLabel.Text = ""; foreach (Page p in navPage.Navigation.NavigationStack) { stackLabel.Text = $"{p.Title}\n{stackLabel.Text}"; } } } // Переход вперед на Page3 private async void GoToForward(object? sender, EventArgs e) { Page3 page = new Page3(); await Navigation.PushAsync(page); page.DisplayStack(); // вызываем у Page3 метод DisplayStack } // Переход обратно на MainPage private async void GoToBack(object? sender, EventArgs e) { await Navigation.PopAsync(); if(Application.Current?.MainPage is NavigationPage navPage) { if (navPage.CurrentPage is MainPage mainPage) { mainPage.DisplayStack(); } } } }
Здесь определены две кнопки для перехода вперед к Page3 и назад к MainPage. И также определен метод DisplayStack()
, который аналогичен версии
в MainPage.
Переход вперед к Page3 здесь аналогичен переходу к Page2 из MainPage.
А вот при переходе назад мы получаем страницу которая является последней в стеке (в данном случае MainPage) и вызываем у нее метод DisplayStack.
Класс NavigationPage определяет свойство CurrentPage. Это свойство указывает на страницу, которая находится последней коллекции NavigationStack
.
И также определим страницу Page3:
namespace HelloApp; public class Page3 : ContentPage { Label stackLabel; public Page3() { Title = "Page 3"; Button backBtn = new Button { Text = "Назад" }; backBtn.Clicked += GoToBack; // метка для вывода стека навигации stackLabel = new Label(); Content = new StackLayout { Children = { backBtn, stackLabel } }; } protected internal void DisplayStack() { if (Application.Current?.MainPage is NavigationPage navPage) { // выводим стек навигации stackLabel.Text = ""; foreach (Page p in navPage.Navigation.NavigationStack) { stackLabel.Text = $"{p.Title}\n{stackLabel.Text}"; } } } // Переход обратно на Page2 private async void GoToBack(object? sender, EventArgs e) { await Navigation.PopAsync(); // получаем страницу навигации if(Application.Current?.MainPage is NavigationPage navPage) { // получаем страницу Page2 и вызываем у ней метод DisplayStack if (navPage.CurrentPage is Page2 page) page.DisplayStack(); } } }
Здесь также определены кнопка назад для перехода к Page2 и метка для вывода стека.
Может возникнуть вопрос: а зачем нам вызывать метод DisplayStack()
у каждой страницы при переходе, если мы, допустим, можем это сделать
в переопределенном методе OnAppearing()
, который в любом случае вызывается системой при переходе на страницу?
Дело в том, что момент вызова OnAppearing()
неопределен в том смысле, что он может происходить в то время, пока вызовы методов перехода
PushAsync()
и PopAsync()
еще полностью не завершились. Соответственно пока не завершатся эти методы, в стеке может сохраняться страница,
с которой был осуществлен переход. Поэтому, чтобы убедиться, что стек изменен, вывод стека делается в обработчиках кнопок именно после завершения методов навигации:
private async void GoToBack(object sender, EventArgs e) { await Navigation.PopAsync(); // переход завершен, стек изменился, можно выводить содержимое стека // получаем страницу навигации if(Application.Current?.MainPage is NavigationPage navPage) { // получаем страницу Page2 и вызываем у ней метод DisplayStack if (navPage.CurrentPage is Page2 page) page.DisplayStack(); } }
Кроме того, мы можем получать различные траницы по индексу в стеке и тем самым манипулировать ими. Например, переход назад из Page3 на Page2 можно было бы осуществить так:
private async void GoToBack(object? sender, EventArgs e) { await Navigation.PopAsync(); if(Application.Current?.MainPage is NavigationPage navPage) { // получаем последнюю страницу в стеке int pageCount = navPage.Navigation.NavigationStack.Count; // navPage.Navigation.NavigationStack[pageCount - 1] - страница Page2 if(navPage.Navigation.NavigationStack[pageCount - 1] is Page2 page) page.DisplayStack(); } }
Для управления стеком страниц интерфейс INavigation определяет три дополнительных метода:
RemovePage(page): удаляет страницу page из стека
InsertPageBefore(pageA, pageB): вставляет страницу pageA в стеке перед страницей pageB
PopToRootAsync(): переходит на главую страницу
Например, добавим в стек страницу Page3 перед Page2, для этого изменим в классе Page3 обработчик кнопки:
private async void GoToBack(object? sender, EventArgs e) { await Navigation.PopAsync(); // получаем страницу навигации if (Application.Current?.MainPage is NavigationPage navPage) { // получаем последнюю страницу в стеке int pageCount = navPage.Navigation.NavigationStack.Count; if (navPage.Navigation.NavigationStack[pageCount - 1] is Page2 page) { // добавляем Page3 перед страницей page navPage.Navigation.InsertPageBefore(new Page3 { Title = "New Page 3" }, page); page.DisplayStack(); } } }
Или добавим на Page3 новую кнопку, а в качестве обработчика кнопки назначим следующий метод:
async void GoToRoot(object? sender, EventArgs e) { await Navigation.PopToRootAsync(); // переход на начальную страницу if (Application.Current?.MainPage is NavigationPage navPage) { if (navPage.CurrentPage is MainPage page) page.DisplayStack(); } }
И в данном случае будет идти перенаправление на страницу навигации MainPage.