Windowsストアアプリにおける グリッドアプリケーションについて(4)
前回にデータソースの説明をしました。SampleDataSourceの構造が理解できましたので、改めてGroupedItemPage.xaml.csのLoadStateのコードを振り返ります。
protected override void LoadState(
Object navigationParameter,
Dictionary<String, Object> pageState)
{
// TODO: 問題のドメインでサンプル データを置き換えるのに適したデータ モデルを作成します
var sampleDataGroups = SampleDataSource.GetGroups(
(String)navigationParameter);
this.DefaultViewModel["Groups"] = sampleDataGroups;
}
SampleDataSource.GetGroupsメソッドは、静的メソッドになっていますから、呼び出されるとメンバー変数の_sampleDataSourceのAllGroupsプロパティの値を返します。そして、 _sampleDataSourceメンバー変数は静的変数となっており、SampleDataSourceクラスのインスタンスを保持しています。_sampleDataSourceメンバー変数が静的変数となっている点が重要です。何故なら、静的変数はアプリケーションドメイン単位に保持されますから、異なるページのインスタンスへナビゲーションしたとしても_sampleDataSourceは、同一のインスタンスを保持し続けるからです(要は、キャッシュの効果があるのです)。
また、AllGroupsメソッドは引数が"AllGroups"であることが要求されますので、このパラメータがどのように渡されるかを確認するためにApp.xaml.cs の OnLaunchedイベントハンドラーを以下に示します。
/// <summary>
/// アプリケーションがエンド ユーザーによって正常に起動されたときに呼び出されます。他のエントリ ポイントは、
/// アプリケーションが特定のファイルを開くために呼び出されたときに
/// 検索結果やその他の情報を表示するために使用されます。
/// </summary>
/// <param name="args">起動要求とプロセスの詳細を表示します。</param>
protected override async void OnLaunched(LaunchActivatedEventArgs args)
{
Frame rootFrame = Window.Current.Content as Frame;
// ウィンドウに既にコンテンツが表示されている場合は、アプリケーションの初期化を繰り返さずに、
// ウィンドウがアクティブであることだけを確認してください
if (rootFrame == null)
{
// ナビゲーション コンテキストとして動作するフレームを作成し、最初のページに移動します
rootFrame = new Frame();
//フレームを SuspensionManager キーに関連付けます
SuspensionManager.RegisterFrame(rootFrame, "AppFrame");
if (args.PreviousExecutionState ==
ApplicationExecutionState.Terminated)
{
// 必要な場合のみ、保存されたセッション状態を復元します
try
{
await SuspensionManager.RestoreAsync();
}
catch (SuspensionManagerException)
{
//状態の復元に何か問題があります。
//状態がないものとして続行します
}
}
// フレームを現在のウィンドウに配置します
Window.Current.Content = rootFrame;
}
if (rootFrame.Content == null)
{
// ナビゲーション スタックが復元されていない場合、最初のページに移動します。
// このとき、必要な情報をナビゲーション パラメーターとして渡して、新しいページを
// を構成します
if (!rootFrame.Navigate(typeof(GroupedItemsPage), "AllGroups"))
{
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();
}
OnLaunchedイベントハンドラーでは、「if (rootFrame == null) 」 で2つのことをしています。
- SuspensionManagerのRegisterFrameメソッドを呼び出している。
Frame オブジェクトのナビゲーション履歴を保存(Frame.GetNavigationState)するためです。 - 強制終了させられた場合の状態の復旧
これは、強制終了させられた時に表示されていたページやナビゲーション履歴を復旧(Frame.SetNavigationState)しています。
SuspensionManagerのコードの提示を行いませんが、上記の処理とOnSuspendingイベントハンドラーのSuspensionManager.SaveAsyncメソッドにより、Frameオブジェクトのナビゲーション履歴などが保存されると理解しておいてください。このようになっている理由は、Windowsストアアプリの望ましい起動方法に起因しています。具体的には、以下のような動作が望まれます。
- 以前に起動していない場合は、トップページが表示される。
- 強制終了させられた場合は、ユーザーが作業を再開できるように最後に操作をしていたページが表示される。
OnLaunchedイベントハンドラーの後半の「if rootFrame.Content == null」で、通常起動(強制終了後の起動であれば、この条件は不成立)でナビゲーション パラメータとして「AllGroups」を指定してGroupedItemPageへナビゲートします。
残ったGroupedItemPageで説明していないことは、GroupDetailPageとItemDetailPageへのナビゲーションになります。ナビゲーションのイベントハンドラーを以下に示します。
/// <summary>
/// グループ ヘッダーがクリックされたときに呼び出されます。
/// </summary>
/// <param name="sender">ボタンは、選択されたグループのグループ ヘッダーとして使用されます。</param>
/// <param name="e">クリックがどのように開始されたかを説明するイベント データ。</param>
void Header_Click(object sender, RoutedEventArgs e)
{
// ボタン インスタンスがどのグループを表すかを確認します
var group = (sender as FrameworkElement).DataContext;
// 適切な移動先のページに移動し、新しいページを構成します。
// このとき、必要な情報をナビゲーション パラメーターとして渡します
this.Frame.Navigate(typeof(GroupDetailPage),
((SampleDataGroup)group).UniqueId);
}
/// <summary>
/// グループ内のアイテムがクリックされたときに呼び出されます。
/// </summary>
/// <param name="sender">クリックされたアイテムを表示する GridView (アプリケーションがスナップ
/// されている場合は ListView) です。</param>
/// <param name="e">クリックされたアイテムを説明するイベント データ。</param>
void ItemView_ItemClick(object sender, ItemClickEventArgs e)
{
// 適切な移動先のページに移動し、新しいページを構成します。
// このとき、必要な情報をナビゲーション パラメーターとして渡します
var itemId = ((SampleDataItem)e.ClickedItem).UniqueId;
this.Frame.Navigate(typeof(ItemDetailPage),
itemId);
}
Header_Clickでは、グループヘッダーのDataContextがSampleGroupオブジェクトであることから、グループのIDを指定してGroupDetailPageへナビゲートしています。ItemView_ItemClickでは、ClickedItemがSampleDataItemオブジェクトであることから、アイテムのIDを指定してItemDetailPageへナビゲートしています。これは、データバインドしていることから、データバインドされた特徴を生かしたコードになっています。
ここまでで、GroupedDetailPageの説明ができました。説明してきたことから、カスタマイズする場合の特徴を以下の記載します。
- 新しいページを作成する場合は、LayoutAwarePageを継承させる。
- データソースをカスタマイズする場合は、SampleDataItem、SampleDataGroupへのプロパティの追加や修正と一緒に、本番データを読み込むメソッドを用意する。
- データソースを変更すれば、ItemTemplateなどを変更内容に応じて変更する。
- ビューの切替は、VisualStateManagerを使って行っている(実行時とVisual Studioのデバイスタブ)。
ユーザーコントロールには、LoadedとUnloadedイベントハンドラーにLayoutAwarePageのイベントハンドラーを設定する。 - SuspensionManager.RegisterFrameメソッドで、Frameのナビゲーション履歴が保存され、復旧される。
復旧時はナビゲーション履歴だけなので、データソースは自分で復旧させる必要があることを意味しています。
また、検索コントラクトのように新しいページで起動される場合は、SuspensionManager.RegisterFrameメソッドでFrameを登録する必要があることを意味しています。(認定要件 3.6 システムが提供するメカニズムを備えた機能をアプリがサポートする場合は、システムが提供するメカニズムを使用しなければならない)。
認定要件3.6は必須事項ではありませんが、グリッドアプリケーションがSuspensionManager.RegisterFrameメソッドでナビゲーション履歴を使った強制終了時の状態復旧に対応しているため、データソースの復旧をした方が良いでしょう。逆に、ナビゲーション履歴を復旧させなければ、データソースを復旧させる必要もありません。次回以降は、GroupDetailPageの解説を行う予定です。