首页 文章

如何在Xamarin Forms中切换页面?

提问于
浏览
77

如何在Xamarin Forms中切换页面?我的主页是ContentPage,我不想切换到Tabbed页面 .

我已经能够通过找到应触发新页面的控件的父级来伪造它,直到我找到ContentPage,然后将内容与控件换成新页面 . 但这看起来真的很草率 .

谢谢

10 回答

  • 46

    在App类中,您可以将MainPage设置为导航页面并将根页面设置为您的ContentPage:

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

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

    Navigation.PushAsync (new SecondContentPage ());
    
  • 55

    呼叫:

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

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

    public void ChangeScreen(Page page)
    {
         MainPage = page;
    }
    
  • 3

    使用 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);
            }
    
        }
    
  • 1

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

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

    您需要先进入导航页面:

    MainPage = NavigationPage(new FirstPage());
    

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

  • 2

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

    通过使用PushAsync()方法,您可以推送PopModalAsync(),您可以在导航堆栈中弹出页面 . 在我的下面的代码示例中,我有一个导航页面(根页面),在我完成登录页面后,我会从这个页面推送一个登录页面的内容页面,然后返回到根页面

    
    Xamarin.Forms有一个NavigationPage类,它实现了这个接口,并将管理Pages的堆栈 .  NavigationPage类还将在屏幕顶部添加一个导航栏,显示一个 Headers ,并且还有一个适合平台的Back按钮,它将返回到上一页 . 以下代码显示如何在应用程序的第一页周围包装NavigationPage:
    
    有关Xamarin表单的更多信息,请参阅上面列出的内容以及您应该查看的链接,请参阅导航部分:
    
    [http://developer.xamarin.com/guides/cross-platform/xamarin-forms/introduction-to-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);
        }
    }
    

    //为简单起见删除了代码,只显示了pop

    private async void AuthenticationResult(bool isValid)
    {
        await navigation.PopModalAsync();
    }
    
  • 1

    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());
            }
    
  • 32

    似乎这个线程非常受欢迎,如果有另一种方法,那就更难过了 - 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 (视图)之间导航 . 除了明确区分关注点之外的一个优点是,您可以轻松地将参数传递给下一个 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.我们需要一个负责检索特定 ViewModelPage (View)的对象,反之亦然 . 在设置应用程序的根/主页面时,第二种情况可能很有用 . 为此我们应该就一个简单的约定达成一致,即所有 ViewModels 应该在 ViewModels 目录中, Pages (视图)应该在 Views 目录中 . 换句话说 ViewModels 应该位于 [MyApp].ViewModels 名称空间和 [MyApp].Views (查看) [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.最后,我们需要一个支持 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电子书,它详细解释了这个以及许多其他有趣的主题 .

  • 2

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

    • NavigationPage ,下一页幻灯片放入,

    • TabbedPage ,你不喜欢的那个

    • CarouselPage ,允许左右切换到下一页/上一页 .

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

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

    替换根页的建议选项也适用,但您必须为每个平台处理不同的方法 .

  • 0

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

    App.Current.MainPage = new HomePage();
    

    如果要启用后退功能,请使用

    Navigation.PushModalAsync(new HomePage())
    

相关问题