Стек навигации

Последнее обновление: 19.02.2023

Когда происходит навигация на страницу с помощью вызова метода 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.

стек навигации NavigationStack в .NET MAUI и C#

Аналогичным образом методы PushModalAsync() и PopModalAsync() изменяют содержимое в ModalStack.

Причем такое разделение на два стека имеет большое значение: мы можем перейти с обычной страницы на любую страницу, но с модальной мы можем перейти только на модальную страницу, или вернуться назад.

Для рассмотрения работы со стеками пусть в проекте будут три страницы - MainPage (главная) и дополнительные страницы Page2 и Page3:

История навигации в .NET MAUI и C#

Код главной страницы 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();
    } 
}
Вывод стека страниц NavigationStack в .NET MAUI и C#

Кроме того, мы можем получать различные траницы по индексу в стеке и тем самым манипулировать ими. Например, переход назад из 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.

Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850