如何检测用户是否在 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 页面上是否有一些未保存的更改,我不得不执行以下程序:

  1. 我在我的 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();
    }
    
  2. 由于用户可以通过两种方式从选项卡页面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 从导航堆栈中弹出,也会留下对它的引用。

  3. 既然我们已经处理了 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 页面上有未保存的更改,我们现在应该在任何子页面上收到对话框提示。我希望这对将来的人有所帮助:)