Как вы переключаете страницы в Xamarin.Forms?

99

Как вы переключаетесь между страницами в Xamarin Forms?

Моя главная страница - ContentPage, и я не хочу переключаться на что-то вроде страницы с вкладками.

Я смог сделать это псевдо, найдя родителей элементов управления, которые должны запускать новую страницу, пока я не найду ContentPage, а затем поменяю Content на элементы управления для новой страницы. Но это кажется действительно небрежным.

Эрик
источник
На этот вопрос уже было дано множество ответов, чтобы увидеть, как это можно сделать с помощью структурного шаблона MVVM, см. Этот stackoverflow.com/a/37142513/9403963
Алиреза Саттари

Ответы:

67

Xamarin.Forms поддерживает несколько встроенных узлов навигации:

  • NavigationPage, где открывается следующая страница,
  • TabbedPage, тот, который тебе не нравится
  • CarouselPage, что позволяет переключаться влево и вправо на следующие / предыдущие страницы.

Вдобавок ко всему, все страницы также поддерживают, PushModalAsync()что просто вставляет новую страницу поверх существующей.

В самом конце, если вы хотите убедиться, что пользователь не может вернуться на предыдущую страницу (с помощью жеста или аппаратной кнопки «Назад»), вы можете оставить то же самое Pageи заменить его Content.

Предлагаемые варианты замены корневой страницы также работают, но вам придется обрабатывать это по-разному для каждой платформы.

Стефан Делькруа
источник
PushModalAsync кажется частью навигации, верно? Я не могу понять, как добраться до объекта / класса навигации. Я предполагаю, что мне нужно получить доступ к чему-то, что реализует INavigation, но что?
Эрик
Если ваша страница содержится в NavigationPage, у вас должна быть возможность получить доступ к свойству Navigation изнутри страницы
Джейсон,
1
Как только я начал использовать NavigationPage, все встало на свои места. Спасибо
Эрик
1
@stephane, пожалуйста, скажите, является ли моя первая страница CarouselPage, а моя вторая страница - masterDetailPage, тогда как я могу переключить страницу stackoverflow.com/questions/31129845/…
Атул Дханука 01
64

В классе App вы можете установить MainPage на страницу навигации и установить корневую страницу на ContentPage:

public App ()
{
    // The root page of your application
    MainPage = new NavigationPage( new FirstContentPage() );
}

Затем в вашем первом вызове ContentPage:

Navigation.PushAsync (new SecondContentPage ());
Дэвид Дуглас
источник
Я сделал это, но по-прежнему открывается главная страница. Любая страница, которую я установил на главную, не имеет никакого эффекта. Я просто открываю первую страницу набора. В чем проблема?
Behzad
Visual Studio предлагает импорт Android.Content.Resдля навигации. Это кажется неправильным, откуда мне его импортировать?
Christian
41

Если ваш проект настроен как проект форм PCL (и, скорее всего, также как общие формы, но я этого не пробовал), существует класс App.cs, который выглядит следующим образом:

public class App
{
    public static Page GetMainPage ()
    {     
        AuditorDB.Model.Extensions.AutoTimestamp = true;
        return new NavigationPage (new LoginPage ());
    }
}

вы можете изменить GetMainPageметод, чтобы вернуть новую TabbedPaged или другую страницу, которую вы определили в проекте.

Оттуда вы можете добавлять команды или обработчики событий для выполнения кода и выполнения

// to show OtherPage and be able to go back
Navigation.PushAsync(new OtherPage());

// to show AnotherPage and not have a Back button
Navigation.PushModalAsync(new AnotherPage()); 

// to go back one step on the navigation stack
Navigation.PopAsync();
Стен Петров
источник
3
Это не переключает между страницами. Это изменяет только то, какая страница загружается изначально.
dakamojo 06
ваш вопрос касался главной страницы. см. обновленный ответ для примеров навигации
Стен Петров
Что, черт возьми, Navigationв этом примере? - Это вы где-то создали? - Я не вижу этого в этом примере кода.
BrainSlugs83
Навигация - это свойство страницы
Стен Петров
Спасибо; FTR PushAsync()у меня не работал, а PushModalAsync()работал
knocte
23

Поместите новую страницу в стопку, затем удалите текущую страницу. Это приводит к переключению.

item.Tapped += async (sender, e) => {
    await Navigation.PushAsync (new SecondPage ());
    Navigation.RemovePage(this);
};

Сначала вам нужно перейти на страницу навигации:

MainPage = NavigationPage(new FirstPage());

Переключение контента не идеально, поскольку у вас есть только одна большая страница и один набор событий страницы, таких как OnAppearing и т. Д.

Дэниел Робертс
источник
Navigation.RemovePage();не поддерживается на Android.
Рохит Випин Мэтьюз
1
Navigation.RemovePage (страница); работает в Android, сначала необходимо перейти на страницу навигации.
Дэниел Робертс
Я широко использую его в своем проекте на Forms 1.4.2. Возможно, они исправили ошибку, или мне просто повезло, и я еще не нашел ее.
Дэниел Робертс
У меня последняя версия, и я могу ее воспроизвести. Так что я считаю, что тебе повезло.
Рохит Випин Мэтьюз
2
Полезный совет - чтобы убрать переходы при смене страницы, добавьте в качестве второго параметра false:await Navigation.PushAsync(new SecondPage(),false);
Дамиан Грин
8

Если вы не хотите переходить на предыдущую страницу, то есть не позволяете пользователю вернуться к экрану входа в систему после завершения авторизации, вы можете использовать;

 App.Current.MainPage = new HomePage();

Если вы хотите включить функцию возврата, просто используйте

Navigation.PushModalAsync(new HomePage())
Бакер Накви
источник
4

Похоже, эта ветка очень популярна, и будет грустно не упомянуть здесь, что есть альтернативный способ - ViewModel First Navigation. Большинство фреймворков MVVM используют его, однако, если вы хотите понять, о чем он, продолжайте читать.

Вся официальная документация Xamarin.Forms демонстрирует простое, но немного не чистое решение MVVM. Это потому, что Page(View) ничего не должен знать о ViewModelи наоборот. Вот отличный пример этого нарушения:

// C# version
public partial class MyPage : ContentPage
{
    public MyPage()
    {
        InitializeComponent();
        // Violation
        this.BindingContext = new MyViewModel();
    }
}

// XAML version
<?xml version="1.0" encoding="utf-8"?>
<ContentPage
    xmlns="http://xamarin.com/schemas/2014/forms"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:viewmodels="clr-namespace:MyApp.ViewModel"
    x:Class="MyApp.Views.MyPage">
    <ContentPage.BindingContext>
        <!-- Violation -->
        <viewmodels:MyViewModel />
    </ContentPage.BindingContext>
</ContentPage>

Если у вас есть двухстраничное приложение, этот подход может вам подойти. Однако, если вы работаете над большим корпоративным решением, вам лучше выбрать ViewModel First Navigationподход. Это немного более сложный, но гораздо более понятный подход, который позволяет вам перемещаться ViewModelsмежду Pages(представлениями), а не между ними . Одним из преимуществ помимо четкого разделения задач является то, что вы можете легко передать параметры следующему ViewModelили выполнить асинхронный код инициализации сразу после навигации. Теперь к деталям.

(Я постараюсь максимально упростить все примеры кода).

1. Прежде всего нам нужно место, где мы могли бы зарегистрировать все наши объекты и, при желании, определить их время жизни. Для этого мы можем использовать контейнер IOC, вы можете выбрать его сами. В этом примере я буду использовать Autofac (это один из самых быстрых доступных). Мы можем сохранить ссылку на него в, Appчтобы он был доступен глобально (не очень хорошая идея, но необходима для упрощения):

public class DependencyResolver
{
    static IContainer container;

    public DependencyResolver(params Module[] modules)
    {
        var builder = new ContainerBuilder();

        if (modules != null)
            foreach (var module in modules)
                builder.RegisterModule(module);

        container = builder.Build();
    }

    public T Resolve<T>() => container.Resolve<T>();
    public object Resolve(Type type) => container.Resolve(type);
}

public partial class App : Application
{
    public DependencyResolver DependencyResolver { get; }

    // Pass here platform specific dependencies
    public App(Module platformIocModule)
    {
        InitializeComponent();
        DependencyResolver = new DependencyResolver(platformIocModule, new IocModule());
        MainPage = new WelcomeView();
    }

    /* The rest of the code ... */
}

2. Нам понадобится объект, ответственный за получение Page(View) для конкретного ViewModelи наоборот. Второй случай может быть полезен в случае установки корневой / главной страницы приложения. Для этого мы должны договориться о простом соглашении, согласно которому все объекты ViewModelsдолжны находиться в ViewModelsкаталоге, а Pages(представления) должны находиться в Viewsкаталоге. Другими словами, он ViewModelsдолжен находиться в [MyApp].ViewModelsпространстве имен, а Pages(Представления) - в [MyApp].Viewsпространстве имен. В дополнение к этому мы должны согласиться, что WelcomeView(Страница) должна иметь WelcomeViewModelи т.д. Вот пример кода преобразователя:

public class TypeMapperService
{
    public Type MapViewModelToView(Type viewModelType)
    {
        var viewName = viewModelType.FullName.Replace("Model", string.Empty);
        var viewAssemblyName = GetTypeAssemblyName(viewModelType);
        var viewTypeName = GenerateTypeName("{0}, {1}", viewName, viewAssemblyName);
        return Type.GetType(viewTypeName);
    }

    public Type MapViewToViewModel(Type viewType)
    {
        var viewModelName = viewType.FullName.Replace(".Views.", ".ViewModels.");
        var viewModelAssemblyName = GetTypeAssemblyName(viewType);
        var viewTypeModelName = GenerateTypeName("{0}Model, {1}", viewModelName, viewModelAssemblyName);
        return Type.GetType(viewTypeModelName);
    }

    string GetTypeAssemblyName(Type type) => type.GetTypeInfo().Assembly.FullName;
    string GenerateTypeName(string format, string typeName, string assemblyName) =>
        string.Format(CultureInfo.InvariantCulture, format, typeName, assemblyName);
}

3. В случае установки корневой страницы нам понадобится что-то вроде того, ViewModelLocatorчто установит BindingContextавтоматически:

public static class ViewModelLocator
{
    public static readonly BindableProperty AutoWireViewModelProperty =
        BindableProperty.CreateAttached("AutoWireViewModel", typeof(bool), typeof(ViewModelLocator), default(bool), propertyChanged: OnAutoWireViewModelChanged);

    public static bool GetAutoWireViewModel(BindableObject bindable) =>
        (bool)bindable.GetValue(AutoWireViewModelProperty);

    public static void SetAutoWireViewModel(BindableObject bindable, bool value) =>
        bindable.SetValue(AutoWireViewModelProperty, value);

    static ITypeMapperService mapper = (Application.Current as App).DependencyResolver.Resolve<ITypeMapperService>();

    static void OnAutoWireViewModelChanged(BindableObject bindable, object oldValue, object newValue)
    {
        var view = bindable as Element;
        var viewType = view.GetType();
        var viewModelType = mapper.MapViewToViewModel(viewType);
        var viewModel =  (Application.Current as App).DependencyResolver.Resolve(viewModelType);
        view.BindingContext = viewModel;
    }
}

// Usage example
<?xml version="1.0" encoding="utf-8"?>
<ContentPage
    xmlns="http://xamarin.com/schemas/2014/forms"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:viewmodels="clr-namespace:MyApp.ViewModel"
    viewmodels:ViewModelLocator.AutoWireViewModel="true"
    x:Class="MyApp.Views.MyPage">
</ContentPage>

4.Наконец, нам понадобится NavigationServiceподдерживающий ViewModel First Navigationподход:

public class NavigationService
{
    TypeMapperService mapperService { get; }

    public NavigationService(TypeMapperService mapperService)
    {
        this.mapperService = mapperService;
    }

    protected Page CreatePage(Type viewModelType)
    {
        Type pageType = mapperService.MapViewModelToView(viewModelType);
        if (pageType == null)
        {
            throw new Exception($"Cannot locate page type for {viewModelType}");
        }

        return Activator.CreateInstance(pageType) as Page;
    }

    protected Page GetCurrentPage()
    {
        var mainPage = Application.Current.MainPage;

        if (mainPage is MasterDetailPage)
        {
            return ((MasterDetailPage)mainPage).Detail;
        }

        // TabbedPage : MultiPage<Page>
        // CarouselPage : MultiPage<ContentPage>
        if (mainPage is TabbedPage || mainPage is CarouselPage)
        {
            return ((MultiPage<Page>)mainPage).CurrentPage;
        }

        return mainPage;
    }

    public Task PushAsync(Page page, bool animated = true)
    {
        var navigationPage = Application.Current.MainPage as NavigationPage;
        return navigationPage.PushAsync(page, animated);
    }

    public Task PopAsync(bool animated = true)
    {
        var mainPage = Application.Current.MainPage as NavigationPage;
        return mainPage.Navigation.PopAsync(animated);
    }

    public Task PushModalAsync<TViewModel>(object parameter = null, bool animated = true) where TViewModel : BaseViewModel =>
        InternalPushModalAsync(typeof(TViewModel), animated, parameter);

    public Task PopModalAsync(bool animated = true)
    {
        var mainPage = GetCurrentPage();
        if (mainPage != null)
            return mainPage.Navigation.PopModalAsync(animated);

        throw new Exception("Current page is null.");
    }

    async Task InternalPushModalAsync(Type viewModelType, bool animated, object parameter)
    {
        var page = CreatePage(viewModelType);
        var currentNavigationPage = GetCurrentPage();

        if (currentNavigationPage != null)
        {
            await currentNavigationPage.Navigation.PushModalAsync(page, animated);
        }
        else
        {
            throw new Exception("Current page is null.");
        }

        await (page.BindingContext as BaseViewModel).InitializeAsync(parameter);
    }
}

Как вы можете видеть, существует BaseViewModelабстрактный базовый класс для всех, ViewModelsгде вы можете определять такие методы, InitializeAsyncкоторые будут выполняться сразу после навигации. А вот пример навигации:

public class WelcomeViewModel : BaseViewModel
{
    public ICommand NewGameCmd { get; }
    public ICommand TopScoreCmd { get; }
    public ICommand AboutCmd { get; }

    public WelcomeViewModel(INavigationService navigation) : base(navigation)
    {
        NewGameCmd = new Command(async () => await Navigation.PushModalAsync<GameViewModel>());
        TopScoreCmd = new Command(async () => await navigation.PushModalAsync<TopScoreViewModel>());
        AboutCmd = new Command(async () => await navigation.PushModalAsync<AboutViewModel>());
    }
}

Как вы понимаете, этот подход более сложен, труднее отлаживать и может сбивать с толку. Однако есть много преимуществ, плюс вам на самом деле не нужно реализовывать это самостоятельно, поскольку большинство фреймворков MVVM поддерживают его из коробки. Демонстрируемый здесь пример кода доступен на github .

Есть много хороших статей о ViewModel First Navigationподходе, и есть бесплатная электронная книга Enterprise Application Patterns с использованием Xamarin.Forms, в которой подробно объясняется эта и многие другие интересные темы.

EvZ
источник
3

Используя метод PushAsync (), вы можете нажимать, а PopModalAsync () вы можете вставлять страницы в стек навигации и из него. В моем примере кода ниже у меня есть страница навигации (корневая страница), и с этой страницы я нажимаю страницу содержимого, которая является страницей входа, как только я завершу свою страницу входа, я возвращаюсь на корневую страницу

~~~ Навигацию можно рассматривать как стек объектов страницы «последним вошел - первым ушел». Для перехода с одной страницы на другую приложение помещает новую страницу в этот стек. Чтобы вернуться на предыдущую страницу, приложение вытолкнет текущую страницу из стека. Эта навигация в Xamarin.Forms обрабатывается интерфейсом INavigation.

Xamarin.Forms имеет класс NavigationPage, который реализует этот интерфейс и будет управлять стеком страниц. Класс NavigationPage также добавит панель навигации в верхнюю часть экрана, которая отображает заголовок, а также будет иметь соответствующую платформу кнопку Назад, которая вернет на предыдущую страницу. Следующий код показывает, как обернуть NavigationPage вокруг первой страницы в приложении:

Ссылку на содержимое, перечисленное выше, и ссылку, которую следует просмотреть для получения дополнительных сведений о формах Xamarin, см. В разделе «Навигация»:

http://developer.xamarin.com/guides/cross-platform/xamarin-forms/introduction-to-xamarin-forms/

~~~

public class MainActivity : AndroidActivity
{
    protected override void OnCreate(Bundle bundle)
    {
        base.OnCreate(bundle);

        Xamarin.Forms.Forms.Init(this, bundle);
        // Set our view from the "main" layout resource
        SetPage(BuildView());
    }

    static Page BuildView()
    {
        var mainNav = new NavigationPage(new RootPage());
        return mainNav;
    }
}


public class RootPage : ContentPage
{
    async void ShowLoginDialog()
    {
        var page = new LoginPage();

        await Navigation.PushModalAsync(page);
    }
}

// Удален код для простоты, отображается только поп

private async void AuthenticationResult(bool isValid)
{
    await navigation.PopModalAsync();
}
Чад Бонтуис
источник
2

Переход с одной страницы на другую в Xamarin.forms с использованием свойства Navigation Ниже образца кода

void addClicked(object sender, EventArgs e)
        {
            //var createEmp = (Employee)BindingContext;
            Employee emp = new Employee();
            emp.Address = AddressEntry.Text;   
            App.Database.SaveItem(emp);
            this.Navigation.PushAsync(new EmployeeDetails());
  this.Navigation.PushModalAsync(new EmployeeDetails());
        }

Для перехода с одной страницы на другую с помощью ячейки просмотра Ниже кода Xamrian.forms

 private async void BtnEdit_Clicked1(object sender, EventArgs e)
        {
            App.Database.GetItem(empid);
            await App.Current.MainPage.Navigation.PushModalAsync(new EmployeeRegistration(empid));
        }

Пример как ниже

public class OptionsViewCell : ViewCell
    {
        int empid;
        Button btnEdit;
        public OptionsViewCell()
        {
        }
        protected override void OnBindingContextChanged()
        {
            base.OnBindingContextChanged();

            if (this.BindingContext == null)
                return;

            dynamic obj = BindingContext;
            empid = Convert.ToInt32(obj.Eid);
            var lblname = new Label
            {
                BackgroundColor = Color.Lime,
                Text = obj.Ename,
            };

            var lblAddress = new Label
            {
                BackgroundColor = Color.Yellow,
                Text = obj.Address,
            };

            var lblphonenumber = new Label
            {
                BackgroundColor = Color.Pink,
                Text = obj.phonenumber,
            };

            var lblemail = new Label
            {
                BackgroundColor = Color.Purple,
                Text = obj.email,
            };

            var lbleid = new Label
            {
                BackgroundColor = Color.Silver,
                Text = (empid).ToString(),
            };

             //var lbleid = new Label
            //{
            //    BackgroundColor = Color.Silver,
            //    // HorizontalOptions = LayoutOptions.CenterAndExpand
            //};
            //lbleid.SetBinding(Label.TextProperty, "Eid");
            Button btnDelete = new Button
            {
                BackgroundColor = Color.Gray,

                Text = "Delete",
                //WidthRequest = 15,
                //HeightRequest = 20,
                TextColor = Color.Red,
                HorizontalOptions = LayoutOptions.EndAndExpand,
            };
            btnDelete.Clicked += BtnDelete_Clicked;
            //btnDelete.PropertyChanged += BtnDelete_PropertyChanged;  

            btnEdit = new Button
            {
                BackgroundColor = Color.Gray,
                Text = "Edit",
                TextColor = Color.Green,
            };
            // lbleid.SetBinding(Label.TextProperty, "Eid");
            btnEdit.Clicked += BtnEdit_Clicked1; ;
            //btnEdit.Clicked += async (s, e) =>{
            //    await App.Current.MainPage.Navigation.PushModalAsync(new EmployeeRegistration());
            //};

            View = new StackLayout()
            {
                Orientation = StackOrientation.Horizontal,
                BackgroundColor = Color.White,
                Children = { lbleid, lblname, lblAddress, lblemail, lblphonenumber, btnDelete, btnEdit },
            };

        }

        private async void BtnEdit_Clicked1(object sender, EventArgs e)
        {
            App.Database.GetItem(empid);
            await App.Current.MainPage.Navigation.PushModalAsync(new EmployeeRegistration(empid));
        }



        private void BtnDelete_Clicked(object sender, EventArgs e)
        {
            // var eid = Convert.ToInt32(empid);
            // var item = (Xamarin.Forms.Button)sender;
            int eid = empid;
            App.Database.DeleteItem(empid);
        }

    }
Манохар
источник
2

Вызов:

((App)App.Current).ChangeScreen(new Map());

Создайте этот метод внутри App.xaml.cs:

public void ChangeScreen(Page page)
{
     MainPage = page;
}
Alansiqueira27
источник
2
In App.Xaml.Cs:

MainPage = new NavigationPage( new YourPage());

Если вы хотите перейти с YourPage на следующую страницу, вы делаете:

await Navigation.PushAsync(new YourSecondPage());

Вы можете узнать больше о навигации Xamarin Forms здесь: https://docs.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/navigation/hierarchical

У Microsoft есть неплохие документы по этому поводу.

Существует также более новая концепция Shell. Это позволяет по-новому структурировать ваше приложение и в некоторых случаях упрощает навигацию.

Введение: https://devblogs.microsoft.com/xamarin/shell-xamarin-forms-4-0-getting-started/

Видео по основам Shell: https://www.youtube.com/watch?v=0y1bUAcOjZY&t=3112s

Документы: https://docs.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/shell/

Йеспер Бальцерсен
источник
0

Страница XAML добавить это

<ContentPage.ToolbarItems>
            <ToolbarItem Text="Next" Order="Primary"
            Activated="Handle_Activated"/>

</ContentPage.ToolbarItems>   

на странице CS

 async void Handle_Activated(object sender, System.EventArgs e)
        {
            await App.Navigator.PushAsync(new PAGE());
        }
Pxaml
источник
0

После PushAsyncиспользования PopAsyncthis) для удаления текущей страницы.

await Navigation.PushAsync(new YourSecondPage());
this.Navigation.PopAsync(this);
АлиСафдер
источник
0

В Xamarin у нас есть страница NavigationPage. Он содержит стек ContentPages. NavigationPage имеет такие методы, как PushAsync () и PopAsync (). PushAsync добавляет страницу вверху стека, тогда эта страница станет активной в данный момент. PopAsync () удаляет страницу из вершины стека.

В App.Xaml.Cs мы можем установить как.

MainPage = новая страница навигации (новая ваша страница ());

ждать Navigation.PushAsync (новый newPage ()); этот метод добавит newPage наверху стека. В это время активной страницей будет nePage.

PRASAD CP
источник