Visão geral sobre Templating de dados
The WPF modelo de modelagem de dados fornece grande flexibilidade para definir a apresentação dos dados. WPF controles têm funcionalidade interna de suporte à personalização da apresentação de dados. Este tópico primeiro demonstra como definir um DataTemplate e, em seguida, apresenta outros recursos de templating de dados, como a seleção de modelos com base em lógica personalizada e o suporte a exibição de dados hierárquicos.
Este tópico contém as seguintes seções.
- Pré-requisitos
- Templating básico de dados
- Adicionando mais ao DataTemplate
- Escolhendo um DataTemplate com base em propriedades do objeto de dados
- Estilizando e fazendo templating de um ItemsControl
- Suporte para dados hierárquicos
- Tópicos relacionados
Pré-requisitos
Este tópico concentra-se nos recursos de templating de dados e não é uma introdução a conceitos de associação de dados. Para obter informações sobre conceitos básicos de associação de dados, consulte Revisão de Associação de Dados.
DataTemplate lida com apresentação de dados e é um dos muitos recursos fornecidos pelo modelo de estilos e templating do WPF. Para obter uma introdução ao modelo de estilos e templating do WPF, tais como como usar um Style para definir propriedades em controles, consulte o tópico Styling and Templating.
Além disso, é importante compreender o conceito de Resources, que são essencialmente o que permite que objetos como Style e DataTemplate possam ser reutilizáveis. Para obter mais informações sobre recursos, consulte Visão geral sobre Recursos.
Templating básico de dados
Esta seção contém as seguintes subseções.
- Sem um DataTemplate.
- Definindo um DataTemplate simples
- Criando o DataTemplate como um recurso
- A propriedade DataType
Para demonstrar por que DataTemplate é importante, vamos examinar um exemplo de associação de dados. Nesse exemplo, temos um ListBox que é vinculado a uma lista de objetos Task. Cada objeto Task possui um TaskName (string), um Description (string), um Priority (int) e uma propriedade do tipo TaskType que é um Enum com valores Home e Work.
<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>
Sem um DataTemplate.
Sem um DataTemplate, nosso ListBox atualmente tem esta aparência:
O que está acontecendo é que sem quaisquer instruções específicas, o ListBox por padrão chama ToString ao tentar exibir os objetos na coleção. Portanto, se o objeto Task substituído o método ToString, então o ListBox exibe a representação de cada objeto de origem na coleção como uma sequência de caracteres.
Por exemplo, se a classe Task substitui o método ToString dessa maneira, onde name é o campo para a propriedade TaskName:
public override string ToString()
{
return name.ToString();
}
A ListBox tem a seguinte forma:
No entanto, isto é inflexível e limitante. Além disso, se você estiver associando dados XML, não será possível sobrescrever ToString.
Definindo um DataTemplate simples
A solução é definir um DataTemplate. Uma maneira para fazer isso é definir a propriedade ItemTemplate de ListBox como um DataTemplate. O que você especificar no seu DataTemplate torna-se a estrutura visual do seu objeto de dados. O DataTemplate a seguir é bastante simples. Nós estamos fornecendo instruções para que cada item seja exibido como três elementos TextBlock em um StackPanel. Cada elemento TextBlock está vinculado a uma propriedade da classe 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>
O dados subjacentes para os exemplos deste tópico são uma coleção de objetos CLR. Se você estiver associando a dados XML os conceitos fundamentais são os mesmos, mas há uma pequena diferença sintática. Por exemplo, em vez de ter Path=TaskName, você poderia definir XPath para @TaskName (se TaskName é um atributo do seu nó XML).
Agora nosso ListBox tem a seguinte forma:
Criando o DataTemplate como um recurso
No exemplo acima, foi definido um DataTemplate embutido. É mais comum defini-lo na seção de recursos para que ele possa ser um objeto reutilizável, como no exemplo a seguir:
<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>
Agora você pode usar myTaskTemplate como um recurso, como no exemplo a seguir:
<ListBox Width="400" Margin="10"
ItemsSource="{Binding Source={StaticResource myTodoList}}"
ItemTemplate="{StaticResource myTaskTemplate}"/>
Como myTaskTemplate é um recurso, agora você pode usá-lo em outros controles que tenham uma propriedade que receba um tipo DataTemplate. Como mostrado acima, para objetos ItemsControl, como o ListBox, ela é a propriedade ItemTemplate. Para objetos ContentControl, é a propriedade ContentTemplate.
A propriedade DataType
A classe DataTemplate possui uma propriedade DataType que é muito semelhante à propriedade TargetType da classe Style. Portanto, em vez de especificar um x:Key para o DataTemplate no exemplo acima, você pode fazer o seguinte:
<DataTemplate DataType="{x:Type local:Task}">
<StackPanel>
<TextBlock Text="{Binding Path=TaskName}" />
<TextBlock Text="{Binding Path=Description}"/>
<TextBlock Text="{Binding Path=Priority}"/>
</StackPanel>
</DataTemplate>
Este DataTemplate é aplicado automaticamente a todos os objetos Task. Observe que neste caso x:Key é definido implicitamente. Portanto, se você atribuir ao DataTemplate um valor x:Key, você está substituíndo o x:Key implícito e o DataTemplate não poderá ser aplicado automaticamente.
Se você estiver vinculando um ContentControl a uma coleção de objetos Task, o ContentControl não usa o DataTemplate acima automaticamente. Isso ocorre porque a associação em um ContentControl precisa de mais informações para distinguir se você deseja fazer associação com uma coleção inteira ou com os objetos individuais. Se seu ContentControl estiver controlando a seleção de um tipo ItemsControl, você pode definir a propriedade Path da associação do ContentControl como "/" para indicar que você está interessado no item corrente. Para um exemplo, consulte Como: Bind to a Collection and Display Information Based on Selection. Caso contrário, você precisará especificar o DataTemplate explicitamente definindo a propriedade ContentTemplate.
A propriedade DataType é particularmente útil quando você tiver um CompositeCollection de diferentes tipos de objetos de dados. Para um exemplo, consulte Como: Implement a CompositeCollection.
Adicionando mais ao DataTemplate
No momento os dados aparecem com as informações necessárias, mas há definitivamente espaço para aprimoramento. Vamos melhorar a apresentação adicionando uma Border, um Grid e alguns elementos TextBlock que descrevam os dados que estão sendo exibidos.
<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>
A captura de tela a seguir mostra a ListBox com este DataTemplate modificado:
Nós podemos definir o HorizontalContentAlignment para Stretch na ListBox para nos certificarmos que a largura dos itens ocupa todo o espaço:
<ListBox Width="400" Margin="10"
ItemsSource="{Binding Source={StaticResource myTodoList}}"
ItemTemplate="{StaticResource myTaskTemplate}"
HorizontalContentAlignment="Stretch"/>
Com a propriedade HorizontalContentAlignment definida como Stretch, o ListBox agora tem esta aparência:
Usar DataTriggers para aplicar valores de propriedade
A apresentação atual não nos informa se uma Task é uma tarefa de casa ou uma tarefa do escritório. Lembre-se de que o objeto Task possui uma propriedade TaskType do tipo TaskType, que é uma enumeração com valores Home e Work.
No exemplo a seguir, o DataTrigger define o BorderBrush do elemento chamado border como Yellow se a propriedade TaskType é 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>
Nosso aplicativo agora tem a seguinte aparência: Tarefas domésticas são exibidas com uma borda amarela e tarefas do escritório aparecem com uma borda aqua:
Nesse exemplo o DataTrigger usa um Setter para definir um valor de propriedade. As classes acionadoras também têm as propriedades EnterActions ExitActions que permitem que você inicie um conjunto de ações, como animações. Além disso, há também uma classe MultiDataTrigger que permite que você aplique alterações com base em vários valores de propriedades associadas a dados.
Uma maneira alternativa para atingir o mesmo efeito é assocar a propriedade BorderBrush à propriedade TaskType e usar um conversor de valor para retornar a cor com base no valor de TaskType. Para obter um exemplo semelhante, consulte Como: Alternate the Background Color for Rows in a ListView. Criar o efeito acima usando um conversor é ligeiramente mais eficiente em termos de desempenho. Além disso, criar seu próprio conversor oferece mais flexibilidade pois você está fornecendo sua própria lógica. Qual a técnica escolhida depende de seu cenário e de sua preferência. Para obter informações adicionais sobre como escrever um conversor, consulte IValueConverter.
O que pertence a um DataTemplate?
No exemplo anterior, colocamos um acionador dentro do DataTemplate usando a propriedade Triggers do DataTemplate. O Setter do acionador define o valor de uma propriedade de um elemento (o elemento Border) que está dentro do DataTemplate. No entanto, se as propriedades em que seus Setters estiverem interessados não forem propriedades de elementos que estejam dentro do DataTemplate atual, talvez seja mais adequado definir as propriedades usando um Style que seja para a classe ListBoxItem (se o controle que você estiver associando for um ListBox). Por exemplo, se você quiser que seu Trigger anime o valor da Opacity do item quando um mouse aponta para um item, você define acionadores em um estilo ListBoxItem. Para um exemplo, veja Introdução Paraos estilos e exemplo de modelParagem.
Em geral, tenha em mente que o DataTemplate está sendo aplicado a cada um dos ListBoxItem gerados (para obter mais informações sobre como e onde ele realmente é aplicado, consulte a página ItemTemplate). O seu DataTemplate só se preocupa com a apresentação e a aparência dos objetos de dados. Na maioria dos casos, todos os outros aspectos da apresentação, como como um item se parece quando estiver marcado ou como o ListBox dispõe os itens, não pertencem a definição de um DataTemplate. Para obter um exemplo, consulte a seção Estilizando e fazendo templating de um ItemsControl.
Escolhendo um DataTemplate com base em propriedades do objeto de dados
Na seção A propriedade DataType, discutimos que se pode definir modelos de dados diferentes para objetos de dados diferentes. Isto é especialmente útil quando você tem uma CompositeCollection de diferentes tipos ou coleções com itens de diferentes tipos. Na seção Usar DataTriggers para aplicar valores de propriedade, mostramos que se você tiver uma coleção do mesmo tipo de objetos de dados você pode criar um DataTemplate e, em seguida, usar acionadores para aplicar as alterações com base nos valores de propriedades de cada objeto de dados. No entanto, acionadores permitem que você aplique valores de propriedade ou inicie animações mas eles não lhe dão a flexibilidade para reconstruir a estrutura dos objetos de dados. Alguns cenários podem exigir que você crie um DataTemplate diferente para objetos de dados que são do mesmo tipo mas têm propriedades diferentes.
Por exemplo, quando um objeto Task tem um valor Priority de 1, convém dar uma aparência completamente diferente para servir como um alerta para você mesmo. Nesse caso, você cria um DataTemplate para a exibição de objetos Task de alta prioridade. Vamos adicionar o seguinte DataTemplate à seção de recursos:
<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>
Observe que este exemplo usa a propriedade Resources do DataTemplate. Recursos definidos nessa seção são compartilhados pelos elementos dentro do DataTemplate.
Para fornecer a lógica para escolher quais DataTemplate usar com base no valor Priority do objeto de dados, crie uma subclasse de DataTemplateSelector e sobrescreva o método SelectTemplate. No exemplo a seguir, o método SelectTemplate fornece lógica para retornar o modelo apropriado com base no valor da propriedade Priority. O modelo a ser retornado é encontrado nos recursos do elemento Window envolvente.
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;
}
}
}
Em seguida, podemos declarar TaskListDataTemplateSelector como um recurso:
<Window.Resources>
...
<local:TaskListDataTemplateSelector x:Key="myDataTemplateSelector"/>
...
</Window.Resources>
Para usar o recurso seletor de modelo, atribua-o à propriedade ItemTemplateSelector de ListBox. O ListBox chama o método SelectTemplate de TaskListDataTemplateSelector para cada um dos itens na coleção subjacente. A chamada passa o objeto de dados como o parâmetro de item. O DataTemplate retornado pelo método é aplicado, em seguida, para aquele objeto de dados.
<ListBox Width="400" Margin="10"
ItemsSource="{Binding Source={StaticResource myTodoList}}"
ItemTemplateSelector="{StaticResource myDataTemplateSelector}"
HorizontalContentAlignment="Stretch"/>
Com o seletor de modelo no lugar, a ListBox agora aparece da seguinte maneira:
Isso conclui nossa discussão sobre este exemplo. For the complete sample, see Introdução Parao DParados modelParagem exemplo.
Estilizando e fazendo templating de um ItemsControl
Embora o ItemsControl não seja o único tipo de controle com o qual você pode usar um DataTemplate, é um cenário muito comum associar um ItemsControl a uma coleção. Na seção O que pertence a um DataTemplate discutimos que a definição de seu DataTemplate só deve se preocupar com a apresentação dos dados. Para saber quando não é adequado usar um DataTemplate é importante entender as diferentes propriedades de estilos e modelo fornecidas pelo ItemsControl. O exemplo a seguir foi projetado para ilustrar a função de cada uma dessas propriedades. O ItemsControl nesse exemplo é associado à mesma coleção de Tasks do exemplo anterior. Para fins de demonstração, os estilos e modelos nesse exemplo são todos declarados embutidos.
<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>
A seguir vemos a tela do exemplo quando renderizada:
Observe que, em vez de usar o ItemTemplate, você pode usar o ItemTemplateSelector. Consulte a seção anterior para obter um exemplo. Da mesma forma, em vez de usar o ItemContainerStyle, você pode para usar o ItemContainerStyleSelector.
Duas outras propriedades relacionadas a estilos do ItemsControl que não são mostradas são GroupStyle e GroupStyleSelector.
Suporte para dados hierárquicos
Até agora somente analisamos como fazer associação e exibição de uma única coleção. Às vezes se tem uma coleção que contém outras coleções. A classe HierarchicalDataTemplate destina-se a ser usada com tipos HeaderedItemsControl para exibir tais dados. No exemplo a seguir, ListLeagueList é uma lista de objetos League. Cada objeto League tem um Name e uma coleção de objetos Division. Cada objeto Division tem um Name e uma coleção de objetos Team, e cada objeto Team tem um 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>
O exemplo mostra que com o uso de HierarchicalDataTemplate você pode facilmente exibir listas de dados que contém outras listas. A seguir vamos a tela do exemplo.
For the complete sample, see Exibindo o exemplo de dados hierárquicos.
Consulte também
Tarefas
Como: Encontrar Elemento DataTemplate-Generated
Conceitos
Otimizando o desempenho: Ligação de Dados