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

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

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

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

【问题讨论】:

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

标签: xamarin xamarin.forms


【解决方案1】:

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

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

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

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

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

【讨论】:

  • PushModalAsync 似乎是导航的一部分,对吧?我不知道如何到达导航对象/类。我假设我需要访问实现 INavigation 的东西,但是什么?
  • 如果您的页面包含在 NavigationPage 中,您应该能够从页面中访问 Navigation 属性
  • 一旦我开始使用 NavigationPage,一切就都到位了。谢谢
  • @stephane 请告诉我我的第一页是否是 CarouselPage 而我的第二页是 masterDetailPage 那么我如何切换页面stackoverflow.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("{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.最后,我们需要一个支持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());
                          }
                  

                  【讨论】:

                    猜你喜欢
                    • 2018-08-03
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 2013-08-17
                    相关资源
                    最近更新 更多