Escribir Aplicaciones Avanzadas para la Plataforma Java 2

Las arquitecturas Java Foundation Classes (JFC) y JavaBeans Enterprise comparten un elemento de dise�o clave: la separaci�n de los datos de su aspecto en pantalla o la manipulaci�n de los datos. En las aplicaciones JavaBeans Enterprise, el beande entidad proporciona una vista de los datos. El mecanismo de los datos oculto puede ser solapado y modificado sin modificar la vista del bean de entidad o recompilar cualquier c�digo que use la vista.

El proyecto Swing separa la vista y control de un componente visual de sus contenidos, o medelo de datos. Sin embargo, aqunque el Proyecto Swing tiene los componentes que crean la arquitectura Modelo-Vista-Controlador (MVC), es m�s seguro describirlo como una arquitectura de modelo-delegado. Esteo eso por la parte controlador de un interface Swing, frecuentemente usa el eventos del rat�n y de teclado para responder al componente, es combinada con la vista f�sica en un objeto "User Interface delegate" (UI delegate).

Cada componente, por ejemplo un JButton o un JScrollBar, tiene una clase UI delegate separada que desciende desde la clase ComponentUI y est� bajo el control de un controlador UI separado. Mientras que cada componente tiene un UI delgate b�sico, no est� m�s unido con los datos ocultos por lo que se pueden intercambiar mientras que la aplicaci�n todav�a se est� ejecutando. La posibilidad de cambiar el aspecto y comportamiento refleja la caracter�stica del aspecto y comportamiento conectable (PLAF) disponible en Swing.

Este cap�tulo describe componentes de usuario Swing en t�rminos de la aplicaci�n AuctionClient.

.�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

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 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_SCRol>LBAR_ALWAYS,          
               JScrollPane.HORIZONTAL_SCRol>LBAR_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.

.�El API de Impresi�n

El paquete java.awt.print de la plataforma Java 2 nos permite imprimir cualquier cosa que pueda ser renderizada a un contexto Graphics o Graphics2D � incluyendo componentes AWT, componentes Swing y gr�ficos 2D. El API de impresi�n es f�cil de usar. Nuestra aplicaci�n le dice al sistema de impresi�n qu� imprimir, y el sistema de impresi�n determina cuando se renderiza cada p�gina. Este modelo de impresi�n por retrollamada permite soporte de impresi�n en un amplio rango de impresoras y sistemas. El modelo de retrollamada tambi�n permite al usuario imprimir a una impresora de mapa de bits desde un ordenador que no tiene suficiente memoria o espacio en disc para contener el bitmap de una p�gina completa.

Un contexto gr�fico permite a un programa dibujar en un dispositivo de renderizaci�n como una pantalla, una impresora o una imagen fuera de pantalla. Como los componentes Swing se renderizan a trav�s de un objeto Graphics usando el soporte de gr�ficos AWT, es f�cil imprimir componentes Swing con el nuevo API de impresi�n. Sin embargo, los componentes AWT no se renderizan a un dispositivo gr�fico, debemos extender la clase del componente AWT e implementar el m�todo de dibujo del componente AWT.

.��Qu� hay en el Paquete?

El java.awt.print contiene los siguientes interfaces, clases y excepciones. Aqu� podr�s encontrar la Especificaci�n del API.

  • Interfaces
    • Pageable
    • Printable
    • PrinterGraphics
  • Clases
    • Book
    • PageFormat
    • Paper
    • PrinterJob
  • Excepciones
    • PrinterAbortException
    • PrinterException
    • PrinterIOException

.�Imprimir un Componente AWT

La aplicaci�n printbutton.java muestra un panel con un MyButton sobre �l. Cuando se pulsa el bot�n, la aplicaci�n imprime el componente MyButton.

En el c�digo, la clase Button se extiende para implementar Printable e incluye los m�todo paint y print. Este �ltimo es necesario porque la clase implementa Printable, y el m�todo paint es necesario porque describe como aparecen la forma del bot�n y la etiqueta de texto cuando se imprimen.

Para ver el bot�n, la contexto gr�fico de impresi�n es trasladado a un �rea imaginable de la impresora, y para ver la etiqueta de texto, se selecciona una fuente en el contexto gr�fico de impresi�n.

En este ejemplo, el bot�n se imprime a 164/72 pulgadas dentro del margen imaginable (hay 72 pixels por pulgada) y a 5/72 pulgadas del margen superior imaginado. Aqu� es donde el bot�n es posicionado por el controlador de distribuci�n y estos mismo n�mero son devultos por las siguientes llamadas:

int X = (int)this.getLocation().getX();
int Y = (int)this.getLocation().getY();

Y aqu� est� el c�digo de la clase MyButton:

class MyButton extends Button 
		implements Printable {

  public MyButton() {
    super("MyButton");
  }

  public void paint(Graphics g) {
  //To see the label text, you must specify a font for
  //the printer graphics context
    Font  f = new Font("Monospaced", Font.PLAIN,12);
    g2.setFont (f);

  //Using "g" render anything you want.
  //Get the button's location, width, and height
    int X = (int)this.getLocation().getX();
    int Y = (int)this.getLocation().getY();
    int W = (int)this.getSize().getWidth();
    int H = (int)this.getSize().getHeight();

  //Draw the button shape
    g.drawRect(X, Y, W, H);

  //Draw the button label
  //For simplicity code to center the label inside the
  //button shape is replaced by integer offset values  
    g.drawString(this.getLabel(), X+10, Y+15);

  }

  public int print(Graphics g, 
                     PageFormat pf, int pi) 
		       throws PrinterException {
    if (pi >= 1) {
      return Printable.NO_SUCH_PAGE;
    }

    Graphics2D g2 = (Graphics2D) g;

  //To see the button on the printed page, you
  //must translate the printer graphics context
  //into the imageable area
    g2.translate(pf.getImageableX(), pf.getImageableY());
    g2.setColor(Color.black);
    paint(g2);
    return Printable.PAGE_EXISTS;
   }

Nota: La impresi�n Graphics2D est� basada en la clase BufferedImage y algunas plataformas no permiten un color de fondo negro por defecto. Si este es nuestro caso tenemos que a�adir g2.setColor(Color.black) al m�todo print antes de la invocaci�n a paint.

.�Imprimir un Componente Swing

Imprimir un componente Swing es casi lo mismo que imprimir un componente AWT, excepto que la clase MyButton no necesita una implementaci�n del m�todo paint. Sin embargo, si teine un m�todo print que llama al m�todo paint del componente. La implementaci�n del m�todo paint no es necesaria porque los componentes Swing saben como dibujarse a s� mismos.

Aqu� est� el c�digo fuente completo para la versi�n Swing de printbutton.java.

class MyButton extends JButton implements Printable {

  public MyButton() {
    super("MyButton");
  }

  public int print(Graphics g, 
                     PageFormat pf, int pi)
                       throws PrinterException {
    if (pi >= 1) {
      return Printable.NO_SUCH_PAGE;
    }

    Graphics2D g2 = (Graphics2D) g;
    g2.translate(pf.getImageableX(), 
                 pf.getImageableY());
    Font  f = new Font("Monospaced", Font.PLAIN,12);
    g2.setFont (f);
    paint(g2);
    return Printable.PAGE_EXISTS;
   }

Si extendemos un JPanel e implementamos Printable, podemos imprimir un componente panel y todos sus componentes.

public class printpanel extends JPanel 
			implements ActionListener, 
			Printable {

Aqu� est� el c�digo de printpanel.java que imprime un objeto JPanel y el JButton que contiene, y el c�digo de ComponentPrinterFrame.java que imprime un ojeto JFrame y los componentes JButton, JList, JCheckBox, y JComboBox que contiene.

.�Imprimir Gr�ficos en Swing

De la misma forma que el ejemplo AWT extiende un componente Button e implementa el m�todo paint para dibujar un bot�n, podemos subclasificar componentes AWT y Swing e implementar el m�todo paint para renderizar gr�ficos 2D en la pantalla o en la impresora. La aplicaci�n Swing ShapesPrint.java muestra como se hace esto.

El m�todo paintComponent llama al m�todo drawShapes para renderizar gr�ficos 2D en la pantalla cuando arranca la aplicaci�n. Cuando pulsamos sobre el bot�n, Print, se crea un contexto gr�fico de impresi�n y es pasado al m�todo drawShapes para el dibujado.

.�Di�logo de Impresi�n

Es f�cil mostrar el Di�logo de Impresi�n para que el usuario final pueda intercambiar las propiedades del rabajo de impresi�n. El m�todo actionPerformed del ejemplo Swing anterior modificado aqu� hace justo esto:

public void actionPerformed(ActionEvent e) {
  PrinterJob printJob = PrinterJob.getPrinterJob();
  printJob.setPrintable((MyButton) e.getSource());
  if(printJob.printDialog()){
    try { printJob.print(); } 
    catch (Exception PrinterExeption) { }
  }
}

Nota: En Swing, la sentencia printJob.setPageable((MyButton) e.getSource()); puede escribirse como printJob.setPrintable((MyButton) e.getSource());. La diferencia es que setPrintable es para aplicaciones que no conocen el n�mero de p�ginas que est�n imprimiendo. Si usamos setPrintable, necesitamos a�adir if(pi >= 1){ return Printable.NO_SUCH_PAGE: } al principio del m�todo print.

.�Di�logo de configuraci�n de P�gina

Podemos a�adir una l�nea de c�digo que le dice al objeto PrinterJob que mueste el Di�logo de Configuraci�n de P�gina para que el usuario final pueda modificar interactivamente el formato de la p�gina para imprimir en vertical u horizontal, etc. El m�todo actionPerformed ejemplo Swing acnterior est� mostrado aqu� para que muestre los di�logos de Impresi�n y Configuraci�n de P�gina:

Nota: Algunas plataformas no soportan el di�logo de configuraci�n de p�gina. En estas plataformas, la llamada a pageDialog simplemente devuelven el objeto PageFormat que se les pas� y no muestran ning�n di�logo.

public void actionPerformed(ActionEvent e) {
  PrinterJob printJob = PrinterJob.getPrinterJob();
  printJob.setPrintable((MyButton) e.getSource());
  PageFormat pf = printJob.pageDialog(
                             printJob.defaultPage());
  if(printJob.printDialog()){
    try { printJob.print(); } catch (Exception ex) { }
  }
}

.�Imprimir una Colecci�n de P�ginas

Podemos usar la clase Book para imprimir una colecci�n de p�ginas que a�adimos al libro. Esta p�ginas pueden est�r en cualquier orden y tener diferentes formatos.

El ejemplo print2button.java pone los botones Print y Print 2 del tipo MyButton en un panel. Crea un libro que contiene las p�ginas para imprimir. Cuando pulsamos algun bot�n, el libro imprime una copia del bot�n Print en modo horizontal y dos copias del bot�n Print 2 en modo vertical, como se especifica en la implementaci�n del m�todo actionPerformed mostrada aqu�:

Nota: Actualmente un Bug restringe a la plataforma Solaris a imprimir s�lo en vertical.

public void actionPerformed(ActionEvent e) {
  PrinterJob printJob = PrinterJob.getPrinterJob();

/* Set up Book */
  PageFormat landscape = printJob.defaultPage();
  PageFormat portrait = printJob.defaultPage();
  landscape.setOrientation(PageFormat.LANDSCAPE);
  portrait.setOrientation(PageFormat.PORTRAIT);
  Book bk = new Book();
  bk.append((Printable)b, landscape);
  bk.append((Printable)b2, portrait, 2);
  printJob.setPageable(bk);

  try { printJob.print(); } catch (Exception ex) { }
}

.�Impresi�n Avanzada

La secci�n anterior explic� c�mo imprimir componentes sencillos y cubr�a las t�cnicas que se pueden usar para imprimir capturas de pantalla. Sin embargo, si queremos imprimir m�s que un componente por cada p�gina, o su nuestro componentes es mayor que el tama�o de una p�gina, necesitamos hacer alg�n trabajo adicional dentro del m�todo print. Esta secci�n explica qu� necesitamos hacer y concluye con un ejemplo de c�mo imprimir los contenidos de un componente JTable.

.�Varios Componentes por P�gina

Hay veces cuando imprimimos un componente en una p�gina que no se cubre las necesidades de impresi�n que queremos. Por ejemplo, podr�amos queren incluir una cabecera o un pie en cada p�gina de impresi�n con un n�mero de p�gina -- algo que no es necesario mostrar en la pantalla.

Desafortunadamente, imprimir m�ltiples componentes sobre una p�gina no es t�n sencillo como a�adir llamadas a paint porque cada llamada sobreescribe la salida de la llamada anterior.

La clave para imprimir m�s de un componente en un p�gina, es usar los m�todos translate(double, double) y setClip de la clase Graphics2D.

El m�todo translate mueve un l�piz imaginario a la siguiente posici�n de la salida de impresi�n donde el componente puede ser dibujado y luego imprimido. Hay dos m�todos translate en la clase Graphics2D. Para imprimir m�ltiples componentes necesitamos el que toma dos argumentos double porque este m�todo permite posiconamiento relativo. Debemos asegurarnos de forzar cualquier valor entero a double o float. El posicionamiento relativo en este contexto significa que las llamadas anteriores a translate son tenidas en cuenta cuando se calqula el nuevo punto de traslado.

El m�todo setClip se usa para restringir que el componente sea pintado, y por lo tanto, imprimido, en el �rea especificada. Esto nos permite imprimir m�ltiples componentes en una p�gina moviendo el l�piz imaginario a diferentes puntos de la p�gina y luego pint�ndo cada componente en el �rea recortada.

.�Ejemplo

Podemos reemplazar el m�todo print de los ejemplos printbutton.java Abstract Window Toolkit (AWT) y Swing con el siguiente c�digo para a�adir un mensaje en el pie de p�gina de Company Confidential.

public int print(Graphics g, PageFormat pf, int pi) 
		throws PrinterException {

    if (pi >= 1) {
       return Printable.NO_SUCH_PAGE;
    }

    Graphics2D g2 = (Graphics2D) g;
    Font f= Font.getFont("Courier");
    double height=pf.getImageableHeight();
    double width=pf.getImageableWidth();
         
    g2.translate(pf.getImageableX(), 
                 pf.getImageableY());
    g2.setColor(Color.black);
    g2.drawString("Company Confidential", (int)width/2, 
	(int)height-g2.getFontMetrics().getHeight());
    g2.translate(0f,0f);
    g2.setClip(0,0,(int)width, 
	(int)(height-g2.getFontMetrics().getHeight()*2));
    paint (g2);
    return Printable.PAGE_EXISTS;
}   

En el nuevo m�todo print, el contexto Graphics2D es recortado antes de llamar al m�todo paint del padre JButton. Esto evita que el m�todo JButton paint sobreescriba el bot�n de la p�gina. El m�todo translate se usa para apuntan el m�todo JButton paint a que empieza el paint con un desplazamiento de 0,0 desde la parte visible de la p�gina. el �rea visible ya est� calculada mediante una llamada anterior a translate:

  g2.translate(pf.getImageableX(), pf.getImageableY());

Para m�s componentes, podr�amos necesitar configurar el color de fondo para ver los resultados. En este ejemplo el color de texto se imprimi� en negro.

.�M�todos �tiles para Llamar en el M�todo print

Los siguientes m�todos son �tiles para calcular el n�mero de p�ginas requeridas y para hacer que un componente se reduzca hasta entrar en una p�gina:

M�todos PageFormat:

getImageableHeight() devuelve la altura de la p�gina que podemos usar para imprimir la salida.

getImageableWidth() devuelve la anchura de la p�gina que podemos usar para imprimir la salida.

M�todo Graphics2D:

scale(xratio, yratio) escala el conexto gr�fico 2D a este tama�o. Un rat�o de uno mantiene el tama�o, menos de uno reduce el tama�o del contexto gr�fico.

.�Componentes Mayores de una P�gina

El API de impresi�n de Java tiene un API Book que proporciona el concepto de p�ginas. Sin embargo, este API s�lo a�ade objetos printables a una colecci�nde objetos printables. No calcula las rupturas de p�gina ni expande componentes sobre m�ltiples p�ginas

Cuando imprimimos un s�lo componente en una p�gina, s�lo tenemos que chequear que el valor del �ndice es mayor o igual que uno y devolver NO_SUCH_PAGE cuando se alcanza este valor.

Para imprimir multiples p�ginas, tenemos que calcular el n�mero de p�ginas necesarias para contener el componente. Podemos calcular el n�mero total de p�ginas necesarias dividiendo el espacio ocupado por el componente por el valor devuelto por el m�todo getImageableHeight. Una vez calculado el n�mero total de p�ginas, podemos ejecutar el siguiente chequeo dentro del m�todo print:

  if (pageIndex >=TotalPages) {
	return NO_SUCH_PAGE;
  }

El marco de trabajo de impresi�n llama al m�todo print multiples veces hasta que pageIndex sea menor o igual que TotalPages. Todo lo que necesitamos hacer es crear una nueva p�gina para del mismo componente encada bucle print. Esto se puede hacer tratando la p�gina impresa como una ventana deslizante sobre el componente. La parte del componente que se est� imprimiendo es seleccionada por una llamada a translate para marcar la parte superior de la p�gina y una llama a setClip para marcar la parte inferior de la p�gina. el siguiente diagrama ilustra este proceso.

El lado izquierdo del diagrama representa la p�gina enviada a la impresora. El lado LEFT contiene la longitud del componente que est� siendo imprimido en el m�todo print. La primera p�gina puede ser representada de esta forma:

Luego la ventana de la p�gina impresa se desliza a lo largo del componente para imprimir la segunda p�gina, con el �ndice uno.

Este proceso contin�a hasta que se alcanza la �ltima p�gina.

.�Imprimir un Componente JTable

La clase Report.java usa muchos de t�cnicas avanzadas cubiertas en esta secci�n para imprimir los datos y la cabecera de un componente JTable que expande muchas p�ginas. La salida de impresi�n tambi�n inlcuye un pi� de p�gina con el n�mero de �sta.

Este diagrama muestra como ser�a la impresi�n:

import javax.swing.*;
import javax.swing.table.*;
import java.awt.print.*;
import java.util.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.awt.Dimension;

public class Report implements Printable{
  JFrame frame;
  JTable tableView;

  public Report() {
    frame = new JFrame("Sales Report");
    frame.addWindowListener(new WindowAdapter() {
    public void windowClosing(WindowEvent e) {
      System.exit(0);}});

    final String[] headers = {"Description", "open price", 
	"latest price", "End Date", "Quantity"};
    final Object[][] data = {
        {"Box of Biros", "1.00", "4.99", new Date(), 
          new Integer(2)},
        {"Blue Biro", "0.10", "0.14", new Date(), 
          new Integer(1)},
        {"legal pad", "1.00", "2.49", new Date(), 
          new Integer(1)},
        {"tape", "1.00", "1.49", new Date(), 
          new Integer(1)},
        {"stapler", "4.00", "4.49", new Date(), 
          new Integer(1)},
        {"legal pad", "1.00", "2.29", new Date(), 
          new Integer(5)}
    };

    TableModel dataModel = new AbstractTableModel() {
        public int getColumnCount() { 
          return headers.length; }
        public int getRowCount() { return data.length;}
        public Object getValueAt(int row, int col) {
        	return data[row][col];}
        public String getColumnName(int column) {
         	return headers[column];}
        public Class getColumnClass(int col) {
                return getValueAt(0,col).getClass();}
        public boolean isCellEditable(int row, int col) {
                return (col==1);}
        public void setValueAt(Object aValue, int row, 
                      int column) {
                data[row][column] = aValue;
       }
     };

     tableView = new JTable(dataModel);
     JScrollPane scrollpane = new JScrollPane(tableView);

     scrollpane.setPreferredSize(new Dimension(500, 80));
     frame.getContentPane().setLayout(
               new BorderLayout());
     frame.getContentPane().add(
               BorderLayout.CENTER,scrollpane);
     frame.pack();
     JButton printButton= new JButton();

     printButton.setText("print me!");

     frame.getContentPane().add(
               BorderLayout.SOUTH,printButton);

     // for faster printing turn double buffering off

     RepaintManager.currentManager(
     	frame).setDoubleBufferingEnabled(false);

     printButton.addActionListener( new ActionListener(){
        public void actionPerformed(ActionEvent evt) {
          PrinterJob pj=PrinterJob.getPrinterJob();
          pj.setPrintable(Report.this);
          pj.printDialog();
          try{ 
            pj.print();
          }catch (Exception PrintException) {}
          }
        });

        frame.setVisible(true);
     }

     public int print(Graphics g, PageFormat pageFormat, 
        int pageIndex) throws PrinterException {
     	Graphics2D  g2 = (Graphics2D) g;
     	g2.setColor(Color.black);
     	int fontHeight=g2.getFontMetrics().getHeight();
     	int fontDesent=g2.getFontMetrics().getDescent();

     	//leave room for page number
     	double pageHeight = 
     	  pageFormat.getImageableHeight()-fontHeight;
     	double pageWidth = 
     	  pageFormat.getImageableWidth();
     	double tableWidth = (double) 
          tableView.getColumnModel(
          ).getTotalColumnWidth();
     	double scale = 1; 
     	if (tableWidth >= pageWidth) {
		scale =  pageWidth / tableWidth;
	}

     	double headerHeightOnPage=
                 tableView.getTableHeader(
                 ).getHeight()*scale;
     	double tableWidthOnPage=tableWidth*scale;

     	double oneRowHeight=(tableView.getRowHeight()+
                      tableView.getRowMargin())*scale;
     	int numRowsOnAPage=
              (int)((pageHeight-headerHeightOnPage)/
                                  oneRowHeight);
     	double pageHeightForTable=oneRowHeight*
     	                            numRowsOnAPage;
     	int totalNumPages= 
     	      (int)Math.ceil((
                (double)tableView.getRowCount())/
                                    numRowsOnAPage);
     	if(pageIndex>=totalNumPages) {
                      return NO_SUCH_PAGE;
     	}

     	g2.translate(pageFormat.getImageableX(), 
                       pageFormat.getImageableY());
//bottom center
     	g2.drawString("Page: "+(pageIndex+1),
     	    (int)pageWidth/2-35, (int)(pageHeight
     	    +fontHeight-fontDesent));

     	g2.translate(0f,headerHeightOnPage);
     	g2.translate(0f,-pageIndex*pageHeightForTable);

     	//If this piece of the table is smaller 
     	//than the size available,
     	//clip to the appropriate bounds.
     	if (pageIndex + 1 == totalNumPages) {
           int lastRowPrinted = 
                 numRowsOnAPage * pageIndex;
           int numRowsLeft = 
                 tableView.getRowCount() 
                 - lastRowPrinted;
           g2.setClip(0, 
             (int)(pageHeightForTable * pageIndex),
             (int) Math.ceil(tableWidthOnPage),
             (int) Math.ceil(oneRowHeight * 
                               numRowsLeft));
     	}
     	//else clip to the entire area available.
     	else{    
             g2.setClip(0, 
             (int)(pageHeightForTable*pageIndex), 
             (int) Math.ceil(tableWidthOnPage),
             (int) Math.ceil(pageHeightForTable));        
     	}

     	g2.scale(scale,scale);
     	tableView.paint(g2);
     	g2.scale(1/scale,1/scale);
     	g2.translate(0f,pageIndex*pageHeightForTable);
     	g2.translate(0f, -headerHeightOnPage);
     	g2.setClip(0, 0,
     	  (int) Math.ceil(tableWidthOnPage), 
          (int)Math.ceil(headerHeightOnPage));
     	g2.scale(scale,scale);
     	tableView.getTableHeader().paint(g2);
     	//paint header at top

     	return Printable.PAGE_EXISTS;
   }

   public static void main(String[] args) {
	new Report();
   }
}

.�Imprimir un Informe de Ventas

La clase Applet SalesReport.java imprime un informe de ventas con filas que exp�nden sobre m�ltiples p�ginas con n�meros en la parte inferior de cada p�gina. Aqu� se v� la aplicaci�n cuando se lanza:

Necesitamos este fichero de polic�a para lanzar el applet:

grant {
  permission java.lang.RuntimePermission 
                         "queuePrintJob";
};

Para lanzar el applet asumiendo un fichero de polic�a llamado printpol y una p�gina HTML llamada SalesReport.html, teclearemos:

  appletviewer -J-Djava.security.policy=
                 printpol SalesReport.html

El diagrama muestra c�mo se ver� la impresi�n del informe:

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP
ARTÍCULO ANTERIOR

SIGUIENTE ARTÍCULO