How do you switch pages in Xamarin.Forms?

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

My main page is a ContentPage and I don't want to switch to something like a Tabbed Page.

I've been able to pseudo-do it by finding parents of the controls that should trigger the new page until I find the ContentPage and then swap out the Content with controls for a new page. But this seems really sloppy.

190979 次浏览

如果您的项目已经被设置为一个 PCL 表单项目(很可能也是 Shared Forms,但我还没有尝试过) ,那么有一个类 App.cs 看起来像这样:

public class App
{
public static Page GetMainPage ()
{
AuditorDB.Model.Extensions.AutoTimestamp = true;
return new NavigationPage (new LoginPage ());
}
}

you can modify the GetMainPage method to return a new TabbedPaged or some other page you have defined in the project

从那时起,您就可以添加命令或事件处理程序来执行代码并执行

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

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

导航可以被看作是一个后进先出的 Page 对象堆栈。要从一个页面移动到另一个页面,应用程序将把一个新的页面推到这个堆栈上。要返回到上一页,应用程序将从堆栈中弹出当前页。这是 Xamarin 的导航。表单由 INavigationinterface 处理

Xamarin.Forms 具有一个 NavigationPage 类,该类实现此接口并管理 Pages 的堆栈。NavigationPage 类还将在屏幕顶部添加一个导航栏,其中显示一个标题,并且还将有一个平台适当的 Back 按钮,该按钮将返回到上一页。下面的代码演示如何将 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);
}
}

//Removed code for simplicity only the pop is displayed

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

Xamarin.Forms supports multiple navigation hosts built-in:

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

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

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

建议的替换根页面的选项也可以工作,但是对于每个平台,您必须以不同的方式处理这些选项。

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

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

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

Navigation.PushAsync (new SecondContentPage ());

将一个新页面推到堆栈上,然后删除当前页面。这将导致切换。

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

You need to be in a Navigation Page first:

MainPage = NavigationPage(new FirstPage());

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

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

Example like below

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


}

电话:

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

在 App.xaml.cs 中创建这个方法:

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

如果你不想进入上一页,即一旦授权完成,不要让用户返回到登录界面,那么你可以使用;

 App.Current.MainPage = new HomePage();

If you want to enable back functionality, just use

Navigation.PushModalAsync(new HomePage())

添加 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());
}

看起来这个帖子很受欢迎,更不用说这里还有一个替代方法—— ViewModel First Navigation。大多数使用它的 MVVM 框架,但是如果您想了解它是关于什么的,请继续阅读。

所有的官方 Xamarin。表单文档演示了一个简单但有点不是 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方法。这种方法稍微复杂一些,但是更简洁,它允许您在 ViewModels之间导航,而不是在 Pages(视图)之间导航。除了清晰的关注点分离之外,其中一个好处是你可以很容易地将参数传递给下一个 ViewModel,或者在导航之后立即执行一个异步初始化代码。现在说细节。

(I will try to simplify all the code examples as much as possible).

1.首先,我们需要一个可以注册所有对象并可选地定义其生存期的地方。对于这个问题,我们可以使用 IOC 容器,您可以自己选择一个。在这个例子中,我将使用 自动工厂(它是可用的最快的之一)。我们可以在 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(视图) ,反之亦然。第二种情况在设置应用程序的 root/main 页面时可能有用。为此,我们应该同意一个简单的约定,即所有的 ViewModels应该在 ViewModels目录中,而 Pages(视图)应该在 Views目录中。换句话说,ViewModels应该存在于 [MyApp].ViewModels名称空间中,而 Pages(视图)应该存在于 [MyApp].Views名称空间中。除此之外,我们应该同意 ViewModel0(页)应该有一个 ViewModel1等。下面是映射器的一个代码示例:

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. 对于设置根页面的情况,我们需要一种自动设置 BindingContextViewModelLocator:

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

正如您可能看到的,对于所有的 ViewModels,都有一个 BaseViewModel抽象基类,您可以在其中定义类似于 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 的企业应用程序模式电子书,详细解释了这个方法和许多其他有趣的主题。

In App.Xaml.Cs:


MainPage = new NavigationPage( new YourPage());

当您希望从 YourPage 导航到下一个页面时,您需要:

await Navigation.PushAsync(new YourSecondPage());

您可以在这里阅读更多关于 Xamarin 窗体导航的内容: https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/navigation/hierarchical

微软在这方面有很好的文档。

还有 Shell的新概念。它提供了一种新的应用程序结构化方法,并在某些情况下简化了导航。

简介: https://devblogs.microsoft.com/xamarin/shell-xamarin-forms-4-0-getting-started/

壳牌基础视频: https://www.youtube.com/watch?v=0y1bUAcOjZY&t=3112s

医生: https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/shell/

PushAsync使用 PopAsync(与 this)删除当前页。

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

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

在 App.Xaml.Cs 中,我们可以像这样设置。

MainPage = new NavigationPage (new YourPage ()) ;

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