我有一个使用TabbedPage的Xamarin.Forms应用程序,我们将其称为T,T由3个ContentPage子项A,B和C组成。由于用户可以编辑选项卡B上的某些数据,因此我想通知用户离开标签页之前,以便允许他取消导航更改并先保存更改或放弃更改并离开。到目前为止,我已经设法重写OnBackButtonPressed()方法和导航栏的后退按钮(它将退出TabbedPage)。但是我很快注意到,在选项卡之间切换时,我仍在丢失更改。我想覆盖对新选项卡的单击,因此我可以先向用户显示离开对话框,然后跳过更改或继续进行更改。最好的方法是什么?我目前仅在Android平台上工作,因此也可以接受平台级别的解决方案。
感谢您的建议和反馈:)
答案 0 :(得分:1)
我认为没有简单的方法可以做到这一点, 您可以对页面使用OnDissappearing和OnAppearing,这很容易。 但是我认为您使用的设计错误。 使用标签页可以使在页面之间导航变得更加容易,如果要在更改标签页时通知用户,那会很烦人。如果您是我,我将在本地保存每个页面的数据。因此,当您返回页面时,无论如何您将获得数据。
答案 1 :(得分:1)
因此,最后我遵循了艾哈迈德(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();
}
因为有两种方法可以使用户从“选项卡式”页面返回(按设备上的“后退”按钮或按导航栏中的“后退”按钮,所以我必须:
A:在我的ChildB页面上覆盖后退按钮方法,因此它调用HandleExit方法。但是由于需要在UI线程上调用Navigation.PopAsync(),因此我必须在UI线程上显式执行该方法,如下所示:
protected override bool OnBackButtonPressed()
{
Device.BeginInvokeOnMainThread(new Action(async () =>
{
await HandleExit();
}));
return true;
}
B:由于无法拦截ContentPage上的导航栏后退按钮,因此我不得不在平台级别(Android)上拦截事件,然后根据需要通过MessagingCenter将事件传递给ContentPage。因此,首先需要拦截事件,方法是在子页面之一中按下导航栏按钮并通过MessagingCenter发送事件。我们可以这样做,但是要在MainActivity.cs类中添加以下方法:
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()中的主题,否则可能会发生奇怪的事情,因为即使从导航堆栈中弹出内容页面,内容页面也会保留引用。
现在我们已经在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页上有未保存的更改,现在应该在任何子页上提示对话框。我希望这会对以后的人有所帮助:)