Visão geral de modelagem dos dados
O modelo de modelagem de dados do WPF fornece grande flexibilidade para definir a apresentação dos dados. Os controles do WPF têm uma funcionalidade interna para dar suporte à personalização da apresentação de dados. Este tópico primeiro demonstra como definir um DataTemplate e, em seguida, introduz 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 dos conceitos de associação de dados. Para obter informações sobre conceitos de associação de dados, consulte Visão geral de associação de dados.
DataTemplate é sobre a apresentação de dados e é um dos muitos recursos fornecidos pelo modelo de estilo e modelagem do WPF. Para obter uma introdução do modelo de estilo e modelagem do WPF, como usar um Style para definir propriedades em controles, consulte o tópico Estilo e modelagem .
Além disso, é importante entender Resources
quais são essencialmente os que permitem que objetos como Style e DataTemplate sejam reutilizáveis. Para obter mais informações sobre recursos, consulte Recursos de XAML.
Noções básicas de modelagem de dados
Para demonstrar por que DataTemplate é importante, vamos percorrer um exemplo de vinculação de dados. Neste exemplo, temos um ListBox que está vinculado a uma lista de Task
objetos. 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 os 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, o nosso ListBox atualmente fica assim:
O que está acontecendo é que, sem instruções específicas, o ListBox padrão chama ToString
ao tentar exibir os objetos na coleção. Portanto, se o objeto substituir o método, o Task
ToString
exibirá ListBox 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 maneira, em que name
é o campo para a propriedade TaskName
:
public override string ToString()
{
return name.ToString();
}
Public Overrides Function ToString() As String
Return _name.ToString()
End Function
Em seguida, a aparência é a ListBox seguinte:
No entanto, isso é inflexível e limitante. Além disso, se você estiver vinculando a dados XML, não poderá substituir ToString
o .
Definindo um DataTemplate simples
A solução é definir um DataTemplatearquivo . Uma maneira de fazer isso é definir a ItemTemplate propriedade do ListBox para um DataTemplate. O que você especifica em seu DataTemplate torna-se a estrutura visual do objeto de dados. O seguinte DataTemplate é bastante simples. Estamos dando instruções para que cada item apareça como três TextBlock elementos dentro de um StackPanelarquivo . Cada TextBlock elemento é vinculado a uma propriedade da Task
classe.
<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 vinculando a dados XML, os conceitos fundamentais serão os mesmos, mas há uma pequena diferença sintática. Por exemplo, em vez de ter Path=TaskName
, você definiria XPath como @TaskName
(if TaskName
é um atributo do nó XML).
Agora a nossa ListBox aparência é a seguinte:
Criando o DataTemplate como um recurso
No exemplo acima, definimos o DataTemplate inline. É mais comum defini-lo na seção de recursos para que 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, é possível usar o myTaskTemplate
como 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 DataTemplate tipo. Como mostrado acima, para ItemsControl objetos, como o ListBox, é a ItemTemplate propriedade. Para ContentControl objetos, é a ContentTemplate propriedade.
A propriedade DataType
A DataTemplate classe tem uma DataType propriedade que é muito semelhante à TargetType propriedade da Style classe. 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>
Isso DataTemplate é aplicado automaticamente a todos os Task
objetos. Observe que, nesse caso, o x:Key
é definido implicitamente. Portanto, se você atribuir a isso DataTemplate um x:Key
valor, você está substituindo o implícito x:Key
e o DataTemplate não seria aplicado automaticamente.
Se você estiver vinculando um ContentControl a uma coleção de Task
objetos, o não usará o ContentControl acima DataTemplate automaticamente. Isso ocorre porque a associação em um ContentControl precisa de mais informações para distinguir se você deseja vincular a uma coleção inteira ou a objetos individuais. Se você ContentControl estiver acompanhando a seleção de um ItemsControl tipo, poderá definir a Path propriedade da ContentControl associação como ""/
para indicar que está interessado no item atual. Para ver um exemplo, consulte Como 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 ContentTemplate propriedade.
A DataType propriedade é particularmente útil quando você tem um CompositeCollection dos diferentes tipos de objetos de dados. Para ver um exemplo, consulte Implementar um CompositeCollection.
Adicionando mais ao DataTemplate
Os dados aparecem com as informações necessárias no momento, mas, sem dúvida, há espaço para melhoria. Vamos melhorar a apresentação adicionando um Border, a Gride alguns TextBlock elementos 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 isso modificado DataTemplate:
Podemos definir HorizontalContentAlignment como Stretch no ListBox para garantir que a largura dos itens ocupe todo o espaço:
<ListBox Width="400" Margin="10"
ItemsSource="{Binding Source={StaticResource myTodoList}}"
ItemTemplate="{StaticResource myTaskTemplate}"
HorizontalContentAlignment="Stretch"/>
Com a propriedade definida como Stretch, o ListBox agora tem a HorizontalContentAlignment seguinte 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 os valores Home
e Work
.
No exemplo a seguir, o DataTrigger define o BorderBrush do elemento nomeado border
como Yellow
se a TaskType
propriedade 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>
Agora, nosso aplicativo tem a seguinte aparência. As tarefas de casa são exibidas com uma borda amarela, enquanto as tarefas de escritório aparecem com uma borda verde-azulada:
Neste exemplo, o DataTrigger usa a Setter para definir um valor de propriedade. As classes trigger também têm as EnterActions propriedades e ExitActions que permitem iniciar um conjunto de ações, como animações. Além disso, há também uma MultiDataTrigger classe que permite aplicar alterações com base em vários valores de propriedade vinculados a dados.
Uma maneira alternativa de obter o mesmo efeito é vincular a propriedade à TaskType
propriedade e usar um conversor de valor para retornar a BorderBrush cor com base no TaskType
valor. Criar o efeito acima usando um conversor é um pouco mais eficiente em termos de desempenho. Além disso, a criação do seu próprio conversor oferece mais flexibilidade, pois você fornecerá sua própria lógica. Por fim, a técnica escolhida depende do seu cenário e da 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 dentro da DataTemplate propriedade using the DataTemplate.Triggers . O Setter do gatilho define o valor de uma propriedade de um elemento (o Border elemento) que está dentro do DataTemplate. No entanto, se as propriedades com as quais você está preocupado não forem propriedades de elementos que estão dentro da corrente, pode ser mais adequado definir as propriedades usando um que é para a ListBoxItem classe (se o controle que você Setters
está vinculando é um StyleListBox).DataTemplate Por exemplo, se quiser Trigger animar o Opacity valor do item quando um mouse aponta para um item, defina gatilhos dentro de um ListBoxItem estilo. Para ver um exemplo, consulte a Amostra de introdução a estilo e modelagem.
Em geral, tenha em mente que o DataTemplate está sendo aplicado a cada um dos gerados ListBoxItem (para obter mais informações sobre como e onde ele é realmente aplicado, consulte a ItemTemplate página.). Seu DataTemplate está preocupado apenas com a apresentação e 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 os ListBox itens são definidos, 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, falamos que é possível definir diferentes modelos de dados para diferentes objetos de dados. Isso é especialmente útil quando você tem um 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 um DataTemplate e usar gatilhos para aplicar alterações com base nos valores de propriedade de cada objeto de dados. Contudo, os gatilhos permitem aplicar valores da propriedade ou iniciar animações, mas não proporcionam flexibilidade para reconstruir a estrutura dos seus objetos de dados. Alguns cenários podem exigir que você crie um diferente DataTemplate para objetos de dados que são do mesmo tipo, mas têm propriedades diferentes.
Por exemplo, se um objeto Task
tiver um valor Priority
de 1
, talvez você queira lhe conferir uma aparência completamente diferente para funcionar como um alerta para si mesmo. Nesse caso, você cria um DataTemplate para a exibição dos objetos de alta prioridade Task
. 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 Priority
valor do objeto de dados, crie uma subclasse de DataTemplateSelector e substitua o SelectTemplate método. No exemplo a seguir, o método fornece lógica para retornar o SelectTemplate modelo apropriado com base no valor da Priority
propriedade. O modelo a ser retornado é encontrado nos recursos do elemento envolvente 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
Em seguida, podemos declarar o TaskListDataTemplateSelector
como um recurso:
<Window.Resources>
<local:TaskListDataTemplateSelector x:Key="myDataTemplateSelector"/>
</Window.Resources>
Para usar o recurso seletor de modelo, atribua-o ItemTemplateSelectorListBoxà propriedade do . O ListBox chama o SelectTemplate método de para TaskListDataTemplateSelector
cada um dos itens na coleção subjacente. A chamada passa o objeto de dados como o parâmetro de item. O DataTemplate que é retornado pelo método é então 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 no lugar, o ListBox agora aparece da seguinte maneira:
Isso conclui nossa discussão sobre este exemplo. Para ver a amostra completa, consulte Amostra da introdução à modelagem de dados.
Estilo e modelagem de um ItemsControl
Embora o não seja o único tipo de controle com o ItemsControl qual você pode usar um com, é um cenário muito comum vincular um ItemsControlDataTemplate a uma coleção. Na seção O que pertence a um DataTemplate, discutimos que a definição de você DataTemplate deve se preocupar apenas com a apresentação de 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 desenvolvido para ilustrar a função de cada uma dessas propriedades. O ItemsControl neste exemplo está vinculado à mesma Tasks
coleção do exemplo anterior. Para fins de demonstração, os estilos e modelos deste exemplo são declarados como 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>
Segue uma captura de tela do exemplo quando é renderizado:
Observe que, em vez de usar o , você pode usar o ItemTemplateItemTemplateSelector. Consulte a seção anterior para ver um exemplo. Da mesma forma, em vez de usar o , você tem a opção de usar o ItemContainerStyleItemContainerStyleSelector.
Duas outras propriedades relacionadas ao ItemsControl estilo do que não são mostradas aqui são GroupStyle e GroupStyleSelector.
Suporte para dados hierárquicos
Até o momento, examinamos apenas como associar a uma única coleção e exibi-la. Às vezes, você tem uma coleção que contém outras coleções. A HierarchicalDataTemplate classe foi projetada para ser usada com HeaderedItemsControl tipos 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
; 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 do , você pode exibir facilmente dados de HierarchicalDataTemplatelista que contêm outras listas. Segue uma captura de tela do exemplo.
Confira também
.NET Desktop feedback