如何在 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 - 如何让 TableView 和 ListView 扩展到 iPhone X 安全区域?
Xamarin Forms - 告诉 Android 在运行时切换样式主题
如何在 Xamarin.Forms 中使用 Tap Gesture 调用另一个页面?