1

I have a Xamarin.Forms application which uses a TabbedPage, let's call it T, T consists of 3 ContentPage children A, B and C. Since the usere has the possibility to edit some data on tab B, I want to notify user before leaving tab in order to allow him to cancel the navigation change and save changes first or to discard changes and leave. So far I have managed to override OnBackButtonPressed() method and the navigation bar back button (which would exit TabbedPage). However I quickly noticed that I am still loosing changes when switching between tabs. I would like to override the click on new tab, so I could first present user with the leaving dialog and the skip the change or continue with it. What would be the best way to do this? I am currently working only on Android platform, so solutions on the platform level are also acceptible.

Thank you for your suggestions and feedback :)

4

2 回答 2

1

所以最后我听从了 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. 由于用户如何从选项卡式页面返回有两种方法(按设备上的后退按钮或按导航栏中的后退按钮,我必须:

    A:重写我的 ChildB 页面上的后退按钮方法,所以它调用 HandleExit 方法。但是由于 Navigation.PopAsync() 需要在 UI 线程上调用,所以我必须在 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() 中的主题,否则可能会发生奇怪的事情,因为即使您从导航堆栈中弹出 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 页面上有未保存的更改,我们现在应该在任何子页面上收到一个对话框提示。我希望这对将来的人有所帮助:)

于 2018-08-21T13:17:17.953 回答
1

我认为没有一种简单的方法可以做到这一点,您可以对页面使用 OnDissappearing 和 OnAppearing,这很容易。但是我认为您使用了错误的设计。设置选项卡可以更轻松地在页面之间导航,如果您要在更改选项卡时通知用户,那将很烦人。如果我是你,我会在本地保存每个页面的数据。因此,当您返回该页面时,无论如何您都将拥有数据。

于 2018-07-10T10:31:26.563 回答