Overzicht van gegevenssjablonering
Het WPF-datatemplatemodel biedt u grote flexibiliteit bij het definiëren van de presentatie van uw gegevens. WPF-besturingselementen hebben ingebouwde functionaliteit ter ondersteuning van de aanpassing van de gegevenspresentatie. In dit onderwerp wordt eerst gedemonstreert hoe u een DataTemplate definieert en vervolgens andere functies voor gegevensverleiden introduceert, zoals de selectie van sjablonen op basis van aangepaste logica en de ondersteuning voor de weergave van hiërarchische gegevens.
Voorwaarden
Dit onderwerp is gericht op functies voor het templeren van gegevens en is geen introductie van concepten voor gegevensbinding. Zie het Overzicht van gegevensbindingenvoor informatie over basisconcepten voor gegevensbinding.
DataTemplate gaat over de presentatie van gegevens en is een van de vele functies van het WPF-stijl en sjabloonmodel. Zie het onderwerp voor een inleiding over het WPF-styling- en sjabloneringmodel, zoals het gebruik van een Style om eigenschappen van besturingselementen in te stellen.
Daarnaast is het belangrijk om inzicht te krijgen in Resources
, die in wezen zijn waarmee objecten zoals Style en DataTemplate herbruikbaar kunnen worden. Zie XAML-resourcesvoor meer informatie over resources.
Basisbeginselen van gegevenssjablonen
Laten we een voorbeeld van een gegevensbinding doorlopen om te laten zien waarom DataTemplate belangrijk is. In dit voorbeeld hebben we een ListBox die is gebonden aan een lijst met Task
objecten. Elk Task
object heeft een TaskName
(tekenreeks), een Description
(tekenreeks), een Priority
(int) en een eigenschap van het type TaskType
, een Enum
met waarden Home
en 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>
Zonder DataTemplate
Zonder een DataTemplateziet onze ListBox er momenteel als volgt uit:
Zonder specifieke instructies roept de ListBox standaard ToString
aan wanneer de objecten in de verzameling worden weergegeven. Als het Task
object daarom de methode ToString
overschrijft, geeft de ListBox de tekenreeksweergave weer van elk bronobject in de onderliggende verzameling.
Als de Task
klasse bijvoorbeeld de ToString
methode op deze manier overschrijft, waarbij name
het veld is voor de eigenschap TaskName
:
public override string ToString()
{
return name.ToString();
}
Public Overrides Function ToString() As String
Return _name.ToString()
End Function
Vervolgens ziet de ListBox er als volgt uit:
Dat is echter beperkend en inflexibel. Als u binding hebt met XML-gegevens, kunt u ToString
niet overschrijven.
Een Simple DataTemplate definiëren
De oplossing is het definiëren van een DataTemplate. Een manier om dat te doen, is door de eigenschap ItemTemplate van de ListBox in te stellen op een DataTemplate. Wat u opgeeft in uw DataTemplate wordt de visuele structuur van uw gegevensobject. De volgende DataTemplate is redelijk eenvoudig. We geven instructies dat elk item wordt weergegeven als drie TextBlock elementen binnen een StackPanel. Elk TextBlock element is gebonden aan een eigenschap van de Task
klasse.
<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>
De onderliggende gegevens voor de voorbeelden in dit onderwerp zijn een verzameling CLR-objecten. Als u verbinding maakt met XML-gegevens, zijn de basisconcepten hetzelfde, maar er is een klein syntactisch verschil. In plaats van bijvoorbeeld Path=TaskName
te hebben, stelt u XPath in op @TaskName
(als TaskName
een kenmerk van uw XML-knooppunt is).
De ListBox ziet er nu als volgt uit:
DataTemplate als een resource aanmaken
In het bovenstaande voorbeeld hebben we de DataTemplate inline gedefinieerd. Het is gebruikelijker om deze te definiëren in de sectie Resources, zodat het een herbruikbaar object kan zijn, zoals in het volgende voorbeeld:
<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>
U kunt nu myTaskTemplate
als resource gebruiken, zoals in het volgende voorbeeld:
<ListBox Width="400" Margin="10"
ItemsSource="{Binding Source={StaticResource myTodoList}}"
ItemTemplate="{StaticResource myTaskTemplate}"/>
Omdat myTaskTemplate
een resource is, kunt u deze nu gebruiken voor andere besturingselementen die een eigenschap hebben die een DataTemplate type gebruikt. Zoals hierboven wordt weergegeven, is het voor ItemsControl objecten, zoals de ListBox, de eigenschap ItemTemplate. Voor ContentControl objecten is dit de eigenschap ContentTemplate.
De eigenschap DataType
De klasse DataTemplate heeft een DataType eigenschap die vergelijkbaar is met de eigenschap TargetType van de klasse Style. Daarom kunt u het volgende doen in plaats van een x:Key
op te geven voor de DataTemplate in het bovenstaande voorbeeld:
<DataTemplate DataType="{x:Type local:Task}">
<StackPanel>
<TextBlock Text="{Binding Path=TaskName}" />
<TextBlock Text="{Binding Path=Description}"/>
<TextBlock Text="{Binding Path=Priority}"/>
</StackPanel>
</DataTemplate>
Deze DataTemplate wordt automatisch toegepast op alle Task
objecten. Houd er rekening mee dat in dit geval de x:Key
impliciet wordt ingesteld. Als u deze DataTemplate een x:Key
waarde toewijst, overschrijft u daarom de impliciete x:Key
en wordt de DataTemplate niet automatisch toegepast.
Als u een ContentControl aan een verzameling Task
objecten bindt, gebruikt de ContentControl de bovenstaande DataTemplate niet automatisch. Dit komt doordat de binding op een ContentControl meer informatie nodig heeft om onderscheid te maken of u verbinding wilt maken met een hele verzameling of de afzonderlijke objecten. Als uw ContentControl de selectie van een ItemsControl type bijhoudt, kunt u de eigenschap Path van de ContentControl binding instellen op '/
' om aan te geven dat u geïnteresseerd bent in het huidige item. Zie voor een voorbeeld Binden aan een verzameling en informatie weergeven op basis van selectie. Anders moet u de DataTemplate expliciet opgeven door de eigenschap ContentTemplate in te stellen.
De eigenschap DataType is met name handig wanneer u een CompositeCollection van verschillende typen gegevensobjecten hebt. Zie voor een voorbeeld Een CompositeCollection-implementeren.
Meer toevoegen aan de DataTemplate
Momenteel worden de gegevens weergegeven met de benodigde informatie, maar er is zeker ruimte voor verbetering. Laten we de presentatie verbeteren door een Bordertoe te voegen, een Griden enkele TextBlock elementen die de gegevens beschrijven die worden weergegeven.
<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>
In de volgende schermopname ziet u de ListBox met deze gewijzigde DataTemplate:
We kunnen HorizontalContentAlignment instellen op Stretch op de ListBox om ervoor te zorgen dat de breedte van de items de hele ruimte in beslag neemt:
<ListBox Width="400" Margin="10"
ItemsSource="{Binding Source={StaticResource myTodoList}}"
ItemTemplate="{StaticResource myTaskTemplate}"
HorizontalContentAlignment="Stretch"/>
Nu de eigenschap HorizontalContentAlignment is ingesteld op Stretch, ziet de ListBox er nu als volgt uit:
DataTriggers gebruiken om eigenschapswaarden toe te passen
In de huidige presentatie wordt niet aangegeven of een Task
een thuistaak of een kantoortaak is. Houd er rekening mee dat het Task
-object een eigenschap TaskType
van het type TaskType
heeft. Dit is een opsomming met waarden Home
en Work
.
In het volgende voorbeeld stelt de DataTrigger het BorderBrush van het element met de naam border
in op Yellow
als de eigenschap TaskType
is 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>
Onze toepassing ziet er nu als volgt uit. Thuistaken worden weergegeven met een gele rand en kantoortaken worden weergegeven met een aquarand:
In dit voorbeeld gebruikt de DataTrigger een Setter om een eigenschapswaarde in te stellen. De triggerklassen hebben ook de eigenschappen EnterActions en ExitActions waarmee u een set acties zoals animaties kunt starten. Daarnaast is er ook een MultiDataTrigger klasse waarmee u wijzigingen kunt toepassen op basis van meerdere gegevensgebonden eigenschapswaarden.
Een andere manier om hetzelfde effect te bereiken, is door de eigenschap BorderBrush te binden aan de eigenschap TaskType
en een waardeconversieprogramma te gebruiken om de kleur te retourneren op basis van de TaskType
waarde. Het maken van het bovenstaande effect met behulp van een conversieprogramma is iets efficiënter in termen van prestaties. Bovendien biedt het maken van uw eigen conversieprogramma u meer flexibiliteit omdat u uw eigen logica levert. Welke techniek u kiest, hangt uiteindelijk af van uw scenario en uw voorkeur. Zie IValueConvertervoor meer informatie over het schrijven van een conversieprogramma.
Wat hoort er in een DataTemplate?
In het vorige voorbeeld hebben we de trigger in de DataTemplate geplaatst met behulp van de eigenschap DataTemplate.Triggers. De Setter van de trigger stelt de waarde in van een eigenschap van een element (het Border-element) die zich in de DataTemplatebevindt. Als de eigenschappen waarmee uw Setters
zich bezig houden echter geen eigenschappen zijn van elementen die zich in de huidige DataTemplatebevinden, is het mogelijk beter geschikt om de eigenschappen in te stellen met behulp van een Style die voor de ListBoxItem klasse is (als het besturingselement dat u bindt een ListBoxis). Als u bijvoorbeeld wilt dat Trigger de Opacity-waarde van het item animeert wanneer een muis naar een item wijst, definieert u triggers binnen een ListBoxItem-opmaak. Zie voor een voorbeeld de Inleiding tot stijl- en voorbeeldvoorbeelden.
Houd er in het algemeen rekening mee dat de DataTemplate wordt toegepast op elk van de gegenereerde ListBoxItem (zie de ItemTemplate pagina voor meer informatie over hoe en waar deze daadwerkelijk wordt toegepast.) Uw DataTemplate houdt zich alleen bezig met de presentatie en het uiterlijk van de gegevensobjecten. In de meeste gevallen horen alle andere aspecten van de presentatie, zoals hoe een item eruitziet wanneer het wordt geselecteerd of hoe de ListBox de items opgeeft, niet behoren tot de definitie van een DataTemplate. Zie voor een voorbeeld de sectie Styling en Templating van ItemsControl.
Een DataTemplate kiezen op basis van eigenschappen van het gegevensobject
In sectie DataType Property hebben we besproken dat u verschillende gegevenssjablonen voor verschillende gegevensobjecten kunt definiëren. Dit is vooral handig wanneer u een CompositeCollection hebt van verschillende typen of verzamelingen met items van verschillende typen. In het gedeelte DataTriggers gebruiken om eigenschapswaarden toe te passen, hebben we aangetoond dat als u een verzameling van hetzelfde type gegevensobjecten hebt, een DataTemplate kunt maken en vervolgens triggers kunt gebruiken om wijzigingen toe te passen op basis van de eigenschapswaarden van elk gegevensobject. Met triggers kunt u echter eigenschapswaarden toepassen of animaties starten, maar ze bieden u niet de flexibiliteit om de structuur van uw gegevensobjecten te reconstrueren. Voor sommige scenario's moet u mogelijk een andere DataTemplate maken voor gegevensobjecten die van hetzelfde type zijn, maar verschillende eigenschappen hebben.
Als een Task
object bijvoorbeeld een Priority
waarde van 1
heeft, kunt u het een heel ander uiterlijk geven om als waarschuwing voor uzelf te fungeren. In dat geval maakt u een DataTemplate voor de weergave van de objecten met hoge prioriteit Task
. Laten we de volgende DataTemplate toevoegen aan de sectie 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>
In dit voorbeeld wordt de eigenschap DataTemplate.Resources gebruikt. Resources die in die sectie zijn gedefinieerd, worden gedeeld door de elementen in de DataTemplate.
Als u logica wilt opgeven om te kiezen welke DataTemplate u wilt gebruiken op basis van de Priority
waarde van het gegevensobject, maakt u een subklasse van DataTemplateSelector en overschrijft u de methode SelectTemplate. In het volgende voorbeeld biedt de methode SelectTemplate logica om de juiste sjabloon te retourneren op basis van de waarde van de eigenschap Priority
. De sjabloon die moet worden geretourneerd, vindt u in de resources van het omhullende Window element.
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
Vervolgens kunnen we de TaskListDataTemplateSelector
declareren als een resource:
<Window.Resources>
<local:TaskListDataTemplateSelector x:Key="myDataTemplateSelector"/>
</Window.Resources>
Als u de sjabloonkiezerresource wilt gebruiken, wijst u deze toe aan de eigenschap ItemTemplateSelector van de ListBox. De ListBox roept de SelectTemplate methode van de TaskListDataTemplateSelector
aan voor elk van de items in de onderliggende verzameling. De aanroep geeft het gegevensobject door als de itemparameter. De DataTemplate die door de methode wordt geretourneerd, wordt vervolgens toegepast op dat gegevensobject.
<ListBox Width="400" Margin="10"
ItemsSource="{Binding Source={StaticResource myTodoList}}"
ItemTemplateSelector="{StaticResource myDataTemplateSelector}"
HorizontalContentAlignment="Stretch"/>
Nu de sjabloonkiezer is ingesteld, wordt de ListBox nu als volgt weergegeven:
Dit sluit onze bespreking van dit voorbeeld af. Voor het volledige voorbeeld, zie Inleiding tot data-templatesample.
Stylen en sjabloneren van een ItemsControl
Hoewel de ItemsControl niet het enige besturingselementtype is waarmee u een DataTemplate kunt gebruiken, is het een zeer gebruikelijk scenario om een ItemsControl aan een verzameling te binden. In de Wat hoort bij een DataTemplate- sectie hebben we besproken dat de definitie van uw DataTemplate alleen betrekking mag hebben op de presentatie van gegevens. Om te weten wanneer het niet geschikt is voor het gebruik van een DataTemplate is het belangrijk om de verschillende stijl- en sjablooneigenschappen van de ItemsControlte begrijpen. Het volgende voorbeeld is ontworpen om de functie van elk van deze eigenschappen te illustreren. De ItemsControl in dit voorbeeld is gebonden aan dezelfde Tasks
verzameling als in het vorige voorbeeld. Voor demonstratiedoeleinden worden de stijlen en sjablonen in dit voorbeeld allemaal inline gedeclareerd.
<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>
Hier volgt een schermopname van het voorbeeld wanneer het wordt weergegeven:
In plaats van de ItemTemplatete gebruiken, kunt u de ItemTemplateSelectorgebruiken. Raadpleeg de vorige sectie voor een voorbeeld. In plaats van de ItemContainerStylete gebruiken, hebt u ook de mogelijkheid om de ItemContainerStyleSelectorte gebruiken.
Twee andere stijlgerelateerde eigenschappen van de ItemsControl die hier niet worden weergegeven, zijn GroupStyle en GroupStyleSelector.
Ondersteuning voor hiërarchische gegevens
Tot nu toe hebben we alleen gekeken naar hoe we één verzameling kunnen binden en weergeven. Soms hebt u een verzameling die andere verzamelingen bevat. De HierarchicalDataTemplate klasse is ontworpen voor gebruik met HeaderedItemsControl typen om dergelijke gegevens weer te geven. In het volgende voorbeeld is ListLeagueList
een lijst met League
objecten. Elk League
-object heeft een Name
en een verzameling Division
objecten. Elke Division
heeft een Name
en een verzameling Team
objecten en elk Team
-object heeft een 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>
In het voorbeeld ziet u dat u met het gebruik van HierarchicalDataTemplateeenvoudig lijstgegevens kunt weergeven die andere lijsten bevatten. Hier volgt een schermopname van het voorbeeld.
schermopname van
Zie ook
.NET Desktop feedback