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 Resourcesquais 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:

Screenshot of the Introduction to Data Templating Sample window showing the My Task List ListBox displaying the string representation SDKSample.Task for each source object.

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 TaskToString 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:

Screenshot of the Introduction to Data Templating Sample window showing the My Task List ListBox displaying a list of tasks.

No entanto, isso é inflexível e limitante. Além disso, se você estiver vinculando a dados XML, não poderá substituir ToStringo .

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:

Screenshot of the Introduction to Data Templating Sample window showing the My Task List ListBox displaying the tasks as TextBlock elements.

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:

Screenshot of the Introduction to Data Templating Sample window showing the My Task List ListBox with the modified 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:

Screenshot of the Introduction to Data Templating Sample window showing the My Task List ListBox stretched to fit the screen horizontally.

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:

Screenshot of the Introduction to Data Templating Sample window showing the My Task List ListBox with the home and office task borders highlighted in color.

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:

Screenshot of Introduction to Data Templating Sample window showing the My Task List ListBox with the Priority 1 tasks prominently displayed with a red border.

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:

ItemsControl example screenshot

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.

HierarchicalDataTemplate sample screenshot

Confira também