Ya vimos una forma sencilla de cambiar los iconos de un JTree usando DefaultTreeCellRenderer. A veces los cambios en el aspecto del JTree que queremos hacer van más allá.
Java nos permite definir nuestro propio TreeCellRenderer y pasárselo al JTree. En este tutorial vamos a ver cómo hacerlo.
Ya comentamos en al tutorial anterior que cuando un JTree necesita pintar un nodo en pantalla, le pregunta a una clase que tiene guardada y que implemente la interface TreeCellRenderer. Esta clase tiene un método getTreeCellRendererComponent() al que el JTree le pasa una serie de datos sobre el nodo que quiere dibujar y espera que este le devuelva un Component de java para ponerlo en el árbol.
Este Component puede ser cualquier Component que queramos: un JPanel, un JButton, un Canvas gráfico, etc. Sin embargo hay un detalle muy importante a tener en cuenta. JTree NO pone ese componente en el árbol. Sólo "le hace una foto" y planta la foto en el árbol.
¿Qué quiere decir eso? ¿Por qué es importante?. El motivo es sencillo. Al ser "una foto" del componente, NO es el componente en sí mismo lo que veremos en el árbol, aunque lo parezca. Si tenemos una foto de un JButton, NO tenemos un JButton. Eso quiere decir que NO podemos pulsarlo. También quiere decir que si ponemos un JLabel con un icono con un gif animado, se le hará la foto al gif animado en un instante dado y eso será lo que veremos. NO veremos el gif animado moviéndose.
Implementar la clase TreeCellRenderer es sencillo. Basta hacer una clase que implemente la interface y defina el único método que lleva.
public class MiRender implements TreeCellRenderer
{
public Component getTreeCellRendererComponent(JTree tree,
Object value, boolean selected, boolean expanded, boolean leaf, int row,
boolean hasFocus)
{
}
}
Vamos a ver los parámetros que nos pasan.
¿Por qué nos pasan tanta cosa?. Si decidimos implementar el TreeCellRenderer estamos adquiriendo la responsabilidad de hacerlo todo, todo, todo nosotros. En un JTree o JTable por defecto, las celdas seleccionadas se pintan de color distinto, las que tienen el foco se suelen pintar con un pequeño cuadradito alrededor, etc, etc. JTree nos pasa todo esto para que podamos elegir los colores y formas adecuadas a nuestro gusto en cualquier situación que se nos ocurra.
Comentamos antes que JTree hará una foto del Component que devolvemos. Como el JTree usará la foto, el Component no sirve para nada una vez hecha dicha foto. Esta características que nos fastidia al no poder pulsar botones o no poder poner iconos animados, tiene una ventaja. Podemos reaprovechar y devolver siempre el mismo Component, para todos los nodos. Simplemente tenemos que cambiarle las cosas cada vez (colores, texto mostrado, iconos...) y devolver el mismo Component una y otra vez.
Aprovechando esto, suele ser habitual que este tipo de clases, además de implementar TreeCellRenderer, hereden del Component que queramos devolver.
Para nuestro ejemplo, como vamos a liar las cosas, usaremos un JPanel para meter dos botones dentro. Uno para el icono y otro para el texto. La foto de lo que queremos conseguir puede ser como esta
Cada nodo es un panel, así que aprovechando lo que acabamos de comentar de que podemos hacer que nuestra clase herede del JPanel y devolver siempre el mismo, haremos esto
public class MiRender extends JPanel implements TreeCellRenderer
Como tenemos tres iconos y no queremos cargarlos miles de veces, pondremos tres atributos a la clase, de forma que se carguen al instanciar la clase. Haremos lo mismo con los dos JButton.
public class MiRender extends JPanel implements TreeCellRenderer
{
private JButton botonIcono = new JButton();
private JButton botonTexto = new JButton();
private ImageIcon iconoHoja = new ImageIcon("d:/futbol.gif");
private ImageIcon iconoAbierto = new ImageIcon("d:/hombre.gif");
private ImageIcon iconoCerrado = new ImageIcon("d:/viejo.gif");
...
Pondremos también un constructor en el que se metan dentro del JPanel los dos botones. Al panel lo haremos transparente además para que quede todo del mismo color que el fondo del JTree.
public MiRender()
{
add(botonIcono);
add(botonTexto);
setOpaque(false);
}
Listo, nos queda la parte fastidiada del asunto, rellenar el método getTreeCellRenderComponent(). Aquí haremos un ejemplo sencillo. Meteremos el icono que corresponda, mirando los flags leaf y expanded en el botonIcono. También obtendremos cogeremos el value para meterlo en el botonTexto.
Un detalle con value. Lo recibimos como Object en el método. En realidad es la clase que nosostros hayamos metido en el modelo de datos. En nuestro ejemplo inicial de JTree, habíamos metido DefaultMutableTreeNode, así que ahí estaremos recibiendo DefaultMutableTreeNode. Hacemos el "cast" a ese tipo de objeto y obtenemos el getUserObject(), que es el dato real que metimos dentro, un String. Ese String es el que queremos meter en el botonTexto.
El método, al final, quedaría más o menos así
public Component getTreeCellRendererComponent(JTree tree,
Object value, boolean selected, boolean expanded, boolean leaf, int row,
boolean hasFocus)
{
// Se pone el icono adecuado
if (leaf)
{
botonIcono.setIcon(iconoHoja);
}
else if (expanded)
{
botonIcono.setIcon(iconoAbierto);
}
else
{
botonIcono.setIcon(iconoCerrado);
}
// Y el texto.
botonTexto.setText(((DefaultMutableTreeNode) value).getUserObject().toString());
return this;
}
Como ves, no nos hemos preocupado de si está seleccionado o no, si tiene el foco o no, pero es por hacer el ejemplo más sencillo y quedarnos con lo importante, que debemos poner el Component como nos guste en función de los datos que nos pasan.
Ahora sólo queda decirle al JTree que use este "render". Eso se hace de forma muy simple
tree.setCellRenderer(new MiRender());
Listo. Aquí tienes los dos fuentes completos, por si quieres jugar con ellos: PruebaJTree3.java y MiRender.java.
Ahora vamos a ver cómo hacer editable el árbol y poner el editor a nuestro gusto.