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

WPF のデータ テンプレート モデルは、データのプレゼンテーションを定義する優れた柔軟性を提供します。 WPF のコントロールには、データ プレゼンテーションのカスタマイズをサポートする組み込み機能があります。 このトピックでは、最初に DataTemplate の定義方法を示した後、カスタム ロジックに基づくテンプレートの選択や、階層データの表示のサポートなど、他のデータ テンプレート機能について説明します。

必須コンポーネント

このトピックは、データ テンプレートの機能に関するものであり、データ バインディングの概念の紹介ではありません。 データ バインディングの基本概念については、「データ バインディングの概要」をご覧ください。

DataTemplate はデータのプレゼンテーションに関するものであり、WPF のスタイルおよびテンプレート モデルによって提供される多くの機能の 1 つです。 Style を使ってコントロールのプロパティを設定する方法など、WPF のスタイルおよびテンプレート モデルの概要については、「スタイルとテンプレート」のトピックをご覧ください。

さらに、Resources について理解することが重要です。これは、基本的に、StyleDataTemplate などのオブジェクトを再利用できるようにするものです。 リソースについて詳しくは、「XAML リソース」をご覧ください。

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

DataTemplate が重要である理由を示すため、データ バインディングの例を見ていきます。 この例では、Task オブジェクトのリストにバインドされた ListBox があります。 各 Task オブジェクトには TaskName (string)、Description (string)、Priority (int)、および TaskType 型のプロパティがあり、これは値が HomeWorkEnum です。

<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>
    <local:Tasks x:Key="myTodoList"/>

</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 は次のようになります。

Screenshot of the Introduction to Data Templating Sample window showing the My Task List ListBox displaying the string representation SDKSample.Task for each source object.

特定の指示がないと、ListBox では、コレクションのオブジェクトを表示しようとしたときに、既定で ToString が呼び出されます。 したがって、Task オブジェクトによって ToString メソッドがオーバーライドされる場合、ListBox では基になっているコレクションの各ソース オブジェクトの文字列表現が表示されます。

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

public override string ToString()
{
    return name.ToString();
}
Public Overrides Function ToString() As String
    Return _name.ToString()
End Function

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

Screenshot of the Introduction to Data Templating Sample window showing the My Task List ListBox displaying a list of tasks.

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

簡単な DataTemplate の定義

解決策は、DataTemplate を定義することです。 そのための 1 つの方法として、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 は次のようになります。

Screenshot of the Introduction to Data Templating Sample window showing the My Task List ListBox displaying the tasks as TextBlock elements.

リソースとしての 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 を明示的に指定する必要があります。

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

DataTemplate へのその他の追加

現在、データでは必要な情報が表示されますが、間違いなく改善の余地があります。 Border 要素、Grid 要素、および表示されているデータについて説明するいくつかの 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 を示しています。

Screenshot of the Introduction to Data Templating Sample window showing the My Task List ListBox with the modified DataTemplate.

ListBoxHorizontalContentAlignmentStretch に設定して、項目の幅がスペース全体を占めるようにできます。

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

HorizontalContentAlignment プロパティが Stretch に設定されているので、ListBox は次のようになります。

Screenshot of the Introduction to Data Templating Sample window showing the My Task List ListBox stretched to fit the screen horizontally.

DataTrigger を使ってプロパティ値を適用する

現在のプレゼンテーションでは、Task が家のタスクか会社のタスクかわかりません。 Task オブジェクトには TaskType 型の TaskType プロパティがあり、これは値が HomeWork の列挙であることを思い出してください。

次の例の DataTrigger では、TaskType プロパティが TaskType.Home である場合、border という名前の要素の 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>

これで、アプリケーションの表示は次のようになります。 家のタスクは黄色の境界線で、会社のタスクは水色の境界線で示されます。

Screenshot of the Introduction to Data Templating Sample window showing the My Task List ListBox with the home and office task borders highlighted in color.

この例では、DataTrigger では Setter を使ってプロパティの値が設定されます。 トリガー クラスには EnterActions および ExitActions プロパティもあり、アニメーションなどのアクションのセットを開始できます。 さらに、複数のデータ バインドされたプロパティ値に基づいて変更を適用できる MultiDataTrigger クラスもあります。

同じ効果を実現できるもう 1 つの方法は、BorderBrush プロパティを TaskType プロパティにバインドし、値コンバーターを使って TaskType の値に基づく色を返すというものです。 コンバーターを使って上の効果を作成するのは、パフォーマンスに関して若干効率的です。 さらに、独自のコンバーターを作成すると、独自のロジックを提供できるので柔軟性が向上します。 最終的に、どの手法を選ぶかは、シナリオと設定に依存します。 コンバーターを作成する方法については、「IValueConverter」をご覧ください。

DataTemplate に含まれるもの

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

一般に、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)
        {
            FrameworkElement element = container as FrameworkElement;

            if (element != null && item != null && item is Task)
            {
                Task taskitem = item as Task;

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

            return null;
        }
    }
}

Namespace SDKSample
    Public Class TaskListDataTemplateSelector
        Inherits DataTemplateSelector
        Public Overrides Function SelectTemplate(ByVal item As Object, ByVal container As DependencyObject) As DataTemplate

            Dim element As FrameworkElement
            element = TryCast(container, FrameworkElement)

            If element IsNot Nothing AndAlso item IsNot Nothing AndAlso TypeOf item Is Task Then

                Dim taskitem As Task = TryCast(item, Task)

                If taskitem.Priority = 1 Then
                    Return TryCast(element.FindResource("importantTaskTemplate"), DataTemplate)
                Else
                    Return TryCast(element.FindResource("myTaskTemplate"), DataTemplate)
                End If
            End If

            Return Nothing
        End Function
    End Class
End Namespace

その後、リソースとして 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 は次のようになります。

Screenshot of Introduction to Data Templating Sample window showing the My Task List ListBox with the Priority 1 tasks prominently displayed with a red border.

これがこの例の結論です。 完全なサンプルについては、「Introduction to Data Templating Sample」(データ テンプレート サンプルの概要) をご覧ください。

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>

次に示すのは、この例がレンダリングされたときのスクリーンショットです。

ItemsControl example screenshot

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

ここでは示されていない ItemsControl の他の 2 つのスタイル関連のプロパティは、GroupStyleGroupStyleSelector です。

階層データのサポート

これまでは、1 つのコレクションにバインドして表示する方法のみを説明してきました。 場合によっては、コレクションに他のコレクションが含まれることがあります。 HierarchicalDataTemplate クラスは、そのようなデータを表示するために、HeaderedItemsControl 型と共に使われるように設計されています。 次の例で、ListLeagueListLeague オブジェクトのリストです。 各 League オブジェクトには、Name と、Division オブジェクトのコレクションがあります。 各 Division には、NameTeam オブジェクトのコレクションがあり、各 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>
      <src:ListLeagueList x:Key="MyList"/>

      <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 screenshot

関連項目