Beginning Sistemas de Información Java Listas Listado siguiente
PresentaciónEntrada/SalidaBibliotecasSwingMarcosBotonesCuadros de texto
ListasImágenesMenúsDiálogosArchivosGráficos 2D 

Listas y tablas en Swing
Las listas de los elementos más frecuentes en cualquier interfaz gráfico de usuario. El componente JList es de utilización bastante sencilla, y permite crear listas para visualizar elementos y realizar selecciones con bastante facilidad. Un pariente algo más complejo, las tablas, se basa a su vez en el componente JTable. En ambos casos, el elemento fundamental es el modelo subyacente (en el sentido MVC), que debe servir de respaldo para la información visualizada por el componente. En este sentido, las tablas ofrecen un mecanismo muy sofisticado para almacenar el contenido de sus celdas. El objetivo perseguido al construir una tabla consiste en ofrecer al usuario la posibilidad de crear o destruir filas y columnas, y también la de modificar el contenido de las celdas de la tabla. Además, el usuario deberá poder seleccionar una o más celdas, y una o más filas o columnas. Una vez efectuada la selección, debe ser posible aplicar la acción deseada a todas las celdas de la misma. Todas estas opciones implican una cierta complejidad en el código, pero la IGU resultante bien merece la pena.

Lista
Una forma muy cómoda de crear una lista consiste en almacenar toda la información deseada en un vector, que después se utilizará como argumento del constructor de la lista.

Ejercicio
Construir una lista que contenga una colección de cadenas y una colección de imágenes. Se supone que las imágenes residen en un directorio previamente conocido.

Una posible solución podría ser la siguiente. Está basada en dos clases:
JFrame4.java
/** 
 * JFrame4.java
 *
 * Descripción: 
 * @author   coti
 * @version   1.1
 */

import java.awt.Dimension;
import java.awt.Component;
import java.awt.Toolkit;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.UIManager;

public class JFrame4 extends JFrame{
 Dimension dim_pantalla, dim_cuadro;
 int posx, posy;
 JFrame4() {
  super("Sin título");
  addWindowListener(new WindowAdapter() {
   public void windowClosing(WindowEvent we) {
    System.exit(0);
   }
  });
  try
   {
    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
   }
  catch(Exception e)
   {
    System.err.println(e);
   }
 } // Constructor sin título
 
 JFrame4(String t) {
  super(t);
  addWindowListener(new WindowAdapter() {
   public void windowClosing(WindowEvent we) {
    System.exit(0);
   }
  });
  try
   {
    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
   }
  catch(Exception e)
   {
    System.err.println(e);
   }

 } // Constructor con título
 

 public void centerThisFrame() {
  dim_pantalla = Toolkit.getDefaultToolkit().getScreenSize();
  dim_cuadro = getContentPane().getPreferredSize();
  //pack();
  setLocation( (dim_pantalla.width-dim_cuadro.width)/2,
      (dim_pantalla.height-dim_cuadro.height)/2);
 }
}


PruebaLista.java
/** 
 * PruebaLista.java
 *
 * Description: 
 * @author   Coti
 * @version   1.2
 */

import java.util.Arrays;
import java.util.Vector;
import javax.swing.ImageIcon;
import javax.swing.JList;
import javax.swing.JScrollPane;

public class PruebaLista extends JFrame4{
 public PruebaLista() {
  String[] texto = { "Uno", "Dos", "Tres","Cuatro",
       "Cinco", "Seis", "Siete", "Ocho"};
  ImageIcon[] imagen = new ImageIcon[4];
  JList lista;
  for(int i=0;i<imagen.length;i++)
   imagen[i] = new ImageIcon("../img/img0"+i+".jpg");
  Vector contenido = new Vector();
  contenido.addAll(Arrays.asList(texto));
  contenido.addAll(Arrays.asList(imagen));
  lista = new JList(contenido);
  getContentPane().add(new JScrollPane(lista));
  pack();
  centerThisFrame();
  setVisible(true);
 }

 // Main entry point
 static public void main(String[] args) {
  new PruebaLista();
 }
 
}


Comentarios.- La clase PruebaLista tiene como única misión crear un ejemplar de JList que contendrá una colección de cadenas (texto) y una colección de imágenes (imagen). Una vez creadas las listas anteriores, se añaden ambas a un Vector y finalmente se crea un ejemplar de JList a cuyo constructor se le pasa el Vector anterior. JList admite todos estos objetos y los muestra correctamente.

Tablas
Una tabla es, en esencia, un elemento visor adecuado para mostrar un modelo formado por filas y columnas. La clase base que se emplea para manejar el modelo se llama AbstractTableModel; sirve únicamente como recordatorio de los métodos que es preciso implementar si se desea construir un modelo real. En el caso más sencillo, se construye una tabla basada en una matriz de dimensiones fijas; un caso más real cuenta con los elementos necesarios para disponer de cualquier número de filas y columnas que admita la memoria disponible para la máquina virtual.



Ejercicio.- Construir una tabla 10x10 visible en pantalla. Los elementos de la tabla no serán editables.

Una posible solución para este problema sería la siguiente, basada en dos clases:
MyFirstTable.java
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JFrame;
import javax.swing.JTable;
import javax.swing.JScrollPane;
import javax.swing.table.TableModel;
import javax.swing.table.AbstractTableModel;

public class MyFirstTable {
 MyFirstTable() {
 }
 public static void main(String[] args) {
  final JFrame jf;
  JTable jt;
  JScrollPane jsp;
  final MyModel model;
  model = new MyModel();
  JTable table = new JTable(model);
  jsp = new JScrollPane(table);
  jf = new JFrame("My First Table");
  jf.addWindowListener(new WindowAdapter() {
   public void windowClosing(WindowEvent we) {
    model.showOnTerminal();
    System.exit(0);
   }
  });
  jf.setLocation(100,100);
  jf.setSize(400,400);
  jf.getContentPane().add(jsp);
  jf.show();
 }
}

Comentarios.- El programa principal es un JFrame que se usa como contenedor para un JScrollPane. Este JScrollPane es el que realmente alberga la JTable; esta tabla se ha creado pasando el modelo al constructor. El modelo es la próxima clase. Téngase en cuenta que esto es solo un ejercicio, y que el número de filas y columnas es fijo. En una situación real será preciso disponer de los mismos métodos que se ven aquí, pero además habrá otros para añadir y eliminar filas y columnas.

MyModel.java
import javax.swing.table.AbstractTableModel;
import com.coti.tools.*;

 public class MyModel extends AbstractTableModel {
   private String[][] db;
   private String[] columnNames = {
                    "One", "Two", "Three",
                    "Four", "Five", "Six",
                    "Seven", "Eight", "Nine",
                    "Ten"
                   };
   private AuxText out;
   MyModel() {
    db = new String[10][10];
    for(int i=0;i<10;i++)
     for(int j=0;j<10;j++)
      db[i][j] = new String(i*j+"");
    out = new AuxText(null);
   }
   
   public Object getValueAt(int row, int col) { return db[row][col]; }
   public void setValueAt(Object o, int row, int col) {
    db[row][col] = (String)o;
    fireTableCellUpdated(row, col);
   };
   public boolean isCellEditable(int row, int col) {return false; };
   public Class getColumnClass( int column ) { return getValueAt(0, column).getClass(); }
   public int getColumnCount() { return columnNames.length; }
   public String getColumnName( int column ) { return columnNames[column]; }
   public int getRowCount(){ return db.length; }
   public void showOnTerminal() {
    int[] lengths = {4,4,4,4,4,4,4,4,4,4};
    out.writeColumnarTable(db, lengths);    
   }

 };


Comentarios.- El modelo construido, al estar basado en una tabla de dimensiones fijas, resulta necesariamente muy limitado. Los métodos que es preciso implementar son estos: En un modelo más sofisticado se crearán métodos para añadir y eliminar filas y columnas.

La clase AuxText se ha incluido en la sección dedicada a Archivos. Nos hemos limitado a crear un ejemplar para utilizar uno de sus métodos; es interesante estudiar detalladamente esta clase porque resultará de utilidad en múltiples ocasiones.

Ejercicio.- Construir un programa que muestre en pantalla el contenido de un archivo delimitado, empleando un JTable para su visualización. No se conoce a priori el número de filas y columnas de la tabla; el delimitador es un asterisco ("*") entre campos.



La solución puede construirse empleando, de nuevo, llamadas a AuxText. El modelo no ha cambiado. El archivo manifest es el esperable y contiene una sola línea de código. El archivo makefile es parecido al que se emplearía en el ejercicio anterior; para pasar de uno a otro basta intercambiar MyFirstTable y MySecondTable. La clase AuxText, ya descrita, tampoco cambia. Como se observará, en este segundo programa hemos obviado la clase MyModel, que ya no existe. Si se dispone de un modelo de manejo sencillo, se pueden crear los métodos necesarios en la forma indicada en este último ejemplo. Es preciso tener un cierto cuidado con los nombres de las columnas (método getColumnName()).

La lista de archivos necesarios para compilar y ejecutar este ejercicio es la que puede verse a continuación. Hemos construido un makefile que genera directamente myprogram.jar cuando el usuario escribe make; de este modo se dispone de un sistema de compilación rápido y sencillo. Recomendamos encarecidamente generar makefiles para todos los proyectos creados, con objeto de agilizar en lo posible la creación de los programas. Si se desea, puede prescindirse de la opción v al crear el jar.


delimited.txt
One*Two*Three
Four*Five*Six
Seven*Eight*Nine
One*Two*Three
Four*Five*Six
Seven*Eight*Nine
One*Two*Three
Four*Five*Six
Seven*Eight*Nine
One*Two*Three
Four*Five*Six
Seven*Eight*Nine
One*Two*Three
Four*Five*Six
Seven*Eight*Nine
One*Two*Three
Four*Five*Six
Seven*Eight*Nine
One*Two*Three
Four*Five*Six
Seven*Eight*Nine
One*Two*Three
Four*Five*Six
Seven*Eight*Nine
One*Two*Three
Four*Five*Six
Seven*Eight*Nine
One*Two*Three
Four*Five*Six
Seven*Eight*Nine
One*Two*Three
Four*Five*Six
Seven*Eight*Nine
One*Two*Three
Four*Five*Six
Seven*Eight*Nine

makefile
myprogram.jar: MySecondTable.class
	jar cmfv manifest myprogram.jar *.class /com/coti/tools/*.class

MySecondTable.class: MySecondTable.java
	javac MySecondTable.java

clean:
	-rm *.class
	-rm myprogram.jar

manifest
Main-Class: MySecondTable

MySecondTable.java
import javax.swing.*;
import javax.swing.table.TableModel;
import javax.swing.table.AbstractTableModel;
import com.coti.tools.*;
import java.io.File;

public class MySecondTable {
 MySecondTable() {
 }
 public static void main(String[] args) {
  JFrame jf;
  JTable jt;
  JScrollPane jsp;
  jf = new JFrame("My Second Table");
  TableModel model = new AbstractTableModel() {
    AuxText indata = new AuxText(null);
    String[][] table = indata.readDelimitedTable("*", new File("delimited.txt"));
    public int getColumnCount() { return this.table[0].length; }
    public int getRowCount() { return table.length;}
    public Object getValueAt(int row, int col) { return table[row][col]; }
  };
  JTable table = new JTable(model);
  jsp = new JScrollPane(table);
  jf.setLocation(100,100);
  jf.setSize(400,400);
  jf.getContentPane().add(jsp);
  jf.show();
 }
}

Comentarios.- En este programa se ha creado un derivado de AbractTableModel que sirve como base para aportar información en el componente JTable. Examínese cuidadosamente la declaración de model. ¿Por qué no aparecen los nombres de las columnas en la imagen, a diferencia del programa anterior? ¿Qué hay que hacer para que aparezcan los nombres de columna que se deseen? Pista: se puede emplear la primera fila del archivo de texto para indicar los nombres de las columnas. Entonces habrá que sumar 1 al índice de fila cuando se pretenda acceder al valor contenido en cualquier celda de la tabla en las demás funciones del modelo.

DefaultTableModel
La clase DefaultTableModel sirve como base para la creación de tablas dotadas de un respaldo "perfecto". Es decir, DefaultTableModel aporta una estructura de datos (un Vector de Vectores) que permite disponer de tantas filas y columnas como se precise, dentro de los límites de memoria disponible. Una vez creado el DefaultTableModel inicial, es posible crear añadir y eliminar filas y columnas, de nuevo sin más limitación que la memoria disponible. Por tanto, una tabla que haga uso de DefaultTableModel será tan flexible como se necesite, y suelen utilizarse de forma habitual.

La combinación de DefaultTableModel con AuxText da lugar a aplicaciones adecuadas para manejar datos organizados en filas y columnas (bases de datos) de manera muy potente y flexible. Considérese, por ejemplo, la posibilidad de emplear mysql como elemento de proceso, y JTable como elemento de visualización y manipulación sencilla de datos.

Ejercicio.- Construir un programa capaz de leer de disco una tabla de formato delimitado por asteriscos y dimensiones desconocidas. El programa deberá mostrar el contenido del archivo en un JTable basado en un DefaultTableModel. La tabla mostrada contendrá un cierto número de filas adicionales vacías; al salir de programa, se mostrará en la terminal el contenido final de la tabla. ¿Sería posible crear botones para añadir filas o columnas? ¿Sería posible crear botones para eliminar filas o columnas? ¿Se puede exportar la tabla a texto de tal modo que resulte legible para mysql? ¿Se puede crear una tabla en mysql para después exportarla a texto y leerla con este programa?

Una posible solución del ejercicio es la formada por las dos clases que se muestran a continuación. Hemos empleado AuxText para leer información de disco; esa información se trata (extrayendo los nombres de columnas y los datos en si) para generar un DefaultTableModel. Cuando el usuario hace clic en el botón de cierre, se vuelca el contenido final del modelo (incluyendo los nombres de columnas) en la terminal.

Las clases utilizadas son:

Spy.java
import javax.swing.table.DefaultTableModel;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
/*
 This class is a trick to dump the contents
 of the model onto the terminal when the user
 clicks on the close button
*/
public class Spy extends WindowAdapter {
 private DefaultTableModel dtm;
 Spy(DefaultTableModel dt) {
  dtm = dt;
 };
 public void windowClosing(WindowEvent we) {
  int finalrows = dtm.getRowCount();
  int finalcolumns = dtm.getColumnCount();
  String[][] finaldata = new String[finalrows][finalcolumns];
  Object value = null;
  for(int i=0;i<finalrows;i++)
   for(int j=0;j<finalcolumns;j++)
    {
     value = dtm.getValueAt(i, j);
     finaldata[i][j] = null==value?"empty":value.toString();
    }
  for(int i=0;i<finalcolumns;i++)
   System.out.print(dtm.getColumnName(i) + " ");
  System.out.println();
  for(int i=0;i<finalrows;i++)
   {
    for(int j=0;j<finalcolumns;j++)
     System.out.print(finaldata[i][j]+ " ");
    System.out.println();
   }
  System.exit(0);
 };
}

TestDTM.java
import com.coti.tools.AuxText;
import java.io.File;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.DefaultTableModel;

public class TestDTM {
 public static void main(String[] args) {
  JFrame jf = new JFrame("Test Data Model");
  AuxText aux = new AuxText(jf);
  String all_data[][] = null, column_names[] = null, table_data[][] = null;
  int number_of_rows, number_of_columns;
  /*
   We read in all the data in delimited.txt
   
   First row is taken to be the column names
  */
  File f = new File("delimited.txt");
  all_data = aux.readDelimitedTable("*",f);
  /*
   We create a list of String with space enough for the
   column names
  */
  number_of_rows = all_data.length - 1;
  number_of_columns = all_data[0].length;
  column_names = new String[number_of_columns];
  /*
   Then we populate it it all_data's first row
  */
  for(int i=0;i<number_of_columns;i++)
   column_names[i] = all_data[0][i];
  /*
   Now we populate the initial data model
  */
  table_data = new String[number_of_rows][number_of_columns];
  for(int i=1;i<number_of_rows;i++)
   for(int j=0;j<number_of_columns;j++)
    table_data[i-1][j] = all_data[i][j];
  /*
   Finally we create a DefaultTableModdel and
   assign it to the JTable
  */
  DefaultTableModel dtm = new DefaultTableModel(table_data, column_names);
  String[] emptyrow = new String[number_of_columns];
  for(int i=0;i<number_of_columns;i++)
   emptyrow[i] = null;
  JTable jt = new JTable(dtm);
  JScrollPane js = new JScrollPane(jt);
  /*
   This is the trick to dump the model's contents
  */
  jf.addWindowListener(new Spy(dtm));
  jf.getContentPane().add(js);
  jf.pack();
  jf.show();
  dtm.addRow(emptyrow);
  dtm.addRow(emptyrow);
  dtm.addRow(emptyrow);
 }
}

Comentarios.- No se incluye AuxText.java, ya conocido. Obsérvese el uso de métodos de DefaultTableModel en la clase Spy.java. En la clase TestDTM.java, la tarea consistente en poblar el modelo partiendo de los datos leídos de la tabla tiene dos fases: en primer lugar, se utilizan como nombres de columnas los datos de la primera fila del archivo. En segundo lugar, se utiliza como contenido del modelo las filas restantes. Hemos añadido tres filas vacías al final del modelo, empleando el método addRow().

Los métodos empleados en este programa son de uso frecuente, y se recomienda su estudio con vistas a la realización de tareas más complicadas.