Windowsストアアプリにおける グリッドアプリケーションについて(1)
久し振りのエントリーになります。これから、何回かに渡ってVisual Studio 2012に含まれる Windows ストア アプリケーションのグリッド アプリケーション テンプレートを紐解いて行きます。
グリッド アプリケーションのナビゲーションは、以下に示すように3階層になっています。
ナビゲーションに含まるXAMLページは、以下のようになっています。
- GroupedItemsPage.xaml
ハブとなるページで、GridViewコントロールとListViewコントロールを持ちます。 - GroupDetailPage.xaml
セクションとなるページで、GridViewコントロールとListViewコントロールを持ちます。 - ItemDetailPage.xaml
詳細となるページで、FlipViewコントロールを持ちます。
また、各ページとLayoutAwarePageクラスの関係は、以下のようになっています。
各ページは、LayoutAwarePageという抽象クラスを継承した構造になっています。このことから、明確になることが1つあります。それは、新しいページを作成した場合は、必ずLayoutAwarePageクラスを継承するようにした方が良いということです。具体的には、検索コントラクトで追加されるSearchResultsPage.xamlなどです。この理由は、LayoutAwarePageを説明していくことで解説をしていきます。
それでは、GroupedItemPage.xaml にある、LayoutAwrePageの定義を見ていきます。
<common:LayoutAwarePage
x:Name="pageRoot"
x:Class="App6.GroupedItemsPage"
DataContext="{Binding DefaultViewModel, RelativeSource={RelativeSource Self}}"
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App6"
xmlns:data="using:App6.Data"
xmlns:common="using:App6.Common"
xmlns:d="https://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Page.Resources>
<!--
このページで表示されるグループ化されたアイテムのコレクションは、グループ内のアイテムを
仮想化できないため、完全なアイテム リストのサブセットにバインドされます
-->
<CollectionViewSource
x:Name="groupedItemsViewSource"
Source="{Binding Groups}"
IsSourceGrouped="true"
ItemsPath="TopItems"
d:Source="{Binding AllGroups, Source={d:DesignInstance Type=data:SampleDataSource, IsDesignTimeCreatable=True}}"/>
</Page.Resources>
この定義で注目したいのが、「DataContext="{Binding DefaultViewModel, RelativeSource={RelativeSource Self}}" 」 です。ここで指定されているDefaultViewModel が何処で定義されているかといえば、LayoutAwarePage クラスになります。その定義を以下に示します。
/// <summary>
/// <see cref="DefaultViewModel"/> 依存関係プロパティを識別します。
/// </summary>
public static readonly DependencyProperty DefaultViewModelProperty =
DependencyProperty.Register("DefaultViewModel",
typeof(IObservableMap<String, Object>),
typeof(LayoutAwarePage), null);
/// <summary>
/// <see cref="IObservableMap<String, Object>"/> の実装です。これは、
/// 単純なビュー モデルとして使用されるように設計されています。
/// </summary>
protected IObservableMap<String, Object> DefaultViewModel
{
get
{
return this.GetValue(DefaultViewModelProperty)
as IObservableMap<String, Object>;
}
set
{
this.SetValue(DefaultViewModelProperty, value);
}
}
この定義をみれば理解できるように、DefaultViewModelは依存プロパティとして、IObservableMap<string, Object> 型として定義されています。そして、IObservableMap<string, Object>の実装として ObservableDictionary<K, V>クラスを定義しています。この型を簡単に説明するとすれば、ディクショナリー型と同じであり、キーに文字列でデータソースの名前を指定して、値にオブジェクトのコレクションを設定して使用します。DefaultViewModelが、Dictionary<string, object>を使って実装していない理由は、ディクショナリーに変更があった場合の変更通知にあります。変更通知を実装するために、 IObservableMap<string, Object>を継承してObservableDictionary<K, V>クラス を定義しています。GroupedItemPage.xamlのCollectionViewSource定義のSource属性に「 {Binding Groups}」 と定義されていたことを思い出してください。ここで定義されている「Groups」と文字列が、ObservableDictionary<K, V>クラスのキーになっています。このことから、理解できることは以下のようになります。
- DataContextで定義しているDefaultViewModel は、依存プロパティであり、LayoutAwarePageクラスで定義している。
- CollectionViewSourceのSource属性に、IObservableMap<string, Object>型のキーを指定する。
- ページ定義XAMLのコードビハインドで、DefaultViewModel ["キー"] に対してオブジェクトコレクションを設定する必要がある
このように理解することができれば、次にLayoutAwarePageクラスのOnNavigatedToメソッドを読み解いてみましょう。このメソッドは、Frameを使ったナビゲーションによって呼び出されるメソッドになります。
/// <summary>
/// このページがフレームに表示されるときに呼び出されます。
/// </summary>
/// <param name="e">このページにどのように到達したかを説明するイベント データ。Parameter
/// プロパティは、表示するグループを示します。</param>
protected override void OnNavigatedTo(NavigationEventArgs e)
{
// ナビゲーションを通じてキャッシュ ページに戻るときに、状態の読み込みが発生しないようにします
if (this._pageKey != null) return;
var frameState = SuspensionManager.SessionStateForFrame(this.Frame);
this._pageKey = "Page-" + this.Frame.BackStackDepth;
if (e.NavigationMode == NavigationMode.New)
{
// 新しいページをナビゲーション スタックに追加するとき、次に進むナビゲーションの
// 既存の状態をクリアします
var nextPageKey = this._pageKey;
int nextPageIndex = this.Frame.BackStackDepth;
while (frameState.Remove(nextPageKey))
{
nextPageIndex++;
nextPageKey = "Page-" + nextPageIndex;
}
// ナビゲーション パラメーターを新しいページに渡します
this.LoadState(e.Parameter, null);
}
else
{
// ナビゲーション パラメーターおよび保存されたページの状態をページに渡します。
// このとき、中断状態の読み込みや、キャッシュから破棄されたページの再作成と同じ対策を
// 使用します
this.LoadState(e.Parameter,
(Dictionary<String, Object>)frameState[this._pageKey]);
}
}
/// <summary>
/// このページには、移動中に渡されるコンテンツを設定します。前のセッションからページを
/// 再作成する場合は、保存状態も指定されます。
/// </summary>
/// <param name="navigationParameter">このページが最初に要求されたときに
/// <see cref="Frame.Navigate(Type, Object)"/> に渡されたパラメーター値。
/// </param>
/// <param name="pageState">前のセッションでこのページによって保存された状態の
/// ディクショナリ。ページに初めてアクセスするとき、状態は null になります。</param>
protected virtual void LoadState(Object navigationParameter,
Dictionary<String, Object> pageState)
{
}
OnNavigatedToメソッドで重要なことは、ページの状態をチェックすることとLoadStateメソッドを呼び出すことです。LoadStateメソッドの定義を見ると仮想メソッドとして定義されています。よって、LayoutAwarePageクラスを継承したクラスで実装する必要があります。GroupedItemPage.xaml.csのLoadStateメソッドを以下に示します。
/// <summary>
/// このページには、移動中に渡されるコンテンツを設定します。前のセッションからページを
/// 再作成する場合は、保存状態も指定されます。
/// </summary>
/// <param name="navigationParameter">このページが最初に要求されたときに
/// <see cref="Frame.Navigate(Type, Object)"/> に渡されたパラメーター値。
/// </param>
/// <param name="pageState">前のセッションでこのページによって保存された状態の
/// ディクショナリ。ページに初めてアクセスするとき、状態は null になります。</param>
protected override void LoadState(Object navigationParameter,
Dictionary<String, Object> pageState)
{
// TODO: 問題のドメインでサンプル データを置き換えるのに適したデータ モデルを作成します
var sampleDataGroups = SampleDataSource.GetGroups((String)navigationParameter);
this.DefaultViewModel["Groups"] = sampleDataGroups;
}
このコードでは、SampleDataSource.GetGroupsメソッドを使ってグループ コレクションを取得してから、DefaultViewModelにキーと値を設定することで、Groupsというキーのデータソースを設定していることを理解することができます。
- LoadStateメソッドは、目的のページにおけるデータソースを作成する責務を持つ
- OnNavigatedToメソッドをオーバーライドする場合は、LayoutAwarePageクラスのOnNavigatedToメソッドを呼び出すか、同等の処理を記述しなければならない
ここまでで、GroupedItemsPageにおけるXAMLとコードを使ったデータソースの設定に関することを説明しました。まだまだ、説明しきれていないことがありますので、その辺りは次回に説明する予定です。