web-dev-qa-db-ja.com

Xamarin.Formsを使用してモーダルフォームの終了を待機するにはどうすればよいですか?

Xamarin.Formsを使用して、フォームが閉じるのを待つ非同期メソッドをどのように使用できますか?私が使うなら

await Navigation.PushModalAsync(page);

ページが閉じられたときではなく、アニメーションが終了したときに戻ります。

サインインが成功した場合にtrueを返すモーダルタスクSignInAsyncメソッドを作成します。

17
Jamey McElveen

ログインページでイベントをトリガーし、続行する前にそのイベントをリッスンすることでこれを行うことができますが、TAPの完全なサポートが必要であり、その場で2番目です。これは、これを行うシンプルでありながら機能する2ページのアプリです。 ContentPageカスタムサブクラスを使用し、私の迅速なCommandsの代わりに適切なメソッドを使用したいのは明らかですが、アイデアが得られ、入力する手間が省けます。

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をプッシュするのを待っている場合にのみ問題になります。そうでなければ、まあ、続けてください。
22

OnAppearingをオーバーライド

まず、多くの状況では、呼び出しページでOnAppearingをオーバーライドするだけで十分な場合があることに注意してください。

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

(プッシュされたページのOnDisappearingオーバーライドが呼び出されることに注意してくださいafter呼び出し元の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
15
noelicus

質問と回答から久しぶりですが、私はこれに貢献すると思いました。 @noelicusの回答に基づいて作成しました。複数の状況でこれを行うための汎用的な方法が必要だったので、タスクはブール値だけでなく何でも返すことができる必要があります。次に、ジェネリックを使用します。

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

これで、モーダルとして実行するページで、次のことができます。

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

お役に立てれば!

2
David

私の実装では私が使用しました:

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);
        }
    }
1
Chad Bonthuys

上記の@Stephane Delcroixによって選択され、与えられた答えは素晴らしいです。しかし、これをさらにプッシュしたい人は、ページの完了を待ち、より構造化されたデータを適切なMVVM方式で返すことで、次のことを実行できます。

ページのOnDisapearingメソッドからイベントを呼び出すことにより、作成したナビゲーションサービスでこのイベントをサブスクライブし、"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);
0
Damien Doumer