Los árboles representan en Swing uno de los elementos más versátiles a la hora de trabajar con ellos por las numerosas posibilidades que ofrecen. Permiten al usuario una vista jerárquica de elementos seleccionarles sobre los que se puede realizar una acción. Inicialmente dentro de un árbol se debe distinguir:

  • Nodo Raiz (root): Definido por el nodo principal del que dependen el resto de nodos
  • Nodo: Definido por un nodo que depende del nodo raíz y a su vez tiene dentro nodos que dependen de él
  • Nodo Hoja (): Definido por un nodo que depende de un solo nodo bien sea del root o de otro.

Este tipo de objeto se define como DefaultMutableTreeNode, donde su construcción queda da la siguiente forma:

//Por parámetro se pasa el objeto asociado al nodo
DefaultMutableTreeNode nodoRoot = new DefaultMutableTreeNode("Raiz");

Al igual que pasaba con los elementos que mostraban datos (JComboBox, JList, JSpinner) este elemento también tiene asignado un modelo de datos y un modelo de selección que más adelante se verán.

Para poder crear un árbol, el constructor admite muchos parámetros, dependiendo de la forma de creación. Los principales son:

//Con constructor vacío para asignar más adelante un modelo
JTree arbol = new JTree();
//Con el nodo root del que partirán todos los nodos
JTree arbol = new JTree(nodoRoot);
//Con una lista hashtable donde se ubica la estructura jerárquica
Persona[] p = new Persona[]{new Persona("N1","A1",123),
                new Persona("N2","A2",123),
                new Persona("N3","A3",123)};
Hashtable lista = new Hashtable();
lista.put(p[0].getNombre(),p[0]);
lista.put(p[1].getNombre(),p[1]);
lista.put(p[2].getNombre(),p[2]);
JTree arbol = new JTree(lista);
//Con un array de objetos
Persona[] p = new Persona[]{new Persona("N1","A1",123),
                new Persona("N2","A2",123),
                new Persona("N3","A3",123)};
arbol = new JTree(p);
//Con un modelo de datos
DefaultTreeModel modelo = new DefaultTreeModel(nodoRoot);

El último constructor es el más utilizado ya que permite personalizar mucho más el árbol. Hay una variante que es pasar un modelo personalizado (clase que extienda de TreeModel).

Rellenar el árbol

[ezcol_2third]Para rellenar el árbol depende de la forma en la que se cree. Las más normales son directamente con nodos o con modelos. Tanto de una forma como de otra el resultado será el mismo, la diferencia será a la hora de manipular tanto el árbol como los eventos que este genere [/ezcol_2third] [ezcol_1third_end][/ezcol_1third_end]

Mediante nodos

Consiste en ir agregando nodos al nodo raíz o al nodo que jerárquicamente se quiera mediante el método add

arbol = new JTree(nodoRoot);
DefaultMutableTreeNode nodoUno = new DefaultMutableTreeNode("Nodo Uno");
DefaultMutableTreeNode nodoDos = new DefaultMutableTreeNode("Nodo Dos");
DefaultMutableTreeNode nodoTres = new DefaultMutableTreeNode("Nodo Tres");
DefaultMutableTreeNode nodoCuatro = new DefaultMutableTreeNode("Nodo Cuatro");
DefaultMutableTreeNode nodoCinco = new DefaultMutableTreeNode("Nodo Cinco");
DefaultMutableTreeNode nodoCincoHijo = new DefaultMutableTreeNode("Nodo Cinco Hijo");
nodoRoot.add(nodoUno);
nodoRoot.add(nodoDos);
nodoRoot.add(nodoTres);
nodoRoot.add(nodoCuatro);
nodoRoot.add(nodoCinco);
nodoCinco.add(nodoCincoHijo);

Mediante Modelo

Consiste en setear al modelo un nodo root y al árbol pasarle el modelo.

DefaultTreeModelo modelo = new DefaultTreeModel(nodoRoot);
JTree arbol = new JTree(modelo);
DefaultMutableTreeNode nodoUno = new DefaultMutableTreeNode("Nodo Uno");
DefaultMutableTreeNode nodoDos = new DefaultMutableTreeNode("Nodo Dos");
DefaultMutableTreeNode nodoTres = new DefaultMutableTreeNode("Nodo Tres");
DefaultMutableTreeNode nodoCuatro = new DefaultMutableTreeNode("Nodo Cuatro");
DefaultMutableTreeNode nodoCinco = new DefaultMutableTreeNode("Nodo Cinco");
DefaultMutableTreeNode nodoCincoHijo = new DefaultMutableTreeNode("Nodo Cinco Hijo");
nodoRoot.add(nodoUno);
nodoRoot.add(nodoDos);
nodoRoot.add(nodoTres);
nodoRoot.add(nodoCuatro);
nodoRoot.add(nodoCinco);
nodoCinco.add(nodoCincoHijo);

Manipulaciones dinámicas

Al igual que se puede construir un árbol de inicio, también se pueden agregar o borrar nodos una vez la aplicación está en ejecución. Para hacerlo tan solo hay que ejecutar el método add sobre el nodo que se quiere añadir el objeto

DefaultMutableTreeNode nodoNuevo = new DefaultMutableTreeNode("Nodo Nuevo");
nodoRoot.add(nodoNuevo);

En el caso de querer borrar se ejecuta el método remove

//Por parámetro se pasa el índice del nodo que se quiere eliminar
nodoRoot.remove(4);

Si estas operaciones se quieren hacer totalmente dinámicas mediante pulsaciones de botón por ejemplo, donde la selección del nodo marca donde se añade el nuevo objeto o que nodo será borrado la programación cambia. En los siguientes ejemplos se añadirá un nodo sobre una selección y se borrará un nodo seleccionado

Para añadir nodos primero hay que obtener el nodo seleccionado mediante el método getSelectedPath() y getLastPathComponent.

if (nodoSeleccionado != null)
{
     TreeModel model = arbol.getModel();
     nodoSeleccionado.add(new DefaultMutableTreeNode(nombreNodo.getText()));
     modelo.reload();
}

O bien utilizar el método insertNodeInto

DefaultMutableTreeNode nodoSeleccionado = (DefaultMutableTreeNode) arbol.getSelectionPath().getLastPathComponent();
if (nodoSeleccionado != null)
{
    modelo.insertNodeInto(new DefaultMutableTreeNode(nombreNodo.getText()),nodoSeleccionado,0);
    modelo.reload();
}

Para eliminar se ejecuta el método removeNodeFromParent donde se le pasa el nodo que se quiera borrar

modelo.removeNodeFromParent((MutableTreeNode) arbol.getSelectionPath().getLastPathComponent());

Opciones gráficas

Los árboles además de tener un modelo que asocia datos, también poseen opciones para la visualización tanto del árbol completo como los nodos y las hojas. Para ello se utiliza un objeto de tipo TreeCellRenderer. Las opciones más comunes son:

DefaultTreeCellRenderer renderArbol = new DefaultTreeCellRenderer();
//Cambia el icono de los nodos hoja
renderArbol.setLeafIcon(new ImageIcon(getClass().getResource("/resources/document.png")));
//Cambiar el icono de los nodos con hijos que están desplegados 
renderArbol.setOpenIcon(new ImageIcon(getClass().getResource("/resources/open.png")));
//Cambiar el icono de los nodos con hijos que están cerrados 
renderArbol.setClosedIcon(new ImageIcon(getClass().getResource("/resources/close.jpeg")));
arbol.setCellRenderer(renderArbol);
//muestra las llaves que indican que se trata de un nodo
arbol.setShowsRootHandles(false);

Para poder personalizar de forma completa un árbol, habría que crear una clase que extienda de TreeCellRenderer.

También se puede configurar cuales son las opciones de seleccionado. Para ello se crea setea la propiedad:

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

Manejo de eventos

El manejo de eventos en un árbol se utiliza la interfaz TreeSelectionListener con el método valueChanged. Este método obtiene el valor de la nueva selección, desde la cual se puede obtener toda la información necesaria:

arbol.addTreeSelectionListener(this);

public void valueChanged(TreeSelectionEvent e) {
//Obtiene el componente del camnino seleccionado
System.out.println(e.getNewLeadSelectionPath().getLastPathComponent());
//Obtiene el componente anteriormente seleccionado
System.out.println(e.getOldLeadSelectionPath().getLastPathComponent());
//Obtiene el objeto asociado a un nodo. Por defecto será el string si no le hemos asociado ningún otro objeto
Object o = ((DefaultMutableTreeNode)e.getNewLeadSelectionPath().getLastPathComponent()).getUserObject();
System.out.println(o.toString());
//Obtiene la ruta seleccionada
System.out.println(e.getPath());
}