Personalizando a Renderização de sua JTree
Este artigo pretende explicar como utilizar apropriadamente a classe DefaultTreeCellRenderer e usá-la como aliada para personalizar uma árvore (JTree) em Java.
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.
Artigos relacionados
-
Artigo
-
Artigo
-
Artigo
-
Artigo
-
Vídeo