概要
DataTemplateSelector
クラスを使用方法の一例を記載しました。
DataTemplateSelector
クラスとは
同じ種類のオブジェクトに対して、複数のDataTemplate
を使用したい場合に用いられるクラスです。例えば、Viewに複数のオブジェクトをバインディングする場合、バインディングしたいオブジェクトの種類は一緒ですが、その表示方法は異なるような場合を作成したい、みたいな場面です。
DataTemplate
とは
データに関するオブジェクトの視覚的な構造を定義できます。
下記のようなItemsControl
をもつのコントロールを使用する場合、リストやコンボボックスの中の、一つ一つのアイテムを表示する箇所の見た目を定義する為にDataTemplate
がよく使用されたりします。
- ComboBox
- ListBox
- ListView
ItemTemplate
とDataTemplate
の違い
同じようなものに、ItemTemplate
というものがあります。
ItemTemplate
は、ListBox
やListView
、ComboBox
のような、複数のアイテムを表示するコントロールで使用されます。コントロール内の各アイテムの見た目を定義するための、DataTemplate
を指定するために使用されるプロパティのイメージです。
DataTemplate
は個々のオブジェクトの表示方法を定義するために使用されます。
例えば、以下のようにListBox.ItemTemplate
の中にDataTemplate
を定義できます。
<ListBox Width="300"
ItemsSource="{Binding hogeList}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding hogeText}" />
<TextBlock Text="{Binding hugaText}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
このような構造により、複数のアイテムを扱うコントロールも、各アイテムの表示方法を定義できます。
また以下のように、DataTemplate
の中のListBox
のItemTemplate
の中に、ListBox
のDataTemplate
を定義する、みたいなこともできます。
※上部UserControl
の名前空間の宣言部分は色々と省略しています。
<UserControl
x:Class="Hoge.HogeView"
xmlns:local="clr-namespace:Hoge.Views">
<UserControl.Resources>
<ResourceDictionary>
<!-- チェックボックスのテンプレート -->
<DataTemplate x:Key="checkBoxTemplate">
<ListBox
ItemsSource="{Binding CheckItemSource}">
<ListBox.ItemTemplate>
<DataTemplate>
<WrapPanel
VerticalAlignment="Center"
Orientation="Horizontal"
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<CheckBox
Grid.Column="0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
IsChecked="{Binding IsChecked}"/>
<Label
Grid.Column="1"
VerticalAlignment="Center">
<TextBlock
MinWidth="100"
MaxWidth="200"
Text="{Binding Content}"
TextWrapping="Wrap">
</TextBlock>
</Label>
</Grid>
</WrapPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DataTemplate>
</ResourceDictionary>
</UserControl.Resources>
<Grid>
<!-- 画面の実装 -->
</Grid>
</UserControl>
上記のListBox
は、CheckBox
とLabel
をひとつのまとまりとして、それを複数水平に並べて表示させるために使用されています。
また、このDataTemplate
は下記のように使用できます。
※上の「画面の実装」の部分で使用するイメージです。
<ListBox ItemsSource="{Binding Items}"
ItemTemplate="{StaticResource checkBoxTemplate}"/>
StaticResource
を使用すると、ResourceDictionary
に定義したリソースにアクセスできます。
DataTemplateSelector
の使用方法
上に記載したDataTemplate
の使用方法をもとに、以下にDataTemplateSelector
の使用方法の一例を記載します。
View (画面) に縦に項目群 (コントロール群) がずらっと並んでいるイメージで、その項目ひとつひとつはオブジェクトになっています。その項目ひとつひとつは同じオブジェクトを使用 (ここではUnitItem
クラス) していますが、Viewに表示されるコントロールは「コンボボックス」「テキストボックス」「チェックボックス」の3種類に分けたい、みたいな内容です。
View
-
ResourceDictionary
に、コンボボックスとテキストボックス、チェックボックスのテンプレートを記載しています。また、テンプレート選択用クラスTemplateSelector
に、各テンプレートをセットしています。 - 以下は実際の画面の部分です。
-
CellTemplateSelector="{StaticResource TemplateSelector}"/>
の箇所が、色々なテンプレートが適用される場所です。
<UserControl
x:Class="Hoge.SampleView"
xmlns:local="clr-namespace:Hoge.Views">
<UserControl.Resources>
<ResourceDictionary>
<!-- コンボボックスのテンプレート -->
<DataTemplate x:Key="comboBoxTemplate">
<Border>
<ComboBox
Width="100"
HorizontalAlignment="Center"
ItemsSource="{Binding ComboItemSource}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}">
</ComboBox>
</Border>
</DataTemplate>
<!-- テキストボックスのテンプレート -->
<DataTemplate x:Key="textBoxTemplate">
<Border>
<TextBox
Width="100"
HorizontalAlignment="Center"
Text="{Binding InputText, Mode=TwoWay}"
TextWrapping="Wrap">
</TextBox>
</Border>
</DataTemplate>
<!-- チェックボックスのテンプレート -->
<DataTemplate x:Key="checkBoxTemplate">
<ListBox
ItemsSource="{Binding CheckItemSource}">
<ListBox.ItemTemplate>
<DataTemplate>
<WrapPanel
VerticalAlignment="Center"
Orientation="Horizontal"
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<CheckBox
Grid.Column="0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
IsChecked="{Binding IsChecked}"/>
<Label
Grid.Column="1"
VerticalAlignment="Center">
<TextBlock
MinWidth="100"
MaxWidth="200"
Text="{Binding Content}"
TextWrapping="Wrap">
</TextBlock>
</Label>
</Grid>
</WrapPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DataTemplate>
<!-- ここで、「TemplateSelector」クラスの各テンプレートのプロパティにテンプレートを設定している -->
<!-- ここの各テンプレートをセットする記述がないと、「TemplateSelector」クラスの各テンプレートのプロパティはnullになってしまう -->
<local:TemplateSelector
x:Key="TemplateSelector"
ComboTemplate="{StaticResource comboBoxTemplate}"
TextTemplate="{StaticResource textBoxTemplate}"
CheckTemplate="{StaticResource checkBoxTemplate}"/>
</ResourceDictionary>
</UserControl.Resources>
<!-- 画面の実装 -->
<Grid>
<TabControl>
<TabItem>
<TabItem.Header>
<TextBlock Text="サンプルタブ"/>
</TabItem.Header>
<DockPanel>
<ListView
ItemsSource="{Binding SampleModel.InputItems}">
<ListView.View>
<GridView>
<GridView.Columns>
<GridViewColumn Width="130" Header="名前">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock
Text="{Binding Name}"
TextWrapping="Wrap" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Width="80" Header="補足">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Remark}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<!-- ここのCellTemplateSelectorで、選択されたテンプレートが適用される -->
<GridViewColumn
Width="180"
Header="選択"
CellTemplateSelector="{StaticResource TemplateSelector}"/>
</GridView.Columns>
</GridView>
</ListView.View>
</ListView>
</DockPanel>
</TabItem>
</TabControl>
</Grid>
</UserControl>
コードビハインド
特別な実装はないです。
public partial class SampleView : UserControl
{
public SampleView()
{
InitializeComponent();
}
}
テンプレート選択用クラス
DataTemplateSelector
を継承したクラスを作成し、SelectTemplate()
メソッドをオーバーライドしたメソッドを作成します。
namespace Hoge.Views
{
public class TemplateSelector : DataTemplateSelector
{
// xamlの「<local:TemplateSelector~」の部分で、この各テンプレートのプロパティにテンプレートがセットされる
/// <summary>
/// コンボボックス用テンプレート
/// </summary>
public DataTemplate ComboTemplate { get; set; }
/// <summary>
/// テキストボックス用テンプレート
/// </summary>
public DataTemplate TextTemplate { get; set; }
/// <summary>
/// チェックボックス用テンプレート
/// </summary>
public DataTemplate CheckTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
// 第一引数「item」に、データバインディングの対象となるオブジェクトのインスタンスが入ってくる
if (item is UnitItem)
{
// ここで、UnitUtemクラスの、あらかじめ値を入れておいたControlTypeプロパティ値を元にして、
// 分岐してテンプレートを分ける
switch (((UnitItem)item).ControlType)
{
case ControlType.Combo:
// コンボボックスのテンプレート
return ComboTemplate;
case ControlType.Text:
// テキストボックスのテンプレート
return TextTemplate;
case ControlType.Check:
// チェックボックスのテンプレート
return CheckTemplate;
default:
// エラー処理
}
}
}
}
}
ViewModel
public class SampleViewModel : BindableBase
{
public SampleModel SampleModel { get; set; } = SampleModel.Instance;
/// <summary>
/// コンストラクタ
/// </summary>
public SampleViewModel(){}
}
Model (SampleModel
クラス)
入力項目群をまとめたModelクラスです
public class SampleModel : BindableBase
{
public static SampleModel Instance { get; set; } = new SampleModel();
public ObservableCollection<UnitItem> InputItems { get; set; } =
new ObservableCollection<UnitItem>();
/// <summary>
/// コンストラクタ
/// </summary>
public SampleModel() {}
public void Init()
{
// InputItemsにデータを入れる処理
// UnitItemクラスを一つずつ new し、InputItemsに Add していく
// SelectTemplateメソッドの処理の差異に必要な「ControlType」プロパティの値も入れておく
}
}
ObservableCollection
を使用すると、コレクション内の項目が追加、削除、変更されたときに自動的にUIに通知するので、コレクション内のデータが変更されるたびにUIがすぐに更新されます。
例えばList<UnitItem>
とするとUIの更新は手動で行う必要がありますので、データバインディングのコードを書く手間が発生してしまいます。
Model (UnitItem
クラス)
各入力項目一行分のクラスです
/// <summary>
/// 項目一行分のクラス
/// </summary>
public class UnitItem : BindableBase
{
/// <summary>
/// コントロールタイプ
/// ここの値をもとに、「TemplateSelector」クラスでこのUnitItemがどのテンプレートを採用するか決めている
/// </summary>
public ControlType ControlType { get; set; }
/// <summary>
/// 名前
/// </summary>
public string Name { get; set; }
/// <summary>
/// 備考
/// </summary>
public string Remark { get; set; }
/// <summary>
/// コンボボックスの選択肢群 ※コンストラクタでデータをセットしておく
/// </summary>
public List<string> ComboItemSource {get; }
/// <summary>
/// コンボボックスの選択されている値
/// </summary>
public string SelectedItem {get; set; }
/// <summary>
/// テキストボックスの入力値
/// </summary>
public string InputText {get; set; }
/// <summary>
/// チェックボックスの選択肢群 ※コンストラクタでデータをセットしておく
/// </summary>
public List<CheckItem> CheckItemSource {get; }
}
CheckItem
クラス
チェックボックス一項目の中の、各チェックボックスとその表示テキストに関するクラスです
/// <summary>
/// チェックボックス1項目分のクラス
/// </summary>
public class CheckItem : BindableBase
{
/// <summary>
/// チェック状態
/// </summary>
public bool IsChecked { get; set; }
/// <summary>
/// 表示テキスト
/// </summary>
public string Content { get; set; }
}
上記の実装で、実際に画面が表示されるタイミングでテンプレート選択用クラスのSelectTemplate()
メソッドが走り、同じ種類のオブジェクト (UnitItem) を使用していても、「コンボボックス」「テキストボックス」「チェックボックス」の3種類のコントロールが画面に表示できます。
終わりに
ユーザーに選択させるコントロールを画面に沢山配置する場合は、意外とあるかと思います。
DataTemplateSelector
の使用方法を抑えておくと、そのような場合も綺麗に書けそうかと感じました。