如何在Xamarin.Forms中切换页面?


99

如何在Xamarin表单中的页面之间切换?

我的主页是ContentPage,我不想切换到选项卡式页面。

通过找到应该触发新页面的控件的父级,直到找到ContentPage,然后将内容与控件换出新页面,我才能够进行伪操作。但这似乎很草率。


该问题已经有了很多答案,以查看如何使用MVVM结构模式完成此问题,请参阅此stackoverflow.com/a/37142513/9403963
Alireza Sattari

Answers:


67

Xamarin.Forms 支持内置的多个导航主机:

  • NavigationPage,下一页会滑入
  • TabbedPage,你不喜欢的那个
  • CarouselPage,可以左右切换到下一页/上一页。

最重要的是,所有页面还支持PushModalAsync()将新页面推到现有页面之上的功能。

最后,如果您要确保用户无法返回上一页(使用手势或后退硬件按钮),则可以保持原样Page显示并替换其页面。Content

建议的替换根页面的选项也可以使用,但是对于每种平台,您都必须以不同的方式进行处理。


PushModalAsync似乎是Navigation的一部分,对不对?我不知道如何到达导航对象/类。我以为我需要访问实现INavigation的内容,但是呢?
埃里克

如果您的网页包含在NavigationPage内,你应该能够从你的页面中访问导航属性
贾森-

1
一旦开始使用NavigationPage,一切都准备就绪。谢谢
Eric

1
@stephane请告诉我,如果第一页是CarouselPage和我的第二页是masterDetailPage那我该怎么切换页面 stackoverflow.com/questions/31129845/...
阿图尔Dhanuka

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导航。这似乎不对,我必须从哪里导入它?
基督教徒

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 2014年

您的问题是在谈论主页。查看导航示例的最新答案
Sten Petrov 2014年

Navigation在这个例子中到底是什么?-那是您在某处创建的对象吗?-在此代码示例中没有看到它。
BrainSlugs83 '16

导航位于页面上
Sten Petrov

谢谢; 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上不受支持。
Rohit Vipin Mathews 2015年

1
Navigation.RemovePage(page); 在Android中工作,首先需要在导航页面中。
Daniel Roberts

我在Forms 1.4.2中的项目中广泛使用了它。也许他们修复了该错误,或​​者我只是很幸运,但是还没有找到它。
Daniel Roberts

我使用的是最新版本,并且能够复制它。所以我相信你太幸运了。
Rohit Vipin Mathews 2015年

2
方便的提示-要在更改页面时删除过渡,请添加false作为第二个参数:await Navigation.PushAsync(new SecondPage(),false);
Damian Green

8

如果您不想转到上一页,即授权完成后不让用户返回登录屏幕,则可以使用。

 App.Current.MainPage = new HomePage();

如果要启用后退功能,只需使用

Navigation.PushModalAsync(new HomePage())

4

似乎该线程非常流行,在这里不提及还有另一种方法是可悲的。 ViewModel First Navigation。那里的大多数MVVM框架都在使用它,但是,如果您想了解它的含义,请继续阅读。

Xamarin.Forms的所有正式文档都演示了一个简单的MVVM纯解决方案,但还不是。那是因为Page(视图)应该不了解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>

如果您有一个2页的应用程序,则此方法可能对您有益。但是,如果您正在开发大型企业解决方案,则最好采用一种ViewModel First Navigation方法。它稍微复杂一些,但是方法更简洁,可以让您ViewModelsPages(视图)之间进行导航,而不是之间进行导航。除了明确分离关注点之外,优点之一是您可以轻松地将参数传递给下一个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(视图)(ViewModel反之亦然)。在设置应用程序的根目录/主页时,第二种情况可能有用。为此,我们应该达成一个简单的约定,所有约定都ViewModels应该在ViewModels目录中,而Pages(Views)应该在Views目录中。换句话说,ViewModels应该位于[MyApp].ViewModels名称空间中,而Pages(视图)应该位于名称[MyApp].Views空间中。除此之外,我们应该同意WelcomeView(Page)应该有一个WelcomeViewModeland等。这是一个映射器的代码示例:

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方法的好文章,还有使用Xamarin.Forms电子书的免费企业应用程序模式,它详细解释了此方法以及许多其他有趣的主题。


3

通过使用PushAsync()方法,您可以推入,而PopModalAsync()则可以在导航堆栈中弹出页面。在下面的代码示例中,我有一个导航页面(Root Page),并从该页面中推送一个内容页面,该内容页面是登录页面,一旦我完成了登录页面,便弹出回到根页面。

~~~导航可以看作是Page对象的后进先出堆栈。要从一个页面移动到另一个页面,应用程序会将新页面推入该堆栈。要返回上一页,应用程序将从堆栈中弹出当前页面。Xamarin.Forms中的此导航由INavigation接口处理

Xamarin.Forms具有一个NavigationPage类,该类实现此接口并将管理Pages的堆栈。NavigationPage类还将在屏幕顶部添加一个导航栏,以显示标题,并且还将具有一个适合平台的“后退”按钮,该按钮将返回到上一页。以下代码显示如何在应用程序的第一页周围包装NavigationPage:

参考上面列出的内容以及您应该查看的链接,以获取有关Xamarin Forms的更多信息,请参阅“导航”部分:

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

使用Navigation属性在Xamarin.forms中的一页到另一页导航下面的示例代码

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;
}

2
In App.Xaml.Cs:

MainPage = new NavigationPage( new YourPage());

当您希望从YourPage导航到下一页时,请执行以下操作:

await Navigation.PushAsync(new YourSecondPage());

您可以在此处阅读有关Xamarin表单导航的更多信息:https : //docs.microsoft.com/zh-cn/xamarin/xamarin-forms/app-fundamentals/navigation/hierarchical

微软在这方面有相当不错的文档。

还有更新的概念 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/zh-TW/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());
        }

0

之后PushAsync使用PopAsync(带this),以去除当前页面。

await Navigation.PushAsync(new YourSecondPage());
this.Navigation.PopAsync(this);

0

在Xamarin中,我们有一个名为NavigationPage的页面。它包含ContentPages堆栈。NavigationPage具有类似PushAsync()和PopAsync()的方法。PushAsync在堆栈顶部添加一个页面,届时该页面将成为当前活动页面。PopAsync()方法从堆栈顶部删除页面。

在App.Xaml.Cs中,我们可以设置为like。

MainPage = new NavigationPage(new YourPage());

等待Navigation.PushAsync(new newPage()); 此方法将在堆栈顶部添加newPage。此时,nePage将是当前活动页面。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.