Anteckning
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
WPF-databearbetningsmodellen ger dig stor flexibilitet att definiera presentationen av dina data. WPF-kontroller har inbyggda funktioner som stöd för anpassning av datapresentation. Det här avsnittet visar först hur du definierar en DataTemplate och sedan introducerar andra datatempereringsfunktioner, till exempel val av mallar baserat på anpassad logik och stöd för visning av hierarkiska data.
Förutsättningar
Det här avsnittet fokuserar på databeräkningsfunktioner och är inte en introduktion till databindningsbegrepp. Information om grundläggande databindningsbegrepp finns i Översikt över databindning.
DataTemplate handlar om presentation av data och är en av de många funktioner som tillhandahålls av WPF styling och templating modell. En introduktion till WPF-formaterings- och mallmodellen, till exempel hur du använder en Style för att ange egenskaper för kontroller, finns i avsnittet Formatering och templating .
Dessutom är det viktigt att förstå Resources, vilket i huvudsak är vad som gör att objekt som Style och DataTemplate kan återanvändas. Mer information om resurser finns i XAML-resurser.
Grunderna för datamallning
För att visa varför DataTemplate är viktigt ska vi gå igenom ett exempel på databindning. I det här exemplet har vi en ListBox som är bunden till en lista över Task objekt. Varje Task objekt har en TaskName (sträng), en Description (sträng), en Priority (int) och en egenskap av typen TaskType, som är en Enum med värden Home och 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>
Utan datatemplate
Utan en DataTemplateser vår ListBox för närvarande ut så här:
Det som händer är att ListBox som standard anropar ToString i ett försök att visa objekten i samlingen utan några specifika instruktioner. Om objektet Task åsidosätter ToString metoden ListBox visas därför strängrepresentationen för varje källobjekt i den underliggande samlingen.
Om klassen till exempel Task åsidosätter ToString metoden på det här sättet, var name är fältet för TaskName egenskapen:
public override string ToString()
{
return name.ToString();
}
Public Overrides Function ToString() As String
Return _name.ToString()
End Function
ListBox Sedan ser det ut så här:
Det är dock begränsande och oflexibelt. Om du binder till XML-data skulle du inte heller kunna åsidosätta ToString.
Definiera ett enkelt datatemplate
Lösningen är att definiera en DataTemplate. Ett sätt att göra det är att ange ItemTemplate-egenskapen för ListBox till en DataTemplate. Det du anger i din DataTemplate blir den visuella strukturen för dataobjektet. Följande DataTemplate är ganska enkelt. Vi ger instruktioner om att varje objekt visas som tre TextBlock element i en StackPanel. Varje TextBlock element är bundet till en egenskap för Task klassen.
<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>
Underliggande data för exemplen i det här avsnittet är en samling CLR-objekt. Om du binder till XML-data är de grundläggande begreppen desamma, men det finns en liten syntaktisk skillnad. I stället för att till exempel ha Path=TaskName sätter du XPath till @TaskName (om TaskName är ett attribut till din XML-nod).
Nu ser vi ListBox ut så här:
Skapa DataTemplate som en resurs
I exemplet ovan definierade vi DataTemplate inline. Det är vanligare att definiera det i resursavsnittet så att det kan vara ett återanvändbart objekt, som i följande exempel:
<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>
Nu kan du använda myTaskTemplate som en resurs, som i följande exempel:
<ListBox Width="400" Margin="10"
ItemsSource="{Binding Source={StaticResource myTodoList}}"
ItemTemplate="{StaticResource myTaskTemplate}"/>
Eftersom myTaskTemplate är en resurs kan du nu använda den på andra kontroller som har en egenskap som tar en DataTemplate typ. Som du ser ovan är ItemsControl det egenskapen för ListBox objekt, till exempel ItemTemplateegenskapen . För ContentControl objekt är det egenskapen ContentTemplate .
Egenskapen DataType
Klassen DataTemplate har en DataType egenskap som liknar TargetType egenskapen för Style klassen. I stället för att ange en x:Key för DataTemplate i exemplet ovan kan du därför göra följande:
<DataTemplate DataType="{x:Type local:Task}">
<StackPanel>
<TextBlock Text="{Binding Path=TaskName}" />
<TextBlock Text="{Binding Path=Description}"/>
<TextBlock Text="{Binding Path=Priority}"/>
</StackPanel>
</DataTemplate>
Detta DataTemplate tillämpas automatiskt på alla Task objekt. Observera att i det här fallet x:Key anges implicit. Om du tilldelar det här DataTemplate värdet x:Key åsidosättas därför implicit x:Key och DataTemplate tillämpas inte automatiskt.
Om du binder en ContentControl till en samling Task objekt ContentControl använder inte ovanstående DataTemplate automatiskt. Det beror på att bindningen för en ContentControl behöver mer information för att skilja mellan om du vill binda till en hel samling eller enskilda objekt. Om du ContentControl spårar ett val av typ ItemsControl kan du ange Path-egenskapen för ContentControl-bindningen till "/" för att indikera att du är intresserad av det aktuella objektet. Ett exempel finns i Binda till en samling och visa information baserat på markering. Annars måste du ange uttryckligen DataTemplate genom att ange egenskapen ContentTemplate .
Egenskapen DataType är särskilt användbar när du har olika CompositeCollection typer av dataobjekt. Ett exempel finns i Implementera en CompositeCollection.
Lägga till mer i DataTemplate
För närvarande visas data med nödvändig information, men det finns definitivt utrymme för förbättringar. Nu ska vi förbättra presentationen genom att lägga till ett Border, ett Gridoch några TextBlock element som beskriver de data som visas.
<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>
Följande skärmbild visar ListBox med den ändrade DataTemplate:
Vi kan konfigurera HorizontalContentAlignment till Stretch på ListBox för att säkerställa att bredden på föremålen fyller hela utrymmet.
<ListBox Width="400" Margin="10"
ItemsSource="{Binding Source={StaticResource myTodoList}}"
ItemTemplate="{StaticResource myTaskTemplate}"
HorizontalContentAlignment="Stretch"/>
Med egenskapen HorizontalContentAlignment inställd på Stretch så ser ListBox nu ut så här:
Använda DataTriggers för att tillämpa egenskapsvärden
Den aktuella presentationen anger inte om en Task är en hemuppgift eller en kontorsuppgift. Kom ihåg att Task objektet har en TaskType egenskap av typen TaskType, vilket är en uppräkning med värden Home och Work.
I följande exempel DataTrigger anger värdet för BorderBrush elementet med namnet border till Yellow om egenskapen TaskType är 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>
Vårt program ser nu ut så här. Hemuppgifter visas med en gul kantlinje och kontorsaktiviteter visas med en vattenkantlinje:
I det här exemplet DataTrigger använder en Setter för att ange ett egenskapsvärde. Utlösarklasserna har också egenskaperna EnterActions och ExitActions som låter dig starta en uppsättning åtgärder, till exempel animeringar. Dessutom finns det också en MultiDataTrigger klass som gör att du kan tillämpa ändringar baserat på flera databundna egenskapsvärden.
Ett annat sätt att uppnå samma effekt är att binda BorderBrush egenskapen till TaskType egenskapen och använda en värdekonverterare för att returnera färgen baserat på TaskType värdet. Att skapa ovanstående effekt med hjälp av en konverterare är något effektivare när det gäller prestanda. Att skapa en egen konverterare ger dig dessutom större flexibilitet eftersom du tillhandahåller din egen logik. Vilken teknik du väljer beror i slutändan på ditt scenario och dina önskemål. Information om hur du skriver en konverterare finns i IValueConverter.
Vad hör hemma i ett DataTemplate?
I föregående exempel placerade vi utlösaren inom DataTemplate med hjälp av DataTemplate.Triggers-egenskapen. Utlösaren Setter anger värdet för en egenskap för ett element (elementet Border ) som finns i DataTemplate. Men om de egenskaper som ditt Setters är intresserad av inte är egenskaper hos element som finns i det aktuella DataTemplate, kan det vara mer lämpligt att ange egenskaperna med en Style som är för ListBoxItem-klassen (om kontrollen du binder är en ListBox). Om du till exempel vill att din Trigger ska animera Opacity objektets värde när muspekaren hålls över ett objekt, definierar du utlösare i en ListBoxItem stil. Ett exempel finns i Exempel på introduktion till formatering och templatering.
I allmänhet bör du tänka på att DataTemplate tillämpas på var och en av de genererade ListBoxItem (mer information om hur och var den faktiskt tillämpas finns på ItemTemplate sidan.). Du DataTemplate bryr dig bara om presentationen och utseendet på dataobjekten. I de flesta fall hör inte alla andra aspekter av presentationen, till exempel hur ett objekt ser ut när det väljs eller hur objekten ListBox anges, inte i definitionen av en DataTemplate. Ett exempel finns i avsnittet Formatering och mallning av en ItemsControl .
Välja ett DataTemplate baserat på egenskaper för dataobjektet
I avsnittet DataType-egenskap diskuterade vi att du kan definiera olika datamallar för olika dataobjekt. Det är särskilt användbart när du har en CompositeCollection av olika typer eller samlingar med objekt av olika typer. I avsnittet Använd DataTriggers för att tillämpa egenskapsvärden har vi visat att om du har en samling av samma typ av dataobjekt kan du skapa en DataTemplate och sedan använda utlösare för att tillämpa ändringar baserat på egenskapsvärdena för varje dataobjekt. Med utlösare kan du dock använda egenskapsvärden eller starta animeringar, men de ger dig inte flexibiliteten att rekonstruera strukturen för dina dataobjekt. Vissa scenarier kan kräva att du skapar en annan DataTemplate för dataobjekt som är av samma typ men som har olika egenskaper.
När ett Task objekt till exempel har värdet Priority1, kanske du vill ge det ett helt annat utseende för att fungera som en avisering för dig själv. I så fall skapar du en DataTemplate för visning av objekt med hög prioritet Task . Nu ska vi lägga till följande DataTemplate i resursavsnittet:
<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>
I det här exemplet används egenskapen DataTemplate.Resources . Resurser som definierats i det avsnittet delas av elementen i DataTemplate.
Om du vill ange logik för att välja vilken som DataTemplate ska användas baserat på värdet för Priority dataobjektet skapar du en underklass av DataTemplateSelector och åsidosätter SelectTemplate metoden. I följande exempel SelectTemplate tillhandahåller metoden logik för att returnera lämplig mall baserat på egenskapens Priority värde. Mallen som ska returneras finns i resurserna i det omslutande Window elementet.
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
Vi kan sedan deklarera TaskListDataTemplateSelector som en resurs:
<Window.Resources>
<local:TaskListDataTemplateSelector x:Key="myDataTemplateSelector"/>
</Window.Resources>
För att använda resursen för mallväljaren, tilldela den till ItemTemplateSelector-egenskapen av ListBox.
ListBox anropar metoden SelectTemplate av TaskListDataTemplateSelector för varje objekt i den underliggande samlingen. Anropet skickar dataobjektet som objektparameter. Det DataTemplate som returneras av metoden tillämpas sedan på det dataobjektet.
<ListBox Width="400" Margin="10"
ItemsSource="{Binding Source={StaticResource myTodoList}}"
ItemTemplateSelector="{StaticResource myDataTemplateSelector}"
HorizontalContentAlignment="Stretch"/>
När mallväljaren är på plats ListBox visas nu på följande sätt:
Detta avslutar vår diskussion om det här exemplet. För det kompletta exemplet, se Introduktion till datamall-exemplet.
Utforma och templatera en ItemsControl
Även om ItemsControl det inte är den enda kontrolltypen som du kan använda en DataTemplate med, är det ett mycket vanligt scenario att binda en ItemsControl till en samling. I avsnittet What Belongs in a DataTemplate (Vad hör hemma i ett DataTemplate ) diskuterade vi att definitionen av din DataTemplate endast ska beröra presentationen av data. För att veta när det inte är lämpligt att använda en DataTemplate är det viktigt att förstå de olika format- och mallegenskaperna som tillhandahålls av ItemsControl. Följande exempel är utformat för att illustrera funktionen för var och en av dessa egenskaper.
ItemsControl Exemplet här är bunden till samma Tasks samling som i föregående exempel. I demonstrationssyfte deklareras alla formatmallar och mallar i det här exemplet inline.
<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>
Följande är en skärmbild av exemplet när det visas:
Observera att i stället för att ItemTemplateanvända kan du använda ItemTemplateSelector. Ett exempel finns i föregående avsnitt. På samma sätt har du, istället för att använda ItemContainerStyle, möjlighet att använda ItemContainerStyleSelector.
Två andra stilrelaterade egenskaper för de ItemsControl som inte visas här är GroupStyle och GroupStyleSelector.
Stöd för hierarkiska data
Hittills har vi bara tittat på hur man binder till och visar en enda samling. Ibland har du en samling som innehåller andra samlingar. Klassen HierarchicalDataTemplate är utformad för att användas med HeaderedItemsControl typer för att visa sådana data. I följande exempel ListLeagueList finns en lista över League objekt. Varje League objekt har en Name och en samling Division objekt. Varje Division har en Name och en samling av Team objekt, och varje Team objekt har en 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>
Exemplet visar att med hjälp av HierarchicalDataTemplatekan du enkelt visa listdata som innehåller andra listor. Följande är en skärmbild av exemplet.
Se även
.NET Desktop feedback