Partilhar via


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:

Captura de tela da janela Introdução ao Exemplo de Modelagem de Dados mostrando o ListBox da Minha Lista de Tarefas exibindo a representação de cadeia de caracteres SDKSample.Task para cada objeto de origem.

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:

Captura de tela da janela Introdução ao Exemplo de Modelagem de Dados mostrando o ListBox da Minha Lista de Tarefas exibindo uma lista de tarefas.

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:

Captura de tela da janela Introdução ao Exemplo de Modelagem de Dados mostrando o ListBox da Minha Lista de Tarefas exibindo as tarefas como elementos TextBlock.

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:

Captura de tela da janela Introdução ao Exemplo de Modelagem de Dados mostrando o ListBox De Lista de Tarefas com o DataTemplate modificado.

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:

Captura de tela da janela Exemplo de Introdução à Modelagem de Dados mostrando o ListBox de Lista de Tarefas estendido para ajustar-se horizontalmente à tela.

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.

Captura de tela da janela Introdução ao Exemplo de Modelagem de Dados mostrando o ListBox Minha Lista de Tarefas com as bordas das tarefas de casa e de escritório destacadas em cores.

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:

Captura de tela da janela Introdução ao Exemplo de Modelagem de Dados mostrando a ListBox Minha Lista de Tarefas com as tarefas prioridade 1 exibidas com destaque com uma borda vermelha.

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 Captura de tela de exemplo de ItemsControl

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 Captura de tela de exemplo HierarchicalDataTemplate

Consulte também