かずきのBlog@hatena

日本マイクロソフトに勤めています。XAML + C#の組み合わせをメインに、たまにASP.NETやJavaなどの.NET系以外のことも書いています。掲載内容は個人の見解であり、所属する企業を代表するものではありません。

入力途中のものを残す(via ページでキャッシュを残す)

id:garicchi:20121019 で紹介されてるページのキャッシュを残すという方法ですが、ページの戻るボタンが押されたときに状態を残しておきたいという要求に応える方法として、リンク先のページまるごとキャッシュに残しておく方法の他に、必要なものだけ保持しておいて、必要なものだけ復元するというアプローチもあります。

ここで紹介するやり方は、App.xaml.csで

SuspensionManager.RegisterFrame(rootFrame, "AppFrame");

が記述されてるフレーム内でLayoutAwarePageを継承したページでのみ有効な方法なので、そこだけ注意してください。因みにこの条件を満たすプロジェクトは以下のものです。

  • グリッドアプリケーション
  • 分割アプリケーション
  • 新しいアプリケーションで以下の手順を行う
    • MainPage.xamlを消す
    • 基本ページをMainPageという名前で作成する
    • App.xaml.csに書きの追記というコメントのある行を追加する
using App1.Common;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Windows.ApplicationModel;
using Windows.ApplicationModel.Activation;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;

// 空のアプリケーション テンプレートについては、http://go.microsoft.com/fwlink/?LinkId=234227 を参照してください

namespace App1
{
    /// <summary>
    /// 既定の Application クラスを補完するアプリケーション固有の動作を提供します。
    /// </summary>
    sealed partial class App : Application
    {
        /// <summary>
        /// 単一アプリケーション オブジェクトを初期化します。これは、実行される作成したコードの
        /// 最初の行であり、main() または WinMain() と論理的に等価です。
        /// </summary>
        public App()
        {
            this.InitializeComponent();
            this.Suspending += OnSuspending;
        }

        /// <summary>
        /// アプリケーションがエンド ユーザーによって正常に起動されたときに呼び出されます。他のエントリ ポイントは、
        /// アプリケーションが特定のファイルを開くために呼び出されたときに
        /// 検索結果やその他の情報を表示するために使用されます。
        /// </summary>
        /// <param name="args">起動要求とプロセスの詳細を表示します。</param>
        protected async override void OnLaunched(LaunchActivatedEventArgs args)
        {
            Frame rootFrame = Window.Current.Content as Frame;

            // ウィンドウに既にコンテンツが表示されている場合は、アプリケーションの初期化を繰り返さずに、
            // ウィンドウがアクティブであることだけを確認してください
            if (rootFrame == null)
            {
                // ナビゲーション コンテキストとして動作するフレームを作成し、最初のページに移動します
                rootFrame = new Frame();
                SuspensionManager.RegisterFrame(rootFrame, "AppFrame"); // 追記

                if (args.PreviousExecutionState == ApplicationExecutionState.Terminated)
                {
                    await SuspensionManager.RestoreAsync(); // 追記
                }

                // フレームを現在のウィンドウに配置します
                Window.Current.Content = rootFrame;
            }

            if (rootFrame.Content == null)
            {
                // ナビゲーション スタックが復元されていない場合、最初のページに移動します。
                // このとき、必要な情報をナビゲーション パラメーターとして渡して、新しいページを
                // 構成します
                if (!rootFrame.Navigate(typeof(MainPage), args.Arguments))
                {
                    throw new Exception("Failed to create initial page");
                }
            }
            // 現在のウィンドウがアクティブであることを確認します
            Window.Current.Activate();
        }

        /// <summary>
        /// アプリケーションの実行が中断されたときに呼び出されます。アプリケーションの状態は、
        /// アプリケーションが終了されるのか、メモリの内容がそのままで再開されるのか
        /// わからない状態で保存されます。
        /// </summary>
        /// <param name="sender">中断要求の送信元。</param>
        /// <param name="e">中断要求の詳細。</param>
        private async void OnSuspending(object sender, SuspendingEventArgs e)
        {
            var deferral = e.SuspendingOperation.GetDeferral();
            await SuspensionManager.SaveAsync(); // 追記
            deferral.Complete();
        }
    }
}

やり方

SaveStateメソッドで保存しておきたいデータをpageStateに保存する。

protected override void SaveState(Dictionary<String, Object> pageState)
{
    // テキストボックスの入力を保存しておく
    pageState["textBox.Text"] = this.textBox.Text;
}

LoadStateでpageStateにデータがある場合は必要に応じて復元する。

protected override void LoadState(Object navigationParameter, Dictionary<String, Object> pageState)
{
    // 前にページで保存された状態があるか
    if (pageState != null)
    {
        // テキストボックスの状態があれば設定する
        var text = default(object);
        if (pageState.TryGetValue("textBox.Text", out text))
        {
            this.textBox.Text = (string)text;
        }
    }
}

あとはページ遷移してかえってくると

private void Button_Click_1(object sender, RoutedEventArgs e)
{
    this.Frame.Navigate(typeof(BasicPage2));
}

ちゃんと残ってます。こっちの方法のメリットはめんどくさいかわりに細かい制御が出来る点と、ページまるまるインスタンス残しておくよりもメモリ消費が抑えれるといったところでしょうか。用途に応じて使い分けるのがいいでしょうね。