資料範本化概觀

更新:2007 年 11 月

WPF 資料樣板化模型提供很大的彈性來定義資料的展示方式。WPF 控制項具有支援自訂資料展示的內建功能。本主題會先示範如何定義 DataTemplate,然後再介紹其他資料樣板化功能,例如根據自訂邏輯選取樣板以及支援顯示階層式資料。

這個主題包含下列章節。

  • 必要條件
  • 資料樣板化基本概念
  • 加入更多內容至 DataTemplate
  • 根據資料物件的屬性選擇 DataTemplate
  • ItemsControl 樣式設計和樣板化
  • 支援階層式資料
  • 相關主題

必要條件

本主題將著重在資料樣板化功能,並不介紹資料繫結概念。如需基本資料繫結概念的詳細資訊,請參閱資料繫結概觀

DataTemplate 與資料展示方式相關,是 WPF 樣式設定和樣板化模型所提供眾多功能的其中一個。如需 WPF 樣式設定和樣板化模型的簡介,例如如何使用 Style 設定控制項上的屬性,請參閱設定樣式和範本主題。

此外,也有必要了解 Resources,這基本上是讓 StyleDataTemplate 等物件可重複使用的要件。如需資源的詳細資訊,請參閱資源概觀

資料樣板化基本概念

這個章節包含下列子章節。

  • 沒有 DataTemplate 的情況
  • 定義簡單的 DataTemplate
  • 建立 DataTemplate 做為資源
  • DataType 屬性

為說明 DataTemplate 的重要性,我們先逐步解說一個資料繫結範例。這個範例有一個 ListBox,它繫結到 Task 物件的清單。每個 Task 物件都有一個 TaskName (string)、一個 Description (string)、一個 Priority (int) 和一個型別為 TaskType 的屬性,這是具有 Home 和 Work 這兩個值的 Enum。

<Window x:Class="SDKSample.Window1"
  xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="https://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();
}

那麼 ListBox 看起來就像下面這樣:

資料範本化範例螢幕擷取畫面

不過,這有諸多限制又不彈性。此外,如果繫結至 XML 資料,就不能覆寫 ToString。

定義簡單的 DataTemplate

解決方法是定義 DataTemplate。進行此作業的其中一種方式是將 ListBoxItemTemplate 屬性設為 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 型別的屬性的其他控制項上。如上述所示,對像是 ListBoxItemsControl 物件來說,這是 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 是隱含設定的。因此,如果您將 x:Key 指派給這個 DataTemplate,就會覆寫隱含的 x:Key,DataTemplate 也就不會自動套用。

如果您將 ContentControl 繫結至 Task 物件的集合,ContentControl 不會自動使用上面的 DataTemplate。這是因為 ContentControl 上的繫結需要更多的資訊,來分辨您是要繫結到整個集合或要繫結個別物件。如果您的 ContentControl 追蹤 ItemsControl 型別的選取範圍,您可以將 ContentControl 繫結的 Path 屬性設為 "/",表示您想要目前的項目。如需範例,請參閱 HOW TO:繫結至集合並根據選取項目顯示資訊。否則,您就需要設定 ContentTemplate 屬性,明確指定 DataTemplate

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

資料範本化範例螢幕擷取畫面

我們可以將 ListBoxHorizontalContentAlignment 設為 Stretch,確保項目的寬度會佔滿整個空間:

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

HorizontalContentAlignment 屬性設為 Stretch 之後,ListBox 現在看起來就像這樣:

資料範本化範例螢幕擷取畫面

使用 DataTrigger 套用屬性值

目前的展示方式並無法分辨出 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 來設定屬性值。觸發程序類別也有 EnterActionsExitActions 屬性,可讓您啟動一組動作,例如動畫。此外,還有一個 MultiDataTrigger 類別,這可讓您根據多個資料繫結屬性值套用變更。

要達到相同效果,另一個方式是將 BorderBrush 屬性繫結至 TaskType 屬性,然後使用值轉換子,根據 TaskType 值傳回色彩。如需類似範例,請參閱 HOW TO:在 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 時,您可能會想讓它看起來完全不同,以便提醒自己。在此情況下,您可以建立 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 內的項目共用。

若要提供邏輯來根據資料物件的 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 會針對基礎集合中的每個項目呼叫 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>

下圖是範例呈現的畫面:

ItemsControl 範例螢幕擷取畫面

請注意,您可以使用 ItemTemplateSelector 取代 ItemTemplate。請參閱上一節中的範例。同樣地,您可以不使用 ItemContainerStyle,而選擇使用 ItemContainerStyleSelector

ItemsControl 還有兩個與樣式相關的屬性未在本文提及,它們分別是 GroupStyleGroupStyleSelector

支援階層式資料

到目前為止,我們只討論了如何繫結和顯示單一集合。有時候集合之中可能還有其他集合。HierarchicalDataTemplate 類別便是設計來與 HeaderedItemsControl 型別搭配使用,以顯示這類的資料。在下列範例中,ListLeagueList 是 League 物件的清單。每個 League 物件都有一個 Name 和一組 Division 物件集合。每個 Division 都有一個 Name 和一組 Team 物件集合,而每個 Team 物件都有一個 Name。

<Window x:Class="SDKSample.Window1"
  xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="https://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 範例螢幕擷取畫面

如需完整範例,請參閱顯示階層式資料範例

請參閱

工作

HOW TO:尋找 DataTemplate 產生的項目

概念

最佳化效能:資料繫結

設定樣式和範本

資料繫結概觀

GridView 資料行行首樣式和範本概觀