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

Posted

技术标签:

【中文标题】如何在 Xamarin.Forms 中切换页面?【英文标题】:How do you switch pages in Xamarin.Forms? 【发布时间】:2014-09-29 16:00:48 【问题描述】:

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

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

我已经能够通过查找应该触发新页面的控件的父级来进行伪操作,直到我找到 ContentPage,然后将 Content 与控件换成新页面。但这似乎真的很草率。

【问题讨论】:

这个问题已经有很多答案了,看看如何使用MVVM结构模式来完成,参考这个***.com/a/37142513/9403963 如果这个问题已经有很多答案了……答案在哪里?您引用的链接只有一个没有投票的答案,甚至没有编译或包含 cmets,此页面确实有有用的内容,而不是您的链接页面 【参考方案1】:

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

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

除此之外,所有页面还支持PushModalAsync(),它只是在现有页面之上推送一个新页面。

最后,如果您想确保用户无法返回上一页(使用手势或返回硬件按钮),您可以保持相同的Page 显示并替换其@987654328 @。

替换根页面的建议选项也可以,但您必须针对每个平台进行不同的处理。

【讨论】:

PushModalAsync 似乎是导航的一部分,对吧?我不知道如何到达导航对象/类。我假设我需要访问实现 INavigation 的东西,但是什么? 如果您的页面包含在 NavigationPage 中,您应该能够从页面中访问 Navigation 属性 一旦我开始使用 NavigationPage,一切就都到位了。谢谢 @stephane 请告诉我我的第一页是否是 CarouselPage 而我的第二页是 masterDetailPage 那么我如何切换页面***.com/questions/31129845/…【参考方案2】:

在 App 类中,您可以将 MainPage 设置为 Navigation Page,并将根页面设置为 ContentPage:

public App ()

    // The root page of your application
    MainPage = new NavigationPage( new FirstContentPage() );

然后在您的第一个 ContentPage 调用中:

Navigation.PushAsync (new SecondContentPage ());

【讨论】:

我这样做了,但主页仍然是打开的默认页面。我设置为主页的任何页面都没有效果。我刚打开的第一页已经设置好了。有什么问题? Visual Studio 建议导入 android.Content.Res 进行导航。这似乎不对,我必须从哪里导入它? 如果要更改默认主页,请将“new FirstContentPage”更改为您想要的新主页。我刚刚尝试过,它对我有用。【参考方案3】:

如果您的项目已设置为 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();

【讨论】:

这不会在页面之间切换。这只会更改最初加载的页面。 您的问题是关于主页。查看导航示例的更新答案 这个例子中的Navigation到底是什么? -- 那是你在某处创建的对象吗? -- 我在这个代码示例中没有看到它。 导航是页面上的属性 谢谢; FTR PushAsync() 对我不起作用,而 PushModalAsync() 对我有用【参考方案4】:

将新页面压入堆栈,然后删除当前页面。这导致切换。

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

您需要先进入导航页面:

MainPage = NavigationPage(new FirstPage());

切换内容并不理想,因为您只有一个大页面和一组页面事件,例如 OnAppearing 等。

【讨论】:

Navigation.RemovePage(); 在 Android 上不受支持。 Navigation.RemovePage(page);在Android中工作,需要先在导航页面内。 我在 Forms 1.4.2 的项目中广泛使用它。也许他们修复了这个错误,或者我只是很幸运还没有击中它。 我使用的是最新版本,并且能够复制它。所以我相信你太幸运了。 方便提示 - 要在更改页面时删除过渡,添加 false 作为第二个参数:await Navigation.PushAsync(new SecondPage(),false);【参考方案5】:

如果你不想去上一页,即一旦授权完成后不要让用户返回登录屏幕,那么你可以使用;

 App.Current.MainPage = new HomePage();

如果要启用返回功能,只需使用

Navigation.PushModalAsync(new HomePage())

【讨论】:

【参考方案6】:

似乎这个线程很受欢迎,如果不提这里还有另一种方法 - 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>

如果您有一个 2 页的应用程序,这种方法可能对您有好处。但是,如果您正在开发大型企业解决方案,您最好使用ViewModel First Navigation 方法。这是一种稍微复杂但更简洁的方法,它允许您在ViewModels 之间导航,而不是在Pages(Views) 之间导航。除了清晰的关注点分离之外,优势之一是您可以轻松地将参数传递给下一个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.我们需要一个对象来负责为特定的ViewModel 检索Page(视图),反之亦然。第二种情况在设置应用程序的根/主页面时可能很有用。为此,我们应该同意一个简单的约定,即所有ViewModels 都应该在ViewModels 目录中,Pages(Views) 应该在Views 目录中。换句话说,ViewModels 应该存在于 [MyApp].ViewModels 命名空间中,Pages(Views) 应该存在于 [MyApp].Views 命名空间中。除此之外,我们应该同意WelcomeView(Page) 应该有一个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("0Model, 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.最后,我们需要一个支持ViewModel First Navigation 方法的NavigationService

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 using Xamarin.Forms 电子书详细解释了这个和许多其他有趣的主题。

【讨论】:

【参考方案7】:

通过使用 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);
    

//为了简单去掉代码只显示pop

private async void AuthenticationResult(bool isValid)

    await navigation.PopModalAsync();

【讨论】:

【参考方案8】:

呼叫:

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

在 App.xaml.cs 中创建此方法:

public void ChangeScreen(Page page)

     MainPage = page;

【讨论】:

【参考方案9】:
In App.Xaml.Cs:

MainPage = new NavigationPage( new YourPage());

当您希望从 YourPage 导航到下一页时:

await Navigation.PushAsync(new YourSecondPage());

您可以在此处阅读有关 Xamarin 表单导航的更多信息:https://docs.microsoft.com/en-us/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/en-us/xamarin/xamarin-forms/app-fundamentals/shell/

【讨论】:

【参考方案10】:

在 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);
        

    

【讨论】:

【参考方案11】:

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

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

MainPage = new NavigationPage(new YourPage());

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

【讨论】:

【参考方案12】:

PushAsync 之后使用PopAsync(和this)删除当前页面。

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

【讨论】:

【参考方案13】:

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());
        

【讨论】:

以上是关于如何在 Xamarin.Forms 中切换页面?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Xamarin.Forms 中强制使用灯光模式?

Xamarin Forms - 如何让 TableView 和 ListView 扩展到 iPhone X 安全区域?

Xamarin Forms - 告诉 Android 在运行时切换样式主题

如何在 Xamarin.Forms 中使用 Tap Gesture 调用另一个页面?

如何以编程方式从Xamarin.Forms ScrollView中删除内容?

如何在 Xamarin.Forms 的 TabbedPage 的选项卡中放置一个按钮?