Tasks(Launcher/Chooser)でのはまりどころと、WP7のタスクスイッチについて

id:ch3cooh393Launcher/Chooserの一覧記事を地獄の底から怒濤の勢いでエントリしていましたが、Taskには二つほどはまりどころがあります。

プライベート変数に格納すると戻りがとれない

プライベート変数でTaskを生成すると、アプリに戻ってきたタイミングで戻り値を得ることができません。次にそのTaskを生成し、イベントをひもづけた行を通過するまでは戻り値のイベントが発生しないようです。

たとえば、こんなコードを書くと、Taskから戻ってきたあとにはメールアドレスが反映されず、もう一度ボタンを押したとき、画面がTaskに移るときに一瞬だけ見えるという事態に陥ります。

private void pickButton_Click(object sender, RoutedEventArgs e)
{
    EmailAddressChooserTask task = new EmailAddressChooserTask();
    task.Completed += new EventHandler<EmailResult>(task_Completed);
    task.Show();
}

void task_Completed(object sender, EmailResult e)
{
    if (e.TaskResult == TaskResult.OK)
    {
        addressTextBox.Text = e.Email;
    }
}

Taskの初期化はLoadedイベントでしましょう。*1

EmailAddressChooserTask task;

private void PhoneApplicationPage_Loaded(object sender, RoutedEventArgs e)
{
    task = new EmailAddressChooserTask();
    task.Completed += new EventHandler<EmailResult>(task_Completed);
}

private void pickButton_Click(object sender, RoutedEventArgs e)
{
    task.Show();
}

void task_Completed(object sender, EmailResult e)
{
    if (e.TaskResult == TaskResult.OK)
    {
        addressTextBox.Text = e.Email;
    }
}

他の入力項目が飛ぶ

Windows Phone 7 ではTaskやスタートボタン押して他のアプリに切り替わるとき、マジで一旦終了してしまうらしく、非アクティブ化のイベントをキャッチしてデータを書いたり戻してあげたりしないといけないようです。

詳しい実行モデルに関してはExecution Model Overview for Windows Phone参照。

で、実際にどうするかですが、アプリ全体に関しては App.xaml.cs に定義されている以下のイベントハンドラが使え、PhoneApplicationService.Current.State のディクショナリにデータを保存することができます。

// 新規に起動したとき(スタートスクリーンからアプリのアイコンを選んだとき)
private void Application_Launching(object sender, LaunchingEventArgs e)
{
}

// タスクから戻ってきたときや、他のアプリからバック(←)ボタンで戻ってきたとき
private void Application_Activated(object sender, ActivatedEventArgs e)
{
}

// スタートボタン押されたり、タスクを起動して他のアプリに切り替わるときに発生
// Deactivatedというイベント名だけど、このイベントのあとプロセスは終了するので、
// PhoneApplicationService.Current.State へ必要なデータをシリアライズする。
private void Application_Deactivated(object sender, DeactivatedEventArgs e)
{
}

// アプリの最初のページでバック(←)ボタンを押すなどして、アプリケーションが終了するとき
private void Application_Closing(object sender, ClosingEventArgs e)
{
}

また、ページごとについては OnNavigateTo、OnNavigateFrom をオーバーライドして、this.State のディクショナリにデータを保存すればよいようです。

protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e)
{
    base.OnNavigatedFrom(e);

    this.State["Saved"] = true; // データが保存されているかどうかを判別するフラグ
    this.State["addressTextBox.Text"] = addressTextBox.Text;
    this.State["messageTextBox.Text"] = messageTextBox.Text;
}

protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
    base.OnNavigatedTo(e);

    if (this.State.ContainsKey("Saved"))
    {
        addressTextBox.Text = (string)this.State["addressTextBox.Text"];
        messageTextBox.Text = (string)this.State["messageTextBox.Text"];
    }
}

*1:コンストラクタですると、後述のOnNavigateToとの実行順序の問題で面倒になるため