Sdílet prostřednictvím


Přehled šablon dat

Model šablon dat WPF poskytuje velkou flexibilitu při definování prezentace dat. Ovládací prvky WPF mají integrované funkce, které podporují přizpůsobení prezentace dat. Toto téma nejprve ukazuje, jak definovat DataTemplate a pak zavést další funkce šablon dat, jako je výběr šablon na základě vlastní logiky a podpora zobrazení hierarchických dat.

Požadavky

Toto téma se zaměřuje na funkce šablonování dat a nejedná se o zavedení konceptů datových vazeb. Informace o základních konceptech datových vazeb naleznete v Přehledu datových vazeb .

DataTemplate se týká prezentace dat a je jednou z mnoha funkcí poskytovaných modelem stylů a šablon WPF. Úvod do modelu stylů a šablon WPF, jako je použití Style k nastavení vlastností ovládacích prvků, najdete v tématu Stylingu a Šablonování.

Kromě toho je důležité pochopit Resources, což jsou v podstatě to, co umožňuje objekty, jako jsou Style a DataTemplate být opakovaně použitelné. Další informace o prostředcích najdete v tématu prostředky XAML.

Základy šablon dat

Abychom si ukázali, proč je důležité DataTemplate, projdeme si příklad datové vazby. V tomto příkladu máme ListBox, který je vázán na seznam Task objektů. Každý objekt TaskTaskName (řetězec), Description (řetězec), Priority (int) a vlastnost typu TaskType, což je Enum s hodnotami Home a 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>

Bez DataTemplate

Bez DataTemplatenáš ListBox momentálně vypadá takto:

cs-CZ: Snímek obrazovky ukázky úvodu do šablonování dat zobrazuje okno obsahující Seznam úkolů (ListBox) s řetězcovou reprezentací SDKSample.Task pro každý zdrojový objekt.

Co se děje, je to, že ListBox ve výchozím nastavení volá ToString při pokusu o zobrazení objektů v kolekci. Pokud objekt Task přepíše metodu ToString, pak ListBox zobrazí řetězcovou reprezentaci každého zdrojového objektu v podkladové kolekci.

Pokud například třída Task přepíše metodu ToString tímto způsobem, kde name je pole pro vlastnost TaskName:

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

Pak ListBox vypadá takto:

Snímek obrazovky okna Ukázka úvodu do šablonování dat, který zobrazuje ListBox Můj seznam úkolů se seznamem úkolů.

To ale omezuje a nepružné. Také pokud vytváříte vazbu na data XML, nebudete moci přepsat ToString.

Definování jednoduchého objektu DataTemplate

Řešením je definovat DataTemplate. Jedním ze způsobů, jak toho dosáhnout, je nastavit vlastnost ItemTemplate objektu ListBox na hodnotu DataTemplate. To, co zadáte ve svém DataTemplate se stane vizuální strukturou datového objektu. Následující DataTemplate je poměrně jednoduchý. Dáváme pokyn, aby se každá položka zobrazila jako tři prvky TextBlock v StackPanel. Každý prvek TextBlock je vázán na vlastnost třídy 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>

Podkladová data pro příklady v tomto tématu jsou kolekce objektů CLR. Pokud vytváříte vazbu na data XML, základní koncepty jsou stejné, ale existuje mírný syntaktický rozdíl. Například místo Path=TaskNamebyste nastavili XPath na @TaskName (pokud je TaskName atributem uzlu XML).

Naše ListBox teď vypadá takto:

Snímek obrazovky okna

Vytvoření objektu DataTemplate jako prostředku

V předchozím příkladu jsme definovali DataTemplate jako vložený prvek. Je častější definovat ho v oddílu prostředků, aby mohl být opakovaně použitelným objektem, jak je znázorněno v následujícím příkladu:

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

Teď můžete jako prostředek použít myTaskTemplate, jak je znázorněno v následujícím příkladu:

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

Vzhledem k tomu, že myTaskTemplate je prostředek, můžete ho nyní použít u jiných ovládacích prvků, které mají vlastnost, která přebírá typ DataTemplate. Jak je znázorněno výše, pro ItemsControl objekty, například ListBox, je to ItemTemplate vlastnost. U ContentControl objektů je to vlastnost ContentTemplate.

Vlastnost DataType

Třída DataTemplate má vlastnost DataType, která je velmi podobná vlastnosti TargetType třídy Style. Proto místo zadání x:Key pro DataTemplate v předchozím příkladu můžete udělat toto:

<DataTemplate DataType="{x:Type local:Task}">
  <StackPanel>
    <TextBlock Text="{Binding Path=TaskName}" />
    <TextBlock Text="{Binding Path=Description}"/>
    <TextBlock Text="{Binding Path=Priority}"/>
  </StackPanel>
</DataTemplate>

Tento DataTemplate se automaticky použije pro všechny objekty Task. Všimněte si, že v tomto případě je x:Key nastaven implicitně. Proto pokud přiřadíte tuto DataTemplatex:Key hodnotu, přepisujete implicitní x:Key a DataTemplate se nepoužije automaticky.

Pokud vytváříte vazbu ContentControl na kolekci Task objektů, ContentControl nepoužívá výše uvedené DataTemplate automaticky. Důvodem je to, že vazba na ContentControl potřebuje více informací k rozlišení, zda chcete vytvořit vazbu k celé kolekci nebo k jednotlivým objektům. Pokud ContentControl sleduje výběr typu ItemsControl, můžete nastavit vlastnost Path vazby ContentControl na "/", abyste označili, že vás zajímá aktuální položka. Příklad najdete v tématu Vázání na kolekci a zobrazení informací na základě výběru. V opačném případě je nutné explicitně zadat DataTemplate nastavením vlastnosti ContentTemplate.

Vlastnost DataType je zvláště užitečná, pokud máte CompositeCollection různých typů datových objektů. Příklad najdete v tématu Implementace CompositeCollection.

Přidání dalších dat do dataTemplate

V současné době se data zobrazují s potřebnými informacemi, ale rozhodně je zde prostor pro zlepšení. Pojďme prezentaci vylepšit přidáním Border, Grida některých TextBlock prvků, které popisují zobrazená data.


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

Následující snímek obrazovky ukazuje ListBox s upraveným DataTemplate:

Snímek obrazovky z okna Ukázka úvodu do šablonování dat, který ukazuje ListBox s názvem Seznam mých úkolů s upravenou šablonou DataTemplate.

Můžeme nastavit HorizontalContentAlignment na Stretch na ListBox, abychom měli jistotu, že šířka položek zabírá celé místo:

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

Vlastnost HorizontalContentAlignment nastavená na Stretch, ListBox teď vypadá takto:

Snímek obrazovky okna Ukázka šablonování dat, na kterém je zobrazen Seznam úkolů (ListBox) roztažený tak, aby se na obrazovku vešel vodorovně.

Použití datových spouštěčů pro nastavení hodnot vlastností

Současná prezentace nám neřekne, jestli je Task domácí nebo kancelářský úkol. Nezapomeňte, že objekt TaskTaskType vlastnost typu TaskType, což je výčet s hodnotami Home a Work.

V následujícím příkladu DataTrigger nastaví BorderBrush elementu s názvem border na Yellow pokud je vlastnost TaskTypeTaskType.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>

Naše aplikace teď vypadá takto. Domácí úkoly mají žluté ohraničení a kancelářské úkoly mají tyrkysové ohraničení.

snímek obrazovky s oknem Ukázka šablonování dat zobrazující seznam úkolů Můj seznam úkolů se zvýrazněnými ohraničením úkolů domů a kanceláře barvou

V tomto příkladu DataTrigger používá Setter k nastavení hodnoty vlastnosti. Třídy triggeru mají také vlastnosti EnterActions a ExitActions, které umožňují spustit sadu akcí, jako jsou animace. Kromě toho existuje také MultiDataTrigger třída, která umožňuje použít změny na základě více hodnot vlastností vázané na data.

Alternativním způsobem, jak dosáhnout stejného efektu, je vytvořit vazbu BorderBrush vlastnosti na vlastnost TaskType a pomocí převaděče hodnot vrátit barvu na základě hodnoty TaskType. Vytvoření výše uvedeného efektu pomocí převaděče je mírně efektivnější z hlediska výkonu. Vytváření vlastního převaděče navíc poskytuje větší flexibilitu, protože dodáváte vlastní logiku. To, kterou techniku zvolíte, nakonec závisí na vašem scénáři a vašich preferencích. Pro více informací o tom, jak napsat převaděč, viz IValueConverter.

Co patří do objektu DataTemplate?

V předchozím příkladu jsme trigger umístili do DataTemplate pomocí vlastnosti DataTemplate.Triggers. Hodnota vlastnosti elementu Border, který se nachází v rámci DataTemplate, je nastavena pomocí triggeru Setter. Pokud vlastnosti, o které se Setters zajímá, nejsou vlastnostmi prvků aktuálního DataTemplate, může být vhodnější nastavit vlastnosti pomocí Style určeného pro třídu ListBoxItem (pokud je ovládací prvek, k němuž vytváříte vazbu, ListBox). Pokud například chcete, aby Trigger animoval Opacity hodnotu položky, když myš ukazuje na položku, definujete triggery v rámci stylu ListBoxItem. Příklad najdete v ukázce Úvod do stylování a šablonování.

Obecně mějte na paměti, že DataTemplate se používá u každého vygenerovaného ListBoxItem (další informace o tom, jak a kde se skutečně používá, najdete na stránce ItemTemplate.) Vaše DataTemplate se zabývá pouze prezentací a vzhledem datových objektů. Ve většině případů všechny ostatní aspekty prezentace, například to, jak položka vypadá, když je vybrána nebo jak ListBox stanoví položky, nepatří do definice DataTemplate. Příklad najdete v oddílu Styling a Šablonování ItemsControl.

Výběr objektu DataTemplate na základě vlastností datového objektu

V Části Vlastnost DataType jsme probrali, že můžete definovat různé šablony dat pro různé datové objekty. To je zvlášť užitečné, pokud máte CompositeCollection různých typů nebo kolekcí s položkami různých typů. V části Použít DataTrigger k aplikaci hodnot vlastností jsme ukázali, že pokud máte kolekci stejného typu datových objektů, můžete vytvořit DataTemplate a potom pomocí triggerů aplikovat změny na základě hodnot vlastností každého datového objektu. Triggery ale umožňují použít hodnoty vlastností nebo počáteční animace, ale neumožňují flexibilitu při rekonstrukci struktury datových objektů. Některé scénáře mohou vyžadovat vytvoření jiného DataTemplate pro datové objekty, které mají stejný typ, ale mají různé vlastnosti.

Pokud má například objekt Task hodnotu Priority1, můžete mu dát úplně jiný vzhled, který bude sloužit jako výstraha pro sebe. V takovém případě vytvoříte DataTemplate pro zobrazení objektů s vysokou prioritou Task. Pojďme do oddílu prostředků přidat následující 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>

Tento příklad používá vlastnost DataTemplate.Resources. Prostředky definované v této sekci jsou sdíleny prvkem v rámci DataTemplate.

Implementujte logiku pro výběr, kterou DataTemplate použít k použití na základě Priority hodnoty datového objektu, vytvořte podtřídu DataTemplateSelector a přepište metodu SelectTemplate. V následujícím příkladu metoda SelectTemplate poskytuje logiku pro vrácení příslušné šablony na základě hodnoty vlastnosti Priority. Šablona, která se má vrátit, se nachází v prostředcích obálkového prvku 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

Pak můžeme deklarovat TaskListDataTemplateSelector jako prostředek:

<Window.Resources>
<local:TaskListDataTemplateSelector x:Key="myDataTemplateSelector"/>
</Window.Resources>

Chcete-li použít prostředek selektoru šablony, přiřaďte ho k vlastnosti ItemTemplateSelector u ListBox. ListBox volá metodu SelectTemplate z TaskListDataTemplateSelector pro každou položku v rámci podkladové kolekce. Volání předá datový objekt jako parametr položky. DataTemplate vrácená metodou se pak použije na tento datový objekt.

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

S použitím selektoru šablony nyní ListBox vypadá takto:

snímek obrazovky ukázkové aplikace Úvod do šablonování dat zobrazující ListBox Seznam mých úkolů s úkoly s prioritou 1 zřetelně označenými červeným ohraničením.

Tím se dokončí naše diskuze o tomto příkladu. Kompletní ukázku najdete v tématu Úvod do ukázky šablon dat.

Stylování a šablonování prvku ItemsControl

I když ItemsControl není jediným typem ovládacího prvku, se kterým můžete DataTemplate použít, je to velmi běžný scénář vytvoření vazby ItemsControl k kolekci. V části What Belongs in a DataTemplate jsme probrali, že definice vašeho DataTemplate by se měla zabývat pouze prezentací dat. Abyste věděli, kdy není vhodné použít DataTemplate je důležité pochopit různé vlastnosti stylu a šablony poskytované ItemsControl. Následující příklad je navržen tak, aby ilustroval funkci každé z těchto vlastností. ItemsControl v tomto příkladu je svázán se stejnou kolekcí Tasks jako v předchozím příkladu. Pro demonstrační účely jsou styly a šablony v tomto příkladu deklarovány přímo.

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

Následuje snímek obrazovky s příkladem, jak se zobrazí:

ItemsControl – příklad snímku obrazovky

Všimněte si, že místo použití ItemTemplatemůžete použít ItemTemplateSelector. Příklad najdete v předchozí části. Podobně místo použití ItemContainerStylemáte možnost použít ItemContainerStyleSelector.

Dvě další vlastnosti související se stylem ItemsControl, které zde nejsou uvedeny, jsou GroupStyle a GroupStyleSelector.

Podpora hierarchických dat

Zatím jsme se podívali pouze na to, jak navázat a zobrazit jedinou kolekci. Někdy máte kolekci, která obsahuje jiné kolekce. Třída HierarchicalDataTemplate je navržená tak, aby byla použita s typy HeaderedItemsControl k zobrazení těchto dat. V následujícím příkladu je ListLeagueList seznam League objektů. Každý objekt LeagueName a kolekci objektů Division. Každý DivisionName a kolekci objektů Team a každý objekt TeamName.

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

Příklad ukazuje, že při použití HierarchicalDataTemplatemůžete snadno zobrazit data seznamu, která obsahují další seznamy. Následuje snímek obrazovky s příkladem.

Ukázkový snímek obrazovky HierarchicalDataTemplate

Viz také