Desenvolvimento - WPF

WPF TreeView: Explorando o componente

Neste artigo veremos como configurar e utilizar o componente TreeView do WPF, adicionando itens estáticos e dinâmicos a partir de coleções de dados.

por Joel Rodrigues



O componente TreeView, presente em diversas plataformas de programação, é utilizado para apresentar itens de forma hierárquica, de forma que o usuário possa navegar entre eles e selecioná-los de forma simples.

Entre os principais usos desse componente está a construção de menus onde o usuário pode visualizar de forma categorizada as opções disponíveis.

No WPF, além de contarmos com um componente muito simples de configurar de forma estática, temos também a possibilidade de utilizar os mecanismos de data binding e gerar os itens do TreeView de forma dinâmica, a partir de coleções de dados.

Itens estáticos

Na Listagem 1 vemos um exemplo simples de uso do TreeView, com apenas alguns itens estáticos exibidos no mesmo nível. O resultado dessa listagem pode ser visto na Figura 1.

Listagem 1. TreeView com itens estáticos

  <TreeView>
      <TreeViewItem Header="Item 1"></TreeViewItem>
      <TreeViewItem Header="Item 2"></TreeViewItem>
      <TreeViewItem Header="Item 3"></TreeViewItem>
  </TreeView>

TreeView com itens estáticos

Figura 1. TreeView com itens estáticos

Para adicionar outros níveis aos já criados, basta adicionar novos elementos do tipo TreeViewItem dentro de cada uma das tags, como mostra a Listagem 2.

Listagem 2. Itens estáticos com subitens

  <TreeView>
      <TreeViewItem Header="Item 1">
          <TreeViewItem Header="Subitem 1.1">
              <TreeViewItem Header="Subitem 1.1.1"/>
          </TreeViewItem>
          <TreeViewItem Header="Subitem 1.2"/>
          <TreeViewItem Header="Subitem 1.3"/>
      </TreeViewItem>
      <TreeViewItem Header="Item 2">
          <TreeViewItem Header="Subitem 2.1"/>
          <TreeViewItem Header="Subitem 2.2"/>
          <TreeViewItem Header="Subitem 2.3"/>
      </TreeViewItem>
      <TreeViewItem Header="Item 3">
          <TreeViewItem Header="Subitem 3.1"/>
          <TreeViewItem Header="Subitem 3.2"/>
          <TreeViewItem Header="Subitem 3.3"/>
      </TreeViewItem>
  </TreeView>

Note que um TreeViewItem pode conter vários outros em seu interior, gerando uma visualização em forma de árvore, como vemos na Figura 2.

TreeView com itens estáticos em vários níveis

Figura 2. TreeView com itens estáticos em vários níveis

Itens dinâmicos

Como nem sempre conhecemos os itens que irão compor o TreeView em tempo de design, precisamos muitas vezes estruturá-lo a partir de informações dinâmicas, muitas vezes provenientes de bancos de dados. Para essas situações, contamos com o recurso de data binding de que dispõem as aplicações cuja interface baseia-se em XAML.

Configurando os templates de item do TreeView, assim como se faz com outros componentes como ListBox e DataGridView, é possível definir a forma como os itens serão exibidos, com múltiplos níveis, a partir de classes de negócio do domínio da aplicação.

Para o próximo exemplo utilizaremos uma classe chamada Categoria, cujo código pode ser visto na Listagem 3. Essa classe representará os itens do primeiro nível do TreeView. Observe, porém, que há uma propriedade que representa uma lista de subcategoria, que serão utilizadas mais adiante neste artigo.

Listagem 3. Classe Categoria

  public class Categoria
  {
      private string _nomeCategoria;
   
      public string NomeCategoria
      {
          get { return _nomeCategoria; }
          set { _nomeCategoria = value; }
      }
   
      private List<Subcategoria> _subcategorias;
   
      public List<Subcategoria> Subcategorias
      {
          get
          {
              if (_subcategorias == null)
                  _subcategorias = new List<Subcategoria>();
              return _subcategorias;
          }
          set { _subcategorias = value; }
      }
  }

Inicialmente teremos simplesmente categorias e as listamos em um único nível. Para isso, começaremos configurando o DataTemplate do TreeView (Listagem 4), que representa o template dos itens no nível mais interno (neste caso, temos apenas um nível).

Listagem 4. Configurando o DataTemplate para um único nível no TreeView

  <TreeView Name="treeViewRelatorios">
      <TreeView.Resources>
          <DataTemplate DataType="{x:Type model:Categoria}">
              <TextBlock Text="{Binding NomeCategoria}"/>
          </DataTemplate>
      </TreeView.Resources>
  </TreeView>

Na propriedade DataType do DataTemplate precisamos definir qual tipo de dado será exibido nesse nível do TreeView. Neste caso, utilizamos a classe Categoria, que por ter sido criada em outro namespace (Model), foi preciso incluí-la na tag, da seguinte forma, para então poder referenciá-la:

xmlns:model="clr-namespace:WpfApplication1.Model"

Para gerar os itens a serem exibidos, basta criar uma coleção de objetos do tipo Categoria e atribuí-la à propriedade ItemsSpurce do TreeView, como mostra a Listagem 5.

Listagem 5. Exibindo dados dinâmicos

  List<Categoria> categorias = new List<Categoria>();
  categorias.Add(new Categoria() { NomeCategoria = "Financeiro" });
  categorias.Add(new Categoria() { NomeCategoria = "Estoque" });
  categorias.Add(new Categoria() { NomeCategoria = "Recursos Humanos" });
   
  treeViewRelatorios.ItemsSource = categorias;

Isso resulta no que vemos na Figura 3.

Itens gerados dinamicamente

Figura 3. Itens gerados dinamicamente

Da mesma forma que fizemos com os itens estáticos, também podemos gerar vários níveis de itens dinamicamente, porém agora precisaremos configurar os templates para cada nível. Além disso, nossos objetos de domínio também precisam possuir uma estrutura hierárquica, com sublistas, como consta na classe Categoria.

Para complementar o exemplo anterior vamos preencher a propriedade Subcategorias de cada categoria com alguns itens, e além disso vamos também criar subitens em cada subcategoria, gerando uma visualização de três níveis, conforme a Listagem 6.

Listagem 6. Gerando itens dinâmicos com três níveis de hierarquia

  List<Categoria> categorias = new List<Categoria>();
  categorias.Add(new Categoria()
  {
      NomeCategoria = "Financeiro",
      Subcategorias = new List<Subcategoria>()
      {
          new Subcategoria()
          {
              NomeSubcategoria = "Contas a pagar",
              Relatorios = new List<Relatorio>()
              {
                  new Relatorio() { TituloRelatorio = "Por período" }
              }
          }
      }
  });
   
  categorias.Add(new Categoria()
  {
      NomeCategoria = "Estoque",
      Subcategorias = new List<Subcategoria>()
      {
          new Subcategoria()
          {
              NomeSubcategoria = "Entradas",
              Relatorios = new List<Relatorio>()
              {
                  new Relatorio() { TituloRelatorio = "Notas Fiscais" },
                  new Relatorio() { TituloRelatorio = "Pedidos" },
                  new Relatorio() { TituloRelatorio = "Balanço" }
              }
          }
      }
  });
   
  categorias.Add(new Categoria() { NomeCategoria = "Recursos Humanos" });
   
  treeViewRelatorios.ItemsSource = categorias;

As classes Subcategoria e Relatorio são bastante simples, como vemos na Listagem 7, mas poderiam ter outros campos para serem exibidos no TreeView.

Listagem 7. Classes Subcategoria e Relatorio

  public class Subcategoria
  {
      private string _nomeSubcategoria;
   
      public string NomeSubcategoria
      {
          get { return _nomeSubcategoria; }
          set { _nomeSubcategoria = value; }
      }
   
      private List<Relatorio> _relatorioas;
   
      public List<Relatorio> Relatorios
      {
          get
          {
              if (_relatorioas == null)
                  _relatorioas = new List<Relatorio>();
              return _relatorioas;
          }
          set { _relatorioas = value; }
      }
  }
  public class Relatorio
  {
      private string _tituloRelatorio;
   
      public string TituloRelatorio
      {
          get { return _tituloRelatorio; }
          set { _tituloRelatorio = value; }
      }
  }

Por fim, precisamos configurar o TreeView. Desta vez, porém, há um novo elemento a ser utilizado: o HierarchicalDataTemplate, que como o nome sugere, define o template de um item hierárquico. Neste caso, utilizaremos tanto o HierarchicalDataTemplate quanto o DataTemplate, sendo as diferenças entre eles as seguintes:

  • HierarchicalDataTemplate: define o template dos itens de nível mais baixos na hierarquia, ou seja, os níveis de 1 até o anterior do mais interno. Cada elemento desse tipo possui também uma propriedade ItemsSource, que através de data binding pode apontar para uma propriedade da classe que contenha uma sublista de itens a serem exibidos.
  • DataTemplate: representa o nível mais alto na hierarquia, ou seja, os itens mais internos.

Neste exemplo, precisamos configurar dois HierarchicalDataTemplate, um para Categoria (nível 1) e um para Subcategoria (nível 2). Os itens do tipo Relatorio, que representam o último nível, são configurados a partir do DataTemplate, como podemos ver na Listagem 8.

Listagem 8. Configuração dos templates para múltiplos níveis

  <TreeView Name="treeViewRelatorios">
      <TreeView.Resources>
          <HierarchicalDataTemplate DataType="{x:Type model:Categoria}" ItemsSource="{Binding Subcategorias}">
              <TextBlock Text="{Binding NomeCategoria}"/>
          </HierarchicalDataTemplate>
          <HierarchicalDataTemplate DataType="{x:Type model:Subcategoria}" ItemsSource="{Binding Relatorios}">
              <TextBlock Text="{Binding NomeSubcategoria}"/>
          </HierarchicalDataTemplate>
          <DataTemplate DataType="{x:Type model:Relatorio}">
              <TextBlock Text="{Binding TituloRelatorio}"/>
          </DataTemplate>
      </TreeView.Resources>
  </TreeView>

O resultado agora é o que vemos na Figura 4, com vários níveis de itens gerados dinamicamente.

Múltiplos níveis gerados dinamicamente

Figura 4. Múltiplos níveis gerados dinamicamente

Alterando a aparência dos itens

Além da forma como os itens são organizados hierarquicamente, podemos alterar também sua configuração visual utilizando toda a flexibilidade que o XAML nos oferece.

Mantendo o exemplo anterior, podemos facilmente adicionar alguns elementos visuais extras e alterar os já existentes. Na Listagem 9 adicionamos algumas formas e alteramos a cor do texto em cada nível.

Listagem 9. Configurações visuais em diversos níveis

  <TreeView Name="treeViewRelatorios">
      <TreeView.Resources>
          <HierarchicalDataTemplate DataType="{x:Type model:Categoria}" ItemsSource="{Binding Subcategorias}">
              <StackPanel Orientation="Horizontal">
                  <Rectangle Height="10" Width="10" Fill="Blue"/>
                  <TextBlock Text="{Binding NomeCategoria}" Foreground="Blue" FontWeight="Bold" Margin="5,0,0,0"/>
              </StackPanel>
          </HierarchicalDataTemplate>
          <HierarchicalDataTemplate DataType="{x:Type model:Subcategoria}" ItemsSource="{Binding Relatorios}">
              <StackPanel Orientation="Horizontal">
                  <Ellipse Height="10" Width="10" Fill="Red"/>
                  <TextBlock Text="{Binding NomeSubcategoria}" Foreground="Red" FontWeight="Bold" Margin="5,0,0,0"/>
              </StackPanel>
          </HierarchicalDataTemplate>
          <DataTemplate DataType="{x:Type model:Relatorio}">
              <TextBlock Text="{Binding TituloRelatorio}" Foreground="Green" FontWeight="Bold"/>
          </DataTemplate>
      </TreeView.Resources>
  </TreeView>

Como resultado, temos a Figura 5.

Configurações visuais alteradas

Figura 5. Configurações visuais alteradas

Identificando o item selecionado

Um ponto fundamental do uso do TreeView é saber qual item foi selecionado, para então poder tomar decisões sobre a escolha do usuário. Quando configuramos os templates, cada item de cada nível é um objeto que foi atribuído em tempo de execução.

No exemplo anterior, os itens do TreeView são objetos do tipo Categoria, Subcategoria e Relatorio, por isso podemos tratá-los como essas instâncias dessas classes e acessar suas propriedades, simplesmente fazendo um cast.

O código da Listagem 10 mostra como identificar e o usuário selecionou um Relatório e em caso positivo, mostra uma mensagem com o item selecionado.

Listagem 10. Identificando o item selecionado

  private void treeViewRelatorios_MouseDoubleClick(object sender, MouseButtonEventArgs e)
  {
      if(treeViewRelatorios.SelectedItem is Relatorio)
      {
          string relatorio = (treeViewRelatorios.SelectedItem as Relatorio).TituloRelatorio;
          MessageBox.Show($"Você selecionou o item {relatorio}");
      }
  }

Da mesma forma, poderíamos identificar se o usuário selecionou um objeto do primeiro ou segundo nível, bastando para isso verificar o tipo do objeto SelectedItem do TreeView.

Em cada situação, podemos utilizar o TreeView de formas diferentes, explorando suas propriedades e configurando-o da maneira mais adequada, graças aos recursos da linguagem XAML.

Joel Rodrigues

Joel Rodrigues - Técnico em Informática - IFRN Cursando Bacharelado em Ciências e Tecnologia - UFRN Programador .NET/C# e Delphi há quase 3 anos, já tendo trabalhado com Webservices, WPF, Windows Phone 7 e ASP.NET, possui ainda conhecimentos em HTML, CSS e Javascript (JQuery).