Está en la página 1de 60

Parte I - Bases

La Parte I se compone de dos captulos que sientan las bases para un aprendizaje productivo y exitoso de la librera de clases JFC/Swing. El primero empieza con un breve vistazo de lo qu es Swing y una introduccin a su arquitectura. El segundo profundiza un poco ms en una discusin detallada de los principales mecanismos subyacentes de Swing, y como interactuar con ellos. Hay varias secciones sobre temas que son bastante avanzados, como la multitarea y el dibujo en pantalla. Este material es comn a varias reas de Swing e introducindolo en el captulo 2, su comprensin de lo que vendr posteriormente mejorar notablemente. Contamos con que tendr que volver a l a menudo, y en algn lugar le instaremos explcitamente a que lo haga. Como mnimo, le recomendamos que conozca los contenidos del captulo 2 antes de seguir adelante.

Captulo 1. Un vistazo a Swing


En este captulo: AWT Swing MVC Delegados UI y PLAF

1.1 AWT
AWT (Abstract Window Toolkit) es la parte de Java diseada para crear interfaces de usuario y para dibujar grficos e imgenes. Es un conjunto de clases que intentan ofrecer al desarrollador todo lo que necesita para crear una interfaz de usuario para cualquier applet o aplicacin Java. La mayora de los componentes AWT descienden de la clase java.awt.Component como podemos ver en la figura 1.1. (Obsrvese que las barras de men de AWT y sus tems no encajan dentro de la jerarqua de Component.)

Figura 1.1 Jerarqua parcial de Components

<<fichero figure1-1.gif>>

JFC est compuesto de cinco partes fundamentales: AWT, Swing, Accesibilidad, Java 2D, y Arrastrar y Soltar. Java 2D se ha convertido en una parte ms de AWT, Swing est construido sobre AWT, el soporte de accesibilidad se ha construido dentro de Swing. Las cinco partes de JFC no son en absoluto mutuamente exclusivas, y se espera que Swing se fusione ms profundamente con AWT en futuras versiones de Java. El API de Arrastrar y Soltar no estaba totalmente desarrollado durante la escritura de este libro pero esperamos que esta tecnologa se integre ms con Swing y AWT en un futuro prximo. De este modo, AWT est en el corazn de JFC, lo que la convierte en una de las libreras ms importantes de Java 2.

1.2 Swing
Swing es un extenso conjunto de componentes que van desde los ms simples, como etiquetas, hasta los ms complejos, como tablas, rboles, y documentos de texto con estilo. Casi todos los componentes Swing descienden de un mismo padre llamado JComponent que desciende de la clase de AWT Container. Es por ello que Swing es ms una capa encima de AWT que una sustitucin del mismo. La figura 1.2 muestra una parte de la jerarqua de JComponent. Si la compara con la jerarqua de Component notar que para cada componente AWT hay otro equivalente en Swing que empieza con "J". La nica excepcin es la clase de AWT Canvas, que se puede reemplazar con JComponent, JLabel, o JPanel (en la seccin 2.8 abordaremos esto en detalle). Asimismo se percatar de que existen algunas clases Swing sin su correspondiente homlogo. La figura 1.2 representa slo una pequea fraccin de la librera Swing, pero esta fraccin son las clases con las que se enfrentar ms a menudo. El resto de Swing existe para suministrar un amplio soporte y la posibilidad de personalizacin a los componentes estas clases definen.

Figura 1.2 Parte de la jerarqua de JComponent

<<fichero figure1-2.gif>>

1.2.1

Orden Z

A los componentes Swing se les denomina ligeros mientras que a los componentes AWT se les denominados pesados. La diferencia entre componentes ligeros y pesados es su orden: la nocin de profundidad. Cada componente pesado ocupa su propia capa de orden Z. Todos los componentes ligeros se encuentran dentro de componentes pesados y mantienen su propio esquema de capas definido por Swing. Cuando colocamos un componente pesado dentro de un contenedor que tambin lo es, se superpondr por definicin a todos los componentes ligeros del contenedor. Lo que esto significa es que debemos intentar evitar el uso de componentes ligeros y pesados en un mismo contenedor siempre que sea posible. Esto no significa que no podamos mezclar nunca con xito componentes AWT y Swing, slo que tenemos que tener cuidado y saber qu situaciones son seguras y cules no. Puesto que probablemente no seremos capaces de prescindir completamente del uso de componentes pesados en un breve espacio de tiempo, debemos encontrar formas de que las dos tecnologas trabajen juntas de manera aceptable. La regla ms importante a seguir es que no deberamos colocar componentes pesados dentro de contenedores ligeros, que comnmente soportan hijos que se superponen. Algunos ejemplos de este tipo de contenedores son JInternalFrame, JScrollPane, JLayeredPane, y JDesktopPane. En segundo lugar, si usamos un men emergente en un contenedor que posee un componente pesado, tenemos que forzar a dicho men a ser pesado. Para controlar esto en una instancia especfica de JPopupMenu podemos usar su mtodo setLightWeightPopupEnabled().
Nota: Para JMenus (que usan JPopupMenus para mostrar sus contenidos) tenemos que usar primero el mtodo getPopupMenu() para recuperar su men emergente asociado. Una vez recuperado podemos llamar entonces a setLightWeightPopupEnabled(false) en l para imponer funcionalidad pesada. Esto tiene que hacerse con cada JMenu de nuestra aplicacin, incluyendo mens dentro de mens, etc.

Alternativamente podemos llamar al mtodo esttico setDefaultLightWeightPopupEnabled() de JPopupMenu y pasarle un valor false para forzar a todos los mens emergentes de una sesin de Java a ser pesados. Tenga en cuenta que slo afectar a los mens emergentes creados a partir de que se ha hecho la llamada. Es por eso una buena idea llamar a este mtodo durante la inicializacin.

1.2.2 Independencia de la plataforma


La caracterstica ms notable de los componentes Swing es que estn escritos al 100% en Java y no dependen de componentes nativos, como sucede con casi todos los componentes AWT. Esto significa que un botn Swing y un rea de texto se vern y funcionarn idnticamente en las plataformas Macintosh, Solaris, Linux y Windows. Este diseo elimina la necesidad de comprobar y depurar las aplicaciones en cada plataforma destino.
Nota: Las nicas excepciones a esto son los cuatro componentes pesados de Swing que son subclases directas de clases de AWT, que dependen de componentes nativos: JApplet, JDialog, JFrame, y JWindow. Ver captulo 3.

1.2.3 Vistazo al paquete Swing


javax.swing

Contiene la mayor parte de los componentes bsicos de Swing, modelos de componente por defecto, e interfaces. (La mayora de las clases mostradas en la Figura 1.2 se encuentran en este

paquete.)
javax.swing.border

Clases e interfaces que se usan para definir estilos de bordes especficos. Observe que los bordes pueden ser compartidos por cualquier nmero de componentes Swing, ya que no son componentes por si mismos. javax.swing.colorchooser Clases e interfaces que dan soporte al componente JColorChooser, usado para seleccin de colores. (Este paquete tambin contiene alguna clase privada interesante sin documentar.)
javax.swing.event

El paquete contiene todos los oyentes y eventos especficos de Swing. Los componentes Swing tambin soportan eventos y oyentes definidos en java.awt.event y java.beans.
javax.swing.filechooser

Clases e interfaces que dan soporte al componente JFileChooser, usado para seleccin de ficheros.
javax.swing.plaf

Contiene el API del comportamiento y aspecto conectable usado para definir componentes de interfaz de usuario personalizados. La mayora de las clases de este paquete son abstractas. Las implementaciones de look-and-feel, como metal, motif y basic, crean subclases e implementan las clases de este paquete. stas estn orientadas a desarrolladores que, por una razn u otra, no pueden usar uno de los look-and-feel existentes.
javax.swing.plaf.basic

Consiste en la implementacin del Basic look-and-feel, encima del cual se construyen los lookand-feels que provee Swing. Normalmente deberemos usar las clases de este paquete si queremos crear nuestro look-and-feel personal.
javax.swing.plaf.metal

Metal es el look-and-feel por defecto de los componentes Swing. Es el nico look-and-feel que viene con Swing y que no est diseado para ser consistente con una plataforma especfica.
javax.swing.plaf.multi

Este es el Multiplexing look-and-feel. No se trata de una implementacin normal de look-andfeel ya que no define ni el aspecto ni el comportamiento de ningn componente. Ms bien ofrece la capacidad de combinar varios look-and-feels para usarlos simultneamente. Un ejemplo tpico podra ser un look-and-feel de audio combinado con metal o motif. Actualmente Java 2 no viene con ninguna implementacin de multiplexing look-and-feel (de todos modos, se rumorea que el equipo de Swing esta trabajando en un audio look-and-feel mientras escribimos estas lneas).
javax.swing.table

Clases e interfaces para dar soporte al control de JTable. Este componente se usa para manejar datos en forma de hoja de clculo. Soporta un alto grado de personalizacin sin requerir mejoras de look-and-feel.
javax.swing.text

Clases e interfaces usadas por los componentes de texto, incluyendo soporte para documentos con o sin estilo, las vistas de estos documentos, resaltado, acciones de editor y personalizacin del teclado.
javax.swing.text.html

Esta extensin del paquete text contiene soporte para componentes de texto HTML. (El soporte de HTML est siendo ampliado y reescrito completamente mientras escribimos este libro. Es por ello que la cobertura que le damos es muy limitada.)
javax.swing.text.html.parser

Soporte para analizar gramaticalmente HTML.


javax.swing.text.rtf

Contiene soporte para documents RTF.


javax.swing.tree

Clases e interfaces que dan soporte al componente JTree. Este componente se usa para mostrar y manejar datos que guardan alguna jerarqua. Soporta un alto grado de personalizacin sin requerir mejoras de look-and-feel.
javax.swing.undo

El paquete undo contiene soporte para implementar y manejar la funcionalidad deshacer/rehacer.

1.3 Arquitectura MVC


MVC es una descomposicin orientada a objeto del diseo de interfaces de usuario bien conocida que data de finales de los 70. Los componentes se descomponen en tres partes: un modelo, una vista, y un controlador. Los componentes Swing estn basados en una versin ms moderna de este diseo. Antes de que abordemos como trabaja MVC en Swing, necesitamos comprender como se dise originalmente su funcionamiento.
Nota: La separacin en tres partes descrita aqu se usa en la actualidad solamente en un pequeo nmero de conjuntos de componentes de interfaz de usuario, entre los que destaca VisualWorks.

Figura 1.3 La arquitectura Modelo-Vista-Controlador

<<fichero figure1-3.gif>>

1.3.1 Modelo
El modelo es el responsable de conservar todos los aspectos del estado del componente. Esto incluye, por ejemplo, aquellos valores como el estado pulsado/no pulsado de un botn, los datos de un carcter de un componente de texto y como esta estructurado, etc. Un modelo puede ser responsable de comunicacin indirecta con la vista y el controlador. Por indirecta queremos decir que el modelo no conoce su vista y controlador--no mantiene referencias hacia ellos. En su lugar el modelo enviar notificaciones o broadcasts (lo que conocemos como eventos). En la figura 1.3 esta comunicacin indirecta se representa con lneas de puntos.

1.3.2 Vista
La vista determina la representacin visual del modelo del componente. Esto es el aspecto(look) del componente. Por ejemplo, la vista muestra el color correcto de un componente, tanto si el componente sobresale como si est hundido (en el caso de un botn), y el renderizado de la fuente deseada. La vista es responsable de mantener actualizada la representacin en pantalla y debe hacerlo recibiendo mensajes indirectos del modelo o mensajes directos del controlador.

1.3.3 Controlador
El controlador es responsable de determinar si el componente debera reaccionar a algn evento proveniente de dispositivos de entrada, tales como el teclado o el ratn. El controlador es el comportamiento(feel) del componente, y determina que acciones se ejecutan cuando se usa el

componente. El controlador puede recibir mensajes directos desde la vista, e indirectos desde el modelo. Por ejemplo, supongamos que tenemos un checkbox seleccionado en nuestro interfaz. Si el controlador determina que el usuario ha pulsado el ratn debe enviar un mensaje a la vista. Si la vista determina que la pulsacin ha sido en el checkbox enva un mensaje al modelo. El modelo se actualiza y lo notifica mediante un mensaje, que ser recibido por la(s) vista(s), para decirle que debera actualizarse basndose en el nuevo estado del modelo. De est manera, el modelo no est ligado a una vista o un controlador especfico, permitindonos tener varias vistas y controladores manipulando un mismo modelo.

1.3.4 Controlador y vista personalizados


Una de las principales ventajas de la arquitectura MVC es la posibilidad de personalizar el aspecto(look) y el comportamiento(feel) de un componente sin modificar el modelo. La Figura 1.4 muestra un grupo de componentes que usan dos interfaces de usuario diferentes. Lo ms importante de esta figura es que los componentes mostrados son los mismos, pero que se estn usando dos implementaciones diferentes de look-and-feel (diferentes vistas y controladores -- como veremos ms adelante).

Figura 1.4 Malachite y Windows look-and-feels de los mismos componentes

<<fichero figure1-4.gif>>

Algunos componentes Swing ofrecen tambin la posibilidad de personalizar partes especficas del componente sin afectar al modelo. Ms especficamente, estos componentes permiten definir nuestros propios editor y visualizador de celdas, que se usan para aceptar y mostrar datos especficos respectivamente. La figura 1.5 muestra las columnas de una tabla que contiene datos del mercado de valores, que se visualizan con iconos y colores personalizados. Veremos como sacar provecho de esta funcionalidad en nuestro estudio de las listas, tablas, rboles y listas despegables (JComboBox).

Figura 1.5 Visualizacin personalizada

<<fichero figure1-5.gif>>

1.3.5 Modelos personalizados


Otra gran ventaja de la arquitectura MCV de Swing es la posibilidad de personalizar y reemplazar el modelo de datos de un componente. Por ejemplo, podemos construir nuestro propio modelo de documento de texto que preste especial atencin a la escritura de una fecha o un nmero de telfono de una manera determinada. Podemos tambin asociar el mismo modelo de datos con ms de un componente (como ya comprobamos viendo MVC). Por ejemplo, dos JTextAreas pueden guardar su texto en el mismo modelo de documento, mientras que estn usando dos vistas diferentes de esa informacin. Disearemos e implementaremos nuestros propios modelos de datos para JComboBox, JList, JTree, JTable, y ms ampliamente a lo largo de nuestro repaso a los componentes de texto. Abajo hemos listado algunas definiciones de interfaces de modelos Swing, con una breve descripcin de los datos para cuyo almacenamiento estn diseados, y con que componentes se usan:
BoundedRangeModel Usado por: JProgressBar, JScrollBar, JSlider.

Guarda: 4 enteros: value, extent, min, max. Value y extent tienen que estar entre los valores de min y max. Extent es siempre <= max y >= value.
ButtonModel

Usado por: Todas las subclases de AbstractButton. Guarda: Un booleano que determina si el botn est seleccionado (armado) o no (desarmado).
ListModel

Usado por: JList. Guarda: Una coleccin de objetos.


ComboBoxModel Usado por: JComboBox.

Guarda: Una coleccin de objetos y un objeto seleccionado.


MutableComboBoxModel Usado por: JComboBox.

Guarda: Un vector (u otra coleccin alterable) de objetos y un objeto seleccionado.


ListSelectionModel Usado por: JList, TableColumnModel.

Guarda: Uno o ms ndices de selecciones de la lista o de tems de la tabla. Permite seleccionar slo uno, un intervalo simple, o un intervalo mltiple (discontinuo).
SingleSelectionModel Usado por: JMenuBar, JPopupMenu, JMenuItem, JTabbedPane.

Guarda: El ndice del elemento seleccionado en una coleccin de objetos perteneciente al implementador.
ColorSelectionModel Usado por: JColorChooser. Guarda: Un Color. TableModel Usado por: JTable.

Guarda: Una matriz de objetos.


TableColumnModel Usado por: JTable.

Guarda: Una coleccin de objetos TableColumn, un conjunto de oyentes para eventos de modelos de una columna, la anchura entre cada columna, la anchura total de todas las columnas, un modelo de seleccin, y un indicador de seleccin de columna.
TreeModel

Usado por: JTree. Guarda: Objetos que se pueden mostrar en un rbol. Las implementaciones tienen que ser capaces de distinguir entre las hojas y el resto de nodos, y los objetos deben estar organizados jerrquicamente.
TreeSelectionModel Usado por: JTree.

Guarda: Las filas seleccionadas. Permite seleccin simple, continua y discontinua.


Document

Usado por: Todos los componentes de texto. Guarda: Contenido. Normalmente es texto (caracteres). Implementaciones ms complejas soportan texto con estilo, imgenes, y otros tipos de contenido. (p.e. componentes embebidos). No todos los componentes Swing tienen modelos, aquellos que se usan como contenedores, como
JApplet, JFrame, JLayeredPane, JDesktopPane, JInternalFrame, etc. no los tienen. Sin embargo, los componentes interactivos como JButton, JTextField, JTable, etc. tienen que tener modelos. De hecho, algunos componentes Swing tienen ms de un modelo (p.e. JList usa un modelo

para mantener informacin sobre la seleccin, y otro para guardar los datos). Esto quiere decir que MVC no es totalmente rgido en Swing. Componentes simples o complejos, que no guardan grandes cantidades de informacin (como JDesktopPane), no necesitan separar los modelos. La vista y el controlador de cada componente estn casi siempre separadas en todos los componentes Swing, como veremos en la siguiente seccin. Entonces, cmo encaja el componente por si mismo dentro del definicin de MVC?. El componente se comporta como un mediador entre el/los modelo(s), la vista y el controlador. No es ni la M, ni la V, ni la C, aunque puede ocupar el lugar de una o incluso todas estas partes si lo diseamos para ello. Esto se ver ms claro cuando progresemos en este captulo y a lo largo del resto del libro.

1.4 Delegados UI y PLAF


Casi todos los conjuntos de componentes modernos combinan la vista y el controlador, tanto si se basan en SmallTalk, como C++, o ahora Java. Ejemplos de ello son MacApp, Smalltalk/V, Interviews, y los widgets X/Motif que se usan en IBM Smalltalk. JFC Swing ha sido el ltimo en aadirse a este grupo. Swing empaqueta todos los controladores y vistas de un componente dentro de un objeto denominado delegado UI. Por esta razn, la arquitectura subyacente de Swing se denomina ms acertadamente como modelo-delegado que como modelo-vista-controlador. Idealmente, la comunicacin entre el modelo y el delegado UI es indirecta, permitiendo as tener asociado ms de un modelo a un delegado UI, y viceversa. Podemos verlo en la Figura 1.6.

Figura 1.6 Model-delegate architecture

<<fichero figure1-6.gif>>

1.4.1 La clase ComponentUI


Todos los delegados UI descienden de una clase abstracta que se llama ComponentUI. Los mtodos de ComponentUI describen los fundamentos de la comunicacin entre un delegado UI y un componente. Observe que a cada mtodo se le pasa como parmetro un JComponent. Mtodos de ComponentUI:
static ComponentUI CreateUI(JComponent c)

Este se implementa normalmente para que devuelva una instancia compartida del delegado UI que define la subclase apropiada de ComponentUI. Esta instancia se usa para ser compartida entre componentes del mismo tipo (p.e. Todos los JButtons que usan el Metal look-and-feel comparten la misma instancia esttica del delegado UI definido en javax.swing.plaf.metal.MetalButtonUI por defecto.)
installUI(JComponent c) Instala el ComponentUI en el componente especificado. Esto aade normalmente oyentes al

componente y/o a su(s) modelo(s), para avisar al delegado UI cuando ocurran cambios en el estado que requieran que se actualice la vista.
uninstallUI(JComponent c) Borra este ComponentUI y cualquier oyente aadido por installUI() del componente

especificado y/o de su(s) modelo(s).


update(Graphics g, JComponent c)

Si el componente es opaco debera pintar su fondo y entonces llamar a paint(Graphics g,


JComponent c). paint(Graphics g, JComponent c)

Coge toda la informacin necesaria del componente y posiblemente de su(s) modelo(s) para dibujarlo correctamente.
getPreferredSize(JComponent c)

Devuelve el tamao preferido del componente especificado por el ComponentUI.


getMinimumSize(JComponent c)

Devuelve el tamao mnimo del componente especificado por el ComponentUI.


getMaximumSize(JComponent c)

Devuelve el tamao mximo del componente especificado por el ComponentUI. Para obligar a usar un delegado UI especfico podemos usar el mtodo setUI() del componente (observe que setUI() est declarado como protected en JComponent porque slo tiene sentido en subclases de JComponent):

JButton m_button = new JButton(); m_button.setUI((MalachiteButtonUI) MalachiteButtonUI.createUI(m_button));

La mayor parte de los delegados UI se construyen de manera que conocen un componente y su(s) modelo(s) slo mientras llevan a cabo tareas de dibujo o de vista-controlador. Swing evita normalmente asociar delegados UI a un componente determinado (a causa de la instancia esttica). De todos modos, nada nos impide asignar el nuestro propio como demuestra el cdigo anterior.
Nota: La clase JComponent define mtodos para asignar delegados UI porque las declaraciones de mtodos no implican cdigo especfico de un componente. Esto no es posible con modelos de datos porque no hay un interface de modelo del que todos ellos desciendan (p.e. no hay una clase base como ComponentUI para los modelos Swing). Por esta razn los mtodos para asignar modelos se definen en las subclases de JComponent que sea necesario.

1.4.2 Pluggable look-and-feel


Swing incluye varios conjuntos de delegados UI. Cada conjunto contiene implementaciones de ComponentUI para casi todos los componentes Swing y podemos llamar a estos conjuntos una implementacin de look-and-feel o pluggable look-and-feel (PLAF). El paquete javax.swing.plaf se componen de clases abstractas que derivan de ComponentUI, y las clases del paquete javax.swing.plaf.basic descienden de ellas para implementar el Basic look-and-feel. ste es un conjunto de delegados UI que se usan como base para construir el resto de clases de look-and-feel. (Observe que el Basic look-and-feel no se puede usar directamente ya que BasicLookAndFeel es una clase abstracta.) Hay tres implementaciones de pluggable look-and-feel que descienden de Basic lookand-feel: Windows: com.sun.java.swing.plaf.windows.WindowsLookAndFeel CDE\Motif: com.sun.java.swing.plaf.motif.MotifLookAndFeel Metal (por defecto): javax.swing.plaf.metal.MetalLookAndFeel Hay tambin un MacLookAndFeel que simula las interfaces de usuario de Macintosh, pero no viene con Java 2 y se debe descargar separadamente. Las libreras de los Windows y Macintosh pluggable look-and-feel slo se soportan en la plataforma correspondiente. El multiplexing look-and-feel, javax.swing.plaf.multi.MultiLookAndFeel, extiende todas las clases abstractas de javax.swing.plaf. Est diseado para permitir que combinaciones de lookand-feels se usen simultneamente, y est enfocado pero no limitado, al uso con look-and-feels de Accesibilidad. El trabajo de cada delegado UI multiplexado es manejar cada uno de sus delegados UI hijos. Todos los paquetes look-and-feel contienen una clase que desciende la clase abstracta
javax.swing.LookAndFeel: BasicLookAndFeel, MetalLookAndFeel, WindowsLookAndFeel, etc. Esas son los puntos centrales de acceso a cada paquete de look-and-feel. Las usamos cuando cambiamos el look-and-feel actual, y la clase UIManager (que maneja los lookand-feels instalados) los usa para acceder a la tabla UIDefaults del look-and-feel actual (que entre

otras cosas contiene los nombres de las clases de los delegados UI del look-and-feel correspondientes a cada componente Swing). Para cambiar el look-and-feel actual de una aplicacin, tenemos simplemente que llamar al mtodo setLookAndFeel() de UIManager, pasndole el nombre completo del LookAndFeel que vamos a usar. El cdigo siguiente se puede usar para llevar esto a cabo en tiempo de ejecucin:

10

try { UIManager.setLookAndFeel( "com.sun.java.swing.plaf.motif.MotifLookAndFeel"); SwingUtilities.updateComponentTreeUI(myJFrame); } catch (Exception e) { System.err.println("Could not load LookAndFeel"); } SwingUtilities.updateComponentTreeUI() informa a todos los hijos del componente

especificado que el look-and-feel ha cambiado y que necesitan reemplazar sus delegados UI por los del tipo especificado.

1.4.3 Dnde estn los delegados UI?


Hemos hablado de ComponentUI, y de los paquetes LookAndFeel donde se encuentran las implementaciones, pero no hemos mencionado nada acerca de las clases especficas de los delegados UI que derivan de ComponentUI. Todas las clases abstractas del paquete javax.swing.plaf descienden de ComponentUI y se corresponden con un componente Swing determinado. El nombre de cada clase sigue con el esquema general (sin la J) aadindole el sufijo UI. Por ejemplo, LabelUI desciende de ComponentUI y es el delegado base usado por JLabel. Estas clases son extendidas por implementaciones concretas como los paquetes basic y multi. Los nombres de estas subclases siguen el esquema general de aadir un prefijo con el nombre del look-andfeel al nombre de la superclase. Por ejemplo, BasicLabelUI y MultiLabelUI descienden ambas de LabelUI y se encuentran en los paquetes basic y multi respectivamente. La figura 1.7 muestra la jerarqua de LabelUI.

Figura 1.7 Jerarqua de LabelUI

<<fichero figure1-7.gif>>

Se espera que la mayora de las implementaciones de look-and-feel extiendan las clases definidas en el paquete basic, o las usen directamente. Los delegados UI de Metal, Motif, y Windows estn construidos encima de las versiones de Basic. Sin embargo, el Multi look-and-feel, es la nica de las implementaciones que no desciende de Basic, y es simplemente un medio para permitir instalar un nmero arbitrario de delegados UI en un componente determinado. La figura 1.7 debera enfatizar el hecho de que Swing suministra un gran numero clases de delegados UI. Si quisiramos crear una implementacin completa de pluggable look-and-feel, queda claro que supondra un gran esfuerzo y llevara bastante tiempo. En el captulo 21 aprenderemos cosas sobre este proceso, as como a modificar y trabajar con los look-and-feels existentes.

11

Captulo 2. Mecnicas de Swing


En este captulo: Cambiando el tamao y la posicin de JComponent, y sus propiedades Manejo y lanzamiento de eventos Multitarea Temporizadores Los servicios de AppContext Interior de los temporizadores y TimerQueue JavaBeans Fuentes, Colores, Grficos y texto Usando el rea de recorte de Graphics Depuracin de Grficos Pintado y validacin Manejo del foco Entrada de teclado, KeyStrokes, y Actions SwingUtilities

2.1 Cambiando el tamao y la posicin de JComponent y sus propiedades


2.1.1 Propiedades
Todos los componentes Swing cumplen la especificacin de los JavaBeans. En la seccin 2.7 veremos esto en detalle. Entre las cinco caractersticas que debe soportar un JavaBean se encuentra un conjunto de propiedades y sus mtodos de acceso asociados. Una propiedad es una variable global, y sus mtodos de acceso, si tiene alguno, son normalmente de la forma setPropertyname(), getPropertyname() o isPropertyname(). Una propiedad que no tienen ningn evento asociado a un cambio en su valor se llama una propiedad simple. Una propiedad ligada (bound property) es aquella para la que se lanzan PropertyChangeEvents despus de un cambio en su estado. Podemos registrar nuestros PropertyChangeListeners para escuchar PropertyChangeEvents a travs del mtodo addPropertyChangeListener() de JComponent. Una propiedad restringida (constrained property) es aquella para la que se lanzan PropertyChangeEvents justo antes de que ocurra un cambio en su estado. Podemos resgistrar VetoableChangeListeners que escuchen a PropertyChangeEvents por medio del mtodo addVetoableChangeListener() de JComponent. Se puede vetar un cambio en el cdigo de manejo de eventos de un VetoableChangeListener lanzando una PropertyVetoException. (Slo hay una clase en

12

Swing con propiedades restringidas: JInternalFrame).


Nota: Todos estos oyentes y eventos estn definidos en el paquete java.awt.beans. Los PropertyChangeEvents llevan consigo tres segmentos de informacin: nombre de la

antiguo, y el nuevo. Los Beans pueden usar instancias de para manejar el lanzamiento de PropertyChangeEvents correspondientes a cada propiedad ligada, a todos los oyentes registrados. De manera similar, una instancia de VetoableChangeSupport se puede usar para manejar el envo de todos los PropertyChangeEvents correspondientes a cada propiedad restringida.
PropertyChangeSupport

propiedad,

el

valor

Swing introduce una nueva clase llamada SwingPropertyChangeSupport (definida en javax.swing.event) que es una subclase casi idntica de PropertyChangeSupport. La diferencia es que SwingPropertyChangeSupport se ha construido para que sea ms eficiente. Lo consigue sacrificando la seguridad entre los hilos, que, como veremos ms tarde en este captulo, no es asunto de Swing si se siguen consistentemente las reglas generales de la multitarea (porque todo el procesamiento de eventos debera llevarse a cabo en un solo hilo-el hilo de despacho de eventos). Por lo tanto, si confiamos en que nuestro cdigo ha sido construido de manera segura respecto a los hilos, deberamos usar esta versin ms eficiente, en lugar de PropertyChangeSupport.
Nota: No hay equivalente en Swing para VetoableChangeSupport porque slo hay cuatro propiedades restringidas en Swing--todas definidas en JInternalFrame.

Swing introduce un nuevo tipo de propiedad que podemos llamar de cambio (change property), a falta de un nombre dado. Usamos ChangeListeners para escuchar ChangeEvents que se lanzan cuando cambia el estado de estas propiedades. Un ChangeEvent slo lleva consigo un segmento de informacin: la fuente del evento. Por esta razn, las propiedades de cambio son menos poderosas que las propiedades ligadas y que las restringidas, pero estn ms extendidas. Un JButton, por ejemplo, enva eventos de cambios todas las veces que se arma (se pulsa por primera vez), se presiona, o se suelta (ver captulo 5). Otro nuevo aspecto en el estilo de las propiedades que introduce Swing es la nocin de propiedades cliente (client properties). Estas son bsicamente pares clave/valor que se guardan en una Hashtable facilitada por todos los componentes Swing. Esto permite aadir y borrar propiedades en tiempo de ejecucin, y se usa a menudo como un sitio donde guardar datos sin tener que construir una nueva subclase.
Peligro: Las propiedades cliente pueden parecer una forma fantstica de aadir soporte al cambio de propiedades para componentes personalizados, pero se nos recomienda explcitamente no hacerlo: El diccionario clientProperty no est pensado para soportar un alto grado de extensiones de JComponent y no se debera considerar como una alternativa a la creacin de subclases cuando se disea un nuevo componente.API

Las propiedades cliente son ligadas: cuando una de ellas cambia, se enva un PropertyChangeEvent a todos los PropertyChangeListeners registrados. Para aadir una propiedad a la Hashtable de propiedades cliente de un componente, tenemos que hacer lo siguiente:
miComponente.putClientProperty("minombre", miValor);

Para recuperar una propiedad cliente:


miObjeto = miComponente.getClientProperty("minombre");

13

Para borrar una propiedad cliente le asignamos un valor null:


miComponente.putClientProperty("minombre", null);

Por ejemplo, JDesktopPane usa una propiedad cliente para controlar la visualizacin del contorno mientras arrastramos JInternalFrames (esto funcionar sin importar el L&F que se est usando):
miDesktop.putClientProperty("JDesktopPane.dragMode", "outline"); Nota: Puede localizar que propiedades tienen tienen eventos de cambio asociados con ellas, as como cualquier otro tipo de evento, inspeccionando el cdigo fuente de Swing. A no ser que est usando Swing para interfaces simples, le recomendamos que se acostumbre a esto.

Cinco componentes Swing tienen propiedades cliente especiales a las que solo el Metal L&F presta atencin. Concretamente son estas:
JTree.lineStyle Un String que se usa para especificar si las relaciones entro los nodos se muestran como

lneas angulosas (Angled), lneas horizontales que definen los lmites de las celdas (Horizontal -- por defecto), o no se muestran lneas (None).
JScrollBar.isFreeStanding Un Boolean que se usa para especificar si JScrollbar tendr un borde (Boolean.FALSE - por defecto) o slo las partes superior e izquierda (Boolean.TRUE). JSlider.isFilled Un Boolean que especifica si la parte ms baja de un deslizador (JSlider) debe estar rellena (Boolean.TRUE) o no (Boolean.FALSE -- por defecto). JToolBar.isRollover Un Boolean que sirve para determinar si un botn de la barra de herramientas muestra un

borde grabado slo cuando el puntero del ratn se encuentra entre sus lmites y ningn borde cuando no (Boolean.TRUE), o se usa siempre un borde grabado (Boolean.FALSE -- por defecto).
JInternalFrame.isPalette Un Boolean que especifica si se usa un borde muy fino (Boolean.TRUE) o el borde normal (Boolean.FALSE -- por defecto). En Java 2 FCS no se usa esta propiedad.

2.1.2 Cambiando el tamao y la posicin


Como JComponent desciende de java.awt.Container, hereda todas las funcionalidades de posicin y tamao a las que estamos acostumbrados. Para manejar el tamao preferido, mximo y mnimo de un componente disponemos de los siguientes mtodos:
setPreferredSize(), getPreferredSize()

El tamao deseable de un componente. Lo usan la mayora de los administradores de disposicin (Layout Managers) para dimensionar los componentes. setMinimumSize(), getMinimumSize() Usados durante el posicionamiento para especificar los lmites inferiores de las dimensiones del componente. setMaximumSize(), getMaximumSize() Usados durante el posicionamiento para especificar los lmites superiores de las dimensiones del componente. Cada uno de los mtodos setXX()/getXX() acepta/devuelve una instancia de Dimension. Aprenderemos ms de lo que significan estos tamaos dependiendo de cada administrador de disposicin en el captulo 4. Si un administrador de disposicin presta atencin o no a estos tamaos

14

depende solamente de la implementacin de dicho administrador. Es perfectamente factible construir un administrador que simplemente los ignore todos, o que slo preste atencin a uno. El dimensionado de los componentes en un contenedor es especfico de cada administrador de disposicin. El mtodo setBounds() de JComponent se puede usar para asignar a un componente el tamao y la posicin dentro de su contenedor padre. Este mtodo est sobrecargado, y puede tomar tanto parmetros de tipo Rectangle (java.awt.Rectangle) como cuatro parmetros de tipo int que represente la altura, la anchura y las coordenadas x e y. Por ejemplo, estas dos formas son equivalentes:
miComponent.setBounds(120,120,300,300); Rectangle rec = new Rectangle(120,120,300,300); miComponent.setBounds(rec);

Ver que setBounds() no pasar por encima de ninguna de las polticas de posicionamiento activas a causa de un administrador de disposicin de un contenedor padre. Por esta razn una llamada a setBounds() puede parecer ignorada en determinadas situaciones porque intent hacer su trabajo, pero el componente fue obligado a volver a su tamao original por el administrador de disposicin (los administradores de disposicin siempre tienen la ltima palabra determinando el tamao de un componente).
setBounds() se usa normalmente para manejar componentes hijos en contenedores sin administrador de disposicin (como JLayeredPane, JDesktopPane, y JComponent). Por ejemplo, usamos normalmente setBounds() cuando aadimos un JInternalFrame a un JDesktopPane.

El tamao de un componente se puede obtener al estilo de AWT:


int h = miComponente.getHeight(); int w = miComponente.getWidth();

El tamao se puede recuperar tambin como una instancia de Rectangle o de Dimension:


Rectangle rec2 = miComponente.getBounds(); Dimension dim = miComponente.getSize(); Rectangle contiene cuatro propiedades accesibles pblicamente que describen su posicin y su

tamao:
int int int int recX = rec2.x; recY = rec2.y; recAnchura = rec2.width; recAltura = rec2.height;

Dimension contiene dos propiedades accesibles pblicamente que describen su tamao: int dimAnchura = dim.width; int dimAltura = dim.height;

Las coordenadas que una instancia de Rectangle devuelve usando su mtodo getBounds() representan la situacin de un componente dentro de su padre. Estas coordenadas se pueden obtener tambin usando los mtodos getX() y getY(). Adicionalmente, podemos determinar la posicin de un componente dentro de su contenedor mediante el mtodo setLocation(int x, int y).
JComponent tambin mantiene una alineacin. La alineacin horizontal o vertical se puede especificar

con valores reales (float) entre 0.0 y 1.0: 0.5 significa el centro, valores ms cercanos a 0.0 significan izquierda o arriba, y ms cercanos a 1.0 significan derecha o abajo. Los correspondientes mtodos de

15

JComponent son: setAlignmentX(float f); setAlignmentY(float f);

Estos valores se usan slo en contenedores que se manejan mediante BoxLayout o OverlayLayout.

2.2 Manejo y lanzamiento de eventos


Los eventos ocurren en cualquier momento que se pulsa una tecla o un botn del ratn. La forma en la que los componentes reciben y procesan los eventos no ha cambiado desde el JDK1.1. Los componentes Swing pueden generar diferentes tipos de eventos, incluyendo los de java.awt.event y por supuesto, los de javax.swing.event. Algunos de estos nuevos tipos de eventos de Swing son especficos del componente. Todos los tipos de eventos se representan por un objeto, que como mnimo, identifica la fuente del evento, y a menudo lleva informacin adicional acerca de la clase especfica de evento que se trata, e informacin acerca del estado de la fuente del evento antes y despus de que ste se generase. Las fuentes de eventos son normalmente componentes o modelos, pero hay tambin clases de objetos diferentes que pueden generar eventos. Como vimos en el ltimo captulo, para recibir la notificacin de eventos, debemos registrar oyentes en el objeto destino. Un oyente es una implementacin de alguna de las clases XXListener (donde XX es un tipo de evento) definidas en los paquetes java.awt.event, java.beans, y javax.swing.event. Como mnimo, siempre hay un mtodo definido en cada interface al que se le pasa el XXEvent correspondiente como parmetro. Las clases que soportan la notificacin de XXEvents implementan generalmente el interface XXListener, y tienen soporte para registrar y cancelar el registro de estos oyentes a travs del uso de los mtodos addXXListener() y removeXXListener() respectivamente. La mayora de los destinos de eventos permiten tener registrados cualquier nmero de oyentes. Igualmente, cualquier instancia de un oyente se puede registrar para recibir eventos de cualquier nmero de fuentes de stos. Normalmente, las clases que soportan XXEvents ofrecen mtodos fireXX() protegidos (protected) que se usan para construir objetos de eventos y para enviarlos a los manejadores de eventos para su proceso.

2.2.1

La clase javax.swing.event.EventListenerList

EventListenerList es un vector de pares XXEvent/XXListener. JComponent y cada uno de sus descendientes usa una EventListenerList para mantener sus oyentes. Todos los modelos por defecto mantienen tambin oyentes y una EventListenerList. Cuando se aade un oyente a un componente Swing o a un modelo, la instancia de Class asociada al evento (usada para identificar el tipo de evento) se aade a un vector EventListenerList, seguida del oyente. Como estos pares se

guardan en un vector en lugar de en una coleccin modificable (por eficiencia), se crea un nuevo vector usando el mtodo System.arrayCopy() en cada adicin o borrado. Cuando se reciben eventos, se recorre la lista y se envan eventos a todos los oyentes de un tipo adecuado. Como el vector est ordenado de la forma XXEvent, XXListener, YYEvent, YYListener, etc., un oyente correspondiente a un determinador tipo de evento est siempre el siguiente en el vector. Esta estrategia permite unas rutinas de manejo de eventos muy eficientes (ver seccin 2.7.7). Para seguridad entre procesos, los mtodos para aadir y borrar oyentes de una EventListenerList sincronizan el acceso al vector cuando lo manipulamos.
JComponent define sus EventListenerList como un campo protegido llamado listenerList,

as que todas sus subclases lo heredan. Los componentes Swing manejan la mayora de sus oyentes directamente a travs de listenerList.

2.2.2

Hilo de despacho de eventos (Event-dispatching thread)

Todos los eventos se procesan por los oyentes que los reciben dentro del hilo de despacho de eventos

16

(una instancia de java.awt.EventDispatchThread). Todo el dibujo y posicionamiento de componentes debera llevarse a cabo en este hilo. El hilo de despacho de eventos es de vital importancia en Swing y AWT, y juega un papel principal manteniendo actualizado el estado y la visualizacin de un componente en una aplicacin bajo control. Asociada con este hilo hay una cola FIFO (primero que entr - primero que sale) de eventos -- la cola de eventos del sistema (una instancia de java.awt.EventQueue). Esta cola se rellena, como cualquier cola FIFO, en serie. Cada peticin toma su turno para ejecutar el cdigo de manejo de eventos, que puede ser para actualizar las propiedades, el posicionamiento o el repintado de un componente. Todos los eventos se procesan en serie para evitar situaciones tales como modificar el estado de un componente en mitad de un repintado. Sabiendo esto, tenemos que ser cuidadosos de no despachar eventos fuera del hilo de despacho de eventos. Por ejemplo, llamar directamente a un mtodo fireXX() desde un hilo de ejecucin separado es inseguro. Tenemos que estar seguros tambin de que el cdigo de manejo de eventos se puede ejecutar rpidamente. En otro caso toda la cola de eventos del sistema se bloqueara esperando a que terminase el proceso de un evento, el repintado, o el posicionamiento, y nuestra aplicacin parecera bloqueada o congelada.

2.3 Multitarea
Para ayudar a asegurarnos que todo nuestro cdigo de manejo de eventos se ejecuta slo dentro del hilo de despacho de eventos, Swing provee una clase de mucha utilidad, que entre otras cosas, nos permite aadir objetos Runnable a la cola de eventos del sistema. Esta clase se llama SwingUtilities y contiene dos mtodos en los que estamos interesados aqu: invokeLater() e invokeAndWait(). El primer mtodo aade un Runnable a la cola de eventos del sistema y vuelve inmediatamente. El segundo mtodo aade un Runnable y espera a que sea despachado, entonces vuelve una vez que termina. La sintaxis bsica de cada una es la siguiente:
Runnable trivialRunnable = new Runnable() { public void run() { hazTrabajo(); // hace algn trabajo } }; SwingUtilities.invokeLater(trivialRunnable); try { Runnable trivialRunnable2 = new Runnable() { public void run() { hazTrabajo(); // hace algn trabajo } }; SwingUtilities.invokeAndWait(trivialRunnable2); } catch (InterruptedException ie) { System.out.println("...Espera del hilo interrumpida!"); } catch (InvocationTargetException ite) { System.out.println( "...excepcin no capturada dentro de run() en Runnable"); }

Como estos Runnables se colocan en la cola de eventos del sistema para ejecutarse dentro del hilo de despacho de eventos, tenemos que tener cuidado de que se ejecuten tan rpidamente como cualquier otro cdigo de manejo de eventos. En los dos ejemplo de arriba, si el mtodo hacerTrabajo() hiciese alguna cosa que le llevase un largo tiempo (como cargar un fichero grande) veramos que la aplicacin se congelara hasta que la carga finalizase. En los casos que conlleven mucho tiempo como este, deberamos usar nuestro propio hilo separado para mantener la sensibilidad de la aplicacin.

17

El cdigo siguiente muestra la forma tpica de construir nuestro propio hilo que haga un trabajo costoso en tiempo. Para actualizar de manera segura el estado de algn componente dentro de este hilo, tenemos que usar invokeLater() o invokeAndWait():
Thread trabajoDuro = new Thread() { public void run() { hacerTrabajoPesado(); // hace algn trabajo costoso en tiempo SwingUtilities.invokeLater( new Runnable () { public void run() { actualizaComponentes(); // actualiza el estado de lo(s) componente(s) } }); } }; trabajoDuro.start(); Nota: se debera usar invokeLater() en lugar de invokeAndWait() siempre que sea posible. Si tenemos que usar invokeAndWait(), debemos estar seguros de que no hay zonas sensibles a bloqueos (p.e. bloques sincronizados) mantenidas por el hilo que llama, que otro hilo podra necesitar durante la operacin.

Esto soluciona el problema de la sensibilidad, y aade cdigo relativo al componente al hilo de despacho de eventos, pero no se puede considerar an amigable al usuario. Normalmente el usuario debera ser capaz de interrumpir una tarea costosa en tiempo. Si estamos esperando una conexin a una red, no queremos esperar indefinidamente si el destino no existe. En casi todas las circunstancias el usuario debera tener la opcin de interrumpir nuestro hilo. El pseudocdigo siguiente nos muestra una manera tpica de llevar esto a cabo, donde stopButton hace que el hilo sea interrumpido, actualizando el estado del componente adecuadamente:
Thread trabajoDuro = new Thread() { public void run() { hacerTrabajoPesado(); SwingUtilities.invokeLater( new Runnable () { public void run() { actualizaComponentes(); // actualiza el estado de lo(s) componente(s) } }); } }; trabajoDuro.start(); public void hacerTrabajoPesado() { try { // [alguna clase de bucle] // ...si, en algn punto, esto supone cambiar // el estado del componente tenemos que usar // invokeLater aqu porque este es un hilo // separado. // // Tenemos como mnimo que hacer una cosa de // las siguientes: // 1. Chequear peridicamente Thread.interrupted() // 2. Dormir o esperar peridicamente if (Thread.interrupted()) { throw new InterruptedException(); } Thread.wait(1000); }

18

catch (InterruptedException e) { // hacer que alguien sepa que hemos sido interrumpidos // ...si esto supone cambiar el estado del componente // tenemos que usar invokeLater aqu. } } JButton stopButton = new JButton("Stop"); ActionListener stopListener = new ActionListener() { public void actionPerformed(ActionEvent event) { // interrumpir el hilo y hacer que el usuario sepa que // el hilo ha sido interrumpido deshabilitando el botn // de stop. // ...esto se har dentro del hilo de despacho de eventos workHarder.interrupt(); stopButton.setEnabled(false); } }; stopButton.addActionListener(stopListener);

Nuestro stopButton interrumpe el hilo workHarder cuando se pulsa. Hay dos formas de que hacerTrabajoPesado() sepa si workHarder, el hilo en el que se ejecuta, ha sido interrumpido. Si est durmiendo o esperando, una InterruptedException ser lanzada, que podremos capturar y procesar adecuadamente. La otra manera de detectar la interrupcin es chequear peridicamente el estado llamando a Thread.interrupted(). Esto se usa para construir y mostrar dilogos complejos, en procesos de E/S que conllevan cambios en el estado del componente (como cargar un documento en un componente de texto), carga de clases o clculo intensivo, para esperar algn mensaje o el establecimiento de una conexin de red, etc.
Referencia: Los miembros del equipo de Swing han escrito algn artculo sobre como utilizar hilos con Swing, y han construido una clase llamada SwingWorker que hace el manejo del tipo de multitarea descrito aqu ms conveniente. Ver http://java.sun.com/products/jfc/tsc/archive/tech_topics_arch/threads/threads.html

2.3.1 Casos especiales


Hay algunos casos especiales en los cuales no necesitamos que cdigo que afecte al estado de componentes se ejecute en el hilo de despacho de eventos: 1. Algunos mtodos en Swing, aunque pocos y distantes entre s, estn marcados como seguros respecto a los hilos y no necesitan consideracin especial. Algunos mtodos que son seguros respecto a los hilos pero que no estn marcados son: repaint(), revalidate(), e invalidate(). 2. Un componente se puede construir y manipular de la forma que queramos, sin tener cuidado con los hilos, siempre que no se haya tenido en cuenta (realized) (lo que quiere decir que no se haya mostrado o que no haya encolado una peticin de repintado). Los contenedore de ms alto nivel (JFrame, JDialog, JApplet) se tienen en cuenta una vez que se ha llamado a setVisible(true), show(), o pack() en ellos. Observe tambin que se considera que un componente se tiene en cuenta tan pronto como se aade a un contenedor que se tiene en cuenta. 3. Cuando trabajamos con applets Swing (JApplets) todos los componentes se pueden construir y manipular sin prestar atencin a los hilos hasta que se llama al mtodo start(), lo que sucede despus del mtodo init().

19

2.3.2 Cmo construimos nuestros mtodos para que sean seguros respecto a los hilos?
Esto es realmente fcil. Aqu tenemos una plantilla de mtodo seguro respecto a los hilos, que podemos usar para garantizar que el cdigo de este mtodo se ejecuta slo en el hilo de despacho de eventos:
public void hacerTrabajoSeguro() { if (SwingUtilities.isEventDispatchThread()) { // // hacer todo el trabajo aqu... // } else { Runnable llamaAhacerTrabajoSeguro = new Runnable() { public void run() { hacerTrabajoSeguro(); } }; SwingUtilities.invokeLater(llamaAhacerTrabajoSeguro); } }

2.3.3 Cmo funcionan invokeLater() e invokeAndWait()? clase javax.swing.SystemEventQueueUtilities [privada de paquete]


Cuando SwingUtilities recibe un objeto Runnable a travs de invokeLater(), lo pasa postRunnable() de una clase llamada inmediatamente al mtodo SystemEventQueueUtilities. Si el Runnable se recibe a travs de invokeAndWait(), se comprueba primero que el hilo actual no sea el hilo de despacho de eventos. (Sera fatal permitir invocar a invokeAndWait() desde el hilo de despacho de eventos) Si este es el caso se lanza un error. En otro caso, construimos un Object que usaremos como bloqueo en una seccin crtica (p.e. un bloque sincronizado). Este bloque contiene dos instrucciones. La primera enva el Runnable al mtodo postRunnable() de SystemEventQueueUtilities, junto con una referencia al objeto de bloqueo. La segunda espera al objeto de bloqueo, de manera que el hilo no avanzar hasta que el objeto sea notificado--por ello invoke and wait (invocar y esperar). Lo primero que hace el mtodo postRunnable() es comunicarse con la cola privada SystemEventQueue, una clase interna de SystemEventQueueUtilities, para obtener un referencia a la cola de eventos del sistema. Se envuelve entonces el Runnable en una instancia de RunnableEvent, otra clase interna privada. Al constructor de RunnableEvent se le pasan un Runnable y un Object, que representa al objeto de bloqueo (null si se llam a invokeLater()) como parmetros. La clase RunnableEvent es una subclase de AWTEvent, y define su propio ID del evento como un int esttico -- EVENT_ID. (Vea que siempre que definimos nuestros propios eventos debemos usar un ID mayor que el valor de AWTEvent.RESERVED_ID_MAX.) El EVENT_ID de RunnableEvent es AWTEvent.RESERVED_ID_MAX + 1000. RunnableEvent contiene tambin una instancia esttica de un RunnableTarget, otra clase interna privada ms. RunnableTarget es una subclase de Component y su nico propsito es actuar como fuente y destino de RunnableEvents. Cmo hace esto RunnableTarget? Su constructor habilita los eventos con un ID que concuerde con el ID del RunnableEvent:
enableEvents(RunnableEvent.EVENT_ID);

Tambin sobrecarga el mtodo protegido de Component processEvent() para recibir RunnableEvents. Dentro de este mtodo lo primero que se hace es ver si en efecto el evento pasado es

20

una instancia de RunnableEvent. Si lo es, es pasado al mtodo processRunnableEvent() de SystemEventQueueUtilities (esto ocurre una vez que el RunnableEvent ha sido despachado de la cola de eventos del sistema.) Volvamos de nuevo a RunnableEvent. El contructor RunnableEvent llama al constructor de su superclase (AWTEvent) pasndole una instancia de RunnableTarget como la fuente del evento, y EVENT_ID como el ID del evento. Mantiene tambin referencias al Runnable y al objeto de bloqueo. Resumiendo: cuando llamamos a invokeLater() o a invokeAndWait(), el Runnable que les pasamos es pasado al mtodo SystemEventQueueUtilities.postRunnable() junto al objeto de bloqueo al que est esperando el hilo que les invoca (si fue un invokeAndWait()). Este mtodo intenta entonces obtener acceso a la cola de eventos del sistema y luego envuelve el Runnable y el objeto de bloqueo en una instancia de RunnableEvent. Una vez que se ha creado la instancia de RunnableEvent, el mtodo postRunnable() (en el que hemos estado todo este tiempo) comprueba si ha obtenido el acceso a la cola de eventos del sistema. Esto ocurrir slo si no estamos ejecutndonos como un applet, ya que los applets no tienen acceso directo a la cola de eventos del sistema. En este punto, tenemos dos posibles caminos dependiendo de si nos estamos ejecutando como un applet o como una aplicacin:

Aplicaciones:
Como tenemos acceso directo a la cola de eventos del sistema de AWT simplemente ponemos el RunnableEvent y volvemos. Entonces el evento es despachado en algn punto del hilo de despacho de eventos envindolo al mtodo processEvent() de RunnableTarget, el cual lo enva entonces al mtodo processRunnableEvent(). Si no se ha usado bloqueo (se llam a invokeLater()) el Runnable se ejecuta y hemos terminado. Si se ha usado un bloqueo (se llam a invokeAndWait()), entramos en un bloque sincronizado en el objeto de bloqueo de forma que nadie ms puede acceder al objeto mientras ejecutamos el Runnable. Recuerde que este es el mismo objeto de bloqueo al que est esperando el hilo que invoc a SwingUtilities.invokeAndWait(). Una vez que el Runnable termina, lo notificamos a ese objeto, que despierta al hilo invocante y hemos acabado.

Applets:
SystemEventQueueUtilities hace algunas cosas muy interesantes para rodear el hecho de que los

applets no tengan acceso directo a la cola de eventos del sistema. Para abreviar una tarea muy complicada, un RunnableCanvas (una clase interna privada que desciende de java.awt.Canvas) invisible se mantiene para cada applet y se guarda en una Hashtable esttica usando el hilo invocante como clave. Un Vector de RunnableEvents se mantiene tambin y, en lugar de aadir manualmente un evento a la cola de eventos del sistema, un RunnableCanvas aade una peticin de repaint(). Entonces, cuando se despacha la peticin de repintado en el hilo de despacho de eventos. El mtodo paint() apropiado de RunnableCanvas es llamado como se esperaba. Este mtodo ha sido construido para que localice cualquier RunnableEvent (guardado en el Vector) asociado con un determinado RunnableCanvas, y lo ejecute (algo rebuscado, pero funciona).

2.4 Temporizadores
clase javax.swing.Timer
Puede pensar en Timer como un hilo nico que Swing provee convenientemente para lanzar ActionEvents a intervalos especificados (aunque no es as como exactamente funciona un Timer internamente, como veremos en la seccin 2.6). Los ActionListeners se pueden registrar para que reciban estos eventos tal y como los registramos en botones, o en otros componentes. Para crear un Timer simple que lance ActionEvents cada segundo podemos hacer algo como lo siguiente:

21

import java.awt.event.*; import javax.swing.*; class TimerTest { public TimerTest() { ActionListener act = new ActionListener() { public void actionPerformed(ActionEvent e) { System.out.println("Swing is powerful!!"); } }; Timer tim = new Timer(1000, act); tim.start(); while(true) {}; } public static void main( String args[] ) { new TimerTest(); } }

En primer lugar configuramos un ActionListener para que reciba ActionEvents. Entonces construimos un nuevo Timer pasando el tiempo entre eventos en milisegundos, el retraso (delay), y un ActionListener al que envirselos. Finalmente llamamos al mtodo start() de Timer para activarlo. Como no hay ejecutndose una GUI el programa saldr inmediatamente, por lo tanto ponemos un bucle para permitir al Timer que siga con su trabajo indefinidamente (explicaremos por qu es necesario esto en la seccin 2.6). Cuando ejecute este cdigo ver que se muestra Swing is powerful!! en la salida estndar cada segundo. Observe que Timer no lanza un evento justo cuando se inicia. Esto es a causa de su retraso inicial (initial delay) que por defecto equivale al tiempo que se le pasa al constructor. Si queremos que el Timer lance un evento justo cuando se inicia debemos poner el retraso inicial a 0 usando su mtodo setInitialDelay(). En cualquier momento podemos llamar a stop() para detener el Timer y start() para reiniciarlo (start() no hace nada si ya se est ejecutando). Podemos llamar a restart() en un Timer para que empiece de nuevo todo el proceso. El mtodo restart() es slo una abreviatura para llamar a stop() y start() secuencialmente. Podemos poner el retraso de un Timer usando su mtodo setDelay() y decirle si debe repetirse o no usando el mtodo setRepeats(). Una vez que hemos hecho que un Timer no se repita, slo lanzar una accin cuando se inicie (o si ya se est ejecutando), y entonces se detendr. El mtodo setCoalesce() permite que varios Timer de lanzamiento de eventos se combinen en uno. Esto puede ser til durante sobrecargas, cuando el hilo de la TimerQueue (que veremos ms adelante) no tiene suficiente tiempo de proceso para manejar todos sus Timers. Los Timers son fciles de usar y a menudo se pueden utilizar como una herramienta conveniente para construir nuestros propios hilos. Sin embargo, hay mucho ms por detrs que merece un poco de atencin. Antes de que veamos a fondo como trabajan los Timers, echaremos un vistazo al servicio de mapeo de clases de Swing (SecurityContext-to-AppContext) para applets, as como a la forma en la que las aplicaciones manejan sus clases de servicio (tambin usando AppContext). Si no siente curiosidad por como comparte Swing las clases de servicio, puede saltarse la siguiente seccin. aunque nos referiremos de vez en cuando a AppContext, no significa que sea necesario para entender los detalles.

22

2.5 Los servicios de AppContext


clase sun.awt.AppContext [especfica de la plataforma]
Peligro: AppContext no est pensada para ser usada por cualquier desarrollador, ya que no es parte del API de Java 2. La abordamos aqu slo para facilitar una mejor comprensin de como las clases de servicio de Swing trabajan entre bastidores. AppContext es una tabla de servicio de una aplicacin o de un applet (diremos app para abreviar) que es nica para cada sesin de Java (applet o aplicacin). Para los applets, existe un AppContext separado para cada SecurityContext que corresponde a la base del cdigo del applet. Por ejemplo,

si tenemos dos applets en la misma pgina, cada uno usando cdigo de un directorio diferente, los dos debern tener asociado con ellos un SecurityContext distinto. Si al contrario, se han cargado desde la misma base de cdigo, tendrn que compartir necesariamente un SecurityContext. Las aplicaciones Java no tienen SecurityContexts. En su lugar, se ejecutan en espacios de nombres que son diferenciados por los ClassLoaders. No profundizaremos en los detalles de SecurityContexts o ClassLoaders aqu, pero es suficiente decir que se pueden usar por los SecurityManagers para indicar dominios de seguridad, y la clase AppContext est diseada para aprovecharse de esto permitiendo que haya tan slo una instancia de ella misma por cada dominio de seguridad. De esta forma, applets de diferentes bases de cdigo no pueden acceder al AppContext del otro. Pero, por qu es esto importante? Vamos all... Una instancia compartida (shared instance) es una instancia de una clase que se puede obtener normalmente usando un mtodo esttico definido en esa clase. Cada AppContext mantiene una Hashtable de instancias compartidas disponibles para el dominio de seguridad asociado, y a cada instancia se le denomina como un servicio. Cuando se pide un servicio por primera vez, ste registra su instancia compartida con su AppContext asociado. Esto consiste en crear una nueva instancia de si mismo y aadirla al mapeo clave/valor del AppContext. Una razn por la cual estas instancias compartidas se registran con un AppContext en lugar de ser implementadas como instancias estticas normales, directamente recuperables por la clase de servicio, es por propsitos de seguridad. Los servicios registrados con un AppContext se pueden acceder slo desde apps seguras (trusted apps), mientras que las clases que proveen directamente instancias estticas de si mismas permiten que stas se usen de manera global (requiriendo que implementemos nuestro propio mecanismo de seguridad si queremos limitar el acceso a ellas). Otra razn para ello es la robustez. Cuantos menos applets interacten con otros de manera indocumentada, ms robustos podrn ser. Por ejemplo, imagine que una app intenta acceder a todos los eventos importantes en la EventQueue del sistema (donde se encolan todos los eventos para que sean procesados en el hilo de despacho de eventos) para intentar lograr contraseas. Usando distintas EventQueues en cada AppContext, a los nicos eventos principales que la app tendra acceso sera a los suyos. (Por esto hay slo una EventQueue por cada AppContext) Entonces, cmo accedemos a nuestro AppContext para aadir, borrar o recuperar servicios? AppContext no est pensado para que sea accedido por desarrolladores, pero podemos si realmente lo necesitamos, y esto garantizara que nuestro cdigo no sera certificado como 100% puro nunca, ya que
AppContext no forma parte del ncleo del API. No obstante, as es como se hace: El mtodo esttico AppContext.getAppContext() determina el AppContext correcto a usar dependiendo de si se est ejecutando una aplicacin o una applet. Podemos usar entonces los mtodos put(), get() y remove() del AppContext devuelto para manejar las instancias compartidas. Para lograr esto,

tenemos que implementar nuestros propios mtodos como sigue:

23

private static Object appContextGet(Object key) { return sun.awt.AppContext.getAppContext().get(key); } private static void appContextPut(Object key, Object value) { sun.awt.AppContext.getAppContext().put(key, value); } private static void appContextRemove(Object key) { sun.awt.AppContext.getAppContext().remove(key); }

En Swing, esta funcionalidad est implementada como tres mtodos estticos de SwingUtilities (vea el cdigo fuente de SwingUtilities.java):
static void appContextPut(Object key, Object value) static void appContextRemove(Object key, Object value) static Object appContextGet(Object key)

De todas formas, no podemos acceder a ellos porque son privados del paquete. Estos son los mtodos usados por las clases de servicio de Swing. Alguna de las clases de servicio de Swing que registran instancias compartidas con AppContext son: EventQueue, TimerQueue, ToolTipManager, RepaintManager, FocusManager y UIManager.LAFState (todas sern abordadas en algn punto de este libro). Es tambin interesante que SwingUtilities provee secretamente una instancia invisible de Frame registrado con AppContext para actuar como el padre de todos los JDialogs y JWindows con propietarios a null.

2.6 Interior de los temporizadores y TimerQueue


class javax.swing.TimerQueue [privada de paquete]
Un Timer es un objeto que contiene un pequeo Runnable capaz de despachar ActionEvents a una lista de ActionListeners (guardados en una EventListenerList). Todas las instancias de Timer se manejan por la instancia compartida de TimerQueue (registrada con AppContext). Un TimerQueue es una clase de servicio cuyo trabajo es manejar todas las instancias de Timer en una sesin de Java. La clase TimerQueue provee el mtodo esttico sharedInstance() para recuperar el servicio TimerQueue de AppContext. Siempre que un nuevo Timer se crea y se inicia, es aadido a la TimerQueue compartida, que mantiene una lista de Timers ordenados por el tiempo en el que expiran (p.e. el tiempo que queda para lanzar el prximo evento). La TimerQueue es un demonio (daemon) que se inicia inmediatamente en la instanciacin. Esto sucede cuando se llama a TimerQueue.sharedInstance() por primera vez (cuando se inicia el primer Timer de una sesin de Java). Espera continuamente a que expire el Timer con el tiempo ms cercano. Una vez que esto ocurre enva una seal al Timer para que enve ActionEvents a todos sus oyentes, entonces asigna un nuevo Timer como cabeza de la lista, y finalmente borra el que ha expirado. Si el Timer que ha expirado est en modo de repeticin, se aade de nuevo a la lista el lugar apropiado basndose en su retraso.
Nota: La razn real por la que el ejemplo de Timer de la seccin 2.4 saldra inmediatamente si no ponemos un bucle, es que la TimerQueue es un demonio. Los demonios son hilos de servicio y cuando la JVM slo tiene ejecutndose demonios terminar ya que asume que no se est haciendo trabajo real. Normalmente este comportamiento es el deseable.

Los eventos de un Timer se envan siempre al hilo de despacho de eventos de manera segura respecto a los hilos enviando su objeto Runnable a SwingUtilities.invokeLater().

24

2.7 La arquitectura JavaBeans


Como en este libro estamos interesados en crear aplicaciones Swing, necesitamos comprender y apreciar el hecho de que cada componente Swing sea un JavaBean.
Nota: Si es familiar con el modelo de componentes JavaBeans puede que quiera saltar a la siguiente seccin.

2.7.1 El modelo de componentes JavaBeans


La especificacin de JavaBeans identifica cinco rasgos que todos los beans deben ofrecer. Revisaremos estos rasgos aqu, junto con las clases y mecanismos que los hacen posibles. La primera cosa que hay que hacer es pensar en un componente simple, como un botn, y aplicar lo que aqu veamos a este componente. Segundo, estamos asumiendo un conocimiento bsico del API Java Reflection: Instancias de Class representan clases e interfaces en una aplicacin Java ejecutndose.API Un Method provee informacin de un nico mtodo de una clase o interface y acceso al mismo.API Una Field provee informacin de un nico campo de una clase o interface y acceso dinmico al mismo.API

2.7.2 Introspeccin
La introspeccin es la facultad de descubrir los mtodos, las propiedades, y la informacin de los eventos, de un bean. Esto se consigue usando la clase java.beans.Introspector. Introspector provee mtodos estticos para generar un objeto BeanInfo que contenga toda la informacin que se pueda descubrir de un bean determinado. Esto incluye informacin sobre todas las superclases del bean, a no ser que especifiquemos en que superclase debe detenerse la introspeccin (podemos especificar la profundidad de una introspeccin). El cdigo siguiente recupera toda la informacin que se puede descubrir de un bean:
BeanInfo myJavaBeanInfo = Introspector.getBeanInfo(myJavaBean);

Un objeto BeanInfo divide toda la informacin del bean en varios grupos, algunos de los cuales son: Un BeanDescriptor: provee informacin general descriptiva, tal como un nombre para que se visualice. Un vector de EventSetDescriptors: provee informacin sobre el conjunto de eventos que un bean lanza. Estos se pueden usar, entre otras cosas, para recuperar los mtodos asociados a oyentes de eventos del bean como instancias de Method. Un vector de MethodDescriptors: provee informacin sobre los mtodos accesibles externamente de un bean (incluira por ejemplo a todos los mtodos pblicos). Esta informacin se usa para construir una instancia de Method para cada mtodo. Un vector de PropertyDescriptors: provee informacin sobre las propiedades que un bean mantiene, y que pueden accederse mediante los mtodos get, set, y/o is. Estos objetos se pueden usar para construir instancias de Method y Class correspondientes a los mtodos de acceso y a los tipos de las clases respectivamente de la propiedad.

2.7.3 Propiedades
Como vimos en la seccin 2.1.1, los beans soportan diferentes tipos de propiedades. Propiedades simples son variables que cuando se modifican, el bean no har nada. Las propiedades ligadas y restringidas son

25

variables que cuando se modifican, el bean mandar eventos de notificacin a todos los oyentes. Esta notificacin tiene la forma de un objeto de evento, que contiene el nombre de la propiedad, el valor anterior de la propiedad, y el valor nuevo. En el momento que una propiedad ligada cambia, debera enviar un PropertyChangeEvent. Cuando va a cambiar una propiedad restringida, el bean debera lanzar un PropertyChangeEvent antes de que ocurra el cambio, permitiendo que ste sea vetado. Otros objetos pueden escuchar estos eventos para procesarlos como corresponda (lo que guia la comunicacin). Asociados con las propiedades estn los mtodos setXX()/getXX() e isXX() de los beans. Si un mtodo setXX() est disponible se dice que su propiedad asociada es escribible. Si un mtodo getXX() o isXX() est disponible se dice que la propiedad asociada es legible. Un mtodo isXX() corresponde normalmente a la obtencin de un propiedad booleana (ocasionalmente los mtodos getXX() se usan para esto tambin).

2.7.4 Personalizacin
Las propiedades de un bean estn expuestas a travs de sus mtodos setXX()/getXX() e isXX(), y se pueden modificar en tiempo de ejecucin (o en tiempo de diseo). Los JavaBeans se usan comnmente en entornos de desarrollo (IDE's) donde las hojas de propiedades se pueden mostrar permitiendo que las propiedades de los beans se lean o se escriban (dependiendo de los mtodos de acceso).

2.7.5 Comunicacin
Los Beans estn diseados para enviar eventos que notifican a todos los oyentes registrados con l cuando cambia de valor una propiedad ligada o una restringida. Las apps se construyen registrando oyentes de bean a bean. Como podemos usar la introspeccin para recoger informacin sobre el envo y el recibo de eventos de cualquier bean, las herramientas de diseo pueden aprovechar este conocimiento para permitir una personalizacin ms poderosa en la etapa de diseo. La comunicacin es la unin bsica que mantiene unido a un GUI interactivo.

2.7.6 Persistencia
Todos los JavaBeans tienen que implementar el interface Serializable (directa o indirectamente) para permitir la serializacin de su estado en un almacenamiento persistente (almacenamiento que existe despus de que termine el programa). Todos los objetos se guardan salvo los que se declaran como transient. (Observe que JComponent implementa directamente este interface.) Las clases que necesiten un procesamiento especial durante la serializacin tienen que implementar los siguientes mtodos privados:
private void writeObject(java.io.ObjectOutputStream out) y private void readObject(java.io.ObjectInputStream in)

Estos mtodos se llaman para escribir o leer una instancia de esta clase de un stream. Observe que el mecanismo de serializacin por defecto ser invocado para serializar todas las subclases porque se trata de mtodos privados. (Vea la documentacin del API o el tutorial de Java para ms informacin sobre la serializacin.)
Nota: Como en la primera versin de Java 2, JComponent implementa readObject() y writeObject() como privados, todas las subclases tienen que implementar estos mtodos si requieren un procesamiento especial. Actualmente la persistencia a largo plazo no se recomienda, y es posible que cambie en futuras versiones. Sin embargo, no hay ningn problema en implementar persistencia a corto plazo (p.e. para RMI, transferencia de datos, etc.).

26

Las clases que quieren tener un control total sobre su serializacin y deserializacin deberan implementar el interface Externalizable. Este interface define dos mtodos:
public void writeExternal(ObjectOutput out) public void readExternal(ObjectInput in)

Estos mtodos se invocarn cuando writeObject() y readObject() (vistos anteriormente) sean invocados para llevar a cabo alguna serializacin/deserializacin.

2.7.7 Un JavaBean simple basado en Swing


El siguiente cdigo nos muestra como construir un JavaBean basado en Swing con propiedades simples, ligadas, restringidas y de cambio. El cdigo: BakedBean.java ver \Chapter1\1
import import import import import javax.swing.*; javax.swing.event.*; java.beans.*; java.awt.*; java.io.*;

public class BakedBean extends JComponent implements Externalizable { // Nombres de la propiedad (slo para propiedades ligadas o restringidas) public static final String BEAN_VALUE = "Value"; public static final String BEAN_COLOR = "Color"; // Propiedades private Font m_beanFont; private Dimension m_beanDimension; private int m_beanValue; private Color m_beanColor; private String m_beanString; // // // // // simple simple ligada restringida de cambio

// Maneja todos los PropertyChangeListeners protected SwingPropertyChangeSupport m_supporter = new SwingPropertyChangeSupport(this); // Maneja todos los VetoableChangeListeners protected VetoableChangeSupport m_vetoer = new VetoableChangeSupport(this); // Slo se necesita un ChangeEvent ya que el nico estado del evento // es la propiedad fuente. La fuente de los eventos generados // es siempre "this". Ver esto en montones de cdigo Swing. protected transient ChangeEvent m_changeEvent = null; // Esto puede manejar todos los tipos de oyentes, siempre que configuremos // los mtodos fireXX para que miren correctamente en esta lista. // Esto har que aprecie las clases XXSupport. protected EventListenerList m_listenerList = new EventListenerList(); public BakedBean() { m_beanFont = new Font("SanSerif", Font.BOLD | Font.ITALIC, 12); m_beanDimension = new Dimension(150,100);

27

m_beanValue = 0; m_beanColor = Color.black; m_beanString = "BakedBean #"; } public void paintComponent(Graphics g) { super.paintComponent(g); g.setColor(m_beanColor); g.setFont(m_beanFont); g.drawString(m_beanString + m_beanValue,30,30); } public void setBeanFont(Font font) { m_beanFont = font; } public Font getBeanFont() { return m_beanFont; } public void setBeanValue(int newValue) { int oldValue = m_beanValue; m_beanValue = newValue; // Avisar a todos los PropertyChangeListeners m_supporter.firePropertyChange(BEAN_VALUE, new Integer(oldValue), new Integer(newValue)); } public int getBeanValue() { return m_beanValue; } public void setBeanColor(Color newColor) throws PropertyVetoException { Color oldColor = m_beanColor; // Avisar a todos los VetoableChangeListeners antes de hacer el cambio // ...se lanzar una excepcin si hay un veto // ...si no continuaremos y haremos el cambio m_vetoer.fireVetoableChange(BEAN_COLOR, oldColor, newColor); m_beanColor = newColor; m_supporter.firePropertyChange(BEAN_COLOR, oldColor, newColor); } public Color getBeanColor() { return m_beanColor; } public void setBeanString(String newString) { m_beanString = newString; // Avisar a todos los ChangeListeners fireStateChanged(); } public String getBeanString() { return m_beanString; } public void setPreferredSize(Dimension dim) { m_beanDimension = dim; }

28

public Dimension getPreferredSize() { return m_beanDimension; } public void setMinimumSize(Dimension dim) { m_beanDimension = dim; } public Dimension getMinimumSize() { return m_beanDimension; } public void addPropertyChangeListener( PropertyChangeListener l) { m_supporter.addPropertyChangeListener(l); } public void removePropertyChangeListener( PropertyChangeListener l) { m_supporter.removePropertyChangeListener(l); } public void addVetoableChangeListener( VetoableChangeListener l) { m_vetoer.addVetoableChangeListener(l); } public void removeVetoableChangeListener( VetoableChangeListener l) { m_vetoer.removeVetoableChangeListener(l); } // Recuerde que EventListenerList es un array de // parejas clave/valor: // key = XXListener referencia a la clase // value = XXListener instancia public void addChangeListener(ChangeListener l) { m_listenerList.add(ChangeListener.class, l); } public void removeChangeListener(ChangeListener l) { m_listenerList.remove(ChangeListener.class, l); } // Este es el cdigo tpico de despacho de EventListenerList. // Ver esto a menudo en cdigo Swing. protected void fireStateChanged() { Object[] listeners = m_listenerList.getListenerList(); // Procesa los oyentes del ltimo al primero, avisando // a los que estn interesados en este evento for (int i = listeners.length-2; i>=0; i-=2) { if (listeners[i]==ChangeListener.class) { if (m_changeEvent == null) m_changeEvent = new ChangeEvent(this); ((ChangeListener)listeners[i+1]).stateChanged(m_changeEvent); } } } public void writeExternal(ObjectOutput out) throws IOException { out.writeObject(m_beanFont); out.writeObject(m_beanDimension); out.writeInt(m_beanValue); out.writeObject(m_beanColor);

29

out.writeObject(m_beanString); } public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { setBeanFont((Font)in.readObject()); setPreferredSize((Dimension)in.readObject()); // Usar el tamao preferido para el mnimo.. setMinimumSize(getPreferredSize()); setBeanValue(in.readInt()); try { setBeanColor((Color)in.readObject()); } catch (PropertyVetoException pve) { System.out.println("Color change vetoed.."); } setBeanString((String)in.readObject()); } public static void main(String[] args) { JFrame frame = new JFrame("BakedBean"); frame.getContentPane().add(new BakedBean()); frame.setVisible(true); frame.pack(); } } BakedBean tiene representacin visual (no es obligatorio para un bean). Tiene las propiedades: m_beanValue, m_beanColor, m_beanFont, m_beanDimension, y m_beanString. Soporta persistencia implementando el interface Externalizable y los mtodos writeExternal() y readExternal() para controlar su propia serializacin (observe que el orden en el que se escriben y se leen los datos coincide). BakedBean soporta personalizacin mediante sus mtodos setXX() y getXX(), y soporta comunicacin permitiendo el registro de PropertyChangeListeners, VetoableChangeListeners, y ChangeListeners. Y, sin tener que hacer nada especial, soporta

introspeccin. Utilizar un mtodo para mostrar BakedBean en un frame no est dentro de la funcionalidad de JavaBeans. La Figura 2.1 muestra BakedBean siendo ejecutado como una aplicacin.

Figura 2.1 BakedBean en nuestro editor personal de JavaBeans

<<fichero figure2-1.gif>>

En el captulo 18 (seccin 18.9) construiremos en entorno completo de edicin de propiedades de JavaBeans. La Figura 2.2 muestra una instancia de BakedBean en este entorno. Al BakedBean mostrado se le han modificado las propiedades m_beanDimension, m_beanColor, y m_beanValue con nuestro editor de propiedades y se ha serializado al disco. Lo que realmente muestra la Figura 2.2 es una instancia de ese BakedBean despus de que ha sido deserializado (cargado desde el disco). Observe que cualquier componente Swing puede ser creado, modificado, serializado y deserializado usando este entorno ya que todos ellos cumplen las especificaciones de los JavaBeans

30

Figura 2.2 BakedBean en nuestro editor de propiedades de JavaBeans personal

<<fichero figure2-2.gif>>

2.8 Fuentes, Colores, Grficos y texto


2.8.1 Fuentes clase java.awt.Font, abstract class java.awt.GraphicsEnvironment
Como vimos anteriormente en BakedBean, las fuentes son muy fciles de crear:
m_beanFont = new Font("SanSerif", Font.BOLD | Font.ITALIC, 12);

En este cdigo "SanSerif" es el nombre de la fuente, Font.Bold | Font.PLAIN es el estilo (que en este caso es negrita (bold) y cursiva (italic)), y 12 es el tamao. La clase Font define tres contantes estticas de tipo int para indicar el estilo: Font.BOLD, Font.ITALIC, FONT.PLAIN. Podemos especificar el tamao de la fuente con un int en el constructor de Font. Usando Java 2, para obtener una lista de los nombres de fuentes disponibles en tiempo de ejecucin, podemos preguntar al GraphicsEnvironment local:
GraphicsEnvironment ge = GraphicsEnvironment. getLocalGraphicsEnvironment(); String[] fontNames = ge.getAvailableFontFamilyNames(); Nota: Java 2 introduce un nuevo, poderoso y completo mecanismo para comunicarse con dispositivos que pueden dibujar grficos, como pantallas o impresoras. Estos dispositivos se representan como instancias de la clase GraphicsDevice. Es interesante, que un GraphicsDevice puede estar en la mquina local o en una remota. Cada GraphicsDevice tiene un conjunto de objetos GraphicsConfiguration asociados con l. Una GraphicsConfiguration describe caractersticas especficas del dispositivo asociado. Normalmente cada GraphicsConfiguration de un GraphicsDevice representa un modo de operacin diferente (por ejemplo resolucin y nmero de colores). Nota: En cdigo para el JDK1.1, para obtener la lista de nombres de fuentes haba que usar el cdigo siguiente: String[] fontnames = Toolkit.getDefaultToolkit().getFontList(); El mtodo Toolkit.getFontList() se ha desaconsejado en Java 2 y este cdigo debera actualizarse.

31

GraphicsEnvironment es una clase abstracta que describe una coleccin de GraphicsDevices. Las subclases de GraphicsEnvironment deben tener tres mtodos para obtener arrays de Fonts e informacin de Font: Font[] getAllFonts(): obtiene todas las Fonts disponibles en tamao de un punto. String[] getAvailableFontFamilyNames(): obtiene los nombres de todas las familias de

fuentes disponibles.
String[] getAvailableFontFamilyNames(Locale l): obtiene los nombres de todas las familias de fuentes disponibles usando el Locale (soporte a la internationalizacin) especificado. GraphicsEnvironment tambin tiene mtodos estticos para recuperar GraphicsDevices y la instancia local de GraphicsEnvironment. Para encontrar que Fonts estn disponibles para el

sistema en el que se est ejecutando nuestro programa, debemos usar esta instancia local de
GraphicsEnvironment como vimos antes. Es mucho ms eficiente y conveniente obtener los nombres de fuentes disponibles y usarlos para construir Fonts que obtener el array de objetos Font.

Podramos pensar que, dado un objeto Font, podemos usar los mtodos tpicos de acceso getXX()/setXX() para cambiar su nombre, estilo y tamao. Bueno, slo habramos acertado a medias. Podemos usar los mtodos getXX() para obtener esta informacin de una Font:
String getName() int getSize() float getSize2D() int getStyle

Sin embargo, no podemos usar los mtodos setXX(). En su lugar debemos usar uno de los siguientes mtodos de instancia de Font para conseguir una nueva Font:
deriveFont(float size) deriveFont(int style) deriveFont(int style, float size) deriveFont(Map attributes) deriveFont(AffineTransform trans) deriveFont(int style, AffineTransform trans)

Normalmente estaremos interesados solamente en los tres primeros mtodos.


Nota: AffineTransforms se usan en el mundo de Java 2D para llevar a cabo cosas como translaciones, escalado, rotaciones, reflejado y recortes. Un Map es un objeto que mapea claves a valores (no contiene los objetos involucrados) y los atributos a los que nos referimos aqu son parejas clave/valor como se describe en los documentos del API de java.text.TextAttribute (esta clase est definida en el paquete java.awt.font que es nuevo en Java 2, y que se considera parte de Java 2D -- ver captulo 23).

2.8.2 Colores
La clase Color tiene varias instancias estticas de Color para ser usadas por comodidad (p.e. Color.blue, Color.yellow, etc.). Podemos construir tambin un Color usando, entre otros, los siguientes constructores:
Color(float r, float g, float b) Color(int r, int g, int b)

32

Color(float r, float g, float b, float a) Color(int r, int g, int b, int a)

Normalmente usamos los dos primeros mtodos, y aquellos familiarizados con el JDK1.1 los reconocern. El primero permite especificar los valores de rojo, azul y verde como floats de 0.0 a 1.0. El segundo toma estos valores como ints de 0 a 255. Los segundos dos mtodos son nuevos en Java 2. Ambos tienen un cuarto parmetro que representa el valor alpha del Color. El valor alpha controla directamente la transparencia. Por defecto es 1.0 o 255 que corresponde a completamente opaco. 0.0 o 0 significa totalmente transparente. Observe que, como con las Fonts, hay un montn de mtodos de acceso getXX() pero no de setXX(). En lugar de modificar un objeto Color es ms normal que creemos uno nuevo.
Nota: La clase Color tiene los mtodos estticos brighter() y darker() que devuelven un Color ms claro (brighter) o ms oscuro (darker) que el Color especificado, pero su comportamiento es impredecible a causa de errores internos de redondeo y sugerimos no usarlos.

Especificando un valor alpha podemos usar el Color resultante como fondo de un componente para hacerlo transparente. Esto funcionar para cualquier componente ligero que Swing provea como etiquetas, componentes de texto, frames internos, etc. Por supuesto habr cuestiones especficas de cada componente involucradas (como hacer transparentes el borde y la barra de ttulo de un frame interno transparentes). La siguiente seccin muestra un ejemplo simple de canvas, que muestra como usar el valor alpha para mostrar alguna superficie transparente.
Nota: La propiedad de opacidad de un componente Swing, controlada mediante setOpaque(), no est relacionada directamente con la transparencia de Color. Por ejemplo, si tenemos una JLabel opaca, cuyo fondo se ha puesto a un verde transparente (p.e. Color(0,255,0,150)) Los lmites de la etiqueta se pintarn slo porque es opaca. Seremos capaces de ver a travs de ella slo porque el color es transparente. Si quitamos la opacidad, el fondo de la etiqueta no se dibujar. Ambas cosas se tienen que usar conjuntamente para crear componentes transparentes, pero no estn directamente relacionadas.

2.8.3 Grficos y texto clase abstracta java.awt.Graphics, clase abstracta java.awt.FontMetrics


Dibujar en Swing es muy distinto a hacerlo en AWT. En AWT sobrescribimos normalmente el mtodo paint() de Component para dibujar y el mtodo update() para otras cosas como implementar nuestro propio doble buffer o rellenar el fondo antes de llamar a paint(). Con Swing, el dibujo de componentes es mucho ms complejo. Como JComponent es una subclase de Component, usa los mtodos update() y paint() por diferentes razones. De hecho, no se invoca nunca al mtodo update() para nada. Hay cinco pasos adicionales en el pintado que normalmente se desarrollan dentro del mtodo paint(). Veremos este proceso en la seccin 2.11, pero basta con decir aqu que cualquier subclase de JComponent que quiera tener el control de su propio dibujado debera sobrescribir el mtodo paintComponent() y no el mtodo paint(). Adicionalmente, debera empezar siempre su mtodo paintComponent() con una llamada a super.paintComponent(). Sabiendo esto, es bastante fcil construir un JComponent que acte como nuestro propio canvas ligero. Todo lo que tenemos que hacer es escribir una subclase y sobreescribir el mtodo paintComponent(). Dentro de ese mtodo podemos hacer todo nuestro pintado. As es como tomamos control del dibujado de nuestros simples componentes personalizados. De todos modos, esto

33

no se debera intentar con los componentes Swing normales porque los delegados UI estn a cargo de su dibujado (veremos como personalizar el dibujado en el delegado UI al final del captulo 6, y durante el captulo 21).
Nota: La clase de awt Canvas se puede reemplazar por una versin simplificada de la clase JCanvas que definiremos en el siguiente ejemplo.

Dentro del mtodo paintComponent() tenemos acceso al objeto Graphics de ese componente (a menudo denominado el contexto grfico del componente) que podemos utilizar para pintar superficies y dibujar lneas y texto. La clase Graphics define una gran cantidad de mtodos que se usan para estos propsitos, y es conveniente que mire los documentos del API. El cdigo siguiente muestra como construir una subclase de Component que pinta un ImageIcon y algunas superficies y texto usando diferentes Fonts y Colors, algunas completamente opacos y otros parcialmente transparentes (vimos una funcionalidad parecida pero menos interesante en BakedBean).

Figura 2.3 Demostracin de Graphics en un canvas ligero.

<<fichero figure2-3.gif>> El Cdigo: TestFrame.java ver \Chapter1\2


import java.awt.*; import javax.swing.*; class TestFrame extends JFrame { public TestFrame() {

34

super( "Graphics demo" ); getContentPane().add(new JCanvas()); } public static void main( String args[] ) { TestFrame mainFrame = new TestFrame(); mainFrame.pack(); mainFrame.setVisible( true ); } } class JCanvas extends JComponent { private static Color m_tRed = new Color(255,0,0,150); private static Color m_tGreen = new Color(0,255,0,150); private static Color m_tBlue = new Color(0,0,255,150); private static Font m_biFont = new Font("Monospaced", Font.BOLD | Font.ITALIC, 36); private static Font m_pFont = new Font("SanSerif", Font.PLAIN, 12); private static Font m_bFont = new Font("Serif", Font.BOLD, 24); private static ImageIcon m_flight = new ImageIcon("flight.gif"); public JCanvas() { setDoubleBuffered(true); setOpaque(true); } public void paintComponent(Graphics g) { super.paintComponent(g); // pinta todo el componente de blanco g.setColor(Color.white); g.fillRect(0,0,getWidth(),getHeight()); // pinta un crculo amarillo g.setColor(Color.yellow); g.fillOval(0,0,240,240); // pinta un crculo magenta g.setColor(Color.magenta); g.fillOval(160,160,240,240); // pinta el icono de debajo del cuadrado azul int w = m_flight.getIconWidth(); int h = m_flight.getIconHeight(); m_flight.paintIcon(this,g,280-(w/2),120-(h/2)); // pinta el icono de debajo del cuadrado rojo m_flight.paintIcon(this,g,120-(w/2),280-(h/2)); // pinta un cuadrado rojo transparente g.setColor(m_tRed); g.fillRect(60,220,120,120); // pinta un crculo verde transparente g.setColor(m_tGreen); g.fillOval(140,140,120,120); // pinta un cuadrado azul transparente g.setColor(m_tBlue); g.fillRect(220,60,120,120); g.setColor(Color.black);

35

// Negrita, Cursiva, 36-puntos "Swing" g.setFont(m_biFont); FontMetrics fm = g.getFontMetrics(); w = fm.stringWidth("Swing"); h = fm.getAscent(); g.drawString("Swing",120-(w/2),120+(h/4)); // Normal, 12-puntos "is" g.setFont(m_pFont); fm = g.getFontMetrics(); w = fm.stringWidth("is"); h = fm.getAscent(); g.drawString("is",200-(w/2),200+(h/4)); // Negrita 24-puntos "powerful!!" g.setFont(m_bFont); fm = g.getFontMetrics(); w = fm.stringWidth("powerful!!"); h = fm.getAscent(); g.drawString("powerful!!",280-(w/2),280+(h/4)); } // Algunos administradores de disposicin necesitan esta informacin public Dimension getPreferredSize() { return new Dimension(400,400); } public Dimension getMinimumSize() { return getPreferredSize(); } public Dimension getMaximumSize() { return getPreferredSize(); } }

Observe

que sobrescribimos los mtodos de JComponent getPreferredSize(), getMinimumSize(), y getMaximumSize(), para que algunos administradores de disposicin puedan dimensionar este componente (de otra manera alguno pondra su tamao a 0x0). Es siempre una buena prctica la sobreescritura de estos mtodos cuando se implementan componentes personalizados. La clase Graphics usa lo que se llama el rea de recorte (clipping area). Dentro del mtodo paint() de un componente, esta es la regin de la vista del componente que se est repintando. Slo el dibujo hecho dentro de los lmites del rea de recorte ser dibujado en el momento. Podemos obtener el tamao y la posicin de estos lmites llamando a getClipBounds() que nos devuelve una instancia de Rectangle describindola. La razn por la que se usa el rea de recorte es por eficiencia: no hay motivo para pintar regiones invisibles cuando no tenemos que hacerlo. (Mostraremos como extender este ejemplo para trabajar con el rea de recorte para una mayor eficiencia en la prxima seccin).
Nota: Todos los componentes Swing tienen doble buffer por defecto. Si estamos construyendo nuestro propio canvas ligero no tenemos que preocuparnos por el doble buffer. Este no es el caso con un Canvas de AWT.

Como mencionamos antes, la manipulacin de Fonts y Font es muy compleja. Estamos viendo su estructura, pero una cosa que deberamos saber es como obtener informacin til sobre las fuentes y el texto dibujado al usarlas. Esto implica el uso de la clase FontMetrics. En el ejemplo anterior, FontMetrics nos permiti determinar la anchura y la altura de tres Strings, dibujados en la Font actual asociada con el objeto Graphics, de forma que pudimos dibujarlos centrados en los crculos.

36

La Figura 2.4 ilustra algunas de las informaciones ms comunes que podemos obtener de un objeto FontMetrics. El significado de base (baseline), subida (ascent), bajada (descent), y altura (height) debera quedar claro con el diagrama. La subida es la distancia de la base hasta lo ms alto de la mayora de las letras de la fuente. Observe que cuando usamos g.drawString() para dibujar texto, las coordenadas especificadas representan la posicin de la base del primer carcter.
FontMetrics ofrece varios mtodos para obtener esta informacin y otras ms detalladas, como la anchura de un String dibujado en la Font asociada.

Figura 2.4 Usando FontMetrics

<<fichero figure2-4.gif>>

Para obtener una instancia de FontMetrics llamamos primero a nuestro objeto Graphics para que use la Font que queremos examinar usando el mtodo setFont(). Creamos entonces la instancia de FontMetrics llamando a getFontMetrics() en nuestro objeto Graphics:
g.setFont(m_biFont); FontMetrics fm = g.getFontMetrics();

Una operacin tpica cuando dibujamos texto es centrarlo en un punto determinado. Suponga que queremos centrar el texto Swing en 200,200. Aqu est el cdigo que deberamos usar (asumiendo que hemos recuperado el objeto FontMetrics, fm, como se mostr anteriormente):
int w = fm.stringWidth("Swing"); int h = fm.getAscent(); g.drawString("Swing",200-(w/2),200+(h/4));

Obtenemos la anchura de Swing en la fuente actual, la dividimos para dos, y se la restamos a 200 para centrar el texto horizontalmente. Para centrarlo verticalmente obtenemos la subida de la fuente actual, la dividimos para cuatro y se la aadimos a 200. La razn por la que dividimos la subida para cuatro NO est probablemente muy clara. Ahora es el momento de acometer un error comn que ha llegado con Java 2. La figura 2.4 no es una forma exacta de documentar FontMetrics. Estas es la forma de la que hemos visto documentadas estas cosas en el tutorial Java y en casi todos los dems sitios. De todas formas, parece que hay unos pocos problemas con FontMetrics en Java 2 FCS. Aqu escribiremos un programa simple que demuestra estos problemas. Nuestro programa dibujar el texto Swing con una fuente de 36 puntos, negrita y monospaced. Dibujamos lneas en su subida, subida/2, subida/4, base, y bajada. La Figura 2.5 muestra el resultado.

37

Figura 2.5 La realidad cuando se trabaja con FontMetrics en Java 2

<<fichero figure2-5.gif>> El Cdigo: TestFrame.java Ver \Chapter1\2\fontmetrics


import java.awt.*; import javax.swing.*; class TestFrame extends JFrame { public TestFrame() { super( "Lets get it straight!" ); getContentPane().add(new JCanvas()); } public static void main( String args[] ) { TestFrame mainFrame = new TestFrame(); mainFrame.pack(); mainFrame.setVisible( true ); } } class JCanvas extends JComponent { private static Font m_biFont = new Font("Monospaced", Font.BOLD, 36); public void paintComponent(Graphics g) { g.setColor(Color.black); // Negrita 36-puntos "Swing" g.setFont(m_biFont); FontMetrics fm = g.getFontMetrics(); int h = fm.getAscent(); g.drawString("Swing",50,50); // Prueba tambin: ^ // dibujar la lnea de Subida g.drawLine(10,50-h,190,50-h); // dibujar la lnea de Subida/2 g.drawLine(10,50-(h/2),190,50-(h/2)); // dibujar la lnea de Subida/4 g.drawLine(10,50-(h/4),190,50-(h/4)); // dibujar la lnea de Base g.drawLine(10,50,190,50); // dibujar la lnea de Bajada g.drawLine(10,50+fm.getDescent(),190,50+fm.getDescent()); }

38

public Dimension getPreferredSize() { return new Dimension(200,100); } }

Le aconsejamos que pruebe este programa con diferentes tipos de fuente, tamaos, y con caracteres con marcas diacrticas como , , o . Observar que la subida es siempre mucho mayor de lo que normalmente est documentado que sera, y que la bajada es siempre menor. La forma ms fiable de centrar el texto verticalmente que hemos encontrado es utilizar base + subida/4. An as, se puede usar tambin base + bajada y dependiendo de la fuente que se use puede ser ms ajustado. La realidad es que no hay una forma de llevar esto a cabo correctamente a causa del estado actual de FontMetrics en Java 2.Puede experimentar resultados muy diferentes si no est usando la primera versin de Java 2. Es una buena idea ejecutar este programa y verificar si los resultados en su sistema son similares o no a los de la figura 2.5. Si no, ser mejor que use un mecanismo de centrado diferente para su texto que debera ser simple de determinar mediante la experimentacin con esta aplicacin.
Nota: En el JDK1.1, para obtener una instancia de FontMetrics haba que hacer lo siguiente: FontMetrics fm = Toolkit.getDefaultToolkit().getFontMetrics(myfont); El mtodo Toolkit.getFontMetrics est desaconsejado en Java 2 y este cdigo debera ser actualizado.

2.9 Usando el rea de recorte de Graphics


Podemos usar el rea de recorte para optimizar el dibujo de componentes. Esto puede que no mejore notablemente la velocidad de dibujado de componentes simples como nuestro JCanvas anterior, pero es importante comprender como implementar esta funcionalidad, ya que todo el sistema de dibujo de Swing est basado en este concepto (veremos ms sobre esto en la siguiente seccin). Modificamos ahora JCanvas para que cada una de nuestras superficies, strings e imgenes se pinte solamente si el rea de recorte intersecciona con el rectngulo que lo limita. (Estas intersecciones son bastante fciles de calcular, y podra ser til que trabajase con ellas y las verificase una a una.) Adicionalmente, mantenemos un contador local que se incrementa cada vez que se pinta una de nuestros tems. Al finalizar el mtodo paintComponent() mostramos el nmero total de tems que se pintaron. A continuacin est nuestro mtodo optimizado paintComponent() de JCanvas (con contador): El Cdigo: JCanvas.java ver \Chapter1\3
public void paintComponent(Graphics g) { super.paintComponent(g); // contador int c = 0; // para int w = int h = int d = usarse a continuacin 0; 0; 0;

// obtener rea de recorte Rectangle r = g.getClipBounds(); int clipx = r.x; int clipy = r.y; int clipw = r.width;

39

int cliph = r.height; // dibujar slo el rea de recorte g.setColor(Color.white); g.fillRect(clipx,clipy,clipw,cliph); // dibujar el crculo amarillo si est dentro del rea de recorte if (clipx <= 240 && clipy <= 240) { g.setColor(Color.yellow); g.fillOval(0,0,240,240); c++; } // dibujar el crculo magenta si est dentro del rea de recorte if (clipx + clipw >= 160 && clipx <= 400 && clipy + cliph >= 160 && clipy <= 400) { g.setColor(Color.magenta); g.fillOval(160,160,240,240); c++; } w = m_flight.getIconWidth(); h = m_flight.getIconHeight(); // pintar el icono de debajo del cuadrado azul si est dentro del // rea de recorte if (clipx + clipw >= 280-(w/2) && clipx <= (280+(w/2)) && clipy + cliph >= 120-(h/2) && clipy <= (120+(h/2))) { m_flight.paintIcon(this,g,280-(w/2),120-(h/2)); c++; } // pintar el icono de debajo del cuadrado rojo si est dentro del // rea de recorte if (clipx + clipw >= 120-(w/2) && clipx <= (120+(w/2)) && clipy + cliph >= 280-(h/2) && clipy <= (280+(h/2))) { m_flight.paintIcon(this,g,120-(w/2),280-(h/2)); c++; } // dibujar el cuadrado rojo transparente si est dentro del rea de // recorte if (clipx + clipw >= 60 && clipx <= 180 && clipy + cliph >= 220 && clipy <= 340) { g.setColor(m_tRed); g.fillRect(60,220,120,120); c++; } // dibujar el crculo verde transparente si est dentro del rea de // recorte if (clipx + clipw > 140 && clipx < 260 && clipy + cliph > 140 && clipy < 260) { g.setColor(m_tGreen); g.fillOval(140,140,120,120); c++; } // dibujar el cuadrado azul transparente si est dentro del rea de // recorte if (clipx + clipw > 220 && clipx < 380 && clipy + cliph > 60 && clipy < 180) { g.setColor(m_tBlue); g.fillRect(220,60,120,120); c++; } g.setColor(Color.black); g.setFont(m_biFont); FontMetrics fm = g.getFontMetrics(); w = fm.stringWidth("Swing");

40

h = fm.getAscent(); d = fm.getDescent(); // Negrita, Cursiva, 36-puntos "Swing" si est dentro del rea de // recorte if (clipx + clipw > 120-(w/2) && clipx < (120+(w/2)) && clipy + cliph > (120+(h/4))-h && clipy < (120+(h/4))+d) { g.drawString("Swing",120-(w/2),120+(h/4)); c++; } g.setFont(m_pFont); fm = g.getFontMetrics(); w = fm.stringWidth("is"); h = fm.getAscent(); d = fm.getDescent(); // Normal, 12-puntos "is" si est dentro del rea de recorte if (clipx + clipw > 200-(w/2) && clipx < (200+(w/2)) && clipy + cliph > (200+(h/4))-h && clipy < (200+(h/4))+d) { g.drawString("is",200-(w/2),200+(h/4)); c++; } g.setFont(m_bFont); fm = g.getFontMetrics(); w = fm.stringWidth("powerful!!"); h = fm.getAscent(); d = fm.getDescent(); // Negrita 24-puntos "powerful!!" si est dentro del rea de recorte if (clipx + clipw > 280-(w/2) && clipx < (280+(w/2)) && clipy + cliph > (280+(h/4))-h && clipy < (280+(h/4))+d) { g.drawString("powerful!!",280-(w/2),280+(h/4)); c++; } System.out.println("# items repainted = " + c + "/10"); }

Pruebe a ejecutar este ejemplo desplazando otra ventana de su escritorio sobre partes del JCanvas. Mantenga la consola a la vista de forma que pueda monitorizar cuantos tems se dibujan en cada repintado. Su salida debera mostrar algo como lo siguiente (por supuesto, probablemente ver otros nmeros diferentes):
# # # # # # # # # # items items items items items items items items items items repainted repainted repainted repainted repainted repainted repainted repainted repainted repainted = = = = = = = = = = 4/10 0/10 2/10 2/10 1/10 2/10 10/10 10/10 8/10 4/10

Optimizar este canvas no fue difcil, pero imagine como sera optimizar un contenedor con un nmero variable de hijos, que probablemente se superponen, con doble buffer y trasparencia. Esto es lo que hace JComponent, y lo hace bastante eficientemente. Aprenderemos un poco ms sobre como se hace esto en la seccin 2.11. Pero primero terminaremos con nuestro vistazo de alto nivel a los grficos introduciendo una funcionalidad nueva de Swing muy poderosa: la depuracin de grficos.

2.10 Depuracin de grficos


La depuracin de grficos nos ofrece la posibilidad de observar todas las operaciones de pintado durante

41

el dibujo de un componente y de todos sus hijos. Esto se consigue con un cambio lento, usando distintos destellos para indicar la regin que se est pintando. Se intenta ayudar a encontrar problemas con el dibujo, la disposicin, y las jerarquas de componentes -- y con cualquier cosa relacionada. Si est habilitada la depuracin de grficos, el objeto Graphics que se usa cuando se pinta es una instancia de DebugGraphics (una subclase de Graphics). JComponent, y por tanto todos los componente Swing, soporta la depuracin de grficos, que se puede activar/desactivar con el mtodo setDebugGraphicsOptions() de JComponent. Este mtodo recibe un int como parmetro que corresponde normalmente a uno de (o una combinacin de bits -- usando el operador binario | ) los cuatro valores estticos definidos en DebugGraphics.

2.10.1 Opciones de la depuracin de grficos


1. DebugGraphics.FLASH_OPTION: Cada operacin de pintado produce un nmero determinado de destellos, de un determinado color y con un intervalo especificado. Los valores por defecto son: 250ms como intervalo, 4 destellos, y color rojo. Estos valores se pueden modificar con los siguientes mtodos estticos de DebugGraphics:
setFlashTime(int flashTime) setFlashCount(int flashCount) setFlashColor(Color flashColor)

Si no deshabilitamos el doble buffer mediante el RepaintManager (que veremos en la siguiente seccin) no veremos el pintado en el momento en que ocurre:
RepaintManager.currentManager(null). setDoubleBufferingEnabled(false); Nota: Desactivar el doble buffer en el RepaintManager tiene el efecto de ignorar la propiedad doubleBuffered de todos los componentes.

2. DebugGraphics.LOG_OPTION: Enva mensajes describiendo todas las operaciones de pintado cuando ocurren. Por defecto estos mensajes se envan a la salida estndar (la consola -- System.out). Pero podemos cambiar el destino con el mtodo esttico setLogStream() de DebugGraphics. Este mtodo recibe un PrintStream como parmetro. Para enviar la salida a un fichero haramos algo como lo siguiente:
PrintStream debugStream = null; try { debugStream = new PrintStream( new FileOutputStream("JCDebug.txt")); } catch (Exception e) { System.out.println("can't open JCDebug.txt.."); } DebugGraphics.setLogStream(debugStream);

Si en algn punto tenemos que redirigir la salida de nuevo hacia la salida estndar:
DebugGraphics.setLogStream(System.out);

Podemos insertar cualquier cadena obteniendo el stream de salida con el mtodo esttico logStream() de DebugGraphics, e imprimiendo en l:
PrintStream ps = DebugGraphics.logStream(); ps.println("\n===> paintComponent ENTERED <===");

42

Peligro: Escribir un registro a un fichero sobrescribir el mismo cada vez que se reinicialice el stream.

Todas las operaciones se imprimen con la siguiente sintaxis:


"Graphics" + (isDrawingBuffer() ? "<B>" : "") + "(" + graphicsID + "-" + debugOptions + ")"

Todas las lneas empiezan con Graphics. El mtodo isDrawingBuffer() nos dice si est habilitado el buffer. Si lo est, se aade <B>. Los valores de graphicsID y de debugOptions se ponen entre parntesis y separados con un -. El valor de graphicsID representa el nmero de instancias de DebugGraphics que se han creado durante la vida de la aplicacin (p.e. es un contador de tipo int esttico). El valor de debugOptions representa el modo de depurado actual:
LOG_OPTION = 1 LOG_OPTION y FLASH_OPTION = 3 LOG_OPTION y BUFFERED_OPTION = 5 LOG_OPTION, FLASH_OPTION, y BUFFERED_OPTION = 7

Por ejemplo, con el registro y los destellos habilitados, vemos una salida parecida a esta para todas las operaciones:
Graphics(1-3) Setting color: java.awt.Color[r=0,g=255,b=0]

Las llamadas a los mtodos de Graphics se aadirn al registro cuando est opcin est habilitada. La lnea anterior se gener al hacerse una llamada a setColor(). 3. DebugGraphics.BUFFERED_OPTION: Se supone que desplegar un frame mostrando el dibujado tal como ocurre en el buffer invisible si est habilitado el doble-buffer. En 2 FCS esta opcin no es funcional. 4. DebugGraphics.NONE_OPTION: Apaga la depuracin de grficos.

2.10.2 Advertencias sobre la depuracin de grficos


Hay varios asuntos con los que hay que tener cuidado cuando usamos la depuracin de grficos: 1. La depuracin de grficos no funcionar para cualquier componente cuyo UI sea null. Por tanto, si ha creado una subclase directa de JComponent sin un delegado UI, como hicimos anteriormente con JCanvas, la depuracin de grficos no har nada. La forma ms simple de evitar esto es definir un delegado UI trivial (vaco). Veremos como hacer esto con un ejemplo ms tarde. 2. DebugGraphics no limpia cuando termina. Por defecto, se usa un color rojo para los destellos. Cuando se marca una regin, sta se rellena con ese color rojo del destello y no se borra (simplemente se pinta encima). Esto supone un problema porque el dibujo transparente no se mostrar transparente. En cambio, se fusionar con el rojo de abajo (o con cualquiera que sea el color del destello). Esto no supone necesariamente un defecto de diseo ya que nada nos impide usar un color completamente transparente para los destellos. Con un valor alpha de 0 el color del destello no se ver nunca. El nico problema de esto es que no veremos ningn destello. De todos modos, en la mayora de los casos es fcil de seguir lo que se est dibujando si ponemos flashTime y flashCount de forma que haya bastante tiempo entre operaciones.

2.10.3 Usando la depuracin de grficos


Ahora habilitaremos la depuracin de grficos en nuestro ejemplo de JCanvas de las dos ltimas secciones. Como tenemos que tener un delegado UI, definimos una subclase trivial de ComponentUI e implementamos su mtodo createUI() para que devuelva una instancia esttica de si mismo:

43

class EmptyUI extends ComponentUI { private static final EmptyUI sharedInstance = new EmptyUI(); public static ComponentUI createUI(JComponent c) { return sharedInstance; } }

Para asociar adecuadamente este delegado UI con JCanvas, simplemente llamamos a super.setUI(EmptyUI.createUI(this)) desde el constructor de JCanvas. Configuramos tambin una variable de tipo PrintStream en JCanvas y la usamos para aadir unas pocas lneas propias al stream de registro durante el mtodo paintComponent (para guardar cuando empieza y termina el mtodo). No se ha hecho ningn otro cambio en el cdigo paintComponent() de JCanvas. En nuestra aplicacin de prueba, TestFrame, creamos una instancia de JCanvas y habilitamos la depuracin de grficos con las opciones LOG_OPTION y FLASH_OPTION. Deshabilitamos el doble buffer con RepaintManager, ponemos el intervalo de los destellos a 100ms, el nmero de stos a 2, y usamos un color totalmente transparente. El Cdigo: TestFrame.java ver \Chapter1\4
import import import import java.awt.*; javax.swing.*; javax.swing.plaf.*; java.io.*;

class TestFrame extends JFrame { public TestFrame() { super( "Graphics demo" ); JCanvas jc = new JCanvas(); RepaintManager.currentManager(jc). setDoubleBufferingEnabled(false); jc.setDebugGraphicsOptions(DebugGraphics.LOG_OPTION | DebugGraphics.FLASH_OPTION); DebugGraphics.setFlashTime( 100 ); DebugGraphics.setFlashCount( 2 ); DebugGraphics.setFlashColor(new Color(0,0,0,0)); getContentPane().add(jc); } public static void main( String args[] ) { TestFrame mainFrame = new TestFrame(); mainFrame.pack(); mainFrame.setVisible( true ); } } class JCanvas extends JComponent { // Cdigo de la seccin 2.9 intacto private PrintStream ps; public JCanvas() { super.setUI(EmptyUI.createUI(this)); }

44

public void paintComponent(Graphics g) { super.paintComponent(g); ps = DebugGraphics.logStream(); ps.println("\n===> paintComponent ENTERED <==="); // Todo el cdigo de pintado intacto ps.println("\n# items repainted = " + c + "/10"); ps.println("===> paintComponent FINISHED <===\n"); } // Cdigo de la seccin 2.9 intacto } class EmptyUI extends ComponentUI { private static final EmptyUI sharedInstance = new EmptyUI(); public static ComponentUI createUI(JComponent c) { return sharedInstance; } }

Poniendo LOG_OPTION, la depuracin de grficos nos ofrece una mejor informacin para verificar correctamente como funciona nuestra optimizacin del rea de recorte (de la ltima seccin). Cuando se ejecuta este ejemplo se ver la siguiente salida en su consola (suponiendo que no tape la regin visible de JCanvas cuando se pinta por primera vez):
Graphics(0-3) Enabling debug Graphics(0-3) Setting color: javax.swing.plaf.ColorUIResource[r=0,g=0,b=0] Graphics(0-3) Setting font: javax.swing.plaf.FontUIResource[family=dialog,name=Dialog, style=plain,size=12] ===> paintComponent ENTERED <=== Graphics(1-3) Setting color: java.awt.Color[r=255,g=255,b=255] Graphics(1-3) Filling rect: java.awt.Rectangle[x=0,y=0, width=400,height=400] Graphics(1-3) Setting color: java.awt.Color[r=255,g=255,b=0] Graphics(1-3) Filling oval: java.awt.Rectangle[x=0,y=0, width=240,height=240] Graphics(1-3) Setting color: java.awt.Color[r=255,g=0,b=255] Graphics(1-3) Filling oval: java.awt.Rectangle[x=160,y=160,width=240,height=240] Graphics(1-3) Drawing image: sun.awt.windows.WImage@32a5625a at: java.awt.Point[x=258,y=97] Graphics(1-3) Drawing image: sun.awt.windows.WImage@32a5625a at: java.awt.Point[x=98,y=257] Graphics(1-3) Setting color: java.awt.Color[r=255,g=0,b=0] Graphics(1-3) Filling rect: java.awt.Rectangle[x=60,y=220,width=120,height=120] Graphics(1-3) Setting color: java.awt.Color[r=0,g=255,b=0] Graphics(1-3) Filling oval: java.awt.Rectangle[x=140,y=140,width=120,height=120] Graphics(1-3) Setting color: java.awt.Color[r=0,g=0,b=255] Graphics(1-3) Filling rect: java.awt.Rectangle[x=220,y=60,width=120,height=120] Graphics(1-3) Setting color: java.awt.Color[r=0,g=0,b=0] Graphics(1-3) Setting font: java.awt.Font[family=monospaced.bolditalic,name=Mono

45

spaced,style=bolditalic,size=36] Graphics(1-3) Drawing string: "Swing" at: java.awt.Point[x=65,y=129] Graphics(1-3) Setting font: java.awt.Font[family=Arial,name=SanSerif,style=plain,size=12] Graphics(1-3) Drawing string: "is" at: java.awt.Point[x=195,y=203] Graphics(1-3) Setting font: java.awt.Font[family=serif.bold,name=Serif,style=bold,size=24] Graphics(1-3) Drawing string: "powerful!!" at: java.awt.Point[x=228,y=286] # items repainted = 10/10 ===> paintComponent FINISHED <===

2.11 Pintado y validacin


En el corazn del mecanismo de pintado y validacin de JComponent est RepaintManager. Es RepaintManager el que es responsable de enviar peticiones de pintado y validacin a la cola de eventos del sistema de AWT para su despacho. Para resumir, lo hace interceptando las peticiones de repaint() y revalidate(), combinando cualquier peticin cuando sea posible, envolvindolas en objetos Runnable, y envindolas a invokeLater(). Hay unos pocos temas que merecen ms atencin aqu antes de que veamos los detalles de los procesos de pintado y validacin.
Nota: Esta seccin contiene una explicacin relativamente exhaustiva de los ms complejos mecanismos subyacentes de Swing. Si es relativamente nuevo en Java o Swing le recomendamos que se la salte . Si est buscando informacin sobre como sobreescribir y usar sus propios mtodos de pintado, vaya a la seccin 2.8. Para personalizar el dibujo de los delegados UI vea el captulo 21.

2.11.1 Doble buffer


Hemos mencionado el doble buffer, como deshabilitarlo en el RepaintManager, y como especificar el doble buffer de componentes individuales con el mtodo setDoubleBuffered() de JComponent. Pero, cmo trabaja? El doble buffer es la tcnica de pintar en una pantalla invisible en lugar de hacerlo directamente en un componente visible. Al final, la imagen resultante se pinta en la pantalla (lo que sucede relativamente deprisa). Cuando se usan componentes AWT, los desarrolladores deban implementar su propio doblebuffer para reducir el parpadeo. Estaba claro que el doble buffer deba ser una funcionalidad interna a causa de su extendido uso. Por tanto, no sorprende mucho encontrar esta funcionalidad en todos los componentes Swing Internamente, el doble buffer consiste en crear una Image y obtener su objeto Graphics para usarlo en todos los mtodos de pintado. Si el componente que vamos a pintar tiene hijos, este objeto Graphics se pasar a ellos para usarlo para pintar, y as sucesivamente. Por tanto, si estamos usando doble-buffer en un componente, todos sus hijos lo estarn haciendo tambin (lo tengan habilitado o no) porque dibujaran en el mismo objeto Graphics. Vea que slo hay una pantalla invisible para cada RepaintManager, y slo hay normalmente una instancia de RepaintManager para cada applet o aplicacin (RepaintManager es una clase de servicio que registra una instancia compartida de si misma con AppContext--ver seccin 2.5). Como veremos en el captulo 3, JRootPane es el componente Swing de ms alto nivel en cualquier ventana (lo que incluye a JInternalFrame -- aunque no sea realmente una ventana). Habilitando el doble buffer en JRootPane, todos sus hijos se pintarn tambin usando doble buffer. Como vimos en la ltima seccin, RepaintManager tambin ofrece un control global sobre el doble buffer de todos los

46

componentes. Por tanto, otra forma de garantizar que todos los componentes usen doble buffer es llamar a:
RepaintManager.currentManager(null).setDoubleBufferingEnabled(true);

2.11.2 Dibujo optimizado


No hemos visto el hecho de que los componentes puedan superponer a cualquier otro en Swing todava, pero pueden hacerlo. JLayeredPane, por ejemplo, es un contenedor que permite que cualquier nmero de componentes se superponga a cualquier otro. Repintar este tipo de contedor es mucho ms complicado que repintar otro contenedor que sepamos que no permite la superposicin, principalmente a causa de la posibilidad de que los componentes sean transparentes. Qu significa para un componente el ser transparente? Tcnicamente significa que su mtodo
isOpaque() devolver false. Podemos modificar esta propiedad llamando al mtodo setOpaque(). Lo que la opacidad significa en este contexto, es que un componente pintar todos los

pixels dentro de sus lmites. Si est a false, no se garantiza que pase esto. Generalmente est puesto a true, pero veremos que cuando est puesta a false se incrementa la carga de trabajo de todo el mecanismo de pintado. A no ser que estemos construyendo un componente que no tiene que rellenar toda su regin rectangular (como haremos en el captulo 5 con los botones poligonales), deberamos dejar siempre esta propiedad a true, como est por defecto para la mayora de los componentes (Este valor lo pone normalmente un delegado UI). El mtodo isOptimizedDrawingEnabled() de JComponent est sobreescrito para que devuelva true para casi todas las subclases de JComponent excepto: JLayeredPane, JViewport, y JDesktopPane (una subclase de JLayeredpane). Bsicamente, llamar a este mtodo es equivalente a preguntarle a un componente: es posible que uno de tus componentes hijos se superponga a los otros? Si lo es, entonces hay un montn de trabajo de repintado ms que hacer para tener en cuenta el hecho de que cualquier nmero de componentes, de cualquier parte de nuestra jerarqua de componentes, se pueda superponer a los dems. Adicionalmente, como los componentes pueden ser transparentes, los componentes situados completamente debajo de otros se pueden ver an a travs de stos. Este tipo de componentes no son necesariamente hermanos (estn en el mismo contenedor) porque podemos tener varios contenedores no opacos puestos uno sobre otro. En situaciones como esta, tenemos que hacer un recorrido completo del rbol para determinar que componentes tienen que ser refrescados. Si se ha sobreescrito isOptimizedDrawingEnabled() para que devuelva true, asumimos que no tenemos que considerar una situacin como esta. Es por ello, que el dibujo es ms eficiente, o 'optimizado'.

2.11.3 El interior de la validacin


Una peticin de revalidate() se genera cuando un componente tiene que ser situado de nuevo, Cuando se recibe una peticin de un determinado componente, tiene que haber alguna manera de determinar si posicionar ese componente afectar a algn otro. El mtodo isValidateRoot() de JComponent devuelve false para la mayora de los componentes. Bsicamente, llamar a este mtodo es equivalente a preguntar: si posiciono tu contenido de nuevo, puedes garantizarme que ninguno de tus padres o hermanos se ver afectado desfavorablemente (tendr que ser posicionado de nuevo)? Por defecto, slo JRootPane, JScrollPane, y JTextField devuelven true. Esto puede parecer sorprendente al principio, pero aparentemente es cierto que estos componentes son los nicos componentes Swing cuyo contenido puede ser posicionado de forma exitosa, en cualquier situacin (suponiendo que no hay componentes pesados), sin afectar a los padres o hermanos. No importa lo grande que hagamos algo dentro de un JRootPane, JScrollPane, o JTextField, ellos no cambiarn el tamao o la posicin si no es a causa de alguna influencia exterior (p.e. un hermano o un padre). Para ayudarle a que se convenza, intente aadir un componente de texto multi-lnea en un contenedor sin ponerlo dentro de un panel de scroll. Puede observar que la creacin de nuevas lneas cambiar su tamao (dependiendo del posicionamiento). Lo importante no es que pase raramente o que

47

se pueda prevenir, sino que puede pasar. Este es el nico tipo de situacin sobre la que isValidateRoot() nos avisar. Por tanto, dnde se usa este mtodo? Un componente o su padre se revalida normalmente cuando el valor de una propiedad cambia y el tamao, la posicin o el posicionamiento interno del componente se ven afectados. Llamando recusivamente a isValidateRoot() en el padre de un componente Swing hasta que obtengamos true, terminaremos con el ascendiente ms cercano de ese componente que nos garantice que su validacin no afectar a sus padres o hermanos. Veremos que RepaintManager depende de este mtodo para despachar peticiones de validacin.
Nota: Por hermanos queremos decir componentes del mismo contenedor. Por padres queremos decir contenedores padre.

2.11.4 RepaintManager
Como sabemos, normalmente hay slo una instancia en uso de una clase de servicio para cada applet o aplicacin. Por tanto, a no ser que creemos especficamente nuestra propia instancia de RepaintManager, lo que no necesitaremos hacer casi nunca, todo el repintado es manejado por la instancia compartida que est registrada con AppContext. Normalmente la obtenemos llamando al mtodo esttico currentManager() de RepaintManager: myRepaintManager = RepaintManager.currentManager(null); Este mtodo recibe un Component como parmetro. De todos modos, no importa lo que le pasemos. De hecho el componente que se pasa a este mtodo no se usa en ningn sitio del mtodo (vea el cdigo fuente de RepaintManager.java), por lo que se puede usar un valor null de forma segura. (Esta definicin existe para que la usen subclases que quieran trabajar con ms de un RepaintManager, posiblemente usando uno para cada componente.)
RepaintManager existe para dos propsitos: para proveer revalidacin y repintado eficientes. Intercepta todas las peticiones repaint() y revalidate(). Esta clase maneja tambin todo el doble buffer en Swing y mantiene una Image sencilla para este propsito. El tamao mximo de esta Image es por defecto el tamao de la pantalla. An as, podemos modificar el mismo manualmente usando el mtodo setDoubleBufferMaximumSize() de RepaintManager. (El resto de funcionalidades de RepaintManager se ver a lo largo de esta seccin donde sean aplicables.) Nota: Los cell renderers que se usan en componentes como JList, JTree, y JTable son especiales ya que estn envueltos en instancias de CellRendererPane y todas las peticiones de validacin y repintado no se propagan por la jerarqua. Vea el captulo 17 para saber ms CellRendererPane y la causa de este comportamiento. Es suficiente que sepa que los cell renderers no siguen el esquema de pintado y validacin que hemos visto en esta seccin.

2.11.5 Revalidacin
RepaintManager mantiene un Vector de componentes que tienen que ser validados. Cuando quiera que una peticin revalidate es interceptada, se enva el componente fuente al mtodo addInvalidComponent() y se chequea su propiedad validateRoot usando isValidateRoot(). Esto sucede recursivamente en los padres del componente hasta que isValidateRoot() devuelve

true. Se chequea entonces la visibilidad del componente resultante, si es que hay alguno. Si alguno de los contenedores padre no es visible no hay razn para revalidarlo. En otro caso RepaintManager recorre su rbol hasta que alcanza el componente raz, un Window o Applet. RepaintManager chequea entonces el Vector de componentes invalidos y si en l no est an el componente lo aade. Despus

48

de

que se aada con xito, RepaintManager pasa entonces la raz al mtodo queueComponentWorkRequest() de SystemEventQueueUtilities (vimos esta clase en la seccin 2.3). Este mtodo comprueba si ya hay un ComponentWorkRequest (esta es una clase privada esttica en SystemEventQueueUtilities que implementa Runnable) que corresponda a esta raz guardada en la tabla de peticiones de trabajos. Si no hay ninguna, se crea una nueva. Si ya hay una, simplemente tomamos una referencia a ella. Entonces sincronizamos el acceso a esa ComponentWorkRequest, la ponemos en la tabla de peticiones de trabajos si es nueva, y comprobamos si est pendiente (p.e. si ha sido aadida a la cola de eventos del sistema de AWT). Si no est pendiente, la enviamos a SwingUtilities.invokeLater(). Se marca entonces como pendiente y dejamos el bloque sincronizado. Cuando se ejecuta finalmente en el hilo de despacho de eventos notifica a RepaintManager que ejecute validateInvalidComponents(), seguido de paintDirtyRegions(). El mtodo validateInvalidComponents() bsicamente comprueba el Vector de RepaintManager que contiene los componentes que necesitan validacin, y llama a validate() en cada uno de ellos. (Este mtodo es en la actualidad un poco ms cuidadoso de lo que describimos aqu, ya que sincroniza el acceso para evitar la adicin de componentes invalidos durante la ejecucin).
Note: Recuerde que se debera llamar a validateInvalidComponents() slo dentro del hilo de despacho de eventos. Nunca llame a este mtodo desde cualquier otro hilo. La misma regla se aplica para paintDirtyRegions().

El mtodo paintDirtyRegions() es mucho ms complicado, y veremos alguno de esos detalles ms adelante. Por ahora, es suficiente con saber que este mtodo pinta todas las regiones que lo precisen de todos los componentes que se mantengan en RepaintManager.

2.11.6 Repintado
JComponent define dos mtodos repaint(), y se hereda la versin sin argumentos de repaint() que existe en java.awt.Container: public void repaint(long tm, int x, int y, int width, int height) public void repaint(Rectangle r) public repaint() // heredado de java.awt.Container

Si llama a la versin sin argumentos se repinta todo el componente. Para componentes pequeos y simples esto est bien, pero para los ms grandes y complejos esto no es eficiente. Los otros dos mtodos reciben los lmites de la regin que debe repintarse (la regin sucia) como parmetro. Los parmetros de tipo int del primer mtodo corresponden a las coordenadas x e y, la anchura y la altura de esa regin. El segundo recibe la misma informacin encapsulada en una instancia de Rectangle. El segundo mtodo repaint() mostrado anteriormente llama directamente al primero. El primer mtodo enva los parametros de la regin sucia al mtodo addDirtyRegion() de RepaintManager.
Nota: El parmetro long del primer mtodo repaint() no representa absolutamente nada y no se usa. No importa el valor que use para l. La nica razn por la que est ah es para sobreescribir el mtodo repaint() correcto de java.awt.Component. RepaintManager contiene una Hashtable de regiones sucias. Cada componente tendr como

mximo una regin sucia en esta tabla en un momento determinado. Cuando se aade una regin sucia, usando addDirtyRegion(), se comprueba el tamao de la regin y del componente. En el caso de que tenga una anchura o una altura <= 0 el mtodo vuelve y no pasa nada. Si es mayor que 0x0, se comprueba la visibilidad del componente fuente y sus ancestros, y, si son todos visibles, su componente raz, una Window o un Applet, se encuentra navegando por el rbol (de forma parecida a como sucede

49

en addInvalidateComponent()). Se pregunta a la Hashtable de regiones sucias si ya tiene guardada una regin sucia de nuestro componente. De ser as, devuelve su valor, un Rectangle, y el mtodo SwingUtilities.computeUnion() conveniente se usa para combinar la nueva regin sucia con la anterior. Finalmente, RepaintManager pasa la raz al mtodo queueComponentWorkRequest() de SystemEventQueueUtilities. Lo que sucede a partir de aqu es idntico a lo que vimos para la revalidacin (ver ms arriba). Ahora podemos hablar un poco sobre el mtodo paintDirtyRegions() que resumimos anteriormente. (Recuerde que debera llamarse slo dentro del hilo de despacho de eventos.) Este mtodo empieza creando una referencia local a la Hashtable de regiones sucias de RepaintManger, y redirigiendo la Hashtable de regiones sucias de RepaintManager hacia una nueva vaca. Esto se hace en una seccin crtica de forma que no se pueden aadir regiones sucias mientras sucede el intercambio.El resto de este mtodo es bastante largo y complicado por lo que concluiremos con un resumen del cdigo ms significativo (ver RepaintManager.java para ms detalles). El mtodo paintDirtyRegions() contina iterando a travs de una Enumeration de componentes sucios. Llamando al mtodo collectDirtyComponents() de RepaintManager para cada uno. Este mtodo mira en todos los ancestros del componente sucio especificado y comprueba cualquier superposicin con su regin sucia usando el mtodo SwingUtilities.computeIntersection(). De esta forma todos los lmites de la regin sucia se minimizan de forma que slo se mantiene la regin visible. (Observe que collectDirtyComponents() tiene en cuenta la transparencia.) Una vez que se ha hecho esto para cada componente sucio, el mtodo paintDirtyRegions() entra en un bucle. Este bucle calcula la interseccin final de cada componente sucio con su regin sucia. Al final de cada iteracin se llama a paintImmediately() en el componente sucio asociado, que pinta en ese momento todas las regiones sucias minimizadas en su posicin correcta (veremos esto ms adelante). Esto completa el mtodo paintDirtyRegions(), pero todava tenemos que ver el aspecto ms importante de todo el proceso: el pintado.

2.11.7 Pintar
JComponent incluye un mtodo update() que simplemente llama a paint(). El mtodo update() no se usa actualmente en ningn componente Swing, pero se provee por compatibilidad. El mtodo paint() de JComponent, al contrario que las implementaciones AWT de paint(), no

maneja todo el pintado de un componente. De hecho, es bastante raro que maneja algo de l directamente. El nico trabajo del mtodo paint() de JComponent es el manejo de las reas de recorte, translaciones, y pintar piezas de Image que se usan por RepaintManager para doble buffer. El resto del trabajo se delega en otros mtodos.Veremos en breve cada uno de estos mtodos y el orden en que las operaciones de pintado suceden, pero primero tenemos que saber como se invoca a paint(). Como sabemos por nuestro repaso al proceso de repintado, RepaintManager es responsable de llamar a un mtodo llamado paintImmediately() en todos los componentes para pintar su regin sucia (recuerde que hay siempre una sola regin sucia para cada componente porque RepaintManager las combina inteligentemente). Este mtodo, y el privado al que llama, hacen un repintado artstico incluso ms espectacular. Primero comprueba si el componente destino es visible, por si ha sido movido, ocultado o eliminado desde que se hizo la peticin. Entonces recorre los padres no opacos del componente (usando isOpaque()) y aumenta los lmites de la regin a repintar adecuadamente hasta que alcanza un padre opaco: 1. Si el padre alcanzado es una subclase de JComponent, se llama al mtodo privado _paintImmediately() y se le pasa la nueva regin calculada. Este mtodo interroga al mtodo isOptimizedDrawing(), comprueba si est habilitado el doble buffer (en cuyo caso utiliza la pantalla invisible del objeto Graphics asociado a la Image del RepaintManager), y contina trabajando con isOpaque() para determinar el componente padre final y sus lmites para invocar a paint().

50

A. Si est habilitado el doble buffer, llama a paintWithBuffer() (otro mtodo privado). Este mtodo trabaja con el objeto Graphics de la pantalla invisible y su rea de recorte para generar llamadas al mtodo paint() del padre (pasndole el objeto Graphics usando un rea de recorte distinta cada vez). Despus de cada llamada a paint(), usa el objeto Graphics resultante para dibujar directamente al componente visible. (En este caso especfico, el mtodo paint() no usar ningn buffer internamente ya que sabe, porque comprueba algunos flags que no explicaremos, que se est teniendo cuidado con el proceso de buffer en algn otro sitio.) B. Si no est habilitado el doble buffer, se hace simplemente una llamada al paint() del padre. 2. Si el padre no es un JComponent, se envian los lmites de la regin al mtodo repaint() de ese padre, que normalmente llamar al mtodo paint() de java.awt.Component. Este mtodo reenviar entonces el trfico a todos los mtodos paint() de sus hijos ligeros. De todas formas, antes de hacer esto se asegura de que todos los hijos ligeros a los que notifica no estn completamente cubiertos por el rea de recorte actual del objeto Graphics que se pas. En todos los casos hemos alcanzado finalmente el mtodo paint() de JComponent! Dentro del mtodo paint() de JComponent, si est habilitada la depuracin de grficos se usar una instancia de DebugGraphics para todo el pintado. Una mirada rpida al cdigo de pintado de JComponent muestra un gran uso de una clase llamada SwingGraphics. sta no est en los documentos del API porque es privada de paquete. Parece ser una clase muy til para manejar translaciones personalizadas, manejo del rea de recorte, y una Stack (pila) de objetos Graphics que se usa para cach, reciclaje, y operaciones de tipo deshacer. SwingGraphics funciona actualmente como un envoltorio para todas las instancias de Graphics usadas durante el proceso de pintado. Slo se puede instanciar pasndole un objeto Graphics existente. Esta funcionalidad se ha hecho incluso ms explcita, por el hecho de que implementa un interface llamado GraphicsWrapper, que es tambin privado de paquete. El mtodo paint() comprueba si el doble buffer est habilitado y si se llam a este mtodo desde paintWithBuffer() (ver ms arriba): 1. Si se llam a paint() desde paintWithBuffer() o si no est habilitado el doble buffer, paint() comprueba si el rea de recorte del objeto Graphics actual est totalmente oscurecida por algn componente hijo. Si no lo est, se llama a paintComponent(), paintBorder(), y paintChildren() en ese orden. Si est completamente oscurecida, slo hace falta llamar a paintChildren(). (Veremos lo que hacen estos tres mtodos dentro de poco.) 2. Si est habilitado el doble buffer y no se llam desde paintWithBuffer(), usar el objeto Graphics de pantalla invisible de la Image asociada con RepaintManager durante el resto de este mtodo. Comprobar entonces si el rea de recorte del objeto Graphics actual est completamente oscurecida por los componentes hijo. Si no lo est, se llama a paintComponent(), paintBorder(), y paintChildren() en ese orden. Si lo est slo es necesario llamar a paintChildren(). A. El mtodo paintComponent() comprueba si el componente tiene un delegado UI instalado. Si no lo tiene simplemente sale. Si lo tiene, llama a update() en ese delegado UI y sale. El mtodo update() de un delegado UI es normalmente responsable del pintado del fondo de un componente, si es opaco, y entonces llama a paint(). El mtodo paint() de un delegado UI es el que pinta el contenido del componente correspondiente. (Veremos como personalizar los delegados UI extensamente a lo largo de este texto.)

51

B. El mtodo paintBorder() pinta simplemente el borde del componente si lo tiene. C. El mtodo paintChildren() est un poco ms implicado en el proceso. Para resumir, busca por todos los componentes hijo y determina si se debera invocar a paint() en stos usando el rea de recorte de Graphics actual, el mtodo isOpaque() y el mtodo isOptimizedDrawingEnabled(). El mtodo paint() llamado en cada hijo iniciar esencialmente el proceso de pintado del hijo desde la parte 2 de arriba, y este proceso se repetir hasta que no existan ms hijos o no necesiten ser pintados. Cuando construimos o creamos subclases de componentes Swing ligeros se espera normalmente que si queremos pintar algo dentro del mismo componente (en lugar de en el delegado UI que es donde lo haremos habitualmente) sobreescribamos el mtodo paintComponent() y llamemos inmediatamente a super.paintComponent(). De esta forma daremos al delegado UI la oportunidad de que dibuje el componente primero. Sobreescribir el mtodo paint(), o cualquier otro de los mtodos mencionado anteriormente ser rara vez necesario, y siempre es una buena prctica evitar hacerlo.

2.12 Manejo del foco


Cuando se sitan componentes Swing dentro de un contenedor Swing, el camino del foco del teclado es, por defecto, de izquierda a derecha y de arriba a abajo. Nos referimos normalmente a este camino como el ciclo del foco, y cambiar el foco de un componente al siguiente del ciclo se logra usando la tecla TAB o CTRL-TAB. Para moverse en la direccin inversa a travs del ciclo usamos SHIFT-TAB o CTRLSHIFT-TAB. El ciclo se controla por una instancia de la clase abstracta FocusManager.
FocusManager utiliza cinco propiedades de JComponent para tratar cuando el foco alcanza a ste o

le abandona:
focusCycleRoot: esta especifica si el componente contiene un ciclo de foco propio. Si contiene un

ciclo de foco, el foco entrar en este componente y se mover a travs de su ciclo de foco hasta que se enve fuera de ese componente manualmente o mediante cdigo. Por defecto est propiedad es false (para la mayora de los componentes), y no se puede cambiar con un mtodo tpico de acceso setXX(). Slo se puede cambiar sobreescribiendo el mtodo isFocusCycleRoot() y devolviendo el valor booleano apropiado.
managingFocus: esta especifica si los KeyEvents correspondientes a un cambio de foco sern enviados al componente o interceptados y consumidos por el FocusManager. Por defecto esta propiedad es false (para la mayora de los componentes), y no se puede cambiar con un mtodo tpico de acceso setXX(). Slo se puede cambiar sobreescribiendo el mtodo isManagingFocus() y devolviendo el valor booleano apropiado. focusTraversable: esta especifica si el foco se puede transferir al componente por el FocusManager a causa de un desplazamiento del foco en el ciclo. Por defecto esta propiedad es true (para la mayora de los componentes), y no se puede cambiar con un mtodo tpico de acceso setXX(). Slo se puede cambiar sobreescribiendo el mtodo isFocusTraversable() y

devolviendo el valor booleano apropiado. (Observe que cuando el foco alcanza a un componente a travs de una pulsacin de ratn se llama a su mtodo requestFocus(). Sobreescribiendo requestFocus() podemos responder a peticiones de foco de manera especfica para cada componente.)
requestFocusEnabled: especifica si una pulsacin de ratn dar el foco a ese componente. Esto no afecta al trabajo del FocusManager , que continuar transfiriendo el foco al componente como parte del ciclo del foco. Por defector esta propiedad es true (para la mayora de los componentes), y se puede cambiar con el mtodo setRequestFocusEnabled() de JComponent . nextFocusableComponent: esta especifica el componente al que se transfiere el foco cuando se

pulsa la tecla TAB. Por defecto est puesto a null, ya que el camino del foco se maneja para

52

nosotros

por el servicio FocusManager. Asignando un componente como el nextFocusableComponent potenciar el mecanismo de foco de FocusManager. Esto se consigue pasando el componente al mtodo setNextFocusableComponent() de JComponent.

2.12.1 FocusManager clase abstracta javax.swing.FocusManager


Esta clase define la responsabilidad de determinar como se mueve el foco de un componente a otro. FocusManager es una clase de servicio cuya instancia compartida se guarda en la tabla de servicio AppContext (ver 2.5). Para acceder a FocusManager usamos su mtodo esttico getCurrentManager(). Para asignar un nuevo FocusManager usamos el mtodo esttico setCurrentManager(). Podemos deshabilitar el servicio actual FocusManager usando el mtodo esttico disableFocusManager(), y podemos comprobar si est habilitado o no en un momento determinado usando el mtodo esttico isFocusManagerEnabled(). Los siguientes tres mtodos abstractos se tienen que definir en las subclases: focusNextComponent(Component aComponent): se debera llamar para desplazar el foco al siguiente componente en el ciclo del foco cuya propiedad focusTraversable sea true.
focusPreviousComponent(Component aComponent): se debera llamar para desplazar el foco al anterior componente en el ciclo del foco cuya propiedad focusTraversable sea true. processKeyEvent(Component focusedComponent, KeyEvent anEvent): se debera llamar para, o bien consumir un KeyEvent enviado al componente, o bien para permitirle ser

procesado por el componente. Este mtodo se usa normalmente para determinar si una pulsacin de teclado corresponde a un desplazamiento en el foco. Si este es el caso, el KeyEvent se consume normalmente y se mueve el foco hacia delante o hacia atrs usando los mtodos focusNextComponent() o focusPreviousComponent() respectivamente.

Nota: FocusManager recibir los eventos de teclado KEY_PRESSED, KEY_RELEASED y KEY_TYPED. Si se consume un evento, todos los dems eventos se deberan consumir tambin.API

2.12.2 DefaultFocusManager clase javax.swing.DefaultFocusManager


DefaultFocusManager desciende de FocusManager y define los tres mtodos requiridos, as como varios mtodos adicionales. El mtodo ms importante en esta clase es compareTabOrder(), que recibe dos Components como parmetros y determina en primer lugar cual de ellos est situado ms

cerca de la parte de arriba del contenedor para que sea la raz del ciclo del foco. Si ambos est situados a la misma altura este mtodo determinar cual de ellos est ms a la izquierda. Se devolver un valor de true si el primer componente pasado debe obtener el foco antes que el segundo. En otro caso devolver false. Los mtodos focusNextComponent() y focusPreviousComponent() desplazan el foco como se esperaba, y los mtodos getComponentBefore() y getComponentAfter() se definen para devolver el componente anterior y posterior respectivamente, que recibirn el foco despus de un determinado componente en el ciclo del foco. Los mtodos getFirstComponent() y getLastComponent() devuelven el primer componente y el ltimo que recibirn el foco en el ciclo del foco de un determinado contenedor.

53

El mtodo processKeyEvent() intercepta KeyEvents enviados al componente que posee actualmente el foco. Si estos eventos corresponden a un desplazamiento del foco (p.e. TAB, CTRLTAB, SHIFT-TAB, y SHIFT-CTRL-TAB) se consumen y se cambia el foco adecuadamente. En caso contrario, estos eventos se envan al componente para ser procesados (ver seccin 2.13). Observe que el FocusManager siempre intercepta los eventos de teclado.
Nota: Por defecto, CTRL-TAB y SHIFT-CTRL-TAB se pueden usar para desplazar el foco fuera de componentes de texto. TAB y SHIFT-TAB movern el cursor en su lugar (ver captulos 11 y 19).

2.12.3 Escuchando cambios del foco


Como con los componentes AWT, podemos escuchar cambios del foco en un componente adjuntando una instancia del interface java.awt.FocusListener. FocusListener define dos mtodos, cada uno de los cuales recibe una instancia de java.awt.FocusEvent como parmetro: focusGained(FocusEvent e): este mtodo recibe un FocusEvent cuando se da el foco a un componente al que se le ha aadido este oyente.
focusLost(FocusEvent e): este mtodo recibe un FocusEvent cuando se pierde el foco en un

componente al que se le ha aadido este oyente.


FocusEvent desciende de java.awt.ComponentEvent y define, entre otros, los identificadores FOCUS_LOST y FOCUS_GAINED para distinguir entre sus dos tipos de eventos. Un evento FOCUS_LOST ocurrir correspondiendo a una prdida de foco temporal o permanente. Las prdidas

ocurren cuando otra aplicacin u otra ventana recibe el foco. Cuando el foco vuelve a esta ventana, el componente que perdi el foco lo obtendr de nuevo, y un evento FOCUS_GAINED se despachar en ese momento. Las prdidas permanentes de foco ocurren cuando el foco se mueve a causa de una pulsacin en otro componente de la misma ventana, o mediante cdigo al invocar a requestFocus() en otro componente, o despachando algn KeyEvent que cause un cambio de foco en el mtodo processKeyEvent() de FocusManager. Como es lgico, podemos aadir o borrar implementaciones de FocusListener a cualquier componente Swing usando los mtodos addFocusListener() y removeFocusListener() de Component respectivamente.

2.13 Entrada de teclado, KeyStrokes, y Actions


2.13.1 Escuchando la entrad de teclado
Se lanzan KeyEvents por un componente siempre que el componente tiene el foco y el usuario pulsa una tecla. Para escuchar estos eventos en un componente particular podemos aadir KeyListeners usando el mtodos addKeyListener(). KeyEvent desciende de InputEvent y, al contrario que la mayora de los eventos, los KeyEvents se despachan antes de que la operacin correspondiente tome parte (p.e. en un cuadro de texto la operacin podra ser aadir un carcter especfico al contenido del documento). Podemos consumir estos eventos usando el mtodo consume() antes de que se manejen ms adelante por asociaciones de teclas u otros oyentes. (ms adelante veremos exactamente como tener notificacin de la entrada de teclado, y en que orden ocurre sta). Hay tres tipos de eventos KeyEvent, cada uno de los cuales ocurre por lo menos una vez cada activacin de teclado (p.e. pulsar y soltar una tecla del teclado): KEY_PRESSED: este tipo de evento de tecla se genera cuando una tecla del teclado se pulsa. La tecla que se ha pulsado queda especificada por la propiedad keyCode y un cdigo virtual de la tecla se puede obtener con el mtodo getKeyCode() de KeyEvent. Un cdigo virtual de tecla se usa para informar de la tecla exacta del teclado que ha causado el evento, tal como KeyEvent.VK_ENTER. KeyEvent define numerosas constantes estticas de tipo int, que

54

empiezan con el prefijo VK, que significa Virtual Key (tecla virtual) (ver los documentos del API de KeyEvent para una lista completa). Por ejemplo, si se pulsa CTRL-C, se lanzarn dos eventos KEY_PRESSED. El int devuelto por getKeyCode() correspondiente a pulsar CTRL ser un KeyEvent.VK_CTRL. Igualmente, el int devuelto por getKeyCode() correspondiente a pulsar la tecla C ser un KeyEvent.VK_C. (Observe que el orden en el que se lanzan depende del orden en el que se pulsan.) KeyEvent tambin tiene un propiedad keyChar que especifica la representacin Unicode del carcter pulsado (si no hay representacin Unicode se usa KeyEvent.CHAR_UNDEFINED--p.e. las teclas de funcin de un teclado normal de PC). Podemos obtener el carcter keyChar correspondiente a un KeyEvent usando el mtodo getKeyChar(). Por ejemplo, el carcter devuelto por getKeyChar() correspondiente a pulsar la tecla C ser c. Si estaba pulsado SHIFT cuando se puls la tecla C, el carcter devuelto por getKeyChar() correspondiente a la tecla C ser C. (Observe que se devuelven distintos keyChars para maysculas y minsculas, a pesar de que se usa el mismo keyCode en ambas situaciones--p.e. el valor VK_C se ser devuelto por getKeyCode() es pulsada o no la tecla SHIFT cuando se pulsa la tecla C. Observe tambin que no hay keyChar asociado con teclas como CTRL, y getKeyChar() devolver simplemente en este caso.)
KEY_RELEASED: este tipo de evento de teclado se genera cuando se suelta una tecla. Salvo por esta diferencia, los eventos KEY_RELEASED son idnticos a los eventos KEY_PRESSED (aunque, como

veremos ms adelante, ocurren mucho menos a menudo).


KEY_TYPED: este tipo de eventos se lanzan en algn momento entre un evento KEY_PRESSED y un evento KEY_RELEASED. Nunca contiene una propiedad keyCode correspondiente a la tecla pulsada, y se devolver 0 siempre que se llame a getKeyCode() en un evento de este tipo.

Observe que para teclas sin representacin Unicode (como RE PAG, PRINT SCREEN, etc.), no se lanzar el evento KEY_TYPED. La mayora de las teclas con representacin Unicode, cuando se mantienen pulsadas durante un rato, generarn repetidos KEY_PRESSED y KEY_TYPED (en este orden). El conjunto de teclas que muestran este comportamiento, y el porcentaje en que lo hacen, no se puede controlar y depende de la plataforma. Cada KeyEvent mantiene un conjunto de modificadores que especifica el estado de las teclas SHIFT, CTRL, ALT, y META. Este es un valor de tipo int que es el resultado de un or binario entre InputEvent.SHIFT_MASK, InputEvent.CTRL_MASK, InputEvent.ALT_MASK, y InputEvent.META_MASK (dependiendo de que teclas estn pulsadas en el momento del evento). Podemos obtener este valor con getModifiers(), y podemos comprobar especficamente cual de estas teclas estaba pulsada en el momento en que se lanz evento usando isShiftDown(), isControlDown(), isAltDown(), y isMetaDown().
KeyEvent tambin contiene la propiedad booleana actionKey que especifica si la tecla que lo ha lanzado corresponde a una accin que debera ejecutar la aplicacin (true) o si son datos que se usan normalmente para cosas como la adicin de contenido a un componente de texto (false). Podemos usar el mtodo isActionKey() de KeyEvent para obtener el valor de esta propiedad.

2.13.2 KeyStrokes
El uso de KeyListeners para manejar la entrada de teclado componente por componente era necesario antes de Java 2. A causa de esto, una significativa, y a menudo tediosa, cantidad de tiempo se gastaba planificando y depurando operaciones de teclado. El equipo de Swing se percat de esto, e incluy la funcionalidad de interceptar eventos de teclado sin tener en cuenta el componente que tenga el foco. Esta funcionalidad est implementada usando asociaciones de instancias de la clase javax.swing.KeyStroke con ActionListeners (normalmente instancias de javax.swing.Action).

55

Nota: A las acciones de teclado registradas se les conoce normalmente como aceleradores de teclado.

Cada instancia de KeyStroke encapsula un keyCode de KeyEvent (ver anteriormente), un valor modifiers (idntico al de KeyEvent -- ver anteriormente), y una propiedad booleana que especifica si se debera activar en una pulsacin de tecla (false -- por defecto) o cuando se suelta la tecla (true). La clase KeyStroke ofrece cinco mtodos estticos para crear objetos KeyStroke (observe que todos los objetos KeyStrokes estn escondidos, y no es necesario que estos mtodos develvan siempre una instancia completamente nueva):
getKeyStroke(char keyChar) getKeyStroke(int keyCode, int modifiers) getKeyStroke(int keyCode, int modifiers, boolean onKeyRelease) getKeyStroke(String representation) getKeyStroke(KeyEvent anEvent)

El ltimo mtodo devolver un KeyStroke con las propiedades correspondientes a los atributos del KeyEvent. Las propiedades keyCode, keyChar, y modifiers se toman del KeyEvent y la propiedad onKeyRelease se pone a true si el tipo del evento es KEY_RELEASED y a false en caso contrario. Para registrar una combinacin KeyStroke/ActionListener con un JComponent podemos usar su mtodo registerKeyBoardAction(ActionListener action, KeyStroke stroke, int condition). El parmetro ActionListener tiene que estar definido de forma que su mtodo actionPerformed() haga las operaciones necesarias cuando se intercepte entrada de teclado correspondiente al parmetro KeyStroke. El parmetro int especifica bajo que condiciones se considera valido el KeyStroke: JComponent.WHEN_FOCUSED: slo se llamar al correspondiente ActionListener si el componente con el que est registrado este KeyStroke tiene el foco.
JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT: slo se llamar al correspondiente ActionListener si el componente con el que est registrado este KeyStroke es ancestro

(contiene) del componente que tiene el foco.


JComponent.WHEN_IN_FOCUSED_WINDOW: slo se llamar al correspondiente ActionListener si el componente con el que est registrado este KeyStroke est en algn lugar de la ventana de ms alto nivel (p.e. JFrame, JDialog, JWindow, JApplet, o algn otro

componente pesado) que tiene el foco. Observe que las acciones de teclado registradas con esta condicin se manejan en una instancia de la clase privada de servicio KeyBoardManager (ver 2.13.4) en lugar de en el componente. Por ejemplo, para asociar la invocacin de un ActionListener a la pulsacin de ALT-H sin importar el componente que tenga el foco en un JFrame determinado, podemos hacer lo siguiente:
KeyStroke myKeyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_H, InputEvent.ALT_MASK, false); myJFrame.getRootPane().registerKeyBoardAction( myActionListener, myKeyStroke, JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);

Cada JComponent mantiene una propiedad cliente de tipo Hashtable que contiene todos los KeyStrokes asociados. Cuando se registra un KeyStroke usando el mtodo registerKeyboardAction(), se aade a esta estructura. Slo se puede registrar un

56

ActionListener para cada KeyStroke, y si ya hay un ActionListener para un determinado KeyStroke, el nuevo seobreescribir al anterior. Podemos obtener un array de KeyStrokes correspondientes a las asociaciones guardadas en esta Hashtable usando el mtodo getRegisteredKeyStrokes() de JComponent, y podemos anular todas las asociaciones con el mtodo resetKeyboardActions(). Dado un objeto KeyStroke podemos obtener su correspondiente ActionListener con el mtodo getActionForKeyStroke() de JComponent,

podemos

obtener

su

correspondiente

propiedad

de

condicin

con

el

mtodo

getConditionForKeyStroke().

2.13.3 Actions
Una instancia de Action es bsicamente una implementacin conveniente de ActionListener que encapsula una Hashtable de propiedades ligadas semejante a la de las propiedades cliente de JComponent (ver captulo 12 para ms detalles sobre el trabajo con implementaciones de Action y sus propiedades). A menudo usamos instancias de Action cuando registramos acciones de teclado.
Nota: Los componentes de texto son especiales porque usan una resolucin jerrquica mediante KeyMaps. Un KeyMap es una lista de asociaciones Action/KeyStroke y JTextComponent soporta mltiples niveles de este tipo de mapeo. Ver captulos 11 y 19.

2.13.4 El flujo de la entrada de teclado


Cada KeyEvent se despacha primero al componente con el foco. El FocusManager tiene la primera oportunidad de procesarlo. Si el FocusManager no lo quiere, entonces se hace que el JComponent llame a super.processKeyEvent() que da la oportunidad a muchos KeyListeners de procesar el evento. Si los oyentes no lo consumen y el componente con el foco es un JTextComponent, se recorre la jerarqua de KeyMap (ver captulos 11 y 19 para ms detalles sobre KeyMaps). Si no se consume el evento, en este momento las asociaciones de teclado registradas con el componente que tiene el foco tienen una oportunidad. Primero, los KeyStrokes definidos con la condicin WHEN_FOCUSED tienen esa oportunidad. Si ninguno de stos maneja el evento, el componente navega por sus contenedores padre (hasta que alcanza un JRootPane) buscando KeyStrokes que estn definidas con la condicin WHEN_ANCESTOR_OF_FOCUSED_COMPONENT. Si el evento no se ha manejado despus de que se haya alcanzado el contenedor de mximo niveI, se enva a KeyboardManager, una clase de servicio que es privada de paquete (observe que al contrario de la mayora de las clases de servicio de Swing, KeyboardManager no registra su instancia compartida con AppContext -- ver seccin 2.5). KeyboardManager busca componentes con KeyStrokes registrados con la condicin WHEN_IN_FOCUSED_WINDOW y les enva el evento. Si no se encuentra ninguno de stos, entonces KeyboardManager pasa el evento a todas las JMenuBars de la ventana actual y les da a sus aceleradores la oportunidad de procesar el evento. Si el evento an no es manejado, comprobamos si el foco reside en un JInternalFrame (porque es el nico RootPaneContainer que puede estar dentro de otro componente Swing). Si es el caso, nos trasladamos al padre del JInternalFrame. Este proceso contina hasta que se procesa el evento o se alcanza la ventana de mximo nivel.

2.14 SwingUtilities
clasa javax.swing.SwingUtilities
En la seccin 2.3 vimos dos mtodos de la clase SwingUtilities que se usaban para ejecutar cdigo en el hilo de despacho de eventos. Estos son slo 2 de los 36 mtodos de utilidad genrica definidos en SwingUtilities, que se dividen en siete grupos: mtodos de clculo, mtodos de conversin, mtodos de accesibilidad, mtodos de recuperacin, mtodos relacionados con la multitarea y los eventos, mtodos para los botones del ratn, y mtodos de disposicin/dibujo/UI. Todos estos mtodos son estticos y se describen muy brevemente en esta seccin (para una comprensin ms avanzada vea el

57

cdigo fuente de SwingUtilities.java).

2.14.1 Mtodos de clculo


Rectangle[] computeDifference(Rectangle rectA, Rectangle rectB): devuelve las regiones rectangulares que representan la porcin de rectA que no intersecciona con rectB. Rectangle computeIntersection(int x, int y, int width, int height, Rectangle dest): devuelve la interseccin de dos reas rectangulares. La primera regin se define con los parmetros de tipo int y la segunda por con el parmetro de tipo Rectangle. El parmetro de tipo Rectangle se modifica y se devuelve como resultado del clculo de forma que no se tiene que instanciar un nuevo Rectangle. Rectangle computeUnion(int x, inty, int width, int height, Rectangle dest): devuelve la unin de dos reas rectangulares. La primera regin se define con los parmetros de tipo int y la segunda por con el parmetro de tipo Rectangle. El parmetro de tipo Rectangle se modifica y se devuelve como resultado del clculo de forma que no se tiene que instanciar un nuevo Rectangle. isRectangleContainingRectangle(Rectangle a, Rectangle b): devuelve true si el Rectangle a contiene completamente al Rectangle b. computeStringWidth(FontMetrics fm, String str): devuelve la achura del String de acuerdo al objeto FontMetrics (ver 2.8.3).

2.14.2 Mtodos de conversin


MouseEvent convertMouseEvent(Component source, MouseEvent sourceEvent, Component destination): devuelve un MouseEvent nuevo con destination como fuente y las coordenadas x e y convertidas al sistema de coordenadas de destination (asumiendo en ambos casos que destination no sea null). Si destination es null las coordenadas se convierten al sistema de coordenadas de source, y se pone source como fuente del evento. Si ambos son null el MouseEvent devuelto es idntico al evento que se pasa. Point convertPoint(Component source, Point aPoint, Component destination): devuelve un Point que representa aPoint convertido al sistema de coordenadas del componente destination como si se hubiese generado en el componente source. Si uno de los componentes es null se usa el sistema de coordenadas del otro, y si ambos son null el Point devuelto es idntico al Point pasado. Point convertPoint(Component source, int x, int y, Component destination): este mtodo funciona igual que el anterior mtodo convertPoint() excepto que recibe parmetros de tipo int que representan las coordenadas del Point a convertir en lugar de una instancia de Point. Rectangle convertRectangle(Component source, Rectangle aRectangle, Component destination): devuelve un Rectangle convertido del sistema de coordenadas del componente source al sistema de coordenadas del componente destination. Este mtodo se comporta de forma similar a convertPoint(). void convertPointFromScreen(Point p, Component c): convierte el Point dado en coordenadas de la pantalla al sistema de coordenadas del Component dado. void convertPointToScreen(Point p, Component c): convierte el Point dado en el sistema de coordenadas del Component dado al sistema de coordenadas de la pantalla.

2.14.3 Mtodos de accesibilidad


Accessible getAccessibleAt(Component c, Point p): devuelve el componente

58

Accessible en el determinado Point del sistema de coordenadas del Component dado (se devolver null si no se encuentra ninguno). Observe que un componente Accessible es aquel que implementa el interface javax.accessibility.Accessible. Accessible getAccessibleChild(Component c, int i): devuelve el i-simo hijo Accessible del Component dado. int getAccessibleChildrenCount(Component Accessible que contiene el Component dado. c): devuelve el nmero de hijos

int getAccessibleIndexInParent(Component c): devuelve el ndice en su padre del Component dado descartando todos los componentes contenidos que no implementen el interface Accessible. Se devolver -1 si el padre es null o no implementa Accessible, o si el Component dado no implementa Accessible. AccessibleStateSet getAccessibleStateSet(Component c): devuelve el conjunto de AccessibleStates que no estn activos para el Component dado.

2.14.4 Mtodos de recuperacin


Component findFocusOwner(Component c): devuelve el componente contenido dentro del Component dado (o el Component dado) que tiene el foco. Si no hay tal componente se devuelve null. Container getAncestorNamed(String name, Component comp): devuelve el ancestro ms cercano del Component dado con el nombre que le pasamos. En otro caso se devuelve null. (Observe que cada Component tiene una propiedad name que se puede asignar y recuperar usando los mtodos setName() y getName() respectivamente.) Container getAncestorOfClass(Class c, Component comp): devuelve el ancestro ms cercano del Component dado que es una instancia de c. En otro caso se devuelve null. Component getDeepestComponentAt(Component parent, int x, int y): devuelve el hijo ms profundo del Component dado que contiene el punto (x,y) en trminos del sistema de coordenadas del Component dado. Si el Component no es un Container este mtodo termina

inmediatamente.
Rectangle getLocalBounds(Component c): devuelve un Rectagle que representa los lmites de un Component determinado en su propio sistema de coordenadas (de este modo siempre

empieza en 0,0).
Component getRoot(Component c): devuelve el primer ancestro de c que es una Window. En otro caso este mtodo devuelve el ltimo ancestro que es un Applet. JRootPane getRootPane(Component c): devuelve el primer JRootPane que es padre de c, o c si es un JRootPane. Window windowForComponent(Component c): devuelve el primer ancestro de c que es una Window. En otro caso devuelve null. boolean isDescendingFrom(Component allegedDescendent, Component allegedAncestor): devulve true si allegedAncestor contiene a allegedDescendent.

2.14.5 Mtodos relacionados con la multitarea y los eventos


Ver seccin 2.3 para ms informacin sobre estos mtodos. void invokeAndWait(Runnable obj): enva el Runnable a la cola de despacho de eventos y bloquea el hilo actual.
void invokeLater(Runnable obj): enva el Runnable a la cola de despacho de eventos y

59

contina.
boolean isEventDispatchThread(): devuelve true si el hilo actual es el hilo de despacho de

eventos.

2.14.6 Mtodos para los botones del ratn


boolean isLeftMouseButton(MouseEvent): devuelve true si el MouseEvent corresponde

a una pulsacin del botn izquierdo del ratn.


boolean boolean isMiddleMouseButton(MouseEvent): devuelve true si el MouseEvent corresponde a una pulsacin del botn de en medio del ratn. isRightMouseButton(MouseEvent): devuelve corresponde a una pulsacin del botn derecho del ratn. true

si

el

MouseEvent

2.14.7 Mtodos de disposicin/dibujo/UI


String layoutCompoundLabel(FontMetrics fm, String text, icon icon, int verticalAlignment, int horizontalAlignment, int verticalTextPosition, int horizontalTextPosition, Rectangle viewR, Rectangle iconR, Rectangle textR, int textIconGap): Este mtodo se usa normalmente por el delegado UI de JLabel para posicionar texto y/o un icono usando el FontMetrics, las condiciones de alineamiento y las posiciones del texto dentro del Rectangle viewR . Si se determina que el texto de la etiqueta no cabr dentro de este Rectangle, se usan puntos suspensivos (...) en lugar del texto que no cabra. Los Rectangles textR e iconR se modifican para reflejar la nueva disposicin, y se devuelve el String resultante de esta disposicin. String layoutCompoundLabel(JComponent c, FontMetrics fm, String text, icon icon, int verticalAlignment, int horizontalAlignment, int verticalTextPosition, int horizontalTextPosition, Rectangle viewR, Rectangle iconR, Rectangle textR, int textIconGap): este mtodo es idntico al

anterior, pero recibe el componente destino para comprobar si el la orientacin del texto se debe tener en cuenta (ver el artculo Component Orientation in Swing: How JFC Components support BIDI text en the Swing Connection para ms informacin sobre orientacin: http://java.sun.com/products/jfc/tsc/tech_topics/bidi/bidi.html).
void paintComponent(Graphics g, Component c, Container p, int x, int y, int w, int h): pinta el Component dado en el contexto grfico dado, usando el rectngulo definido por los cuatro parmetros de tipo int como rea de recorte. El Container se usa para que acte como el padre del Component de forma que cualquier peticin de validacin o repintado

que sucedan en ese componente no se propaguen por el rbol de ancestros del componente al que pertenece el contexto grfico dado. Esta es la misma metodologa que usan los pintores de componentes de JList, JTree, y JTable para mostrar correctamente el comportamiento de "sello de goma" (rubber stamp). Este comportamiento se logra mediante el uso de un CellRendererPane (ver captulo 17 para ms informacin sobre esta clase y por qu se usa para envolver los pintores).
void paintComponent(Graphics g, Component c, Container p, Rectangle r): funciona de forma idntica al mtodo anterior, pero recibe un Rectangle como parmetro en lugar de cuatro ints. void updateComponentTreeUI(Component c): notifica a todos los componentes que contiene c, y a c, que actualicen su delegado UI para que correspondan a los actuales UIManager y UIDefaults (ver captulo 21).

60

También podría gustarte