Neste artigo, veremos como criar uma árvore (JTree) com nós e folhas personalizados. Para isso, criaremos nossas classes auxiliares que representam nós (terminação Node) e folhas (terminação Leaf). Todas necessitam ter um método toString() sobrescrito para que o objeto JTree apresente o texto de cada nó/folha apropriadamente.

 

Vejam o exemplo das classes abaixo:

 

...

 

// Classe que representa um "nó" para Projeto

  private class ProjectNode {

    public String name;

    public int id;

 

    public ProjectNode(String name, int id) {

      this.name = name;

      this.id = id;

    }

 

    public String toString() {

      return name;

    }

  }

 

  // Classe que representa um "nó" para Contrato

  private class ContractNode {

    public String name;

 

    public ContractNode(String name) {

      this.name = name;

    }

 

    public String toString() {

      return name;

    }

  }

 

  // Classe que representa um "nó" para Conta

  private class AccountNode {

    public String name;

 

    public AccountNode(String name) {

      this.name = name;

    }

 

    public String toString() {

      return name;

    }

  }

 

  // Classe que representa um "nó" para Equipe

  private class TeamNode {

    public String name;

 

    public TeamNode(String name) {

      this.name = name;

    }

 

    public String toString() {

      return name;

    }

  }

 

  // Classe que representa um "sub-nó" para Equipe

  private class TeamSubNode {

    public String name;

 

    public TeamSubNode(String name) {

      this.name = name;

    }

 

    public String toString() {

      return name;

    }

  }

 

  // Classe que representa um "nó" para Tarefa

  private class TaskNode {

    public String name;

 

    public TaskNode(String name) {

      this.name = name;

    }

 

    public String toString() {

      return name;

    }

  }

 

  // Classe que representa uma "folha" para Contrato

  private class ContractLeaf {

    public String contract, company;

    public double value;

 

    public ContractLeaf(String contract, String company, double value) {

      this.contract = contract;

      this.company = company;

      this.value = value;

    }

 

    public String toString() {

      return contract + " - Empresa: " + company + " - Valor: " + value;

    }

  }

 

  // Classe que representa uma "folha" para Conta

  private class AccountLeaf {

    public String login, name;

 

    public AccountLeaf(String login, String name) {

      this.login = login;

      this.name = name;

    }

 

    public String toString() {

      return login + " - " + name;

    }

  }

 

  // Classe que representa uma "folha" para membro de Equipe

  private class MemberLeaf {

    public String name, company;

 

    public MemberLeaf(String name, String company) {

      this.name = name;

      this.company = company;

    }

 

    public String toString() {

      return name + " - Empresa: " + company;

    }

  }

 

  // Classe que representa uma "folha" para Tarefa

  private class TaskLeaf {

    public String desc, priority, step;

    public double prevHours, realHours;

 

    public TaskLeaf(String desc, double prevHours, double realHours, String priority,

                    String step) {

      this.desc = desc;

      this.prevHours = prevHours;

      this.realHours = realHours;

      this.priority = priority;

      this.step = step;

    }

 

    public String toString() {

      return desc + " - HH Prev: " + prevHours + " - HH Real: " + realHours +

             " - Prioridade: " + priority + " - " + step;

    }

  }

 

...

 

A seguir, criaremos um conjunto de ícones para serem usados depois; eles podem ser criados no construtor da classe principal:

 

// Cria os ícones; IMG_PATH é uma constante que representa o caminho para as imagens

projectNodeIcon = new ImageIcon(IMG_PATH + "project.jpg");

contractNodeIcon = new ImageIcon(IMG_PATH + "contracts.jpg");

contractLeafIcon = new ImageIcon(IMG_PATH + "contract.jpg");

accountNodeIcon = new ImageIcon(IMG_PATH + "accounts.jpg");

accountLeafIcon = new ImageIcon(IMG_PATH + "account.jpg");

teamNodeIcon = new ImageIcon(IMG_PATH + "team.jpg");

memberLeafIcon = new ImageIcon(IMG_PATH + "member.jpg");

taskNodeIcon = new ImageIcon(IMG_PATH + "tasks.jpg");

taskLeafIcon = new ImageIcon(IMG_PATH + "task.jpg");

 

 

Agora que as classes nó/folha estão criadas (e seus respectivos ícones), podemos partir para a classe que será a responsável por renderizar tais nós/folhas. Ela deve extender a classe DefaultTreeCellRenderer do pacote javax.swing.tree. A chave para essa classe é o método public Component getTreeCellRendererComponent(...). Neste método, obtemos o valor de um nó da árvore pelo parâmetro value, e fazemos um "cast" como em:


DefaultMutableTreeNode node = (DefaultMutableTreeNode) value;


a seguir, obtemos um Object usando Object object = node.getUserObject(); de posse dessa variável object, usamos uma cadeia de if's com comparações do tipo instanceof para verificar à qual classe pertence a nossa variável object; dependendo de qual classe nó/folha ela pertencer, nós ajustamos a fonte e o ícone de acordo.

 

Vamos ver abaixo como deve se comportar esta classe (aqui, chamada de CustomRenderer):

 

// Classe interna para renderizar as imagens dos nodos da árvore

  private class CustomRenderer extends DefaultTreeCellRenderer {

 

    // Sobrescreve este método para obter os efeitos desejados

    public Component getTreeCellRendererComponent(JTree tree, Object value,

      boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) {

 

      super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus);

 

      DefaultMutableTreeNode node = (DefaultMutableTreeNode) value;

      Object object = node.getUserObject();

 

      if (object instanceof ProjectNode) {

        setIcon(projectNodeIcon);

        setFont(new Font("Tahoma", Font.BOLD, 11));

      }

      else if (object instanceof ContractNode) {

        setIcon(contractNodeIcon);

        setFont(new Font("Tahoma", Font.BOLD, 11));

      }

      else if (object instanceof AccountNode) {

        setIcon(accountNodeIcon);

        setFont(new Font("Tahoma", Font.BOLD, 11));

      }

      else if (object instanceof TeamNode) {

        setIcon(teamNodeIcon);

        setFont(new Font("Tahoma", Font.BOLD, 11));

      }

      else if (object instanceof TeamSubNode) {

        setIcon(teamNodeIcon);

        setFont(new Font("Tahoma", Font.PLAIN, 11));

      }

      else if (object instanceof TaskNode) {

        setIcon(taskNodeIcon);

        setFont(new Font("Tahoma", Font.BOLD, 11));

      }

      else if (object instanceof ContractLeaf) {

        setIcon(contractLeafIcon);

        setFont(new Font("Tahoma", Font.PLAIN, 11));

      }

      else if (object instanceof AccountLeaf) {

        setIcon(accountLeafIcon);

        setFont(new Font("Tahoma", Font.PLAIN, 11));

      }

      else if (object instanceof MemberLeaf) {

        setIcon(memberLeafIcon);

        setFont(new Font("Tahoma", Font.PLAIN, 11));

      }

      else if (object instanceof TaskLeaf) {

        setIcon(taskLeafIcon);

        setFont(new Font("Tahoma", Font.PLAIN, 11));

      }

      else

        setFont(new Font("Tahoma", Font.PLAIN, 11));

 

      return this;

    }

  }

 

...

 

O nosso objeto JTree deve ser criado como segue:

 

    // Cria a árvore; inicialmente vazia

    root = new DefaultMutableTreeNode("-- Selecione um Projeto --");

 

    projectTree = new JTree(root);

    projectTree.setShowsRootHandles(true);

    projectTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);

    projectTree.putClientProperty("JTree.lineStyle", "Angled");

    projectTree.setCellRenderer(new CustomRenderer()); // Aqui usamos a nossa classe renderizadora !!!

 

    scroller = new JScrollPane(projectTree);

 

...

 

Quase tudo está pronto. O único ponto a se observar agora é que quando formos popular a árvore (a partir de um banco de dados, por exemplo), devemos criar seus objetos desse modo:

 

...

 

  // Método que recebe o nome e o id_projeto e cria toda a estrutura da árvore de

  // projetos, realizando as consultas necessárias


  private void populateProjectTree(String projectName, int projectId) {

    // "Nós" genéricos para uso

    DefaultMutableTreeNode node, subNode, child;

 

    // Cria um objeto ProjectNode com o nome do projeto

    ProjectNode projectNode = new ProjectNode(projectName, projectId);

 

    // Atualiza o nó raiz com um objeto ProjectNode

    root.setUserObject(projectNode);

 

    // Limpa toda a estrutura da árvore

    root.removeAllChildren();

 

 ...

 

    // Preenche os contratos

    node = new DefaultMutableTreeNode(new ContractNode("Contratos"));

    root.add(node);

 

...

 

      while (resultSet.next()) {

        String contractNumber = resultSet.getString("numero_contrato");

        String companyName = resultSet.getString("E.nome");

        double contractValue = resultSet.getDouble("valor_contrato");

 

        child = new DefaultMutableTreeNode(

          new ContractLeaf(contractNumber, companyName, contractValue));

 

        node.add(child);

      }

 

...

 

    // Preenche as contas

    node = new DefaultMutableTreeNode(new AccountNode("Contas"));

    root.add(node);

 

...

 

     while (resultSet.next()) {

        String login = resultSet.getString("login");

        String userName = resultSet.getString("F.nome");

 

        child = new DefaultMutableTreeNode(new AccountLeaf(login, userName));

 

        node.add(child);

      }

 

...

 

     // Preencher todos os outros objetos de modo similar...

 

...

 

    // Atualiza a árvore

    projectTree.updateUI();

 

...

 

E assim por diante. Supomos aqui, por conveniência, que estamos conectados a um banco de dados e emitindo consultas SQL para popular os nós/folhas da árvore. Na prática, use objetos DAO (Data Access Objects) para emitir as consultas e fornecer os valores para os objetos nós/folhas, separando as camadas devidamente.

 

Pronto!! É isso... Espero que o código essencial mostrado aqui tenha sido claro para ilustrar o artigo. Abraços e até o próximo artigo.