Ayudanos contestando la siguiente encuesta acerca de Videojuegos!
Ir a la encuesta
>

JTree

 

Archivos necesarios:

En este trial vamos a utilizar uno de los controles avanzados de Java. El JTree nos permite mostrar información jerárquica, es excelente para mostrar datos como un árbol familiar o la estructura del sistema de archivos. Utilizar un JTree es algo sencillo, pero no siempre es muy claro cómo llenarlo para no gastar mucha memoria y para que no se gaste mucho tiempo al llenarlo. En este Trial vamos a hacer una aplicación guíada en la que utilizaremos un JTree para mostrar el sistema de archivos de la partición raíz de la computadora.

El archivo contiene una versión termianda del programa que vamos a crear en este Trial.

Main.java
Main.java

 

¿Cómo utilizar un JTree?

La aplicación que queremos crear es una aplicación similar al Explorador de Windows. En la aplicación vamos a tener un JTree con los directorios que se encuentran en la estructura de archivos, y en la otra parte vamos a tener los archivos que se encuentren dentro del directorio seleccionado. Además de utilizar el JTree vamos a utilizar extensivamente la clase File que nos permite muchas facilidades en el manejo de archivos.

Primero que nada crea una nueva aplicación de Java en que la clase principal se llame Main. En la clase Main copia el siguiente código:

En este código estamos creando una nueva ventana. La ventana va a contener un JTree y un JList y cada uno va a tener su propio JScrollPane para permitir que tengan barras de desplazamiento.

En el constructor de la clase podemos ver que estamos creando un nuevo objeto de la clase File. La clase File contiene muchos métodos que nos permiten manejar archivos, saber de que tipo son y algunos otros datos importantes sobre ellos.

Nuestro JTree es un conjunto de nodos que tienen un padre y cero o más hijos, así como un directorio tiene un directorio padre y puede contener cero o más directorios. En este caso, cada directorio va a ser un nuevo nodo de la clase DefaultMutableTreeNode. En el código podemos ver que creamos un nuevo objeto de esta clase con un constructor que recibe un objeto (en este caso el objeto File que creamos pero podría recibir cualquier otra cosa, incluyendo objetos creados por nosotros). Este nodo, al que estoy llamando top, va a ser la raíz de nuestro JTree y vamos a agregarle elementos más adelante para crear nuestro sistema de archivos.

Una vez que definimos nuestro nodo inicial (y que agregamos los componentes necesarios) vamos a crear un nuevo JTree que reciba como parámetro del constructor el nodo que acabamos de crear.

Después de definir el JTree le ponemos un JScrollPane para permitir que tenga barras de desplazamiento y después creamos todo el resto de la interfaz.

Esta clase utiliza el método pack(). Este método le pregunta a los componentes de la interfaz gráfica cuánto espacio necesitan y, dependiendo de esto, construye una ventana suficientemente grande para que todos los componentes puedan desplegarse de manera satisfactoria.

En el método main() únicamente creamos una instancia nueva de nuestro programa.

 

Al ejecutar el programa podemos ver que nuestra interfaz todavía es muy sencilla y no tiene ninguna funcionalidad. Nuestro siguiente paso es agregarle esta funcionalidad.

El arbol es por naturaleza una estructura recursiva. Lo que esto significa es que la mayoría de las veces vamos a tener que plantearnos soluciones recursivas para agregar y eliminar nodos. Por ejemplo, si tenemos un directorio (1), debemos encontrar sus hijos (1.1 y 1.2) y agregarlos, pero si estso hijos tienen hijos a su vez (1.1.1), también tenemos que agregar esos hijos. Es decir: Para un directorio cualquiera, debemos agregar sus subdirectorios y repetir el proceso con estos.

Para hacer esto con código debemos crear un método que agregue los hijos al padre y que por cada hijo se mande a llamar a si mismo, pero ahora con el hijo. Es decir, debemos crear un método recursivo. Agrega el siguiente código a tu aplicación:

Algunas veces, cuando manejamos métodos recursivos, es prudente poner un método de inicio, que reciba únicamente las variables indispensables para iniciar. Este método es el primer populateNode(). Recibe un nodo y un archivo y lo único que hace es eliminar los hijos y llamar el otro método populateNode().

Ahora vamos a analizar el segundo método. Lo primero que hacemos es utilizar el método listFiles() del obejo File que pasamos como parámetro (el directorio padre) para obtener una lista con todos los subdirectorios de este. Este método no es objetivo del trial, por lo que únicamente debes entender que funciona para esto.

En caso de que si tenga hijos (files !=null) vamos a ir creando nodos nuevos con cada uno de los hijos y vamos a mandar llamar el mismo método con cada uno de ellos. Esto nos garantiza que se agruegen todos los directorios de todos los hijos.

Una vez que hayamos terminado de escribir el método agrega la última línea de código del isguiente bloque en el constructor de la clase para iniciar el nodo después de que lo creas:

DefaultMutableTreeNode top = //Esta ya estaba
  new DefaultMutableTreeNode(f);
populateNode(top, f); //Agrega esta linea

¡Al correr el programa nos damos cuenta que faltan muchos directorios! Esto es comportamiento esperado porque al ir reduciendo la variable depth le indicamos al programa que no ponga todos los directorios sino únicamente los primeros dos niveles. Esto lo hacemos ya que tomaría muchísimo tiempo poner todos los directorios (un conteo rápido del número de archivos de mi disco duro indica que tengo más de cien mil archivos, imagina todo el tiempo que tomaría agregarlos todos). Este problema vamos a dejarlo volando un momento y antes vamos a resolver un punto un poco más importante.

Ya que tenemos un poco más de directorios en nuestro JTree podemos hacer que una vez que el usuario haga click sobre el directorio aparezcan los archivos que contiene en la lista de la izquierda. Para esto vamos a utilizar un TreeSelectionListener y el método valueChanged().

Primero que nada agrega el código:

 implements TreeSelectionListener

En el encabezado de la clase y utiliza la siguiente línea de código para agregar la capacidad de manejar eventos a nuestro JTree:

 treeDir.getSelectionModel().addTreeSelectionListener(this);

Después copia el siguiente código:

Al manejar eventos con un JTree es muy importante saber qué nodo está seleccionado y qué objeto está guardado en ese nodo. Para poder obtener el nodo seleccionado vamos a utilizar el método getLastSelectedPathComponent() este método que parece trabalenguas nos devuelve un DefaultMutableTreeNode con el último nodo que fue seleccionado.

Para obtener el objeto que está guardado dentro del nodo podemos utilizar el método getUserObject() del nodo que acabamos de obtener. Como este método nos devuelve un objeto nosotros debemos hacer un cast al tipo de dato que está guardado.

En este método podemos ver que obtenemos el nodo y luego el objeto guardado, con este objeto obtenemos una lista de todos los archivos que están guardados en el directorio. Una vez más, el código que hace esto no es objetivo del Trial y no lo vamos a ver. Por último borramos la lista y agregamos todos los archivos, para saber cómo funciona el DefaultListModel y un JList puedes consultar el trial de Listas.

Al correr el programa podemos ver que ya aparecen los archivos que contiene el directorio seleccionado al hacer click sobre él. Por último vamos a resolver el problema que dejamos volando antes. Lo que queremos hacer ahora es que al expandir alguna carpeta se lean los directorios que contiene y que los agregue a esta carpeta para que los muestre de la misma manera que lo hace con el nodo raíz. Para resolver este problema podemos utilizar un TreeExpansionListener.

Agrega el TreeExpansionListener a la declaración de la clase y después agrega la siguiente línea de código para permitir que nuestro JTree responda a eventos de expansión y reducción de carpetas:

treeDir.addTreeExpansionListener(this);

Ahora agrega el siguiente código para manejar el evento de expansión:

Para obtener el nodo que se va a expandir en este método debemos hacer dos pasos, el primero es obtener un objeto TreePath mediante el método getPath() de event y el segundo es obtener el DefaultMutableTreeNode utilizando el método getLastPathComponent(). Una vez que conseguimos el nodo podemos utilizar el método getUserObject() de la misma manera que arriba para poder obtener el archivo.

Para agregar los subdirectorios al directorio seleccionado podemos utilizar el método populateNode() que ya habíamos creado.

Por último obtenemos el DefaultTreeModel que guarda todos los nodos de nuestro JTree (más o menos en la misma manera que el DefaultListModel guarda los objetos del JList) meidante el método getModel() y vamos a utilizar el método nodeStructureChanged() que recibe el nodo que ha cambiado como parámetro para avisarle al JTree que debe volverse a dibujar utilizando los valores nuevos.

En este método también podemos ver el método setCursor() que nos permite cambiar el cursor para avisarle al usuario que el método puede tardar.

Nuestro JTree ya nos permite ver archivos y nos permite navegar por todos los directorios de nuestro sistema de archivos, pero tiene muchas deficiencias.

La más notable es que los íconos no son consistentes y visualmente el programa es muy poco amigable. Para poder resolver este problema es necesario cambiar el DefaultCellRenderer que utiliza el JTree para dibujarse en pantalla, esto es un poco complicado por lo que no lo vamos a cubrir en el Trial, pero si quieres ver cómo se hace el código viene en el archivo de inicio.

Otra cosa que podríamos hacer es utilizar el método treeCollapsed para descargar los folders que no se están viendo, pero la memoria que podríamos recuperar es insignificante, por lo que no vale la pena descargar las carpetas de memoria y luego tener que perder en tiempo.

RegresarRegresar

               
Ayudanos contestando la siguiente encuesta acerca de Videojuegos!
Ir a la encuesta