Visão geral da modelagem de dados
O modelo de modelagem de dados do WPF oferece grande flexibilidade para definir a apresentação de seus dados. Os controles do WPF têm funcionalidade interna para dar suporte à personalização da apresentação de dados. Este tópico demonstra primeiro como definir um DataTemplate e, em seguida, apresenta outros recursos de modelagem de dados, como a seleção de modelos com base na lógica personalizada e o suporte para a exibição de dados hierárquicos.
Pré-requisitos
Este tópico se concentra nos recursos de modelagem de dados e não é uma introdução aos conceitos de associação de dados. Para obter informações sobre conceitos básicos de associação de dados, consulte a visão geral da associação de dados .
DataTemplate é sobre a apresentação de dados e é um dos muitos recursos fornecidos pelo modelo de modelagem e estilo do WPF. Para obter uma introdução ao modelo de estilização e modelagem do WPF, como usar um Style para definir propriedades em controles, consulte o tópico "Estilização e Modelagem" .
Além disso, é importante entender Resources
, que são essencialmente o que permite que objetos como Style e DataTemplate sejam reutilizáveis. Para obter mais informações sobre recursos, consulte Recursos XAML.
Noções básicas de modelagem de dados
Para demonstrar por que DataTemplate é importante, vamos percorrer um exemplo de associação de dados. Neste exemplo, temos uma ListBox associada a uma lista de objetos Task
. Cada objeto Task
tem um TaskName
(cadeia de caracteres), um Description
(cadeia de caracteres), um Priority
(int) e uma propriedade do tipo TaskType
, que é um Enum
com valores Home
e 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>
Sem um DataTemplate
Sem um DataTemplate, nosso ListBox atualmente tem esta aparência:
O que está acontecendo é que, sem instruções específicas, o ListBox por padrão chama ToString
ao tentar exibir os objetos na coleção. Portanto, se o objeto Task
substituir o método ToString
, o ListBox exibirá a representação de cadeia de caracteres de cada objeto de origem na coleção subjacente.
Por exemplo, se a classe Task
substituir o método ToString
dessa forma, em que name
é o campo da propriedade TaskName
:
public override string ToString()
{
return name.ToString();
}
Public Overrides Function ToString() As String
Return _name.ToString()
End Function
Em seguida, o ListBox fica da seguinte forma:
No entanto, isso é limitador e inflexível. Além disso, se você estiver associando dados XML, não poderá substituir ToString
.
Definindo um DataTemplate simples
A solução é definir um DataTemplate. Uma maneira de fazer isso é definir a propriedade ItemTemplate do ListBox como um DataTemplate. O que você especifica em seu DataTemplate se torna a estrutura visual do objeto de dados. O DataTemplate a seguir é bastante simples. Estão sendo dadas instruções para que cada item esteja disposto como três elementos TextBlock dentro de um StackPanel. Cada elemento TextBlock está associado 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>
Os dados subjacentes para os exemplos neste tópico são uma coleção de objetos CLR. Se você estiver associando dados XML, os conceitos fundamentais serão os mesmos, mas há uma pequena diferença sintactica. Por exemplo, em vez de ter Path=TaskName
, você definiria XPath como @TaskName
(se TaskName
fosse um atributo do nó XML).
Agora nosso ListBox está da seguinte forma:
Criando o DataTemplate como um recurso
No exemplo acima, definimos o DataTemplate em linha. É 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 têm uma propriedade que usa um tipo de DataTemplate. Conforme mostrado acima, para objetos ItemsControl, como o ListBox, é a propriedade ItemTemplate. Para objetos ContentControl, é a propriedade ContentTemplate.
A propriedade DataType
A classe DataTemplate tem uma propriedade DataType 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>
Esse DataTemplate é aplicado automaticamente a todos os objetos Task
. Observe que, nesse caso, o x:Key
está definido implicitamente. Portanto, se você atribuir esse DataTemplate um valor x:Key
, você substituirá o x:Key
implícito e o DataTemplate não será aplicado automaticamente.
Se você estiver associando um ContentControl a uma coleção de objetos Task
, o ContentControl não usará o DataTemplate acima automaticamente. Isso ocorre porque a associação em um ContentControl precisa de mais informações para distinguir se você deseja associar a uma coleção inteira ou a objetos individuais. Se o ContentControl estiver monitorando a seleção de um tipo ItemsControl, você pode definir a propriedade Path da associação ContentControl como "/
" para indicar seu interesse no item atual. Para obter um exemplo, consulte Associar a uma coleção e exibir informações com base na seleção. Caso contrário, você precisará especificar o DataTemplate explicitamente definindo a propriedade ContentTemplate.
A propriedade DataType é particularmente útil quando você tem um CompositeCollection de diferentes tipos de objetos de dados. Para ver um exemplo, consulte Implementar um CompositeCollection.
Adicionando mais ao DataTemplate
Atualmente, os dados são exibidos com as informações necessárias, mas definitivamente há espaço para melhorias. Vamos melhorar a apresentação adicionando um Border, um Gride alguns elementos TextBlock que descrevem 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 o ListBox com este DataTemplatemodificado:
Podemos definir HorizontalContentAlignment para Stretch no ListBox para garantir 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 da propriedade
A apresentação atual não nos informa se uma Task
é uma tarefa de casa ou uma tarefa de escritório. Lembre-se de que o objeto Task
tem uma propriedade TaskType
do tipo TaskType
, que é uma enumeração com valores Home
e Work
.
No exemplo a seguir, o DataTrigger definirá o BorderBrush do elemento chamado border
como Yellow
se a propriedade TaskType
for 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 se parece com o seguinte. As tarefas domésticas aparecem com uma borda amarela e as tarefas de escritório aparecem com uma borda aqua.
Neste exemplo, o DataTrigger usa um Setter para definir um valor de propriedade. As classes de gatilho também têm as propriedades EnterActions e ExitActions que permitem iniciar um conjunto de ações, como animações. Além disso, há também uma classe MultiDataTrigger que permite aplicar alterações com base em vários valores de propriedade associados a dados.
Uma maneira alternativa de obter o mesmo efeito é associar a propriedade BorderBrush à propriedade TaskType
e usar um conversor de valor para retornar a cor com base no valor TaskType
. Criar o efeito acima usando um conversor é um pouco mais eficiente em termos de desempenho. Além disso, criar seu próprio conversor oferece mais flexibilidade porque você está fornecendo sua própria lógica. Em última análise, qual técnica você escolher depende do seu cenário e de sua preferência. Para obter informações sobre como escrever um conversor, consulte IValueConverter.
O que pertence a um DataTemplate?
No exemplo anterior, colocamos o gatilho no DataTemplate usando a propriedade DataTemplate.Triggers. O Setter do gatilho define o valor de uma propriedade de um elemento (o elemento Border) que está dentro do DataTemplate. No entanto, se as propriedades com as quais Setters
está preocupado não se referem a propriedades de elementos que estão dentro do DataTemplateatual, pode ser mais adequado definir as propriedades usando um Style destinado à classe ListBoxItem (se o controle que você está associando for um ListBox). Por exemplo, se você quiser que seu Trigger anime o valor Opacity do item quando um mouse apontar para um item, você definirá gatilhos dentro de um estilo ListBoxItem. Para ver um exemplo, consulte a Amostra de introdução a estilo e modelagem.
Em geral, tenha em mente que a DataTemplate está sendo aplicada a cada uma das ListBoxItem geradas (para obter mais informações sobre como e onde ela é realmente aplicada, consulte a página ItemTemplate.). Seu DataTemplate se preocupa somente com a apresentação e a aparência dos objetos de dados. Na maioria dos casos, todos os outros aspectos da apresentação, como a aparência de um item quando ele é selecionado ou como o ListBox dispõe os itens, não pertencem à definição de um DataTemplate. Para ver um exemplo, consulte a seção Estilo e modelagem de um ItemsControl.
Escolhendo um DataTemplate com base nas propriedades do objeto de dados
Na seção a propriedade DataType, discutimos que você pode definir modelos de dados diferentes para diferentes objetos de dados. Isso é muito ú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, poderá criar uma DataTemplate e então usar gatilhos para aplicar alterações com base nos valores de propriedade de cada objeto de dados. No entanto, os gatilhos permitem que você aplique valores de propriedade ou inicie animações, mas eles não oferecem flexibilidade para reconstruir a estrutura de seus objetos de dados. Alguns cenários podem exigir que você crie uma DataTemplate diferente para objetos de dados do mesmo tipo, mas que tenham propriedades diferentes.
Por exemplo, quando um objeto Task
tem um valor Priority
de 1
, talvez você queira dar a ele uma aparência completamente diferente para servir como um alerta para si mesmo. Nesse caso, é criado um DataTemplate para a exibição dos 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>
Este exemplo usa a propriedade DataTemplate.Resources. Os recursos definidos nessa seção são compartilhados pelos elementos dentro do DataTemplate.
Para fornecer lógica para escolher qual DataTemplate usar com base no valor Priority
do objeto de dados, crie uma subclasse de DataTemplateSelector e substitua 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)
{
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
Em seguida, podemos declarar o TaskListDataTemplateSelector
como um recurso:
<Window.Resources>
<local:TaskListDataTemplateSelector x:Key="myDataTemplateSelector"/>
</Window.Resources>
Para usar o recurso de seletor de modelo, atribua-o à propriedade ItemTemplateSelector do ListBox. O ListBox chama o método SelectTemplate do 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 a esse objeto de dados.
<ListBox Width="400" Margin="10"
ItemsSource="{Binding Source={StaticResource myTodoList}}"
ItemTemplateSelector="{StaticResource myDataTemplateSelector}"
HorizontalContentAlignment="Stretch"/>
Com o seletor de modelo em vigor, o ListBox agora aparece da seguinte maneira:
Isso conclui nossa discussão sobre este exemplo. Para obter o exemplo completo, consulte Introdução ao exemplo de modelagem de dados.
Estilo e modelagem 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, foi dito que a definição do seu DataTemplate deve tratar apenas da apresentação dos dados. Para saber quando não é adequado usar um DataTemplate é importante entender as diferentes propriedades de estilo e modelo fornecidas pelo ItemsControl. O exemplo a seguir foi projetado para ilustrar a função de cada uma dessas propriedades. O ItemsControl neste exemplo está associado à mesma coleção de Tasks
que no exemplo anterior. Para fins de demonstração, os estilos e modelos neste exemplo são todos declarados em linha.
<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>
Veja a seguir uma captura de tela do exemplo quando ele é renderizado:
captura de tela de exemplo
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ê tem a opção de usar o ItemContainerStyleSelector.
Duas outras propriedades relacionadas ao estilo do ItemsControl que não são mostradas aqui são GroupStyle e GroupStyleSelector.
Suporte para dados hierárquicos
Até agora, só analisamos como associar e exibir uma única coleção. Às vezes, você tem uma coleção que contém outras coleções. A classe HierarchicalDataTemplate foi projetada para ser usada com tipos HeaderedItemsControl para exibir esses 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 Division
tem um Name
e uma coleção de objetos Team
, e cada objeto Team
tem um 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>
O exemplo mostra que, com o uso de HierarchicalDataTemplate, você pode exibir facilmente dados de lista que contêm outras listas. Veja a seguir uma captura de tela do exemplo.
Captura de tela de exemplo da HierarchicalDataTemplate
Consulte também
.NET Desktop feedback