XAML を使った UI 構築の実装手法には便利な仕組みがたくさんありますが、自分が最も好きな仕組みは DataTemplateSelector などのテンプレートセレクタです!
え?つい最近こんなネタを見たって?ネタがかぶっただけきっと気のせいです!!
なんでテンプレートセレクタが好きかというと、オブジェクト指向言語でいうポリモーフィズムを UI の世界で体現したような仕組みだからです
例えばこんな画面のように、先頭だけが他と異なるタイルデザインだったとしても DataTemplateSelector を利用すれば、1つの GridView で ItemsSource に突っ込む ViewModel を先頭だけ変更するだけですっきりと実装できてしまいます
やり方は、まず次のような DataTempleteSelector を記述してビルドします
/// <summary> /// DataTemplete のコレクション /// </summary> public class DataTemplateCollection : List<DataTemplate> { } /// <summary> /// 型によってテンプレートを切り替えるセレクタ /// </summary> [ContentProperty(Name = "Templates")] public class ContentTypeTemplateSelector : DataTemplateSelector { #region TargetType 添付プロパティ /// <summary> /// 対象の型を指定するための添付プロパティ /// </summary> public static readonly DependencyProperty TargetTypeProperty = DependencyProperty.RegisterAttached( "TargetType", typeof(string), typeof(ContentTypeTemplateSelector), new PropertyMetadata("ViewModelBase")); /// <summary> /// 対象の型を設定します /// </summary> /// <param name="template">テンプレート</param> /// <param name="value">型</param> public static void SetTargetType(DataTemplate template, string value) { template.SetValue(TargetTypeProperty, value); } /// <summary> /// 対象の型を取得します /// </summary> /// <param name="template">テンプレート</param> /// <returns></returns> public static string GetTargetType(DataTemplate template) { return (string)template.GetValue(TargetTypeProperty); } #endregion TargetType 添付プロパティ /// <summary> /// 変換先テンプレート /// </summary> public DataTemplateCollection Templates { get; set; } public ContentTypeTemplateSelector() { this.Templates = new DataTemplateCollection(); } /// <summary> /// アイテムに対応する DataTemplate を返却します /// </summary> /// <param name="item">アイテム</param> /// <returns>DataTemplate</returns> protected override DataTemplate SelectTemplateCore(object item) { return base.SelectTemplateCore(item, null); } /// <summary> /// アイテムに対応する DataTemplate を返却します /// </summary> /// <param name="item">アイテム</param> /// <param name="container">コンテナ</param> /// <returns>DataTemplate</returns> protected override DataTemplate SelectTemplateCore(object item, DependencyObject container) { var vm = item as ItemContainerViewModel; if (vm == null) { return null; } var assembly = typeof(ViewModelBase).GetTypeInfo().Assembly; return this.Templates.FirstOrDefault(t => ContentTypeTemplateSelector.GetTargetType(t).Equals(vm.ContentType.Name)); } }
GridView に設定する ItemsSource は先頭だけ ViewModel を変えておきます
/// <summary> /// 画面に遷移したときの処理 /// </summary> /// <param name="navigationParameter">遷移パラメータ</param> /// <param name="navigationMode">遷移モード</param> /// <param name="viewModelState">画面状態データ</param> public override void OnNavigatedTo(object navigationParameter, NavigationMode navigationMode, Dictionary<string, object> viewModelState) { base.OnNavigatedTo(navigationParameter, navigationMode, viewModelState); if (this.ViewModel.Items.Count > 0) { return; } // 先頭のアイテム追加 this.ViewModel.Items.Add( new ItemContainerViewModel() { UniqueId = Guid.NewGuid().ToString(), ContentId = Guid.NewGuid().ToString(), Content = new AddItemViewModel(), ColumnSpan = 1, RowSpan = 4, IsActive = true, }); // 2番目以降のアイテム追加 int i = 0; foreach (var photo in App.AppSettings.Settings.Items) { var item = PhotoViewModel.Convert(photo); if (i < 2 || i == 3) { this.ViewModel.Items.Add( new ItemContainerViewModel() { UniqueId = Guid.NewGuid().ToString(), ContentId = item.UniqueId, Content = item, ColumnSpan = 1, RowSpan = 2, IsActive = true, }); } if (i == 2) { this.ViewModel.Items.Add( new ItemContainerViewModel() { UniqueId = Guid.NewGuid().ToString(), ContentId = item.UniqueId, Content = item, ColumnSpan = 1, RowSpan = 4, IsActive = true, }); } if (i > 3) { this.ViewModel.Items.Add( new ItemContainerViewModel() { UniqueId = Guid.NewGuid().ToString(), ContentId = item.UniqueId, Content = item, ColumnSpan = 1, RowSpan = 1, IsActive = true, }); } i++; } }
あとは作った DataTemplateSelector を GridView の ItemTemplateSelector に設定するだけ
<Page.Resources> <!-- このページで表示されるアイテムのコレクション --> <CollectionViewSource x:Name="itemsViewSource" Source="{Binding Items}" /> <!-- GridView 用 ItemTemplateSelector --> <selector:ContentTypeTemplateSelector x:Key="GridViewItemTemplateSelector"> <!-- アイテムの中身が AddItemViewModel だった場合のテンプレート --> <DataTemplate selector:ContentTypeTemplateSelector.TargetType="AddItemViewModel"> <Border Background="#20486EC6"> <Grid Margin="10" Background="#FF486EC6"> <Grid.RowDefinitions> <RowDefinition Height="1*" /> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> <RowDefinition Height="2*" /> </Grid.RowDefinitions> <TextBlock Grid.Row="1" HorizontalAlignment="Center" FontSize="144" Foreground="White" Text="+" /> <TextBlock Grid.Row="2" HorizontalAlignment="Center" FontSize="26" Foreground="White" Text="新しい写真を追加" TextAlignment="Left" /> </Grid> </Border> </DataTemplate> <!-- アイテムの中身が PhotoViewModel だった場合のテンプレート --> <DataTemplate selector:ContentTypeTemplateSelector.TargetType="PhotoViewModel"> <Grid> <Image HorizontalAlignment="Center" VerticalAlignment="Top" Source="{Binding Content.ImageUri}" Stretch="UniformToFill" /> </Grid> </DataTemplate> </selector:ContentTypeTemplateSelector> </Page.Resources> <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="140" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <!-- 水平スクロール グリッド --> <ctrl:VariableSizedGridView x:Name="itemGridView" Grid.RowSpan="2" ItemTemplateSelector="{StaticResource GridViewItemTemplateSelector}" ItemsSource="{Binding Source={StaticResource itemsViewSource}}" Padding="116,186,116,0" SelectionMode="Multiple"> <GridView.ItemsPanel> <ItemsPanelTemplate> <VariableSizedWrapGrid ItemHeight="120" ItemWidth="300" Orientation="Vertical" /> </ItemsPanelTemplate> </GridView.ItemsPanel> <GridView.ItemContainerStyle> <Style TargetType="GridViewItem"> <Setter Property="Margin" Value="0,0,20,20" /> <Setter Property="HorizontalContentAlignment" Value="Stretch" /> <Setter Property="VerticalContentAlignment" Value="Stretch" /> </Style> </GridView.ItemContainerStyle> </ctrl:VariableSizedGridView> ~ 中略 ~ </Grid> </Grid>
先頭もそれ以外も 1つの GridView 上に配置できるので、選択枠やスクロール、クリックなど統一して振る舞えます
こういったことがスマートにできるのでテンプレートセレクタは結構使いやすいと思うんです