如何使用Xamarin.Forms等待模态表单解散?



使用Xamarin。窗体我如何使用使异步方法等待窗体解散?如果我使用

await Navigation.PushModalAsync(page);

它将在动画完成后返回,而不是在页面被解散时返回。

我想创建一个模态任务SignInAsync方法,如果登录成功返回true。

您可以通过在登录页面中触发一个事件并在继续之前侦听该事件来实现这一点,但是您需要完整的TAP支持,我支持您。这里有一个简单而有效的2页应用程序,它就是这样做的。你显然想要使用ContentPage自定义子类,并有适当的方法,而不是我的快速Command,但是你明白了,它节省了我的输入。

public static Page GetFormsApp ()
{
    NavigationPage navpage = null;
    return navpage = new NavigationPage (new ContentPage { 
        Content = new Button {
            Text = "Show Login dialog",
            Command = new Command (async o => {
                Debug.WriteLine ("Showing sign in dialog");
                var result = await SignInAsync (navpage);
                Debug.WriteLine (result);
            })
        }
    });
}
static Task<bool> SignInAsync (NavigationPage navpage)
{
    Random rnd = new Random ();
    var tcs = new TaskCompletionSource<bool> ();
    navpage.Navigation.PushModalAsync (new ContentPage {
        Content = new Button {
            Text = "Try login",
            Command = new Command ( o => {
                var result = rnd.Next (2) == 1;
                navpage.Navigation.PopModalAsync ();
                tcs.SetResult (result);
            })
        }
    });
    return tcs.Task;
}

小的缺点是,Task<bool>返回之前的流行模式动画的结束,但那是:

  1. 易于修复
  2. 只有一个问题,如果你正在等待的结果,推动一个新的模式Page。否则,就继续吧。

覆盖onappear

首先,值得注意的是,在许多情况下,在调用页中简单地重写OnAppearing可能就足够了。

protected override void OnAppearing()
{
    base.OnAppearing();
    ...
    // Handle any change here from returning from a Pushed Page
}

(注意,被推送页面的OnDisappearing覆盖被称为调用者的OnAppearing之后-对我来说似乎有点落后!)


AwaitableContentPage

其次……这是我对@Chad Bonthuys的回答的看法:

public class AwaitableContentPage : ContentPage
{
    // Use this to wait on the page to be finished with/closed/dismissed
    public Task PageClosedTask { get { return tcs.Task; } }
    private TaskCompletionSource<bool> tcs { get; set; }
    public AwaitableContentPage()
    {
        tcs = new System.Threading.Tasks.TaskCompletionSource<bool>();
    }       
    // Either override OnDisappearing 
    protected override void OnDisappearing()
    {
        base.OnDisappearing();
        tcs.SetResult(true);
    }
    // Or provide your own PopAsync function so that when you decide to leave the page explicitly the TaskCompletion is triggered
    public async Task PopAwaitableAsync()
    {
        await Navigation.PopAsync();
        tcs.SetResult(true);
    }
}

然后这样调用:

SettingsPage sp = new SettingsPage();
await Navigation.PushAsync(sp);
await sp.PageClosedTask; // Wait here until the SettingsPage is dismissed

我只是想为这个问题做点贡献,尽管这个问题已经有一段时间没有被问到和回答了。我以@noelicus的回答为基础。我想用一种通用的方式来处理多种情况,所以任务需要能够返回的不仅仅是bool,而是任何东西。然后,使用泛型:

public class AwaitableContentPage<T> : ContentPage
{
    // Use this to wait on the page to be finished with/closed/dismissed
    public Task<T> PageClosedTask => tcs.Task;
    // Children classes should simply set this to the value being returned and pop async() 
    protected T PageResult { get; set; }
    private TaskCompletionSource<T> tcs { get; set; }
    public AwaitableContentPage()
    {
        tcs = new TaskCompletionSource<T>();
    }
    protected override void OnDisappearing()
    {
        base.OnDisappearing();
        tcs.SetResult(PageResult);
    }
}

现在,在你想要作为modal模式运行的页面中,你可以这样做:

public partial class NewPerson : AwaitableContentPage<Person>

完成后,只需执行:

            base.PageResult = newPerson; // object you created previously
            await base.Navigation.PopAsync();
然后,为了使它使用起来超级简单,使用一个扩展方法:
public static class ExtensionMethods
{
    async public static Task<T> GetResultFromModalPage<T>(this INavigation nav, AwaitableContentPage<T> page)
    {
        await nav.PushAsync(page);
        return await page.PageClosedTask;
    }

。现在,在你的代码中,在任何你想要使用这个的页面中,语法都是这样结束的:

            Person newPerson = await Navigation.GetResultFromModalPage<string>(new NewPersonCreatePage());
            if (newPerson != null)
                UseNewPersonCreatedByOtherPage();

希望这对你有帮助!

在我的实现中我使用:

await navigation.PopModalAsync();

完整例子:

private INavigation navigation;
    public LoginPageModel(INavigation navigation, LoginPage loginPage)
    {
        this.navigation = navigation;
        this.loginPage = loginPage;
    }

public bool IsValid { get; set; }
    protected async void ExecuteLoginCommand()
    {
        var loginResult = await AuthenticationHelper.Authenticate(Email, Password);
        var isValid = false;
        if (loginResult != null)
        {
            isValid = true;
        }
   //return isValid;
        AuthenticationResult(isValid);
    }
private async void AuthenticationResult(bool isValid)
    {
        if (isValid)
        {
            Debug.WriteLine("Logged in");
            await navigation.PopModalAsync();
        }
        else
        {
            Debug.WriteLine("Failed" + email + password);
            await loginPage.DisplayAlert("Authentication Failed", "Incorrect email and password combination","Ok", null);
        }
    }

@Stephane Delcroix选择并给出的答案非常棒。但是对于任何愿意进一步推动这一点的人来说,通过等待页面完成并以良好的MVVM方式返回更多结构化数据,您可以执行以下操作:

通过从页面的ondisappear方法调用一个事件,这个事件可以被你创建的导航服务订阅,然后你可以使用"TaskCompletionSource"来等待,直到你的页面完成它的工作,然后完成任务。要了解更多关于实现这一目标的细节,您可以查看这篇博客文章。

这是基本页面的实现,这个演示应用程序中的每个页面都继承这个页面:

public class BasePage<T> : ContentPage
{
    public event Action<T> PageDisapearing;
    protected T _navigationResut;
    public BasePage()
    {
    }
    protected override void OnDisappearing()
    {
        PageDisapearing?.Invoke(_navigationResut);
        if (PageDisapearing != null)
        {
            foreach (var @delegate in PageDisapearing.GetInvocationList())
            {
                PageDisapearing -= @delegate as Action<T>;
            }
        }
        base.OnDisappearing();
    }
}

下面是你应该使用的导航服务的概述:

public async Task<T> NavigateToModal<T>(string modalName)
    {
        var source = new TaskCompletionSource<T>();
        if (modalName == nameof(NewItemPage))
        {
            var page = new NewItemPage();
            page.PageDisapearing += (result) =>
            {
                var res = (T)Convert.ChangeType(result, typeof(T));
                source.SetResult(res);
            };
            await App.Current.MainPage.Navigation.PushModalAsync(new NavigationPage(page));
        }
        return await source.Task;
    }

要使用导航服务调用该页,可以使用以下代码:

 var item = await new SimpleNavigationService().NavigateToModal<Item>(nameof(NewItemPage));
        Items.Add(item);

最新更新