如何检测用户是否在 Xamarin.Forms 中请求在 TabbedPage 上更改选项卡
How to detect if user requested a tab change on TabbedPage in Xamarin.Forms
我有一个使用 TabbedPage 的 Xamarin.Forms 应用程序,我们称它为 T,T 由 3 个 ContentPage 子项 A、B 和 C 组成。由于用户可以在选项卡 B 上编辑一些数据,我想在离开选项卡之前通知用户,以便他取消导航更改并先保存更改或放弃更改并离开。到目前为止,我已经设法覆盖了 OnBackButtonPressed() 方法和导航栏后退按钮(这将退出 TabbedPage)。但是我很快注意到在选项卡之间切换时我仍然丢失更改。我想覆盖对新选项卡的点击,所以我可以首先向用户展示离开对话框并跳过更改或继续它。最好的方法是什么?我目前只在Android平台上工作,所以平台级别的解决方案也是可以接受的。
感谢您的建议和反馈:)
我认为没有简单的方法可以做到这一点,
您可以对页面使用 OnDissappearing 和 OnAppearing,这非常简单。
但是我认为你使用了错误的设计。
拥有选项卡是为了让页面之间的导航更容易,如果您要在更改选项卡时通知用户,那会很烦人。如果我是你,我会在本地保存每个页面的数据。因此,当您返回页面时,无论如何您都会获得数据。
所以最后我听从了 Ahmad 的建议,在各个选项卡上实现了数据的持久化,这样当切换选项卡时它们就不会丢失。 (当调用 OnAppearing 时,我不再刷新来自模型数据的输入字段)。
但是为了知道我的 ChildB 页面上是否有一些未保存的更改,我不得不执行以下程序:
我在我的 ChildB 页面上创建了 HandleExit 方法,它检查字段中是否有未保存的更改(输入字段中至少有一个值与存储模型中的值不同)并且要么提示用户有未保存的更改(如果有的话)或如果没有更改则弹出导航堆栈。
private async Task HandleExit()
{
if(HasUnsavedChanges())
{
var action = await DisplayAlert("Alert", "There are unsaved changes, do you want to discard them?", "Discard changes", "Cancel");
if(!action)
{
return;
}
}
await Navigation.PopAsync();
}
由于用户可以通过两种方式从选项卡页面return(按设备上的后退按钮或按导航栏中的后退按钮,我不得不:
A: 在我的ChildB 页面上重写后退按钮方法,所以它调用了HandleExit 方法。但是由于 Navigation.PopAsync() 需要在 UI 线程上调用,我不得不在 UI 线程上显式执行该方法,如下所示:
protected override bool OnBackButtonPressed()
{
Device.BeginInvokeOnMainThread(new Action(async () =>
{
await HandleExit();
}));
return true;
}
B:由于没有办法拦截ContentPage上的导航栏后退按钮,只好在平台层面(Android)拦截事件,必要时再将事件传给ContentPage通过消息中心。因此,首先我们需要拦截事件,当在其中一个子页面中按下导航栏按钮时,并通过 MessagingCenter 发送事件。我们可以做到这一点,但在 MainActivity.cs class:
中添加以下方法
public override bool OnOptionsItemSelected(IMenuItem item)
{
// check if the current item id
// is equals to the back button id
if (item.ItemId == 16908332)
{
// retrieve the current xamarin forms page instance
var currentpage = Xamarin.Forms.Application.Current.MainPage.Navigation.NavigationStack.LastOrDefault();
var name = currentpage.GetType().Name;
if(name == "ChildA" || name == "ChildB" || name == "ChildC")
{
MessagingCenter.Send("1", "NavigationBack");
return false;
}
}
return base.OnOptionsItemSelected(item);
}
现在,无论何时我们在其中一个子页面(ChildA、ChildB、ChildC)中按下导航栏后退按钮,都不会发生任何事情。但该按钮将在其余页面上像以前一样工作。对于解决方案的第二部分,我们需要处理来自 MessagingCenter 的消息,因此我们需要在我们的 ChildB 页面中订阅它。我们可以在OnAppearing方法中订阅消息主题,如下:
MessagingCenter.Subscribe<string>(this, "NavigationBack", async (arg) => {
await HandleExit();
});
小心取消订阅 OnDisappearing() 中的主题,否则可能会发生奇怪的事情,因为即使您将 ContentPage 从导航堆栈中弹出,也会留下对它的引用。
既然我们已经处理了 ChildB 页面中的两个后退导航请求,我们还需要在所有剩余的子页面(ChildA、ChildC)中处理它们,这样他们就会知道是否有ChildB 页面中未保存的更改,即使它当前未被选中。所以解决方案再次包含处理设备后退按钮和导航栏后退按钮,但首先我们注意一种方法来检查当我们在剩余页面之一时 ChildB 是否有未保存的更改,所以我们再次编写 HandleExit 方法但是这个时间如下:
private async Task HandleExit()
{
var root = (TabbedPage)this.Parent;
var editPage = root.Children.Where(x => x.GetType() == typeof(ChildB)).FirstOrDefault();
if(editPage != null)
{
var casted = editPage as ChildB;
if (casted.HasUnsavedChanges())
{
var action = await DisplayAlert("Alert", "There are unsaved changes, do you want to discard them?", "Discard changes", "Cancel");
if (!action)
{
return;
}
}
}
await Navigation.PopAsync();
}
现在唯一剩下的就是处理剩余子页面内的导航返回事件。它们的代码与实际ChildB页面中的代码相同。
A:处理设备后退按钮。
protected override bool OnBackButtonPressed()
{
Device.BeginInvokeOnMainThread(new Action(async () =>
{
await HandleExit();
}));
return true;
}
B:正在从 MessagingCenter 订阅主题
MessagingCenter.Subscribe<string>(this, "NavigationBack", async (arg) => {
await HandleExit();
});
如果一切都正确完成,如果 ChildB 页面上有未保存的更改,我们现在应该在任何子页面上收到对话框提示。我希望这对将来的人有所帮助:)
我有一个使用 TabbedPage 的 Xamarin.Forms 应用程序,我们称它为 T,T 由 3 个 ContentPage 子项 A、B 和 C 组成。由于用户可以在选项卡 B 上编辑一些数据,我想在离开选项卡之前通知用户,以便他取消导航更改并先保存更改或放弃更改并离开。到目前为止,我已经设法覆盖了 OnBackButtonPressed() 方法和导航栏后退按钮(这将退出 TabbedPage)。但是我很快注意到在选项卡之间切换时我仍然丢失更改。我想覆盖对新选项卡的点击,所以我可以首先向用户展示离开对话框并跳过更改或继续它。最好的方法是什么?我目前只在Android平台上工作,所以平台级别的解决方案也是可以接受的。
感谢您的建议和反馈:)
我认为没有简单的方法可以做到这一点, 您可以对页面使用 OnDissappearing 和 OnAppearing,这非常简单。 但是我认为你使用了错误的设计。 拥有选项卡是为了让页面之间的导航更容易,如果您要在更改选项卡时通知用户,那会很烦人。如果我是你,我会在本地保存每个页面的数据。因此,当您返回页面时,无论如何您都会获得数据。
所以最后我听从了 Ahmad 的建议,在各个选项卡上实现了数据的持久化,这样当切换选项卡时它们就不会丢失。 (当调用 OnAppearing 时,我不再刷新来自模型数据的输入字段)。
但是为了知道我的 ChildB 页面上是否有一些未保存的更改,我不得不执行以下程序:
我在我的 ChildB 页面上创建了 HandleExit 方法,它检查字段中是否有未保存的更改(输入字段中至少有一个值与存储模型中的值不同)并且要么提示用户有未保存的更改(如果有的话)或如果没有更改则弹出导航堆栈。
private async Task HandleExit() { if(HasUnsavedChanges()) { var action = await DisplayAlert("Alert", "There are unsaved changes, do you want to discard them?", "Discard changes", "Cancel"); if(!action) { return; } } await Navigation.PopAsync(); }
由于用户可以通过两种方式从选项卡页面return(按设备上的后退按钮或按导航栏中的后退按钮,我不得不:
A: 在我的ChildB 页面上重写后退按钮方法,所以它调用了HandleExit 方法。但是由于 Navigation.PopAsync() 需要在 UI 线程上调用,我不得不在 UI 线程上显式执行该方法,如下所示:
protected override bool OnBackButtonPressed() { Device.BeginInvokeOnMainThread(new Action(async () => { await HandleExit(); })); return true; }
B:由于没有办法拦截ContentPage上的导航栏后退按钮,只好在平台层面(Android)拦截事件,必要时再将事件传给ContentPage通过消息中心。因此,首先我们需要拦截事件,当在其中一个子页面中按下导航栏按钮时,并通过 MessagingCenter 发送事件。我们可以做到这一点,但在 MainActivity.cs class:
中添加以下方法public override bool OnOptionsItemSelected(IMenuItem item) { // check if the current item id // is equals to the back button id if (item.ItemId == 16908332) { // retrieve the current xamarin forms page instance var currentpage = Xamarin.Forms.Application.Current.MainPage.Navigation.NavigationStack.LastOrDefault(); var name = currentpage.GetType().Name; if(name == "ChildA" || name == "ChildB" || name == "ChildC") { MessagingCenter.Send("1", "NavigationBack"); return false; } } return base.OnOptionsItemSelected(item); }
现在,无论何时我们在其中一个子页面(ChildA、ChildB、ChildC)中按下导航栏后退按钮,都不会发生任何事情。但该按钮将在其余页面上像以前一样工作。对于解决方案的第二部分,我们需要处理来自 MessagingCenter 的消息,因此我们需要在我们的 ChildB 页面中订阅它。我们可以在OnAppearing方法中订阅消息主题,如下:
MessagingCenter.Subscribe<string>(this, "NavigationBack", async (arg) => { await HandleExit(); });
小心取消订阅 OnDisappearing() 中的主题,否则可能会发生奇怪的事情,因为即使您将 ContentPage 从导航堆栈中弹出,也会留下对它的引用。
既然我们已经处理了 ChildB 页面中的两个后退导航请求,我们还需要在所有剩余的子页面(ChildA、ChildC)中处理它们,这样他们就会知道是否有ChildB 页面中未保存的更改,即使它当前未被选中。所以解决方案再次包含处理设备后退按钮和导航栏后退按钮,但首先我们注意一种方法来检查当我们在剩余页面之一时 ChildB 是否有未保存的更改,所以我们再次编写 HandleExit 方法但是这个时间如下:
private async Task HandleExit() { var root = (TabbedPage)this.Parent; var editPage = root.Children.Where(x => x.GetType() == typeof(ChildB)).FirstOrDefault(); if(editPage != null) { var casted = editPage as ChildB; if (casted.HasUnsavedChanges()) { var action = await DisplayAlert("Alert", "There are unsaved changes, do you want to discard them?", "Discard changes", "Cancel"); if (!action) { return; } } } await Navigation.PopAsync(); }
现在唯一剩下的就是处理剩余子页面内的导航返回事件。它们的代码与实际ChildB页面中的代码相同。
A:处理设备后退按钮。
protected override bool OnBackButtonPressed() { Device.BeginInvokeOnMainThread(new Action(async () => { await HandleExit(); })); return true; }
B:正在从 MessagingCenter 订阅主题
MessagingCenter.Subscribe<string>(this, "NavigationBack", async (arg) => { await HandleExit(); });
如果一切都正确完成,如果 ChildB 页面上有未保存的更改,我们现在应该在任何子页面上收到对话框提示。我希望这对将来的人有所帮助:)