简体   繁体   English

如何检测用户是否在Xamarin.Forms的TabbedPage上请求了选项卡更改

[英]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. 我有一个使用TabbedPage的Xamarin.Forms应用程序,我们将其称为T,T由3个ContentPage子项A,B和C组成。由于用户可以编辑选项卡B上的某些数据,因此我想在离开前通知用户标签以允许他取消导航更改并先保存更改,或者放弃更改并离开。 So far I have managed to override OnBackButtonPressed() method and the navigation bar back button (which would exit TabbedPage). 到目前为止,我已经成功重写了OnBackButtonPressed()方法和导航栏的后退按钮(它将退出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. 我目前仅在Android平台上工作,因此平台级别的解决方案也是可以接受的。

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 . 我认为没有简单的方法可以执行此操作,您可以对页面使用OnDissappearing和OnAppearing,这很简单。 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. 因此,最后,我遵循了艾哈迈德(Ahmad)的建议,并实现了将数据保留在各个选项卡上,以便在切换选项卡时不会丢失数据。 (I no longer refresh input fields from data from model when OnAppearing is called). (调用OnAppearing时,我不再从模型数据中刷新输入字段)。

But in order to know if there are some unsaved changes on my ChildB page, I had to implement the following procedures: 但是,为了知道ChildB页面上是否有未保存的更改,我必须执行以下过程:

  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. 我在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. 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. 答:覆盖我的ChildB页面上的后退按钮方法,因此它调用HandleExit方法。 But since Navigation.PopAsync() needs to be called on UI thread, I had to explicitly execute the method on UI thread as written below: 但是由于需要在UI线程上调用Navigation.PopAsync(),因此我必须在UI线程上显式执行该方法,如下所示:

     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. B:由于无法拦截ContentPage上的导航栏后退按钮,因此我不得不在平台级别(Android)上拦截事件,然后在必要时通过MessagingCenter将事件传递给ContentPage。 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. 因此,首先需要拦截事件,方法是在子页面之一中按下导航栏按钮并通过MessagingCenter发送事件。 We can do that but adding the following method in our MainActivity.cs class: 我们可以这样做,但是在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); } 

    Now whenever we will press the navigation bar back button in one of the child pages (ChildA, ChildB, ChildC) nothing will happen. 现在,只要我们在其中一个子页面(ChildA,ChildB,ChildC)中按导航栏的后退按钮,都不会发生任何事情。 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. 对于解决方案的第二部分,我们需要处理来自MessagingCenter的消息,因此我们需要在ChildB页面中对其进行订阅。 We can subsribe to the message topic in OnAppearing method as follows: 我们可以在OnAppearing方法中订阅消息主题,如下所示:

     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. 请小心取消订阅OnDisappearing()中的主题,否则可能会发生奇怪的事情,因为即使从导航堆栈中弹出ContentPage,也将保留对ContentPage的引用。

  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. 现在我们已经在ChildB页面中处理了两个向后导航的请求,我们还需要在所有剩余的子页面(ChildA,ChildC)中处理它们,因此他们将知道ChildB页面中是否有未保存的更改,即使是当前未选择。 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: 因此,该解决方案再次经过比较,分别处理了设备的后退按钮和导航栏的后退按钮,但是当我们在其余页面之一上时,我们首先想到了一种方法来检查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(); } 

    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. 它们的代码与实际的ChildB页面中的代码相同。

    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 B:从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. 如果一切都正确完成,那么如果ChildB页面上有未保存的更改,现在应该在任何子页面上提示对话框。 I hope this will help somebody in the future :) 我希望这会在将来对某人有所帮助:)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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