資料範本化概觀
WPF 資料範本化模型對於資料呈現方式的定義,具有相當大的彈性。 WPF 控制項的內建功能支援自訂資料呈現方式。 本主題會先示範如何定義 DataTemplate (英文),之後再介紹其他資料範本化功能,例如以自訂邏輯為基礎的範本選擇,以及階層式資料的顯示支援。
必要條件
本主題著重資料範本化功能,而不是資料繫結概念簡介。 如需基本資料繫結概念的資訊,請參閱資料繫結概觀。
DataTemplate (英文) 與資料的呈現方式有關,而且是 WPF 樣式設定和範本化模型提供的眾多功能之一。 如需 WPF 樣式設定和範本化模型 (例如,如何使用 Style (部分機器翻譯) 在控制項上設定屬性) 的簡介,請參閱樣式設定和範本化主題。
此外,請務必了解 Resources
,這基本上是讓物件 (例如 Style (部分機器翻譯) 和 DataTemplate (英文)) 可重複使用的要素。 如需詳細資訊,請參閱 XAML 資源。
資料範本化基本概念
為了示範 DataTemplate (英文) 之所以重要的原因,我們來研究以下的資料繫結範例。 在這裡範例中,我們有繫結至 Task
物件清單的 ListBox (部分機器翻譯)。 每個 Task
物件各有一個 TaskName
(字串)、Description
(字串)、Priority
(整數),和一個 TaskType
型別的屬性,該型別是含有值 Home
和 Work
的 Enum
。
<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 (英文)。 若要這麼做,其中一種方法是將 ListBox (部分機器翻譯) 的 ItemTemplate (部分機器翻譯) 屬性設定為 DataTemplate (英文)。 在 DataTemplate (英文) 中指定的內容會變成您的資料物件的視覺化結構。 以下 DataTemplate (英文) 相當簡單。 我們提供的指示是,每個項目在 StackPanel (部分機器翻譯) 內會顯示為三個 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 (部分機器翻譯) 看起來會像這樣:
將 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 (英文) 型別的其他控制項上。 如上所示,若為 ItemsControl (部分機器翻譯) 物件 (例如 ListBox (部分機器翻譯)),其為 ItemTemplate (部分機器翻譯) 屬性。 若為 ContentControl (部分機器翻譯) 物件,其為 ContentTemplate (英文) 屬性。
DataType 屬性
DataTemplate (英文) 類別擁有的 DataType (部分機器翻譯) 屬性與 Style (部分機器翻譯) 類別的 TargetType (部分機器翻譯) 屬性非常類似。 因此,您無須照上面的範例為 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
是隱含設定的。 因此,如果您為這個 DataTemplate (英文) 指派 x:Key
值,則會覆寫隱含的 x:Key
,而且不會自動套用 DataTemplate (英文)。
如果您要將 ContentControl (部分機器翻譯) 繫結至 Task
物件的集合,則 ContentControl (部分機器翻譯) 不會自動使用上面的 DataTemplate (英文)。 這是因為 ContentControl (部分機器翻譯) 上的繫結需要更多的資訊,來分辨您是要繫結到整個集合或要繫結個別物件。 如果您的 ContentControl (部分機器翻譯) 要追蹤 ItemsControl (部分機器翻譯) 類型的選取,您可以將 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 (部分機器翻譯):
我們可以在 ListBox (部分機器翻譯) 上將 HorizontalContentAlignment (英文) 設定為 Stretch (英文) 以確保項目的寬度佔滿整個空間:
<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
這兩個值的列舉。
在下列範例中,如果 TaskType
屬性是 TaskType.Home
,則 DataTrigger (英文) 會將名為 border
之元素的 BorderBrush (英文) 設定為 Yellow
。
<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.Triggers (英文) 屬性將觸發程序放在 DataTemplate (英文) 內。 觸發程序的 Setter (部分機器翻譯) 會設定 DataTemplate (英文) 內元素 (Border (部分機器翻譯) 元素) 的屬性值。 不過,如果與您的 Setters
有關的屬性不是目前 DataTemplate (英文) 內元素的屬性,則使用適用於 ListBoxItem (部分機器翻譯) 類別的 Style (部分機器翻譯) 來設定屬性可能會更適合 (如果您要繫結的控制項是 ListBox (部分機器翻譯) 的話)。 例如,如果您希望 Trigger (部分機器翻譯) 在滑鼠指向某個項目時以動畫顯示該項目的 Opacity (部分機器翻譯) 值,您可以在 ListBoxItem (部分機器翻譯) 樣式內定義觸發程序。 如需範例,請參閱樣式設定和範本化範例的簡介。
一般而言,請記住 DataTemplate (英文) 會套用至每個產生的 ListBoxItem (部分機器翻譯) (如需其實際套用方式和套用位置的詳細資訊,請參閱 ItemTemplate (部分機器翻譯) 頁面)。 DataTemplate (英文) 只與資料物件的展示方式和外觀有關。 在大多數情況下,所有其他展示方面,例如項目在選取之後看起來的樣子,或是 ListBox (部分機器翻譯) 如何配置項目,都不屬於 DataTemplate (英文) 的定義範圍。 如需範例,請參閱 ItemsControl 的樣式設定和範本化一節。
根據資料物件的屬性選擇 DataTemplate
在 DataType 屬性一節中提到過,您可以為不同的資料物件定義不同的資料範本。 當您有不同型別的 CompositeCollection (部分機器翻譯) 或項目為不同型別的集合時,這就特別有用。 在使用 DataTriggers 套用屬性值一節中,我們說明過,如果集合有相同型別的資料物件時,您可以建立 DataTemplate (英文),然後使用觸發程序,根據每個資料物件的屬性值套用變更。 不過,觸發程序雖然可以讓您套用屬性值或啟動動畫,但無法提供重新建構資料物件結構的彈性。 在某些情況下,您可能需要為相同型別但有不同屬性的資料物件建立不同的 DataTemplate (英文)。
例如,當 Task
物件有一個 Priority
值為 1
時,您可能會想讓它看起來完全不同,以便提醒自己。 在此情況下,您可以建立 DataTemplate (英文) 來顯示高優先順序的 Task
物件。 讓我們將下列 DataTemplate (英文) 新增至 resources 區段:
<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>
若要使用範本選取器資源,請將其指派給 ListBox (部分機器翻譯) 的 ItemTemplateSelector (部分機器翻譯) 屬性。 ListBox (部分機器翻譯) 會針對基礎集合中的每個項目呼叫 TaskListDataTemplateSelector
的 SelectTemplate (部分機器翻譯) 方法。 該呼叫會將資料物件當做項目參數傳遞。 由該方法傳回的 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 (部分機器翻譯) 的另外兩個樣式相關屬性為 GroupStyle (部分機器翻譯) 和 GroupStyleSelector (部分機器翻譯)。
支援階層式資料
到目前為止,我們只討論了如何繫結和顯示單一集合。 有時候集合之中可能還有其他集合。 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 (部分機器翻譯) 可以輕鬆地顯示包含其他清單的清單資料。 以下是範例的螢幕擷取畫面。