简体   繁体   中英

How to detect if user requested a tab change on TabbedPage in Xamarin.Forms

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 :)

I do not think there is an easy way to do this , you can use OnDissappearing and OnAppearing for the pages, that is as easy as it gets . However I think you are using the wrong design. Having tabs are ment to make it easier to navigate between pages, if you are going to notify the user when changing the tabs then it would be annoying . If I were you i would save the data for each page locally. so when you get back to the page you will have the data anyway.

So in the end I followed the advice of Ahmad and implemented the persisting of data on individual tabs so they are not lost when tabs are switched. (I no longer refresh input fields from data from model when OnAppearing is called).

But in order to know if there are some unsaved changes on my ChildB page, I had to implement the following procedures:

  1. I created the method HandleExit on my ChildB page, which checks for unsaved changes in fields (at least one value in input fields is different from the ones in stored model) and the either prompts the user that there are unsaved changes (if there are some) or pops the navigation stack if there are no changes.

      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. Since there are two ways on how user can return from Tabbed page (pressing the back button on device or pressing the back button in navigation bar, I had to:

    A: override the back button method on my ChildB page, so it calls the HandleExit method. But since Navigation.PopAsync() needs to be called on UI thread, I had to explicitly execute the method on UI thread as written below:

     protected override bool OnBackButtonPressed() { Device.BeginInvokeOnMainThread(new Action(async () => { await HandleExit(); })); return true; } 

    B: Since there is no way to intercept the navigation bar back button on the ContentPage, I had to intercept the event on the platform level (Android) and then pass the event to the ContentPage if necessary via MessagingCenter. So first we need to intercept the event, when navigation bar button is pressed in one of the child pages and send the event via MessagingCenter. We can do that but adding the following method in our 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); } 

    Now whenever we will press the navigation bar back button in one of the child pages (ChildA, ChildB, ChildC) nothing will happen. But the button will work as before on the rest of the pages. For the second part of solution we need to handle the message from MessagingCenter, so we need to subscribe to it in our ChildB page. We can subsribe to the message topic in OnAppearing method as follows:

     MessagingCenter.Subscribe<string>(this, "NavigationBack", async (arg) => { await HandleExit(); }); 

    Be careful to unsubscribe to the topic in OnDisappearing() otherwise strange things could happen, since there will be references left to your ContentPage even if you pop it from your navigation stack.

  3. Now that we have handled both requests for back navigation in our ChildB page, we also need to handle them in all of remaining child pages (ChildA, ChildC), so they will know if there are unsaved changes in ChildB page, even if it is currently not selected. So the solution is again compraised of handling the device back button, and navigation bar back button, but first we heed a way to check if ChildB has unsaved changes when we are on one of the remaining pages, so we again write HandleExit method but this time it is as follows:

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

    The only thing that remains now is to handle both navigation back events inside remaing child pages. The code for them is the same as in the actual ChildB page.

    A: Handling the device back button.

     protected override bool OnBackButtonPressed() { Device.BeginInvokeOnMainThread(new Action(async () => { await HandleExit(); })); return true; } 

    B: Subscribing to topic from MessagingCenter

     MessagingCenter.Subscribe<string>(this, "NavigationBack", async (arg) => { await HandleExit(); }); 

If everthing has been done correctly, we should now be prompted with a dialog on any of the child pages if there are unsaved changes on the ChildB page. I hope this will help somebody in the future :)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM