Windows 8 のスタート画面にはアプリのタイルがさまざまなサイズで表示されます。
こんな風にストアアプリでいろんなサイズのタイルを GridView に表示したい! と思う方も多いハズ。
この記事では GridView を拡張し、VariableSizedWrapGrid を利用してこれを実現する方法についてご紹介します。
サンプルコード
下記の GitHub リポジトリにて公開しています。
GitHub VariableSizedGridViewSample
※情報取得に Flickr API を利用していますが、API キーを指定するまではダミーのキャッシュデータを読み込むようになっています。
実際に Flickr のデータを取得するにはアプリを Flickr に登録し、コード内の FlickrConstants.cs に API キーを指定してください。
実装のポイント
- GridView を継承した VariableSizedGridView を作成し PrepareContainerForItemOverride メソッドを override します
- override した PrepareContainerForItemOverride メソッド内でコンテナである GridViewItem の VariableSizedWrapGrid.ColumnSpan と RowSpan の添付プロパティに ViewModel のプロパティをバインドします
- GridView の GroupStyle.Panel に VariableSizedWrapGrid を設定します
- GridView の ItemsPanel に VirtualizingStackPanel を設定します
具体的なやり方
まずは下記のように GridView を継承した VariableSizedGridView を作成し、PrepareContainerForItemOverride メソッドを override します。
/// <summary>
/// 色々なサイズのタイルを表示する GridView
/// </summary>
public class VariableSizedGridView : GridView
{
/// <summary>
/// 指定された項目を表示するために、指定された要素を準備します
/// </summary>
/// <param name="element">指定された項目を表示するために使用する要素</param>
/// <param name="item">表示する項目</param>
protected override void PrepareContainerForItemOverride(
DependencyObject element, object item)
{
var container = element as FrameworkElement;
if(container != null)
{
// Container に ViewModel の ColumnSpan と RowSpan をバインドする
container.SetBinding(VariableSizedWrapGrid.ColumnSpanProperty,
new Binding()
{
Source = item,
Path = new PropertyPath("ColumnSpan"),
Mode = BindingMode.OneTime,
TargetNullValue = 1,
FallbackValue = 1,
});
container.SetBinding(VariableSizedWrapGrid.RowSpanProperty,
new Binding()
{
Source = item,
Path = new PropertyPath("RowSpan"),
Mode = BindingMode.OneTime,
TargetNullValue = 1,
FallbackValue = 1,
});
}
base.PrepareContainerForItemOverride(element, item);
}
}
PrepareContainerForItemOverride の引数、element には GridViewItem、item には ViewModel が入ってくるので両者の ColumnSpan、RowSpan プロパティをバインドします。バインド失敗時や Null 値が指定された場合は 1 が設定されるようにしておきます。
次に画面側の XAML に GridView の代わりに拡張した VariableSizedGridView を配置し、GroupStyle.Panel に VariableSizedWrapGrid、ItemsPanel に VirtualizingStackPanel を設定します。
※グループ化表示しない場合は ItemsPanel に VariableSizedWrapGrid を設定するだけで大丈夫です。
<control:VariableSizedGridView
x:Name="itemGridView"
AutomationProperties.AutomationId="ItemGridView"
AutomationProperties.Name="Grouped Items"
Grid.Row="1"
Padding="120,0,120,40"
SizeChanged="OnSizeChanged"
ItemsSource="{Binding Source={StaticResource groupedItemsViewSource}}"
ItemTemplate="{StaticResource ItemTemplate}"
IsItemClickEnabled="True"
ItemClick="OnItemClick"
SelectionMode="None"
IsSwipeEnabled="false">
<GridView.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</GridView.ItemsPanel>
<GridView.GroupStyle>
<GroupStyle HeaderTemplate="{StaticResource GroupHeaderTemplate}">
<GroupStyle.Panel>
<ItemsPanelTemplate>
<VariableSizedWrapGrid ItemWidth="100" ItemHeight="80"/>
</ItemsPanelTemplate>
</GroupStyle.Panel>
</GroupStyle>
</GridView.GroupStyle>
</control:VariableSizedGridView>
VariableSizedWrapGrid の ItemWidth、ItemHeight にはそれぞれ最小単位のサイズを指定します。
ItemsPanel に VirtualizingStackPanel を指定するのは、下記の情報にある通り、既定の ItemsStackPanel の場合に GroupStyle の設定が無視されてしまうためです。
Windows 8.1 での API の変更点 ー GroupStyle クラス
ListView と GridView の GroupStyle クラスの 3 つのプロパティは非推奨となり、無視されます。ItemsPanel が ItemsStackPanel の場合、GroupStyle の Panel、ContainerStyle、ContainerStyleSelector プロパティは優先されません。
・・・これを回避するには、Windows 8 パネル (VSP) に ItemsPanel を設定し直し、ビジュアル ツリーの構造に関する推定を削除します。
次期バージョンで使えなくなるのか気になるところですが 8.1 までは大丈夫そうです。
あとはタイルにバインドする ViewModel に ColumnSpan、RowSpan のプロパティを追加し、表示したい大きさに合わせて VariableSizedWrapGrid.ItemsWidth と ItemsHeight の何倍の大きさにするのか、それぞれの数値を指定します。
/// <summary>
/// 写真アイテムの ViewModel
/// </summary>
[DataContract]
public class PhotoItemViewModel : ViewModelBase
{
/// <summary>
/// Column サイズ
/// </summary>
[DataMember]
public int ColumnSpan
{
get { return this.columnSpan; }
set { this.SetProperty(ref this.columnSpan, value); }
}
/// <summary>
/// Row サイズ
/// </summary>
[DataMember]
public int RowSpan
{
get { return this.rowSpan; }
set { this.SetProperty(ref this.rowSpan, value); }
}
必要な実装をした上で実行してみると・・・
無事にいろんなサイズのタイルが表示されました。