データ テンプレートの概要

WPF のデータ テンプレート モデルにより、データの表示を非常に柔軟に定義できます。WPF コントロールには、データ表示のカスタマイズをサポートする機能が組み込まれています。ここでは、まず DataTemplate を定義する方法を示し、次にカスタム ロジックに基づくテンプレートの選択や階層データの表示のサポートなどのその他のデータ テンプレート機能について説明します。

このトピックには次のセクションが含まれています。

  • 必要条件
  • データ テンプレートの基本
  • DataTemplate への追加
  • データ オブジェクトのプロパティに基づく DataTemplate の選択
  • ItemsControl のスタイルとテンプレートの設定
  • 階層データのサポート
  • 関連トピック

必要条件

ここでは、データ テンプレート機能に焦点を当て、データ バインディングの概念については説明しません。基本的なデータ バインディングの概念については、「データ バインディングの概要」を参照してください。

DataTemplate はデータの表示に関する機能で、WPF のスタイルとテンプレート モデルが提供する多くの機能の 1 つです。Style を使用してコントロールのプロパティを設定する方法など、WPF のスタイルとテンプレート モデルの概要については、「スタイルとテンプレート」を参照してください。

また、StyleDataTemplate などのオブジェクトを再利用できるようにする Resources について理解することも重要です。リソースの詳細については、「リソースの概要」を参照してください。

データ テンプレートの基本

このセクションには次のサブセクションが含まれています。

  • DataTemplate を使用しない場合
  • 単純な DataTemplate の定義
  • リソースとしての DataTemplate の作成
  • DataType プロパティ

DataTemplate が重要である理由を説明するために、データ バインディングの例を示します。この例では、ListBoxTask オブジェクトのリストにバインドされています。各 Task オブジェクトには、TaskName (文字列)、Description (文字列)、Priority (int)、および値 HomeWork を持つ Enum である TaskType 型のプロパティが含まれます。

<Window x:Class="SDKSample.Window1"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:local="clr-namespace:SDKSample"
  Title="Introduction to Data Templating Sample">
  <Window.Resources>
    <ObjectDataProvider x:Key="myTodoList" ObjectType="{x:Type local:Tasks}" />
...


</Window.Resources>
  <StackPanel>
    <TextBlock Name="blah" FontSize="20" Text="My Task List:"/>
    <ListBox Width="400" Margin="10"
             ItemsSource="{Binding Source={StaticResource myTodoList}}"/>
...

  </StackPanel>
</Window>

DataTemplate を使用しない場合

DataTemplate を使用しない場合、現在の ListBox は次のようになります。

Data templating sample screen shot

具体的な指示がない場合、コレクション内のオブジェクトの表示を試行するときに ListBox は既定で ToString を呼び出します。そのため、Task オブジェクトが ToString メソッドをオーバーライドすると、ListBox は、基になるコレクション内の各ソース オブジェクトの文字列表現を表示します。

たとえば、次のように Task クラスが ToString メソッドをオーバーライドするとします。ここで、nameTaskName プロパティのフィールドです。

public override string ToString()
{
    return name.ToString();
}

この場合、ListBox は次のようになります。

Data templating sample screen shot

ただし、これには制限があり、柔軟性に欠けます。また、XML データにバインドする場合、ToString をオーバーライドすることはできません。

単純な DataTemplate の定義

解決策は、DataTemplate を定義することです。定義するには、ListBoxItemTemplate プロパティを DataTemplate に設定します。DataTemplate で指定した内容が、データ オブジェクトのビジュアル構造になります。非常に単純な DataTemplate を次に示します。各項目を StackPanel 内の 3 つの TextBlock 要素として表示することを指定します。各 TextBlock 要素は、Task クラスのプロパティにバインドされます。

<ListBox Width="400" Margin="10"
         ItemsSource="{Binding Source={StaticResource myTodoList}}">
   <ListBox.ItemTemplate>
     <DataTemplate>
       <StackPanel>
         <TextBlock Text="{Binding Path=TaskName}" />
         <TextBlock Text="{Binding Path=Description}"/>
         <TextBlock Text="{Binding Path=Priority}"/>
       </StackPanel>
     </DataTemplate>
   </ListBox.ItemTemplate>
 </ListBox>

ここでの例の基になるデータは、CLR オブジェクトのコレクションです。XML データにバインドする場合、基本概念は同じですが、構文がわずかに異なります。たとえば、Path=TaskName の代わりに、XPath@TaskName に設定します (TaskName が XML ノードの属性である場合)。

ここでは、ListBox は次のようになります。

Data templating sample screen shot

リソースとしての DataTemplate の作成

上記の例では、DataTemplate インラインを定義しました。次の例に示すように、このテンプレートは、再利用可能なオブジェクトにするために、リソース セクションで定義するのが一般的です。

  <Window.Resources>
...

    <DataTemplate x:Key="myTaskTemplate">
      <StackPanel>
        <TextBlock Text="{Binding Path=TaskName}" />
        <TextBlock Text="{Binding Path=Description}"/>
        <TextBlock Text="{Binding Path=Priority}"/>
      </StackPanel>
    </DataTemplate>
...

</Window.Resources>

これで、次の例に示すように、myTaskTemplate をリソースとして使用できます。

<ListBox Width="400" Margin="10"
         ItemsSource="{Binding Source={StaticResource myTodoList}}"
         ItemTemplate="{StaticResource myTaskTemplate}"/>

myTaskTemplate はリソースであるため、DataTemplate 型を受け取るプロパティを含む他のコントロールで使用できます。上記のように、ListBox などの ItemsControl オブジェクトでは、これは ItemTemplate プロパティになります。ContentControl オブジェクトでは、ContentTemplate プロパティになります。

DataType プロパティ

DataTemplate クラスには、Style クラスの TargetType プロパティに非常によく似ている DataType プロパティがあります。そのため、上記の例で DataTemplate に対して x:Key を指定する代わりに、次のように指定できます。

<DataTemplate DataType="{x:Type local:Task}">
  <StackPanel>
    <TextBlock Text="{Binding Path=TaskName}" />
    <TextBlock Text="{Binding Path=Description}"/>
    <TextBlock Text="{Binding Path=Priority}"/>
  </StackPanel>
</DataTemplate>

この DataTemplate は、すべての Task オブジェクトに自動的に適用されます。この場合、x:Key が暗黙的に設定されることに注意してください。したがって、この DataTemplatex:Key 値を割り当てる場合は、暗黙の x:Key をオーバーライドします。DataTemplate は自動的に適用されません。

また、ContentControlTask オブジェクトのコレクションにバインドする場合、ContentControl は上記の DataTemplate を自動的に使用しません。これは、ContentControl のバインディングは、コレクション全体または個々のオブジェクトのどちらにバインドするのかを区別するための追加情報を必要とするためです。ContentControlItemsControl 型の選択内容を追跡する場合、ContentControl バインディングの Path プロパティを "/" に設定すると、現在の項目を対象とすることを示すことができます。例については、「方法 : コレクションにバインドして選択に基づく情報を表示する」を参照してください。それ以外の場合は、ContentTemplate プロパティを設定して DataTemplate を明示的に指定する必要があります。

DataType プロパティは、データ オブジェクトの異なる型の CompositeCollection がある場合に特に役立ちます。例については、「方法 : CompositeCollection を実装する」を参照してください。

DataTemplate への追加

現在、必要な情報を含むデータが表示されていますが、改善の余地があります。ここでは、BorderGrid、および表示されるデータを記述する TextBlock 要素を追加して表示を改善します。

    <DataTemplate x:Key="myTaskTemplate">
      <Border Name="border" BorderBrush="Aqua" BorderThickness="1"
              Padding="5" Margin="5">
        <Grid>
          <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition/>
            <RowDefinition/>
          </Grid.RowDefinitions>
          <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition />
          </Grid.ColumnDefinitions>
          <TextBlock Grid.Row="0" Grid.Column="0" Text="Task Name:"/>
          <TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding Path=TaskName}" />
          <TextBlock Grid.Row="1" Grid.Column="0" Text="Description:"/>
          <TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding Path=Description}"/>
          <TextBlock Grid.Row="2" Grid.Column="0" Text="Priority:"/>
          <TextBlock Grid.Row="2" Grid.Column="1" Text="{Binding Path=Priority}"/>
        </Grid>
      </Border>
...

    </DataTemplate>

次のスクリーンショットは、この変更された DataTemplate を使用した ListBox を示しています。

Data templating sample screen shot

ListBoxHorizontalContentAlignmentStretch に設定して、項目の幅をスペース全体に引き伸ばすことができます。

<ListBox Width="400" Margin="10"
     ItemsSource="{Binding Source={StaticResource myTodoList}}"
     ItemTemplate="{StaticResource myTaskTemplate}" 
     HorizontalContentAlignment="Stretch"/>

HorizontalContentAlignment プロパティを Stretch に設定すると、ListBox は次のようになります。

Data templating sample screen shot

DataTrigger を使用したプロパティ値の適用

現在の表示では、Task が家庭の用事か職場の仕事かを判断できません。ここで、Task オブジェクトには TaskType プロパティがあることに留意してください。

次の例では、TaskType プロパティが TaskType.Home の場合に、DataTriggerborder という名前の要素の BorderBrushYellow に設定します。

    <DataTemplate x:Key="myTaskTemplate">
...

    <DataTemplate.Triggers>
      <DataTrigger Binding="{Binding Path=TaskType}">
        <DataTrigger.Value>
          <local:TaskType>Home</local:TaskType>
        </DataTrigger.Value>
        <Setter TargetName="border" Property="BorderBrush" Value="Yellow"/>
      </DataTrigger>
    </DataTemplate.Triggers>
...

    </DataTemplate>

アプリケーションは、次のようになります。家庭の用事は黄色の境界線で表示され、職場の仕事は水色の境界線で表示されます。

Data templating sample screen shot

この例では、DataTriggerSetter を使用してプロパティ値を設定します。トリガ クラスには、アニメーションなどの一連のアクションを開始できるようにする EnterActions および ExitActions プロパティもあることに注意してください。また、複数のデータ バインドされたプロパティ値に基づいて変更を適用できるようにする MultiDataTrigger クラスもあります。

同じ効果を得る別の方法として、BorderBrush プロパティを TaskType プロパティにバインドし、値コンバータを使用して TaskType 値に基づいて色を返す方法が挙げられます。同様の例については、「方法 : ListView の行の背景色を変更する」を参照してください。コンバータを使用して上記の効果を得る方が、パフォーマンスの点でわずかに効率的です。また、独自のコンバータを作成すると、独自のロジックを指定できるため、柔軟性が高まります。最終的に、どちらの手法を選択するかはシナリオと設定によって決まります。コンバータの記述については、IValueConverter を参照してください。

DataTemplate に属しているもの

前の例では、DataTemplate.Triggers プロパティを使用して DataTemplate 内にトリガを配置しました。トリガの Setter は、DataTemplate 内の要素 (Border 要素) のプロパティの値を設定します。ただし、Setters が関係するプロパティが現在の DataTemplate 内の要素のプロパティではない場合は、ListBoxItem クラス (バインドするコントロールが ListBox である場合) の Style を使用してプロパティを設定する方が適切な場合があります。たとえば、マウスが項目を指したときに Trigger で項目の Opacity 値をアニメーション化する場合は、トリガを ListBoxItem スタイル内で定義します。例については、「スタイルとテンプレート サンプルの概要」を参照してください。

通常、DataTemplate は生成された各 ListBoxItem に適用されることに留意してください (実際の適用方法と適用場所の詳細については、ItemTemplate のページを参照してください)。DataTemplate は、データ オブジェクトの表示と外観のみに関係します。多くの場合、項目の選択時の表示や ListBox の項目の配置方法など、他のすべての表示の特性は DataTemplate の定義には含まれません。例については、「ItemsControl のスタイルとテンプレートの設定」を参照してください。

データ オブジェクトのプロパティに基づく DataTemplate の選択

「DataType プロパティ」で、異なるデータ オブジェクトに対して異なるデータ テンプレートを定義できることを説明しました。これは、異なる型の CompositeCollection または異なる型の項目を含むコレクションがある場合に特に役立ちます。「DataTrigger を使用したプロパティ値の適用」で、同じ型のデータ オブジェクトのコレクションがある場合、DataTemplate を作成してトリガを使用し、各データ オブジェクトのプロパティ値に基づいて変更を適用できることを示しました。ただし、トリガを使用すると、プロパティ値を適用したりアニメーションを開始したりすることができますが、データ オブジェクトの構造を柔軟に再構築することはできません。シナリオによっては、異なるプロパティを持つ同じ型の複数のデータ オブジェクトに対して別の DataTemplate を作成する必要が生じることもあります。

たとえば、Task オブジェクトの Priority 値が 1 である場合、注意を促すようにその外観をまったく異なるものにすることができます。この場合、優先順位の高い Task オブジェクトの表示用の DataTemplate を作成します。次の DataTemplate をリソース セクションに追加します。

<DataTemplate x:Key="importantTaskTemplate">
  <DataTemplate.Resources>
    <Style TargetType="TextBlock">
      <Setter Property="FontSize" Value="20"/>
    </Style>
  </DataTemplate.Resources>
  <Border Name="border" BorderBrush="Red" BorderThickness="1"
          Padding="5" Margin="5">
    <DockPanel HorizontalAlignment="Center">
      <TextBlock Text="{Binding Path=Description}" />
      <TextBlock>!</TextBlock>
    </DockPanel>
  </Border>
</DataTemplate>

この例では、DataTemplate.Resources プロパティを使用しています。このセクションで定義されたリソースは、DataTemplate 内の要素によって共有されます。

データ オブジェクトの Priority 値に基づいて使用する DataTemplate を選択するロジックを指定するには、DataTemplateSelector のサブクラスを作成し、SelectTemplate メソッドをオーバーライドします。次の例では、SelectTemplate メソッドが Priority プロパティの値に基づいて適切なテンプレートを返すロジックを提供します。返されるテンプレートは、エンベロープしている Window 要素のリソース内にあります。

using System.Windows;
using System.Windows.Controls;

namespace SDKSample
{
    public class TaskListDataTemplateSelector : DataTemplateSelector
    {
        public override DataTemplate
            SelectTemplate(object item, DependencyObject container)
        {
            if (item != null && item is Task)
            {
                Task taskitem = item as Task;
                Window window = Application.Current.MainWindow;

                if (taskitem.Priority == 1)
                    return
                        window.FindResource("importantTaskTemplate") as DataTemplate;
                else
                    return
                        window.FindResource("myTaskTemplate") as DataTemplate;
            }

            return null;
        }
    }
}

その後、リソースとして TaskListDataTemplateSelector を宣言できます。

  <Window.Resources>
...

    <local:TaskListDataTemplateSelector x:Key="myDataTemplateSelector"/>
...

</Window.Resources>

テンプレート セレクタ リソースを使用するには、それを ListBoxItemTemplateSelector プロパティに割り当てます。ListBox は、基になるコレクション内の各項目に対して TaskListDataTemplateSelectorSelectTemplate メソッドを呼び出します。この呼び出しは、項目パラメータとしてデータ オブジェクトを渡します。次に、メソッドから返された DataTemplate がそのデータ オブジェクトに適用されます。

<ListBox Width="400" Margin="10"
         ItemsSource="{Binding Source={StaticResource myTodoList}}"
         ItemTemplateSelector="{StaticResource myDataTemplateSelector}"
         HorizontalContentAlignment="Stretch"/>

テンプレート セレクタを配置すると、ListBox は次のようになります。

Data templating sample screen shot

これで、この例の説明を終了します。サンプル全体については、「データ テンプレート サンプルの概要」を参照してください。

ItemsControl のスタイルとテンプレートの設定

ItemsControlDataTemplate で使用できる唯一のコントロールではありませんが、ItemsControl をコレクションにバインドするシナリオが一般的です。「DataTemplate に属しているもの」で、DataTemplate の定義はデータの表示のみに関係することを説明しました。DataTemplate の使用が適していない場合を判断するには、ItemsControl によって提供されるさまざまなスタイル プロパティやテンプレート プロパティを理解することが重要です。このような各種プロパティの機能を説明するために用意された例を次に示します。この例の ItemsControl は、前の例と同じ Tasks コレクションにバインドされています。この例にあるスタイルとテンプレートは、デモンストレーション用にすべてインラインで宣言されています。

<ItemsControl Margin="10"
              ItemsSource="{Binding Source={StaticResource myTodoList}}">
  <!--The ItemsControl has no default visual appearance.
      Use the Template property to specify a ControlTemplate to define
      the appearance of an ItemsControl. The ItemsPresenter uses the specified
      ItemsPanelTemplate (see below) to layout the items. If an
      ItemsPanelTemplate is not specified, the default is used. (For ItemsControl,
      the default is an ItemsPanelTemplate that specifies a StackPanel.-->
  <ItemsControl.Template>
    <ControlTemplate TargetType="ItemsControl">
      <Border BorderBrush="Aqua" BorderThickness="1" CornerRadius="15">
        <ItemsPresenter/>
      </Border>
    </ControlTemplate>
  </ItemsControl.Template>
  <!--Use the ItemsPanel property to specify an ItemsPanelTemplate
      that defines the panel that is used to hold the generated items.
      In other words, use this property if you want to affect
      how the items are laid out.-->
  <ItemsControl.ItemsPanel>
    <ItemsPanelTemplate>
      <WrapPanel />
    </ItemsPanelTemplate>
  </ItemsControl.ItemsPanel>
  <!--Use the ItemTemplate to set a DataTemplate to define
      the visualization of the data objects. This DataTemplate
      specifies that each data object appears with the Proriity
      and TaskName on top of a silver ellipse.-->
  <ItemsControl.ItemTemplate>
    <DataTemplate>
      <DataTemplate.Resources>
        <Style TargetType="TextBlock">
          <Setter Property="FontSize" Value="18"/>
          <Setter Property="HorizontalAlignment" Value="Center"/>
        </Style>
      </DataTemplate.Resources>
      <Grid>
        <Ellipse Fill="Silver"/>
        <StackPanel>
          <TextBlock Margin="3,3,3,0"
                     Text="{Binding Path=Priority}"/>
          <TextBlock Margin="3,0,3,7"
                     Text="{Binding Path=TaskName}"/>
        </StackPanel>
      </Grid>
    </DataTemplate>
  </ItemsControl.ItemTemplate>
  <!--Use the ItemContainerStyle property to specify the appearance
      of the element that contains the data. This ItemContainerStyle
      gives each item container a margin and a width. There is also
      a trigger that sets a tooltip that shows the description of
      the data object when the mouse hovers over the item container.-->
  <ItemsControl.ItemContainerStyle>
    <Style>
      <Setter Property="Control.Width" Value="100"/>
      <Setter Property="Control.Margin" Value="5"/>
      <Style.Triggers>
        <Trigger Property="Control.IsMouseOver" Value="True">
          <Setter Property="Control.ToolTip"
                  Value="{Binding RelativeSource={x:Static RelativeSource.Self},
                          Path=Content.Description}"/>
        </Trigger>
      </Style.Triggers>
    </Style>
  </ItemsControl.ItemContainerStyle>
</ItemsControl>

この例が描画されたときのスクリーンショットを次に示します。

ItemTemplate を使用する代わりに ItemTemplateSelector を使用できることに注意してください。例については、前のセクションを参照してください。同様に、ItemContainerStyle を使用する代わりに ItemContainerStyleSelector を使用することもできます。

ここに表示されていない ItemsControl の他の 2 つのスタイル関連プロパティに、GroupStyleGroupStyleSelector があります。

階層データのサポート

これまで、1 つのコレクションへのバインディングとその表示のみについて説明してきました。場合によっては、他のコレクションを含むコレクションもあります。HierarchicalDataTemplate クラスは、そのようなデータを表示するために HeaderedItemsControl 型と共に使用されます。次の例では、ListLeagueListLeague オブジェクトのリストです。League オブジェクトにはそれぞれ Division オブジェクトのコレクションと Name があります。Division にはそれぞれ Team オブジェクトのコレクションと Name があり、Team オブジェクトにはそれぞれ Name があります。

<Window x:Class="SDKSample.Window1"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  Title="HierarchicalDataTemplate Sample"
  xmlns:src="clr-namespace:SDKSample">
  <DockPanel>
    <DockPanel.Resources>
      <ObjectDataProvider x:Key="MyList" ObjectType="{x:Type src:ListLeagueList}"/>

      <HierarchicalDataTemplate DataType    = "{x:Type src:League}"
                                ItemsSource = "{Binding Path=Divisions}">
        <TextBlock Text="{Binding Path=Name}"/>
      </HierarchicalDataTemplate>

      <HierarchicalDataTemplate DataType    = "{x:Type src:Division}"
                                ItemsSource = "{Binding Path=Teams}">
        <TextBlock Text="{Binding Path=Name}"/>
      </HierarchicalDataTemplate>

      <DataTemplate DataType="{x:Type src:Team}">
        <TextBlock Text="{Binding Path=Name}"/>
      </DataTemplate>
    </DockPanel.Resources>

    <Menu Name="menu1" DockPanel.Dock="Top" Margin="10,10,10,10">
        <MenuItem Header="My Soccer Leagues"
                  ItemsSource="{Binding Source={StaticResource MyList}}" />
    </Menu>

    <TreeView>
      <TreeViewItem ItemsSource="{Binding Source={StaticResource MyList}}" Header="My Soccer Leagues" />
    </TreeView>

  </DockPanel>
</Window>

この例は、HierarchicalDataTemplate を使用することにより、他のリストが含まれるリスト データを簡単に表示できることを示しています。この例のスクリーンショットを次に示します。

HierarchicalDataTemplate sample screen shot

サンプル全体については、「階層データの表示のサンプル」を参照してください。

参照

概念

スタイルとテンプレート
データ バインディングの概要
GridView の列ヘッダー スタイルおyびテンプレートの概要