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()); }