Documentos de Académico
Documentos de Profesional
Documentos de Cultura
Java Swing
Java Swing
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.
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.)
<<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.
<<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.
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
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
<<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.
<<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).
<<fichero figure1-5.gif>>
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
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 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.
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.
<<fichero figure1-6.gif>>
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
Coge toda la informacin necesaria del componente y posiblemente de su(s) modelo(s) para dibujarlo correctamente.
getPreferredSize(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):
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.
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.
<<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
12
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);
13
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.
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.
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
Estos valores se usan slo en contenedores que se manejan mediante BoxLayout o OverlayLayout.
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
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
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); } }
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
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,
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.
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.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.
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.
<<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
<<fichero figure2-2.gif>>
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)
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
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.
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).
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.
<<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
38
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.
// 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.
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.
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 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.
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 <===
46
componentes. Por tanto, otra forma de garantizar que todos los componentes usen doble buffer es llamar a:
RepaintManager.currentManager(null).setDoubleBufferingEnabled(true);
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'.
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.
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.
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
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).
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.
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
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
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.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
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.
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.
59
contina.
boolean isEventDispatchThread(): devuelve true si el hilo actual es el hilo de despacho de
eventos.
si
el
MouseEvent
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