資料範本化概觀

WPF 資料範本化模型對於資料呈現方式的定義,具有相當大的彈性。 WPF 控制項的內建功能支援自訂資料呈現方式。 本主題先示範如何定義 DataTemplate ,然後介紹其他資料範本化功能,例如根據自訂邏輯選取範本,以及顯示階層式資料的支援。

必要條件

本主題著重資料範本化功能,而不是資料繫結概念簡介。 如需基本資料繫結概念的資訊,請參閱資料繫結概觀

DataTemplate 是關於資料的呈現方式,而且是 WPF 樣式和範本化模型所提供的許多功能之一。 如需 WPF 樣式和範本化模型的簡介,例如如何使用 Style 在控制項上設定屬性,請參閱 樣式和範本化 主題。

此外,請務必瞭解 Resources ,這基本上是讓 和 等 StyleDataTemplate 物件可重複使用的物件。 如需詳細資訊,請參閱 XAML 資源

資料範本化基本概念

為了示範為什麼 DataTemplate 很重要,讓我們逐步解說資料系結範例。 在此範例中,我們有 ListBox 系結至 物件清單的 Task 。 每個 Task 物件各有一個 TaskName (字串)、Description (字串)、Priority (整數),和一個 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 。 其中一種方法是將 的 ListBox 屬性設定 ItemTemplateDataTemplate 。 您在 中指定的 DataTemplate 內容會成為資料物件的視覺結構。 以下是 DataTemplate 相當簡單的。 我們會提供指示,指出每個專案在 內顯示為三 TextBlockStackPanel 元素。 每個 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 類型之屬性的其他控制項上。 如上所示,針對 ItemsControl 物件,例如 ListBox ,它是 ItemTemplate 屬性。 如果是 ContentControl 物件,它是 ContentTemplate 屬性。

DataType 屬性

類別 DataTemplate 的屬性 DataType 與 類別的 Style 屬性非常類似 TargetType 。 因此,您可以執行下列動作,而不是在上述範例中指定 x:KeyDataTemplate 的 :

<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 至 物件的集合 TaskContentControl 則 不會自動使用上述 DataTemplate 。 這是因為 上的 ContentControl 系結需要更多資訊來區別您要系結至整個集合或個別物件。 ContentControl如果您要追蹤類型的選取 ItemsControl 範圍,您可以將系結的 ContentControl 屬性設定 Path 為 「 / 」,以指出您對目前專案感興趣。 如需範例,請參閱繫結至集合並根據選取項目顯示資訊。 否則,您必須藉由設定 ContentTemplate 屬性來明確指定 DataTemplate

當您有 CompositeCollection 不同類型的資料物件時,屬性 DataType 特別有用。 如需範例,請參閱實作 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>

下列螢幕擷取畫面顯示 ListBox 已修改 DataTemplate 的 :

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

我們可以將 設定 HorizontalContentAlignmentStretchListBox 以確保專案的寬度佔用整個空間:

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

HorizontalContentAlignment 屬性設定為 StretchListBox 現在看起來會像這樣:

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

使用 DataTriggers 套用屬性值

目前的展示方式並無法分辨出 Task 是家務還是公司的工作。 前面提過,Task 物件有一個型別為 TaskTypeTaskType 屬性,這是具有 HomeWork 這兩個值的列舉。

在下列範例中,如果 屬性是 ,則會 DataTrigger 將名為 borderYellow 的專案設定 BorderBrushTaskType.HomeTaskType

<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 來設定 屬性值。 觸發程式類別也有 EnterActionsExitActions 屬性,可讓您啟動一組動作,例如動畫。 此外,還有一個 MultiDataTrigger 類別可讓您根據多個資料系結屬性值來套用變更。

達成相同效果的替代方式是將 屬性系結 BorderBrushTaskType 屬性,並使用值轉換器根據值傳回色彩 TaskType 。 使用轉換器建立上述效果,就效能上來說會快些。 此外,建立自己的轉換器能提供更多的彈性,因為您可以提供自己的邏輯。 最後,要選擇使用哪種方式,取決於個別情況和您的偏好而定。 如需如何撰寫轉換器的資訊,請參閱 IValueConverter

哪些內容屬於 DataTemplate 的範圍

在上一個範例中,我們使用 屬性將觸發程式放在 內 DataTemplateDataTemplate.TriggersSetter觸發程式的 會設定 中專案 (專案 BorderDataTemplate 的 屬性值。 不過,如果您關心的屬性 Setters 不是目前 DataTemplate 內之元素的屬性,則使用 類別的屬性可能更適合設定 ListBoxItemStyle 屬性(如果您要系結的控制項是 。 ListBox 例如,如果您想要 Trigger 在滑鼠指向專案時以動畫顯示 Opacity 專案的值,您可以在樣式中 ListBoxItem 定義觸發程式。 如需範例,請參閱樣式設定和範本化範例的簡介

一般而言,請記住 DataTemplate ,正在套用至每個產生的 ListBoxItem 專案(如需實際套用方式和位置的詳細資訊,請參閱 ItemTemplate 頁面。 您 DataTemplate 只關心資料物件的呈現和外觀。 在大部分情況下,呈現的所有其他層面,例如選取專案時的外觀,或設定項目的方式 ListBox ,不屬於 的定義 DataTemplate 。 如需範例,請參閱 ItemsControl 的樣式設定和範本化一節。

根據資料物件的屬性選擇 DataTemplate

DataType 屬性一節中提到過,您可以為不同的資料物件定義不同的資料範本。 當您有 CompositeCollection 不同類型或具有不同類型專案的集合時,這特別有用。 在 [ 使用 DataTriggers 套用屬性值 ] 區段中,我們已經顯示,如果您有相同類型的資料物件集合,您可以建立 DataTemplate ,然後使用觸發程式根據每個資料物件的屬性值來套用變更。 不過,觸發程序雖然可以讓您套用屬性值或啟動動畫,但無法提供重新建構資料物件結構的彈性。 有些案例可能會要求您為相同類型但具有不同屬性的資料物件建立不同的 DataTemplate

例如,當 Task 物件有一個 Priority 值為 1 時,您可能會想讓它看起來完全不同,以便提醒自己。 在此情況下,您會為高優先順序 Task 物件的顯示建立 DataTemplate 。 讓我們將下列內容 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 傳回適當的範本。 要傳回的範本位於 enveloping 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>

若要使用範本選取器資源,請將它指派給 ItemTemplateSelectorListBox 屬性。 會 ListBox 針對基礎集合中的每個專案呼叫 SelectTemplate 的 方法 TaskListDataTemplateSelector 。 該呼叫會將資料物件當做項目參數傳遞。 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.

這個範例的討論到此結束。 如需完整範例,請參閱資料範本化範例簡介

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>

下圖是範例呈現的畫面:

ItemsControl example screenshot

請注意,您可以使用 ,而不是使用 ItemTemplateItemTemplateSelector 。 請參閱上一節中的範例。 同樣地,您可以選擇使用 ,而不是使用 ItemContainerStyleItemContainerStyleSelector

此處未顯示 之 的兩個其他樣式相關屬性 ItemsControlGroupStyleGroupStyleSelector

支援階層式資料

到目前為止,我們只討論了如何繫結和顯示單一集合。 有時候集合之中可能還有其他集合。 類別 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

另請參閱