## Componentes y Modelos de Datos

El programa AuctionClient es una sencilla aplicacion GUI que permite a los administradores de la casa de subastas listar y navegar por los ítems de la subasta, e imprime informes sobre estos ítems. Esta sección describe el código Swing de la aplicación que utiliza componentes de peso ligero y otras características Swing:

- Componentes de Peso Ligero
- Ordenar Componentes
- Modelos de Datos
- Dibujo de Celdas Personalizadas
- Edición en Celdas Personalizadas
- Manejo de Eventos Personalizados
- Direcciones Swing

### Componentes de Peso Ligero

Todos los componentes Swing, excepto JApplet, JDialog, JFrame y JWindow son componentes de peso ligero. Los componentes de peso ligero, al contrario que sus contraparte del AWT, no dependen del toolkit local del sistema.

Por ejemplo, un componente pesado java.awt.Button ejecutándose sobre la plataforma Java para Unix mapea el botón Motif real. En esta relación es botón Motif es llamado "par" del java.awt.Button. Si hemos creado dos java.awt.Button en una aplicación, también se crearán dos "pares" y dos botones Motif. La plataforma Java comunicac con los botones Motif usando el JNI. Para cada componente añadido a la aplicación, hay una pila adicional unida al sistema de ventanas local, que es por lo que estos componentes se llaman de peso pesado.

Los componentes de peso ligero no tiene "pares" y emulan a los componentes del sistema local de ventanas. Un botón de peso ligero está representado por un rectángulo con una etiqueta dentro que acepta eventos del ratón. Añadir más botones significa dibujar más rectángulos.

Un componente de peso ligero necesita dibujarse obre algo, y una aplicación escrita en Java necesita interactuar con el controlador de ventanas local para que la ventana principal de la aplicación pueda ser cerrada o minimizada. Esto es porque los componentes padres de nivel superior mencionados arriba (JFrame, JApplet, y otros) están implementado como componentes de peso pesado -- necesitan ser mapeados sobre un componente en el sistema local de ventanas.

Un JButton es una forma muy sencilla de dibujar. Para componentes más complejos, como JList o JTable, los elementos o celdas de la lista o la tabla son dibujadas por un objeto CellRenderer. Un objeto CellRenderer proporciona flexibilidad porque hace posible que cualquier tipo de objeto pueda ser mostrado en cualquier fila o columna.

Por ejemplo, un JTable puede usar un CellRenderer diferente para cada columna. Este segmento de código selscciona la segunda columna, que está referenciada como índice 1, para usar un objeto CustomRenderer para crear las celdas de esa columna.

>       JTable scrollTable=new JTable(rm);
>       TableColumnModel scrollColumnModel =
>     		scrollTable.getColumnModel();
>       CustomRenderer custom = new CustomRenderer();
>       scrollColumnModel.getColumn(1).setCellRenderer(custom);

### Ordenar Componente

Cada aplicación o applet Swing necesita al menos un componente contenedor de peso pesado (un JFrame, JWindow, JApplet, o JDialog). Cada uno de estos componentes con la contraparte de JFrame: JInternalFrame, contiene un componente llamado RootPane. El JRootPane controla la adición de capas adicionales usadas en dicho contenedor como JLayeredPane, JContentPane, GlassPane y la opcionalJMenuBar. También les permite a todos los componentes emulados (de peso ligero) interactuar con la cola de eventos AWT para enviar y recibir eventos. Al interactuar con la cola de eventos, todos los componentes emulados obteinen una interacción indirecta con el controlador de ventanas local.

#### JLayeredPane

El JLayeredPane se sitúa sobre el JRootPane, y como su nombre indica, controla las capas del componente contenidas dentro de los límites del contenedor de peso pesado. Los componentes no son añadidos al JLayeredPane, sino al JContentPane. El JLayeredPane determina el orden Z de los componentes del JRootPane. Se puede pensar en el orden Z como el orden de solapamiento de varios componentes. Si arrastramos y soltamos un componente o solicitamos un diálogo desplegable, queremos que el componente aparezca encima de todas las otras ventana de la aplicación. El JLayeredPane nos permite poner los componentes en capas.

El JLayeredPane divide la profundidad del contenedor en diferentes bandas que pueden usarsr para asignarle a un componente un tipo de nivel apropiado. La banda DRAG_LAYER , valor 400, aparece sobre todas las demás capas. El nivel más ingerior de JLayeredpane, la banda DEFAULT_FRAME_LAYER, tiene valor -3000 y y es el nivel de los contenedores de peso pesado, incluyendo el MenuBar. Las bandas son las siguientes:

Valor | Nombre de Banda | Tipos de Componentes
--- | --- | ---
-3000 | DEFAULT_FRAME_LAYER | JMenubar
0 | DEFAULT_LAYER | JButton, JTable, ..
| PALETTE_LAYER | Componentes flotantes como un JToolBar
| MODAL_LAYER | Diálogos Modales
400 | DRAG_LAYER | FONT FACE="Verdana, Arial, Helvetica, sans-serif">Arrastrar y Soltar sobre todas las capas

Dentro de estas bandas de profundidad generales, los componentes peuden estár organizados con un sistema de ordenación para ordenar los componentes dentro de una banda particular, pero este sistema invierte la prioridad de los números. Por ejemplo, en una banda especificada como DEFAULT_LAYER, los componentes con un valor, aparecen delante de los otros componentes de la banda; mientras, componentes con un número mayor o -1 aparecen por detrás de él. El número más alto en es esque de numeración es .1, por eso una forma de visualizarlo es un vector de componentes que pasa a través de dibujar primero los componentes con un número mayor terminando con el componente en la posición 0.

Por ejemplo, el siguiente código añade un JButton a la capa por defecto y especifica que aparezca encima de los otros componentes de esa misma capa:

>       JButton enterButton = new JButton("Enter");
>       layeredPane.add(enterButton,
>     		  JLayeredPane.Default_Layer, 0);

Podemos conseguir el mismo efecto llamando al método LayeredPane.moveToFont dentro de una capa o usando el método LayeredPane.setLayer método para moverlo a una capa diferente.

#### JContentPane

El JContentPane controla la adición de componentes a los contenedores de peso pesado. Por eso, tenemos que llamar al método getContentPane para añadir un componente al ContentPane del RootPane. Por defecto, un ContentPane se inicializa con un controlador de distribución BorderLayout. Hay dos formas de cambiar el controlador de distribución. Podemos llamar al método setLayout de esta forma:

>       getContentPane()).setLayout(new BoxLayout())

O podemos reemplazar el ContentPane por defecto con nuestro propio ContentPane, como un JPanel, como este:

>       JPanel pane= new JPanel();
>       pane.setLayout(new BoxLayout());
>       setContentPane(pane);

#### GlassPane

El GlassPane normalmente es completamente transparente y solo actúa como una hoja de cristal delante de los componentes. Podemos implementar nuestro propio GlassPane usando un componente como JPanel e instalándolo como el GlassPane llamando al método setGlassPane. El RootPane se configura con un GlassPane que puede ser recuperado llamando a getGlassPane.

Una forma de usar un GlassPane es para implementar un componente que de forma invisble maneje todos los eventos de teclado y de ratón, bloqueando efectivamente la entrada del usuario hasta que se complete un evento. El GlassPane puede bloquear los eventos, pero realmente el cursor no volverá a su estado por defecto si tenermos seleccionar el cursor para que sea un cursor ocupado en el GlassPane. Se requiere un evento de ratón adicional para el refresco:

>       MyGlassPane  glassPane = new MyGlassPane();
>       setGlassPane(glassPane);
>       setGlassPane.setVisible(true); //before worker thread
>       ..
>       setGlassPane.setVisible(false); //after worker thread

>       private class MyGlassPane extends JPanel {

>         public MyGlassPane() {
>           addKeyListener(new KeyAdapter() { });
>           addMouseListener(new MouseAdapter() { });
>           super.setCursor(
>     	Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
>         }
>       }

### Modelos de Datos

Se han combinado numerosos modelos de capas para formar las tablas del GUI AuctionClient. A un nivel fundacional, el interface TableModel y sus dos implementaciones AbstractTableModel y DefaultTableModel propocionan las opciones más básicas para almacenar, recupear y modificar los datos básicos.

El TableModel es responsable de definir y categorizar los datos por sus clases. También determina si el dato puede ser editado y cómo se agrupan los datos en columnas y filas. Sin embargo, es importante observar que mientras el interface TableModel se usa más frecuentemente en la construcción de un JTable, no está unido fundamentalmente a su apariencia en pantalla. Las implementaciones podría fácilmente formar la parte básica de la hoja de cálculo, o incluiso una clase no-GUI que pida la organización de los datos de una forma tabular.

La clase ResultsModel es el corazón de las tablas AuctionClient. Define una hoja de datos dimámica, dicta qué usuarios de la clase pueden editar los datos a través del método ResultsModel.isCellEditable, y proporciona el método update para mantener los datos actualizados. El modelo es la base de la tablas fijas y escrollables, y deja que las modificaciones se reflejen en cada vista.

A un alto nivel, y representado unca capa intermedia entre los datos y su representación gráfica esta el TableColumnModel. En este nivel los datos son agrupados por columnas en anticipación de su aparición gráfica en la tabla. La visibilidad y tamaño de las columnas, sus cabeceras, y los tipos de componentes de sus renderizadores de celdas y editores son todos manejados por la clase TableColumnModel.

Por ejemplo, congelar la columna más ala izquierda del GUI AuctionClient es posible porque los datos de la columna sin fácilmente intercambiables entre múltiples objetos TableColumnModel y JTable. Esto traduce los objetos fixedTable y scrollTable del programa AuctionClient.

Más alto tadavía se unen los distintos rederizadores, editores y componentes de cabecera cuya combinación define el aspecto y organización del componente JTable. Este nivel es onde se tomas las decisiones fundamentales sobre la distribución del JTable.

La creacción de las clases internas CustomRenderer y CustomButtonRenderer dentro de la aplicación AuctionClient permite a los usuarios de esas clases redefinir los componentes sobre los que se basa la apariencia de las celdas de la tabla. De igual forma, la clase CustomButtonEditor toma el lugar del editor por defecto de la tabla. De una forma verdaderamente orientada a ojetos, los editores por defecto y renderizadores son fácilmente reemplazados si afectar a los datos que ellos representan ni la función del componente en el que residen.

Finalmente, los distintos interfaces de los componente de usuario son responsavles de la apariencia última de la JTable. Esta es la representación específica del aspecto y comportamiento de las tablas AuctionClient y sus datos de una forma final. El resultado final es que añadir una parte final Swing a unos servicios existentes requiere muy código adicional. De hecho, la codificación del modelo es una de las tareas más sencillas al construir una aplicación Swing.

#### Modelo de la Tabla

La clase JTable tiene asociada una clase DefaultTableModel que internamente usa un vector para almacenar datos. Los datos de cada fila son almacenados en un objeto Vector singl mientras que otro objeto Vector almacena cada una de esas columnas y los elementos que las constituyen. El objeto DefaultTableModel puede ser inicializado con datos de varias formas diferentes. Este código muesta el DefaultTableModel creado con un array de dos dimensiones y un segundo array que representa las cabeceras de columnas. El DefaultTableModel convierte el array de Object en los objetos Vector apropidos:

>       Object[][] data = new Object[][]{ {"row 1 col1",
>     			            "Row 1 col2" },
>                             	    {"row 2 col 1",
>     				    "row 2 col 2"}
>     				  };
>       Object[] headers = new Object[] {"first header",
>     			           "second header"};
>       DefaultTableModel model = new DefaultTableModel(data,
>     				  headers);

>       table = new JTable(model);
>       table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);

Crear un modelo de tabla personalizado es tan cercano y sencillo como usar DefaultTableModel, y requiere muy poca codificación adicional. Podemos implementar un modelo de tabla implementando un método que devuelva el número de entradas del modelo, y un método que recupere un elemento en un posición específica de ese modelo. Por ejemplo, el modelo JTable puede ser implementado desde javax.swing.table.AbstractTableModel mediante la implementación de los métodos getColumnCount, getRowCount y getValueAt como se ve aquí:

>       final Object[][] data = new Object[][]{ {
>     			"row 1 col1",
>     			"row 1 col2" },
>                             {"row 2 col 1",
>     			"row 2 col 2"} };
>       final Object[] headers = new Object[] {
>     			"first header",
>     			"second header"};

>       TableModel model = new AbstractTableModel(){
>         public int getColumnCount() {
>           return data[0].length;
>         }
>         public int getRowCount() {
>           return data.length;
>         }
>         public String getColumnName(int col) {
>           return (String)headers[col];
>         }

>         public Object getValueAt(int row,int col) {
>           return data[row][col];
>         }
>       };
>       table = new JTable(model);
>       table.setAutoResizeMode(
>     	JTable.AUTO_RESIZE_OFF);

Esta tabla es de sólo lectura y los valores de sus datos ya son conocidos. De hecho, incluso los datos son declarados final para que peudan ser recuperados por la clase interna TableModel. Esta no es la situación normal cuando trabajamos con datos vivos.

Podemos crear una tabla editable añadiendo el método de verificación isCellEditable, que es usado por el editor de celda por defecto, y el método AbstractTableModel para configurar un valor en una posición. Hasta este cambio, el AbstractTableModel ha estado manejando el redibujado y el redimensionado de la tabla disparando distintos eventos de cambio de tabla. como el AbtractTableModel no conoce nada de lo ocurrido a los datos de la tabla, necesitamos informarle llamando al método fireTableCellUpdated. Las siguientes líneas han añadido la clase interna AbstractTableModel para permitir la edición de los datos:

>       public void setValueAt (Object value,
>     			int row, int col) {
>         data[row][col] = value;
>         fireTableCellUpdated (row, col);
>       }

>       public boolean isCellEditable(int row,
>     			int col) {
>         return true;
>       }

#### Más Modelos de Tablas

Un requerimiento común para mostrar datos tabulares es la inclusión de un columna no desplazable. Este columna proporcina una conjunto de datos anclados que permanecen estacionarios y visibles mientras que sus columnas vecinas son desplazadas horizontalmente (y frecuentemente fuera de la vista). Esto es importante en casos donde la fila de datos puede ser identificada por un único valor en la columna fijada, como un nombre o número identificador. el siguiente código de ejemplo usa una columna de tabla fijada para mostrar una lista de ítems de la subasta.

El modelo de tabla base de este ejemplo implementa la clase AbstractTableModel. Su método update rellena dinámicamente los datos de la tabla desde una llamada a la base de datos. Envían un evento de la tabla ha sido actualizada llamando al método fireTableStructureChanged para indicar el número de filas o columnas de la tabla que se han modificado.

>     package auction;

>     import javax.swing.table.AbstractTableModel;
>     import javax.swing.event.TableModelEvent;
>     import java.text.NumberFormat;
>     import java.util.*;
>     import java.awt.*;

>     public class ResultsModel extends AbstractTableModel{
>       String[]  columnNames={};
>       Vector rows = new Vector();

>       public String getColumnName(int column) {
>         if (columnNames[column] != null) {
>           return columnNames[column];
>         } else {
>           return "";
>         }
>       }

>       public boolean isCellEditable(int row, int column){
>         return false;
>       }

>       public int getColumnCount() {
>         return columnNames.length;
>       }

>       public int getRowCount() {
>         return rows.size();
>       }

>       public Object getValueAt(int row, int column){
>         Vector tmprow = (Vector)rows.elementAt(row);
>         return tmprow.elementAt(column);
>       }

>       public  void update(Enumeration enum) {
>         try {
>           columnNames = new String[5];
>           columnNames[0]=new String("Auction Id #");
>           columnNames[1]=new String("Description");
>           columnNames[2]=new String("High Bid");
>           columnNames[3]=new String("# of bids");
>           columnNames[4]=new String("End Date");
>           while((enum !=null) &&
>     		(enum.hasMoreElements())) {
>             while(enum.hasMoreElements()) {
>               AuctionItem auctionItem=(
>     		AuctionItem)enum.nextElement();
>               Vector items=new Vector();
>               items.addElement(new Integer(
>     		auctionItem.getId()));
>               items.addElement(
>     		auctionItem.getSummary());
>               int bidcount= auctionItem.getBidCount();
>               if(bidcount >0) {
>                 items.addElement(
>     		NumberFormat.getCurrencyInstance().
>     		format(auctionItem.getHighBid()));
>               } else {
>                 items.addElement("-");
>               }
>               items.addElement(new Integer(bidcount));
>               items.addElement(auctionItem.getEndDate());
>               rows.addElement(items);
>             }
>           }

>           fireTableStructureChanged();
>         } catch (Exception e) {
>           System.out.println("Exception e"+e);
>         }
>       }
>     }

La tabla es creada desde el modelo ResultsModel, Luego se elimina la primera columna de la tabla y se añade a una nueva tabla. Como ahora tenemos dos tablas, la única forma de que las selecciones estén sincronizadas es usar un objeto ListSelectionModel para configurar la selección sobre la fila de la tabla en la sotras tablas que no fueron seleccionadas llamando al método setRowSelectionInterval.

El ejemplo completo lo podemos encontrar en el ficheo fuente AuctionClient.java:

>       private void listAllItems() throws IOException{
>         ResultsModel rm=new ResultsModel();
>         if (!standaloneMode) {
>           try {
>              BidderHome bhome=(BidderHome)
>     		ctx.lookup("bidder");
>              Bidder bid=bhome.create();
>              Enumeration enum=
>     		(Enumeration)bid.getItemList();
>              if (enum != null) {
>                rm.update(enum);
>              }
>            } catch (Exception e) {
>              System.out.println(
>     		"AuctionServlet <list>:"+e);
>            }
>         } else {
>           TestData td= new TestData();
>           rm.update(td.results());
>         }
>         scrollTable=new JTable(rm);
>         adjustColumnWidth(scrollTable.getColumn(
>     	"End Date"), 150);
>         adjustColumnWidth(scrollTable.getColumn(
>     	"Description"), 120);
>         scrollColumnModel = scrollTable.getColumnModel();
>         fixedColumnModel = new DefaultTableColumnModel();

>         TableColumn col = scrollColumnModel.getColumn(0);
>         scrollColumnModel.removeColumn(col);
>         fixedColumnModel.addColumn(col);

>         fixedTable = new JTable(rm,fixedColumnModel);
>         fixedTable.setRowHeight(scrollTable.getRowHeight());
>         headers = new JViewport();

>         ListSelectionModel fixedSelection =
>     		fixedTable.getSelectionModel();
>         fixedSelection.addListSelectionListener(
>     		new ListSelectionListener() {
>           public void valueChanged(ListSelectionEvent e) {
>             ListSelectionModel lsm = (
>        	   ListSelectionModel)e.getSource();
>             if (!lsm.isSelectionEmpty())  {
>                setScrollableRow();
>             }
>           }
>          });

>          ListSelectionModel scrollSelection =
>     		scrollTable.getSelectionModel();
>          scrollSelection.addListSelectionListener(
>     		new ListSelectionListener() {
>            public void valueChanged(ListSelectionEvent e) {
>              ListSelectionModel lsm =
>     		(ListSelectionModel)e.getSource();
>              if (!lsm.isSelectionEmpty())  {
>                setFixedRow();
>              }
>            }
>          });

>          CustomRenderer custom = new CustomRenderer();
>          custom.setHorizontalAlignment(JLabel.CENTER);
>          scrollColumnModel.getColumn(2).setCellRenderer(
>     		custom);
>          scrollColumnModel.getColumn(3).setCellRenderer(
>     		new CustomButtonRenderer());

>          CustomButtonEditor customEdit=new
>     			CustomButtonEditor(frame);
>          scrollColumnModel.getColumn(3).setCellEditor(
>     		customEdit);

>          headers.add(scrollTable.getTableHeader());

>          JPanel topPanel = new JPanel();
>          topPanel.setLayout(new BoxLayout(topPanel,
>     		BoxLayout.X_AXIS));
>          adjustColumnWidth(
>     		fixedColumnModel.getColumn(0), 100);

>          JTableHeader fixedHeader=
>                         fixedTable.getTableHeader();
>          fixedHeader.setAlignmentY(Component.TOP_ALIGNMENT);
>          topPanel.add(fixedHeader);
>          topPanel.add(Box.createRigidArea(
>                             new Dimension(2, 0)));
>          topPanel.setPreferredSize(new Dimension(400, 40));

>          JPanel headerPanel = new JPanel();
>          headerPanel.setAlignmentY(Component.TOP_ALIGNMENT);
>          headerPanel.setLayout(new BorderLayout());

>          JScrollPane scrollpane = new JScrollPane();
>          scrollBar = scrollpane.getHorizontalScrollBar();

>          headerPanel.add(headers, "North");
>          headerPanel.add(scrollBar, "South");
>          topPanel.add(headerPanel);

>          scrollTable.setPreferredScrollableViewportSize(
>     		new Dimension(300,180));
>          fixedTable.setPreferredScrollableViewportSize(
>     		new Dimension(100,180));
>          fixedTable.setPreferredSize(
>                     new Dimension(100,180));

>          innerPort = new JViewport();
>          innerPort.setView(scrollTable);
>          scrollpane.setViewport(innerPort);

>          scrollBar.getModel().addChangeListener(
>     		new ChangeListener()  {
>            public void stateChanged(ChangeEvent e) {
>              Point q = headers.getViewPosition();
>              Point p = innerPort.getViewPosition();
>              int val = scrollBar.getModel().getValue();
>              p.x = val;
>              q.x = val;
>              headers.setViewPosition(p);
>              headers.repaint(headers.getViewRect());
>              innerPort.setViewPosition(p);
>              innerPort.repaint(innerPort.getViewRect());
>            }
>          });

>          scrollTable.getTableHeader(
>                        ).setUpdateTableInRealTime(
>     		   false);

>          JPanel bottomPanel = new JPanel();
>          bottomPanel.setLayout(new BoxLayout(
>     		bottomPanel, BoxLayout.X_AXIS));
>          fixedTable.setAlignmentY(Component.TOP_ALIGNMENT);
>          bottomPanel.add(fixedTable);
>          bottomPanel.add(Box.createRigidArea(
>                                new Dimension(2, 0)));
>          innerPort.setAlignmentY(Component.TOP_ALIGNMENT);
>          bottomPanel.add(innerPort);
>          bottomPanel.add(Box.createRigidArea(
>                                new Dimension(2, 0)));

>          scrollPane= new JScrollPane(bottomPanel,
>                    JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
>                    JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
>          JViewport outerPort = new JViewport();
>          outerPort.add(bottomPanel);
>          scrollPane.setColumnHeaderView(topPanel);
>          scrollPane.setViewport(outerPort);

>          scrollTable.setAutoResizeMode(
>                        JTable.AUTO_RESIZE_OFF);
>          frame.getContentPane().add(scrollPane);

>          scrollTable.validate();
>          frame.setSize(450,200);
>       }

>       void setFixedRow() {
>         int index=scrollTable.getSelectedRow();
>         fixedTable.setRowSelectionInterval(index, index);
>       }

>       void setScrollableRow() {
>         int index=fixedTable.getSelectedRow();
>         scrollTable.setRowSelectionInterval(index, index);
>       }

>       void adjustColumnWidth(TableColumn c, int size) {
>         c.setPreferredWidth(size);
>         c.setMaxWidth(size);
>         c.setMinWidth(size);
>       }

#### Modelo JList

El componente JList muestra una lista verticla de datos y usa un ListModel para contener y manipular los datos. También usa un objeto ListSelectionModel para permitir la selección y subsecuente recuperación de elementos de la lista.

Las implementaciones por defecto de las clases AbstractListModel y AbstractListSelectionModel las proporciona el API Swing desde las clases DefaultListModel y DefaultListSelectionModel. Si usamos estos dos modelos por defecto y el renderizador de celdas por defecto, obtendremos una lista que muestra elementos modelo llamado al método toString sobre cada objeto. La lista usa el modelo MULTIPLE_INTERVAL_SELECTION de selección de lista para seleccionar cada elemento de la lista.

Hay disponibles tres modos de selección para DefaultListSelectionModel: SINGLE_SELECTION, donde sólo se puede seleccionar un ítem a la vez; SINGLE_INTERVAL_SELECTION en el que se puede seleccionar un rango de items secuenciales; y MULTIPLE_INTERVAL_SELECTION, en el que se permite que cualquir o todos los elementos sean seleccionados. El modo de selección puede cambiarse llamando al método setSelectionMode de clase JList.

>         public SimpleList() {
>            JList list;
>            DefaultListModel deflist;
>            deflist= new DefaultListModel();
>            deflist.addElement("element 1");
>            deflist.addElement("element 2");
>            list = new JList(deflist);

>            JScrollPane scroll = new JScrollPane(list);
>            getContentPane().add(scroll, BorderLayout.CENTER);
>          }

#### Modelo JTree

La clase JTree modela y muestra una lista vertical de elementos o nodos ordenados en una forma de árbol de herencia.

Un objeto JTree teine un nodo raíz y uno o más nodos hijos, que pueden contener más nodos hijos. Cada nodo padre puede expandirse para mostrar sus hijos de forma similar a los familiares árboles de directorios de los usuarios de Windows.

Como los componentes JList y JTable, el JTree consta de más de un modelo. El modo de selección es similar al detallado para el modelo JList. El modo de selección tiene estás ligeras diferencias en los nombres: SINGLE_TREE_SELECTION, DISCONTIGUOUS_TREE_SELECTION, y CONTIGUOUS_TREE_SELECTION.

Mientras que DefaultTreeModel mantiene los datos en un árbol y es responsable de añadir y eliminar nodos, es la clase DefaultTreeMutableTreeNode la que define los métodos usados para moverse por los nodos. El DefaultTreeModel se usa frecuentemente para implementar modelos personaloizados porque no hay un AbstractTreeModel en el paquete JTree. Sin embargo, si usamos objetos personalizados, debemos implementar TreeModel. Este código de ejemplo crea un JTree usando el DefaultTreeModel.

>     import java.awt.*;
>     import java.awt.event.*;
>     import javax.swing.*;
>     import javax.swing.tree.*;

>     public class SimpleTree extends JFrame {
>       public SimpleTree() {
>         String[] treelabels =   {
>     			     "All Auctions",
>     			     "Closed Auction",
>     			     "Open Auctions"};
>         Integer[] closedItems = { new Integer(500144),
>     			      new Integer(500146),
>     			      new Integer(500147)};

>         Integer[] openItems = { new Integer(500148),
>     			    new Integer(500149)};

>         DefaultMutableTreeNode[] nodes = new
>             DefaultMutableTreeNode[treelabels.length];
>         DefaultMutableTreeNode[] closednodes = new
>     	DefaultMutableTreeNode[closedItems.length];
>         DefaultMutableTreeNode[] opennodes = new
>     	DefaultMutableTreeNode[openItems.length];

>         for (int i=0; i < treelabels.length; i++) {
>           nodes[i] = new
>             DefaultMutableTreeNode(treelabels[i]);
>         }
>         nodes[0].add(nodes[1]);
>         nodes[0].add(nodes[2]);

>         for (int i=0; i < closedItems.length; i++) {
>            closednodes[i] = new
>     		DefaultMutableTreeNode(closedItems[i]);
>            nodes[1].add(closednodes[i]);
>         }

>         for (int i=0; i < openItems.length; i++) {
>            opennodes[i] = new
>     		DefaultMutableTreeNode(openItems[i]);
>            nodes[2].add(opennodes[i]);
>         }
>         DefaultTreeModel model=new
>                     DefaultTreeModel(nodes[0]);

>         JTree tree = new JTree(model);

>         JScrollPane scroll = new JScrollPane(tree);
>         getContentPane().add(scroll, BorderLayout.CENTER);
>       }

>       public static void main(String[] args) {
>         SimpleTree frame = new SimpleTree();
>         frame.addWindowListener( new WindowAdapter() {
>           public void windowClosing( WindowEvent e ) {
>             System.exit(0);
>           }
>         });
>         frame.setVisible(true);
>         frame.pack();
>         frame.setSize(150,150);
>       }
>     }

El método toString se usa para recuperar el valor de los objetos Integer en ek árbol. Y aunque se usa DefaultTreeModel para mantener los datos en el árbol y para añadir y eliminar nodos, la clase DefaultMutableTreeNode def¡ne los métodos usados para moverse a través de los nodos de un árbol.

con el método depthFirstEnumeration se consigue una búsqueda de nodos dentro de un JTree, que es el mismo que el método postorderEnumeration desde el punto final hasta el primer árbol. O podemos llamar al método preorderEnumeration, el inverso del método postorderEnumeration, que empieza desde la raíz y desciende cada rama por orden. O podemos llamar al método breadthFirstEnumeration, que empieza en la raíx y visita todos los nodos hijos en un nivel nates de visitar los nodos hijos de una profundidad inferior.

El siguiente código de ejemplo expande el nodo padre si conteine un nodo hijo que corresponda con el campo de búsqueda introducido. Usa una llamada a Enumeration e = nodes[0].depthFirstEnumeration(); para devolver la lista de todos los nodos del árbol. Una vez que ha encontrado una correspondencia, construye el TreePath desde el nodo raíz hacia el nodo que concuerda con la cadena búsqueda pasada a makeVisible de la clase JTree que se asegura de que nodo se expandirá en el árbol.

>     import java.awt.*;
>     import java.util.*;
>     import java.awt.event.*;
>     import javax.swing.*;
>     import javax.swing.tree.*;

>     public class SimpleSearchTree extends JFrame {
>       JPanel findPanel;
>       JTextField findField;
>       JTree tree;
>       JButton findButton;
>       DefaultMutableTreeNode[] nodes;

>       public SimpleSearchTree() {
>         String[] treelabels =  { "All Auctions",
>     			     "Closed Auction",
>     			     "Open Auctions" };
>         Integer[] closedItems = { new Integer(500144),
>     			      new Integer(500146),
>     			      new Integer(500147) };

>         Integer[] openItems ={ new Integer(500148),
>     			   new Integer(500149)};

>         nodes = new
>           DefaultMutableTreeNode[treelabels.length];
>         DefaultMutableTreeNode[] closednodes = new
>     	    DefaultMutableTreeNode[closedItems.length];
>         DefaultMutableTreeNode[] opennodes = new
>     	    DefaultMutableTreeNode[openItems.length];
>         for (int i=0; i < treelabels.length; i++) {
>            nodes[i] = new
>              DefaultMutableTreeNode(treelabels[i]);
>         }
>         nodes[0].add(nodes[1]);
>         nodes[0].add(nodes[2]);

>         for (int i=0; i < closedItems.length; i++) {
>            closednodes[i] = new
>     		DefaultMutableTreeNode(closedItems[i]);
>            nodes[1].add(closednodes[i]);
>         }

>         for (int i=0; i < openItems.length; i++) {
>            opennodes[i] = new DefaultMutableTreeNode(
>                                 openItems[i]);
>            nodes[2].add(opennodes[i]);
>         }

>         DefaultTreeModel model=new
>                            DefaultTreeModel(nodes[0]);
>         tree = new JTree(model);

>         JScrollPane scroll = new JScrollPane(tree);
>         getContentPane().add(scroll, BorderLayout.CENTER);
>         findPanel= new JPanel();
>         findField= new JTextField(10);
>         findButton= new JButton("find");
>         findButton.addActionListener (new ActionListener() {
>           public void actionPerformed (ActionEvent e) {
>             String field=findField.getText();
>             if (field != null) {
>               findNode(findField.getText());
>             } else {
>               return;
>             }
>           }
>         });
>         findPanel.add(findField);
>         findPanel.add(findButton);
>         getContentPane().add(findPanel, BorderLayout.SOUTH);
>       }
>       public void findNode(String field) {
>         Enumeration e = nodes[0].depthFirstEnumeration();
>         Object currNode;
>         while (e.hasMoreElements()) {
>           currNode = e.nextElement();
>           if (currNode.toString().equals(field)) {
>             TreePath path=new TreePath(((
>     	     DefaultMutableTreeNode)currNode).getPath());
>             tree.makeVisible(path);
>             tree.setSelectionRow(tree.getRowForPath(path));
>             return;
>           }
>         }
>       }

>       public static void main(String[] args) {
>         SimpleSearchTree frame = new SimpleSearchTree();
>         frame.addWindowListener( new WindowAdapter() {
>           public void windowClosing( WindowEvent e ) {
>             System.exit(0);
>           }
>         });
>         frame.setVisible(true);
>         frame.pack();
>         frame.setSize(300,150);
>       }
>     }

JTree, JTable y JList probablemente son los modelos más comunes que querremos personalizar. Pero podemos usar modelos como SingleSelectionModel para manipulación de datos en general. Esta clase nos permite especificar como se seleccionan los datos en un componente.

### Dibujo de Celdas Personalizado

Como hemos aprendido arriba, muchos componentes tienen un renderizador de celdas por defecto para dibujar cada elemento de la tabla, árbol o lista. El renderizador de celdas por defecto normalmente es un JLabel y muestra una representación String de los datos del elemento.

Un sencillo renderizador de celda personalizado puede extender la clase DefaultXXXCellRenderer para proporcionar personalización adicional en el getXXXCellRenderer. Los componentes DefaultTableCellRenderer y DefaultTreeCellRenderer usan un JLabel para dibujar la celda. Esto significa que cualquier personalización que pueda ser aplicada a un JLabel también puede ser usada en una celda de JTable o de JTree.

Por ejemplo, el siguiente renderizador selecciona el color del fondo del componente si el ítem de la subasta ha recibido un alto número de pujas:

>     class CustomRenderer extends DefaultTableCellRenderer {
>       public Component getTableCellRendererComponent(
>                          JTable table,Object value,
>                          boolean isSelected,
>                          boolean hasFocus,
>                          int row, int column) {

>            Component comp =
>                        super.getTableCellRendererComponent(
>     		     table,value,isSelected,hasFocus,
>     		     row,column);

>            JLabel label = (JLabel)comp;

>            if(((Integer)value).intValue() >= 30) {
>                label.setIcon(new ImageIcon("Hot.gif"));
>            } else {
>                label.setIcon(new ImageIcon("Normal.gif"));
>            }

>            return label;
>        }
>     }

El renderizador se selecciona sobre una columna de esta forma:

>       CustomRenderer custom = new CustomRenderer();
>       custom.setHorizontalAlignment(JLabel.CENTER);
>       scrollColumnModel.getColumn(2).setCellRenderer(
>                                               custom);

Si el componente que está siendo mostrado dentro de la columna JTable requiere más funcionalidad que la disponible usando un JLabel, podemos crear nuestro propio TableCellRenderer. Este código de ejemplo usa un JButton como renderizador de celdas:

>     class CustomButtonRenderer extends JButton
>     		implements TableCellRenderer {
>        public CustomButtonRenderer() {
>          setOpaque(true);
>        }

>        public Component getTableCellRendererComponent(
>                           JTable table, Object value,
>                           boolean isSelected,
>                           boolean hasFocus, int row,
>                           int column) {

>           if (isSelected) {
>              ((JButton)value).setForeground(
>     		table.getSelectionForeground());
>              ((JButton)value).setBackground(
>     		table.getSelectionBackground());
>           } else {
>              ((JButton)value).setForeground(table.getForeground());
>              ((JButton)value).setBackground(table.getBackground());
>           }
>           return (JButton)value;
>         }
>     }

Al igual que el renderizador de celdas por defecto JLabel, esta clase trata con el componente principal (en este caso JButton) para hacer el dibujado. La selección de la celda cambia los colores del botón. Como antes, el renderizador de celdas está seguro sobre la columna apropiada de la tabla de subastas con el método setCellRenderer:

>        scrollColumnModel.getColumn(3).setCellRenderer(
>     	new CustomButtonRenderer());

De forma alternativa, todos los componentes JButton pueden configurarse para usar el CustomButtonRenderer en la tabla con una llamada a setDefaultRenderer de esta forma:

>       table.setDefaultRenderer(
>     	JButton.class, new CustomButtonRenderer());

### Editor de Celdas Personalizado

De la misma forma que podemos configurar como se dibujan las celdas en una JTable o en un JTree, también podemos configurar como una celda editable responde a la ediciones. Una diferencia entre usar editores y renderizadores de cledas es que hay un DefaultCellEditor para todos los componentes, pero no hay un DefaultTableCellEditor para celdas de tablas.

Mientras existen renderizadores separados para JTree y JTable, una sóla clase DefaultCellEditor implementa los dos interfaces TableCellEditor y TreeCellEditor. Sin embargo, la clase DefaultCellEditor sólo tiene constructores para los componentes JComboBox, JCheckBox, y JTextField. La clase JButton no se mapea con tinguno de estos constructores por eso se crea un JCheckBox inútil para satisfacer los requerimientos de la clase DefaultCellEditor.

El siguiente ejemplo usa un editor de botón personalizado que muestra el número de días que quedan de subasta cuando se hacer doble click sobre él. El doble click para disparar la acción se especifica seleccionando el valor clickCountToStart a dos. Una copia exacta del método getTableCellEditorComponent dibuja el botón en modo edición. Un componente JDialog que muestra el número de días que quedan aparecerá cuando se llame al método getCellEditorValue. El valor del número de días que quedan se calcula moviendo la fecha del calendario actual hasta la fecha final. La clase Calendar no tiene un método que exprese una diferencia entre dos fechas distinto a los milisegundos que haya entre esas dos fechas.

>     class CustomButtonEditor extends DefaultCellEditor {
>       final JButton mybutton;
>       JFrame frame;

>       CustomButtonEditor(JFrame frame) {
>         super(new JCheckBox());
>         mybutton = new JButton();
>         this.editorComponent = mybutton;
>         this.clickCountToStart = 2;
>         this.frame=frame;
>         mybutton.setOpaque(true);
>         mybutton.addActionListener(new ActionListener() {
>           public void actionPerformed(ActionEvent e) {
>             fireEditingStopped();
>           }
>         });
>       }

>       protected void fireEditingStopped() {
>         super.fireEditingStopped();
>       }

>       public Object getCellEditorValue() {
>         JDialog jd= new JDialog(frame, "Time left");
>         Calendar today=Calendar.getInstance();
>         Calendar end=Calendar.getInstance();
>         SimpleDateFormat in=new SimpleDateFormat("yyyy-MM-dd");
>         try {
>           end.setTime(in.parse(mybutton.getText()));
>         } catch (Exception e){
>           System.out.println("Error in date"+mybutton.getText()+e);
>         }
>         int days = 0;
>         while(today.before(end)) {
>         today.roll(Calendar.DATE,true);
>         days++;
>         }
>         jd.setSize(200,100);
>         if (today.after(end)) {
>            jd.getContentPane().add(new JLabel("Auction completed"));
>         } else {
>           jd.getContentPane().add(new JLabel("Days left="+days));
>         }
>         jd.setVisible(true);
>          return new String(mybutton.getText());
>       }

>       public Component getTableCellEditorComponent(JTable table,
>     		Object value, boolean isSelected,
>     		int row, int column) {

>         ((JButton) editorComponent).setText(((
>     		JButton)value).getText());
>         if (isSelected) {
>           ((JButton) editorComponent).setForeground(
>     		table.getSelectionForeground());
>           ((JButton) editorComponent).setBackground(
>     		table.getSelectionBackground());
>         } else {
>           ((JButton) editorComponent).setForeground(
>     		table.getForeground());
>           ((JButton) editorComponent).setBackground(
>     		table.getBackground());
>         }
>         return editorComponent;
>         }
>      }

### Manejo de Eventos Especializados

Swing usa las clases de manejo de eventos disponibles en el API AWT desde el JDK 1.1. Sin embargo, algunos APIs nuevos están disponibles en la clase SwingUtilities que se usan para añadir más control sobre la cola de eventos. Los dos nuevos métodos manejadores de eventos son invokeLater y invokeAndWait. Este último espera a que el evento sea procesador en la cola de eventos.

Estos métodos se usan frecuentemente para solicitar el foco sobre un componente después de que otro evento haya ocurrido y que podría afectar al foco de componentes. Podemos devolver el foco llamando al método invokeLater y pasando un Thread:

>       JButton button =new JButton();
>       SwingUtilities.invokeLater(new Runnable() {
>         public void run() {
>           button.requestFocus();
>         }
>       });

### Direcciones Swing

Mientras que la arquitectura básica Swing ha permanecido estable a su diseño original, se han realizado muchas mejoras y optimizaciones sobre componentes como JTable y en áreas desplazables.

Sin embargo, como veremos en la sección Analizar un Programa, una simple tabla de 700x300 requiere casi medio megabyte de memoria cuando se usa doble buffer. La creacción de 10 tablas probablemente necesitaría el intercambio de memoria a disco, afectando severamenta al rendimiento en máquinas de bajo nivel.

Ozito