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 WPF têm funcionalidade interna para suportar a personalização da apresentação de dados. Este tópico primeiro demonstra 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 em recursos de modelagem de dados e não é uma introdução de conceitos de vinculação de dados. Para obter informações sobre conceitos básicos de vinculação de dados, consulte Visão geral da vinculaçã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 do modelo de modelagem e estilo do WPF, como usar um Style para definir propriedades em controles, consulte o tópico Styling and Templating .

Além disso, é importante entender Resources, que são essencialmente o que permitem 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 vinculação de dados. Neste exemplo, temos um ListBox que está vinculado a uma lista de Task objetos. Cada Task objeto tem 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="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 se parece com isto:

Captura de tela da janela Introduction to Data Templating Sample mostrando a My Task List ListBox 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 Task objeto substituir o ToString método, o ListBox exibirá a representação de cadeia de caracteres de cada objeto de origem na coleção subjacente.

Por exemplo, se a Task classe substituir o ToString método dessa maneira, onde name está o campo para a TaskName propriedade:

public override string ToString()
{
    return name.ToString();
}
Public Overrides Function ToString() As String
    Return _name.ToString()
End Function

Em seguida, o ListBox se parece com o seguinte:

Captura de tela da janela Introdução ao Exemplo de Modelo de Dados mostrando a Caixa de Lista de Minhas Tarefas exibindo uma lista de tarefas.

No entanto, isso é limitativo e inflexível. Além disso, se estiveres a vincular a dados XML, não poderás substituir ToString.

Definindo um DataTemplate simples

A solução é definir um DataTemplate. Uma maneira de fazer isso é definir a propriedade ItemTemplate do ListBox para um DataTemplate. O que você especifica em seu DataTemplate torna-se a estrutura visual do seu 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 StackPanel. Cada TextBlock elemento está 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 (se TaskName for um atributo do nó XML).

Agora a nossa ListBox aparência é a seguinte:

Captura de ecrã da janela de introdução ao exemplo de modelação de dados mostrando o ListBox Minha Lista de Tarefas exibindo as tarefas como elementos TextBlock.

Criando o DataTemplate como um recurso

No exemplo acima, definimos o DataTemplate de forma 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 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 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. Note que, neste caso, o x:Key é definido implicitamente. Portanto, se atribuires um valor a DataTemplatex:Key, estás a substituir o implícito x:Key e o DataTemplate não seria aplicado automaticamente.

Se você estiver vinculando a ContentControl a uma coleção de Task objetos, o ContentControl não usa o 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 aos objetos individuais. Se o seu ContentControl estiver acompanhando a seleção de um tipo de ItemsControl, pode definir a propriedade Path da vinculação ContentControl para "/" a fim de indicar que está interessado no item atual. Para obter um exemplo, consulte Vincular 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 de diferentes tipos de objetos de dados. Para obter um exemplo, consulte Implementar um CompositeCollection.

Adicionando mais ao DataTemplate

Atualmente, os dados aparecem com as informações necessárias, mas definitivamente há espaço para melhorias. 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:

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

Podemos definir HorizontalContentAlignment para StretchListBox no 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 HorizontalContentAlignment propriedade definida como Stretch, o ListBox agora tem esta aparência:

Captura de ecrã da janela Introdução ao Exemplo de Modelo de Dados a mostrar a Caixa de Lista da Minha Lista de Tarefas esticada para caber horizontalmente no ecrã.

Usar DataTriggers para aplicar valores de propriedade

A apresentação atual não nos diz se uma Task é uma tarefa doméstica ou uma tarefa de escritório. Lembre-se de que o Task objeto tem uma TaskType propriedade do tipo TaskType, que é uma enumeração com 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>

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 ecrã da janela Introdução ao Exemplo de Modelo de Dados que mostra a Caixa de Lista da Minha Lista de Tarefas com os limites das tarefas doméstica e do escritório realçados a cores.

Neste exemplo, o DataTrigger usa a Setter para definir um valor de propriedade. As classes de gatilho 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 ligados a dados.

Uma maneira alternativa de obter o mesmo efeito é vincular a BorderBrush propriedade à TaskType propriedade e usar um conversor de valor para retornar a cor com base no TaskType valor. Criar o efeito acima usando um conversor é um pouco mais eficiente em termos de desempenho. Além disso, criar seu próprio conversor lhe dá mais flexibilidade porque você está fornecendo sua própria lógica. Em última análise, qual técnica você escolhe 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, inserimos o gatilho dentro do DataTemplate utilizando a propriedade 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 o utilizador Setters está preocupado não são propriedades de elementos que estão dentro do atual DataTemplate, pode ser mais adequado definir as propriedades usando um Style que pertence à classe ListBoxItem (se o controlo que está a vincular é um ListBox). Por exemplo, se quiser que o Trigger anime o valor Opacity do item quando um rato aponta para um item, define disparos dentro de um estilo ListBoxItem. Para obter um exemplo, consulte o Exemplo de Introdução ao Estilo e Modelos.

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.). Você DataTemplate está preocupado apenas com a apresentação e aparência dos objetos de dados. Na maioria dos casos, todos os outros aspetos da apresentação, como a aparência de um item quando é selecionado ou como ListBox organiza os itens, não pertencem à definição de um DataTemplate. Para obter um exemplo, consulte a seção Styling and Templating an ItemsControl .

Escolhendo um DataTemplate com base nas propriedades do objeto de dados

Na seção DataType Property , discutimos que você pode 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 Use DataTriggers para aplicar valores de propriedade, mostramos que, se tiver uma coleção do mesmo tipo de objetos de dados, pode criar um DataTemplate e usar triggers 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 a flexibilidade para reconstruir a estrutura de 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, quando um Task objeto tem um Priority valor de 1, você pode querer dar-lhe uma aparência completamente diferente para servir 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 valor Priority do objeto de dados, crie uma subclasse de DataTemplateSelector e substitua o método SelectTemplate. No exemplo a seguir, o SelectTemplate método fornece lógica para retornar o 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

Podemos então declarar o TaskListDataTemplateSelector como um recurso:

<Window.Resources>
<local:TaskListDataTemplateSelector x:Key="myDataTemplateSelector"/>
</Window.Resources>

Para utilizar o recurso de seletor de modelo, atribua-o à ItemTemplateSelector propriedade do ListBox. O ListBox chama o método SelectTemplate do TaskListDataTemplateSelector para cada item da coleção subjacente. A chamada passa o objeto de dados como parâmetro do 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:

Captura de ecrã da janela Introdução ao Modelação de Dados - Exemplo, a mostrar a Caixa de Lista 'Minha Lista de Tarefas' com as tarefas de Prioridade 1 realçadas com uma borda vermelha.

Fica assim concluída a nossa discussão sobre este exemplo. Para obter o exemplo completo, consulte Introdução ao exemplo de modelagem de dados.

Estilizando e modelando um ItemsControl

Embora o ItemsControl não seja o único tipo de controle que você pode usar um DataTemplate com, é um cenário muito comum vincular um ItemsControl a uma coleção. Na secção O que pertence num DataTemplate, discutimos que a definição do seu DataTemplate deve dedicar-se apenas à 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 projetado para ilustrar a função de cada uma dessas propriedades. O ItemsControl neste exemplo está vinculado à mesma Tasks coleção 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>

A seguir está uma captura de tela do exemplo quando ele é renderizado:

Exemplo de captura de tela 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ó vimos como ligar e exibir uma única coleção. Às vezes, você tem uma coleção que contém outras coleções. A HierarchicalDataTemplate classe é projetada para ser usada com HeaderedItemsControl tipos para exibir esses dados. No exemplo a seguir, ListLeagueList é uma lista de League objetos. Cada League objeto tem uma Name e uma coleção de Division objetos. Cada Division tem uma 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 listas que contêm outras listas. A seguir está uma captura de tela do exemplo.

Exemplo de captura de tela do HierarchicalDataTemplate

Ver também