Przegląd Szablonowanie danych
Model tworzenia szablonów danych WPF zapewnia dużą elastyczność definiowania prezentacji danych. Kontrolki WPF mają wbudowaną funkcjonalność do obsługi dostosowywania prezentacji danych. W tym temacie najpierw pokazano, jak zdefiniować element DataTemplate , a następnie wprowadzić inne funkcje tworzenia szablonów danych, takie jak wybór szablonów na podstawie logiki niestandardowej i obsługa wyświetlania danych hierarchicznych.
Wymagania wstępne
Ten temat koncentruje się na funkcjach tworzenia szablonów danych i nie jest wprowadzeniem pojęć związanych z powiązaniem danych. Aby uzyskać informacje na temat podstawowych pojęć związanych z powiązaniem danych, zobacz Omówienie powiązania danych.
DataTemplate dotyczy prezentacji danych i jest jedną z wielu funkcji oferowanych przez model stylów I szablonów WPF. Aby zapoznać się z wprowadzeniem modelu stylów I tworzenia szablonów WPF, na przykład sposobu ustawiania Style właściwości kontrolek, zobacz temat Styling and Templating (Styling and Templating ).
Ponadto ważne jest, aby zrozumieć Resources
element , które zasadniczo umożliwiają korzystanie z obiektów, takich jak Style i DataTemplate wielokrotnego użytku. Aby uzyskać więcej informacji na temat zasobów, zobacz Zasoby XAML.
Podstawy tworzenia szablonów danych
Aby zademonstrować, dlaczego DataTemplate jest to ważne, zapoznajmy się z przykładem powiązania danych. W tym przykładzie mamy obiekt ListBox powiązany z listą Task
obiektów. Każdy Task
obiekt ma TaskName
(ciąg), Description
ciąg (ciąg), ( Priority
int) i właściwość typu TaskType
, która jest Enum
elementem z wartościami Home
i 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 elementu DataTemplate
Bez elementu DataTemplatenasz ListBox obecnie wygląda następująco:
Dzieje się tak, ListBox że bez żadnych konkretnych instrukcji domyślne wywołania ToString
podczas próby wyświetlenia obiektów w kolekcji. W związku z tym, jeśli Task
obiekt zastępuje metodę ToString
, ListBox zostanie wyświetlona reprezentacja ciągu każdego obiektu źródłowego w kolekcji bazowej.
Jeśli na przykład Task
klasa zastępuje metodę ToString
w ten sposób, gdzie name
jest polem dla TaskName
właściwości:
public override string ToString()
{
return name.ToString();
}
Public Overrides Function ToString() As String
Return _name.ToString()
End Function
Następnie element ListBox wygląda następująco:
Jednak jest to ograniczenie i nieelastyczne. Ponadto w przypadku powiązania z danymi XML nie będzie można zastąpić ToString
elementu .
Definiowanie prostego elementu DataTemplate
Rozwiązaniem jest zdefiniowanie .DataTemplate Jednym ze sposobów, aby to zrobić, jest ustawienie ItemTemplate właściwości ListBoxDataTemplatena wartość . To, co określisz w obiekcie DataTemplate danych, staje się wizualną strukturą obiektu danych. Poniżej DataTemplate przedstawiono dość proste. Udostępniamy instrukcje, że każdy element jest wyświetlany jako trzy TextBlock elementy w elemencie StackPanel. Każdy TextBlock element jest powiązany z właściwością Task
klasy.
<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>
Podstawowe dane przykładów w tym temacie to kolekcja obiektów CLR. Jeśli wiążesz się z danymi XML, podstawowe pojęcia są takie same, ale istnieje niewielka różnica składniowa. Na przykład zamiast , Path=TaskName
należy ustawić wartość XPath@TaskName
na (jeśli TaskName
jest atrybutem węzła XML).
Teraz nasz ListBox wygląd wygląda następująco:
Tworzenie elementu DataTemplate jako zasobu
W powyższym przykładzie zdefiniowaliśmy DataTemplate wbudowany wiersz. Bardziej typowe jest zdefiniowanie go w sekcji zasobów, dzięki czemu może być obiektem wielokrotnego użytku, jak w poniższym przykładzie:
<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>
Teraz możesz użyć myTaskTemplate
jako zasobu, jak w poniższym przykładzie:
<ListBox Width="400" Margin="10"
ItemsSource="{Binding Source={StaticResource myTodoList}}"
ItemTemplate="{StaticResource myTaskTemplate}"/>
Ponieważ myTaskTemplate
jest to zasób, można go teraz używać w innych kontrolkach, które mają właściwość, która przyjmuje DataTemplate typ. Jak pokazano powyżej, dla ItemsControl obiektów, takich jak ListBox, jest ItemTemplate to właściwość . W przypadku ContentControl obiektów jest ContentTemplate to właściwość .
Właściwość DataType
Klasa DataTemplate ma właściwość bardzo podobną DataTypeTargetType do właściwości Style klasy. W związku z tym zamiast określać x:Key
element dla DataTemplate w powyższym przykładzie, można wykonać następujące czynności:
<DataTemplate DataType="{x:Type local:Task}">
<StackPanel>
<TextBlock Text="{Binding Path=TaskName}" />
<TextBlock Text="{Binding Path=Description}"/>
<TextBlock Text="{Binding Path=Priority}"/>
</StackPanel>
</DataTemplate>
Spowoduje to DataTemplate automatyczne zastosowanie do wszystkich Task
obiektów. Należy pamiętać, że w tym przypadku x:Key
parametr jest ustawiany niejawnie. W związku z tym, jeśli przypiszesz tę wartość, zastąpisz niejawną DataTemplatex:Key
x:Key
wartość i DataTemplate nie zostanie ona zastosowana automatycznie.
W przypadku powiązania ContentControl obiektu z kolekcją Task
obiektów ContentControl obiekt nie jest używany automatycznie DataTemplate . Wynika to z faktu, że powiązanie elementu ContentControl wymaga dodatkowych informacji, aby określić, czy chcesz powiązać z całą kolekcją, czy poszczególnymi obiektami. Jeśli śledzisz ContentControl wybór ItemsControl typu, możesz ustawić Path właściwość ContentControl powiązania na "/
", aby wskazać, że interesuje Cię bieżący element. Aby zapoznać się z przykładem, zobacz Wiązanie z kolekcją i Wyświetlanie informacji na podstawie zaznaczenia. W przeciwnym razie należy jawnie określić właściwość DataTemplate , ustawiając ContentTemplate właściwość .
Właściwość jest szczególnie przydatna DataType , gdy masz CompositeCollection różne typy obiektów danych. Przykład można znaleźć w temacie Implement a CompositeCollection (Implementowanie kolekcji CompositeCollection).
Dodawanie więcej do elementu DataTemplate
Obecnie dane są wyświetlane z niezbędnymi informacjami, ale zdecydowanie jest miejsce na poprawę. Ulepszmy prezentację, dodając element Border, a Gridi niektóre TextBlock elementy, które opisują wyświetlane dane.
<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>
Poniższy zrzut ekranu przedstawia element ListBox z tym zmodyfikowanym DataTemplateelementem :
Możemy ustawić HorizontalContentAlignmentStretch wartość na na , ListBox aby upewnić się, że szerokość elementów zajmuje całe miejsce:
<ListBox Width="400" Margin="10"
ItemsSource="{Binding Source={StaticResource myTodoList}}"
ItemTemplate="{StaticResource myTaskTemplate}"
HorizontalContentAlignment="Stretch"/>
Po ustawieniu HorizontalContentAlignment właściwości na Stretch, ListBox teraz wygląda następująco:
Stosowanie wartości właściwości za pomocą elementów DataTriggers
Bieżąca prezentacja nie informuje nas, czy Task
jest to zadanie domowe, czy zadanie biurowe. Pamiętaj, że Task
obiekt ma TaskType
właściwość typu TaskType
, która jest wyliczeniem z wartościami Home
i Work
.
W poniższym przykładzie DataTrigger ustawia element BorderBrush o nazwie border
na Yellow
, jeśli TaskType
właściwość ma wartość 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>
Nasza aplikacja wygląda teraz następująco. Zadania domowe pojawiają się z żółtym obramowaniem, a zadania biurowe pojawiają się z obramowaniem wodnym:
W tym przykładzie użyto DataTrigger elementu , Setter aby ustawić wartość właściwości. Klasy wyzwalacza mają EnterActions również właściwości i ExitActions , które umożliwiają uruchomienie zestawu akcji, takich jak animacje. Ponadto istnieje również MultiDataTrigger klasa, która umożliwia stosowanie zmian na podstawie wielu wartości właściwości powiązanych z danymi.
Alternatywnym sposobem osiągnięcia tego samego efektu jest powiązanie BorderBrush właściwości z właściwością TaskType
i użycie konwertera wartości w celu zwrócenia koloru na TaskType
podstawie wartości. Tworzenie powyższego efektu przy użyciu konwertera jest nieco bardziej wydajne pod względem wydajności. Ponadto tworzenie własnego konwertera zapewnia większą elastyczność, ponieważ dostarczasz własną logikę. Ostatecznie wybrana technika zależy od scenariusza i preferencji. Aby uzyskać informacje o sposobie pisania konwertera, zobacz IValueConverter.
Co należy do obiektu DataTemplate?
W poprzednim przykładzie umieściliśmy wyzwalacz w obiekcie DataTemplate przy użyciu DataTemplate.Triggers właściwości . Wyzwalacz Setter ustawia wartość właściwości elementu ( Border elementu), który znajduje się w obiekcie DataTemplate. Jeśli jednak właściwości, Setters
których dotyczą dane, nie są właściwościami elementów znajdujących się w bieżącym DataTemplateelemencie , może być bardziej odpowiednie do ustawienia właściwości przy użyciu klasy Style , która jest przeznaczona dla ListBoxItem klasy (jeśli formant, który jest powiązaniem ListBox, jest ). Jeśli na przykład chcesz Trigger animować Opacity wartość elementu, gdy mysz wskazuje element, zdefiniujesz wyzwalacze w ListBoxItem stylu. Przykład można znaleźć w przykładzie Wprowadzenie do stylów i tworzenia szablonów.
Ogólnie rzecz biorąc, należy pamiętać, że DataTemplate element jest stosowany do każdego wygenerowanego ListBoxItem elementu (aby uzyskać więcej informacji na temat sposobu i miejsca rzeczywistego zastosowania, zobacz ItemTemplate stronę). Zajmujesz DataTemplate się tylko prezentacją i wyglądem obiektów danych. W większości przypadków wszystkie inne aspekty prezentacji, takie jak wygląd elementu w przypadku wybrania lub sposobu ListBox definiowania elementów, nie należą do definicji DataTemplateelementu . Aby zapoznać się z przykładem, zobacz sekcję Styling and Templating an ItemsControl (Styleing and Templating an ItemsControl ).
Wybieranie elementu DataTemplate na podstawie właściwości obiektu danych
W sekcji Właściwość DataType omówiliśmy, że można zdefiniować różne szablony danych dla różnych obiektów danych. Jest to szczególnie przydatne, gdy masz CompositeCollection różne typy lub kolekcje z elementami różnych typów. W sekcji Use DataTriggers to Apply Property Values (Stosowanie wartości właściwości) pokazano, że jeśli masz kolekcję obiektów danych tego samego typu, możesz utworzyć DataTemplate element , a następnie użyć wyzwalaczy, aby zastosować zmiany na podstawie wartości właściwości każdego obiektu danych. Jednak wyzwalacze umożliwiają stosowanie wartości właściwości lub animacji początkowej, ale nie zapewniają one elastyczności w celu odtworzenia struktury obiektów danych. Niektóre scenariusze mogą wymagać utworzenia innego DataTemplate obiektu danych, które są tego samego typu, ale mają różne właściwości.
Na przykład gdy Task
obiekt ma Priority
wartość 1
, możesz chcieć nadać mu zupełnie inny wygląd, aby służyć jako alert dla siebie. W takim przypadku utworzysz obiekt DataTemplate dla wyświetlania obiektów o wysokim priorytcie Task
. Dodajmy następujące elementy DataTemplate do sekcji zasobów:
<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>
W tym przykładzie użyto właściwości DataTemplate.Resources . Zasoby zdefiniowane w tej sekcji są współużytkowane przez elementy w obiekcie DataTemplate.
Aby podać logikę, która DataTemplate ma być używana na Priority
podstawie wartości obiektu danych, utwórz podklasę DataTemplateSelector i przesłoń metodę SelectTemplate . W poniższym przykładzie SelectTemplate metoda zapewnia logikę, aby zwrócić odpowiedni szablon na podstawie wartości Priority
właściwości. Szablon do zwrócenia znajduje się w zasobach elementu 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
Następnie możemy zadeklarować jako TaskListDataTemplateSelector
zasób:
<Window.Resources>
<local:TaskListDataTemplateSelector x:Key="myDataTemplateSelector"/>
</Window.Resources>
Aby użyć zasobu selektora szablonów, przypisz go do ItemTemplateSelector właściwości ListBox. Metoda ListBox wywołuje metodę SelectTemplateTaskListDataTemplateSelector
dla każdego elementu w kolekcji bazowej. Wywołanie przekazuje obiekt danych jako parametr elementu. Metoda DataTemplate zwracana przez metodę jest następnie stosowana do tego obiektu danych.
<ListBox Width="400" Margin="10"
ItemsSource="{Binding Source={StaticResource myTodoList}}"
ItemTemplateSelector="{StaticResource myDataTemplateSelector}"
HorizontalContentAlignment="Stretch"/>
Selektor szablonu jest teraz ListBox wyświetlany w następujący sposób:
To kończy naszą dyskusję na temat tego przykładu. Pełny przykład można znaleźć w temacie Introduction to Data Templating Sample (Wprowadzenie do przykładu tworzenia szablonów danych).
Stylowanie i tworzenie szablonów kontrolki ItemsControl
Mimo że ItemsControl nie jest to jedyny typ kontrolki, którego można użyć DataTemplate z, jest to bardzo typowy scenariusz powiązania elementu ItemsControl z kolekcją. W sekcji What Belongs in a DataTemplate (Co należy do elementu DataTemplate), omówiliśmy, że definicja elementu DataTemplate powinna dotyczyć tylko prezentacji danych. Aby dowiedzieć się, kiedy nie nadaje się do używania elementu DataTemplate , ważne jest, aby zrozumieć różne właściwości stylu i szablonu dostarczone przez element ItemsControl. Poniższy przykład został zaprojektowany w celu zilustrowania funkcji każdej z tych właściwości. W ItemsControl tym przykładzie element jest powiązany z tą samą Tasks
kolekcją, co w poprzednim przykładzie. W celach demonstracyjnych style i szablony w tym przykładzie są zadeklarowane w tekście.
<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>
Poniżej przedstawiono zrzut ekranu przedstawiający przykład renderowania:
Należy pamiętać, że zamiast używać ItemTemplateelementu , możesz użyć elementu ItemTemplateSelector. Zapoznaj się z poprzednią sekcją, aby zapoznać się z przykładem. Podobnie zamiast używać ItemContainerStyleelementu , możesz użyć polecenia ItemContainerStyleSelector.
Dwie inne właściwości powiązane ze stylem elementu ItemsControl , które nie są tutaj wyświetlane, to GroupStyle i GroupStyleSelector.
Obsługa danych hierarchicznych
Do tej pory przyjrzeliśmy się tylko temu, jak powiązać i wyświetlić pojedynczą kolekcję. Czasami masz kolekcję zawierającą inne kolekcje. Klasa jest przeznaczona HierarchicalDataTemplate do użycia z typami HeaderedItemsControl do wyświetlania takich danych. W poniższym przykładzie ListLeagueList
jest to lista League
obiektów. Każdy League
obiekt ma Name
obiekt i kolekcję Division
obiektów. Każda Division
z nich ma Name
obiekt i kolekcję Team
obiektów, a każdy Team
obiekt ma obiekt 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>
W przykładzie pokazano, że przy użyciu programu HierarchicalDataTemplatemożna łatwo wyświetlać dane listy zawierające inne listy. Poniżej przedstawiono zrzut ekranu przedstawiający przykład.
Zobacz też
.NET Desktop feedback