WPF データ テンプレート モデルを使用すると、データのプレゼンテーションを柔軟に定義できます。 WPF コントロールには、データプレゼンテーションのカスタマイズをサポートする機能が組み込まれています。 このトピックでは、最初に DataTemplate を定義する方法について説明してから、カスタム ロジックに基づくテンプレートの選択や階層データの表示のサポートなど、他のデータ テンプレート機能について説明します。
[前提条件]
このトピックでは、データ テンプレート機能に焦点を当てており、データ バインディングの概念の導入ではありません。 基本的なデータ バインディングの概念については、「 データ バインディングの概要」を参照してください。
DataTemplate はデータの表示に関するものであり、WPF のスタイルとテンプレート モデルによって提供される多くの機能の 1 つです。 Styleを使用してコントロールのプロパティを設定する方法など、WPF スタイルとテンプレート モデルの概要については、「スタイル設定とテンプレート」トピックを参照してください。
さらに、Resources
やStyleなどのオブジェクトを再利用できるようにするための基本的なDataTemplateを理解することが重要です。 リソースの詳細については、「 XAML リソース」を参照してください。
データ テンプレートの基本
DataTemplateが重要である理由を示すために、データ バインディングの例を見てみましょう。 この例では、ListBoxがTask
オブジェクトのリストにバインドされています。 各Task
オブジェクトには、TaskName
(文字列)、Description
(文字列)、Priority
(int)、およびTaskType
型のプロパティがあります。これは、値がEnum
およびHome
のWork
です。
<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は次のようになります。
特に指示がない場合、コレクション内のオブジェクトを表示しようとすると、既定でListBoxがToString
を呼び出します。 したがって、 Task
オブジェクトが ToString
メソッドをオーバーライドする場合、 ListBox は基になるコレクション内の各ソース オブジェクトの文字列形式を表示します。
たとえば、 Task
クラスがこのように ToString
メソッドをオーバーライドする場合、 name
は TaskName
プロパティのフィールドです。
public override string ToString()
{
return name.ToString();
}
Public Overrides Function ToString() As String
Return _name.ToString()
End Function
その後、 ListBox は次のようになります。
ただし、これは制限されており、柔軟性はありません。 また、XML データにバインドする場合、 ToString
をオーバーライドすることはできません。
単純な DataTemplate の定義
解決策は、 DataTemplateを定義することです。 これを行う方法の 1 つは、ItemTemplateの ListBox プロパティをDataTemplateに設定する方法です。
DataTemplateで指定した内容は、データ オブジェクトの視覚的な構造になります。 次の DataTemplate は非常に簡単です。 各項目が 1 つのTextBlock内で 3 つのStackPanel要素として表示されるように指示しています。 各 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は次のようになります。
DataTemplate をリソースとして作成する
上記の例では、 DataTemplate をインラインで定義しました。 次の例のように、再利用可能なオブジェクトになるように resources セクションで定義する方が一般的です。
<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型を受け取るプロパティを持つ他のコントロールで使用できるようになりました。 上に示すように、ItemsControlなどのListBox オブジェクトの場合は、ItemTemplate プロパティです。
ContentControl オブジェクトの場合は、ContentTemplate プロパティです。
データタイププロパティ
DataTemplate クラスには、DataType クラスのTargetType プロパティによく似たStyle プロパティがあります。 そのため、上記の例でx:Key
のDataTemplateを指定する代わりに、次の操作を行うことができます。
<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は自動的には適用されません。
ContentControlをTask
オブジェクトのコレクションにバインドする場合、ContentControlは上記のDataTemplateを自動的に使用しません。 これは、 ContentControl のバインドでは、コレクション全体にバインドするか、個々のオブジェクトにバインドするかを区別するために、より多くの情報が必要になるためです。
ContentControlがItemsControl型の選択を追跡している場合は、Path バインドのContentControl プロパティを "/
" に設定して、現在の項目に関心があることを示すことができます。 例については、「 コレクションへのバインド」および「選択項目に基づく情報の表示」を参照してください。 それ以外の場合は、DataTemplate プロパティを設定して、ContentTemplateを明示的に指定する必要があります。
DataType プロパティは、さまざまな種類のデータ オブジェクトのCompositeCollectionがある場合に特に便利です。 例については、「 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>
次のスクリーンショットは、この変更されたListBoxを含むDataTemplateを示しています。
HorizontalContentAlignmentをStretchにListBoxに設定して、項目の幅がスペース全体を占めるようにすることができます。
<ListBox Width="400" Margin="10"
ItemsSource="{Binding Source={StaticResource myTodoList}}"
ItemTemplate="{StaticResource myTaskTemplate}"
HorizontalContentAlignment="Stretch"/>
HorizontalContentAlignment プロパティを Stretch に設定すると、ListBoxは次のようになります。
DataTriggers を使用してプロパティ値を適用する
現在のプレゼンテーションでは、 Task
がホーム タスクかオフィス タスクかは表示されません。
Task
オブジェクトには、TaskType
型のTaskType
プロパティがあります。これは値としてHome
とWork
を持つ列挙型です。
次の例では、DataTriggerは、BorderBrush プロパティがborder
場合、Yellow
という名前の要素のTaskType
をTaskType.Home
に設定します。
<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>
アプリケーションは次のようになります。 ホーム タスクは黄色の枠線で表示され、オフィス タスクはアクア罫線で表示されます。
この例では、 DataTrigger は Setter を使用してプロパティ値を設定します。 トリガー クラスには、アニメーションなどの一連のアクションを開始できる EnterActions プロパティと ExitActions プロパティもあります。 さらに、複数のデータ バインド プロパティ値に基づいて変更を適用できる MultiDataTrigger クラスもあります。
同じ効果を得る別の方法として、 BorderBrush プロパティを TaskType
プロパティにバインドし、値コンバーターを使用して TaskType
値に基づいて色を返します。 コンバーターを使用して上記の効果を作成すると、パフォーマンスの点で若干効率的です。 さらに、独自のコンバーターを作成すると、独自のロジックを提供するため、柔軟性が向上します。 最終的に、どの手法を選択するかは、シナリオとユーザーの好みによって異なります。 コンバーターを記述する方法については、 IValueConverterを参照してください。
DataTemplate に属するもの
前の例では、DataTemplate プロパティを使用して、DataTemplate.Triggers内にトリガーを配置しました。 トリガーのSetterは、Border内にある要素 (DataTemplate 要素) のプロパティの値を設定します。 ただし、Setters
が関係するプロパティが現在のDataTemplate内にある要素のプロパティでない場合は、Style クラス用のListBoxItemを使用してプロパティを設定する方が適している場合があります (バインドするコントロールがListBoxの場合)。 たとえば、マウスで項目をポイントしたときに Trigger で項目の Opacity 値をアニメーション化する場合は、 ListBoxItem スタイル内でトリガーを定義します。 例については、「 スタイル設定とテンプレートのサンプルの概要」を参照してください。
一般に、生成された各DataTemplateにListBoxItemが適用されることに注意してください (実際に適用される方法と場所の詳細については、ItemTemplateページを参照してください)。 DataTemplateは、データ オブジェクトのプレゼンテーションと外観にのみ関係します。 ほとんどの場合、アイテムが選択されたときの外観や、 ListBox がアイテムをレイアウトする方法など、プレゼンテーションの他のすべての側面は、 DataTemplateの定義に属していません。 例については、「 ItemsControl のスタイル設定とテンプレート」セクションを 参照してください。
データ オブジェクトのプロパティに基づく DataTemplate の選択
「DataType プロパティ」セクションでは、さまざまなデータ オブジェクトに対して異なるデータ テンプレートを定義できることを説明しました。 これは、異なる型の CompositeCollection がある場合や、異なる型の項目を持つコレクションがある場合に特に便利です。 「 DataTriggers を使用してプロパティ値を適用する 」セクションでは、同じ種類のデータ オブジェクトのコレクションがある場合は、 DataTemplate を作成し、トリガーを使用して各データ オブジェクトのプロパティ値に基づいて変更を適用できることを示しました。 ただし、トリガーを使用すると、プロパティ値を適用したり、アニメーションを開始したりできますが、データ オブジェクトの構造を柔軟に再構築することはできません。 一部のシナリオでは、同じ型でプロパティが異なるデータ オブジェクトに対して異なる DataTemplate を作成することが必要になる場合があります。
たとえば、Task
オブジェクトに Priority
の1
値がある場合は、自分のアラートとして機能するようにまったく異なる外観を指定できます。 その場合は、優先度の高いDataTemplateオブジェクトを表示するためのTask
を作成します。 リソース セクションに次の 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内の要素によって共有されます。
データ オブジェクトのDataTemplate値に基づいて使用するPriority
を選択するロジックを指定するには、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>
テンプレート セレクター リソースを使用するには、ItemTemplateSelectorの ListBox プロパティに割り当てます。
ListBoxは、基になるコレクション内の各項目のSelectTemplateのTaskListDataTemplateSelector
メソッドを呼び出します。 呼び出しは、項目パラメーターとしてデータ オブジェクトを渡します。 メソッドによって返される DataTemplate は、そのデータ オブジェクトに適用されます。
<ListBox Width="400" Margin="10"
ItemsSource="{Binding Source={StaticResource myTodoList}}"
ItemTemplateSelector="{StaticResource myDataTemplateSelector}"
HorizontalContentAlignment="Stretch"/>
テンプレート セレクターを配置すると、 ListBox は次のように表示されます。
これで、この例の説明は終了です。 完全なサンプルについては、「 データ テンプレートサンプルの概要」を参照してください。
ItemsControl のスタイル設定とテンプレート化
ItemsControlは、DataTemplateを使用できる唯一のコントロール型ではありませんが、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 つのスタイル関連プロパティは、 GroupStyle と GroupStyleSelectorです。
階層データのサポート
ここまでは、1 つのコレクションにバインドして表示する方法のみを見てきた。 他のコレクションを含むコレクションがある場合があります。
HierarchicalDataTemplate クラスは、このようなデータを表示するためにHeaderedItemsControl型と共に使用するように設計されています。 次の例では、 ListLeagueList
は League
オブジェクトの一覧です。 各 League
オブジェクトには、 Name
と Division
オブジェクトのコレクションがあります。 各 Division
には Name
と Team
オブジェクトのコレクションがあり、各 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を使用すると、他のリストを含むリスト データを簡単に表示できることを示しています。 この例のスクリーンショットを次に示します。
こちらも参照ください
.NET Desktop feedback