Parte I - Bases

La Parte I se compone de dos capítulos que sientan las bases para un aprendizaje productivo y exitoso de la librería de clases JFC/Swing. El primero empieza con un breve vistazo de lo qué es Swing y una introducción a su arquitectura. El segundo profundiza un poco más en una discusión 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 común a varias áreas de Swing e introduciéndolo en el capítulo 2, su comprensión de lo que vendrá posteriormente mejorará notablemente. Contamos con que tendrá que volver a él a menudo, y en algún lugar le instaremos explícitamente a que lo haga. Como mínimo, le recomendamos que conozca los contenidos del capítulo 2 antes de seguir adelante.

Capítulo 1. Un vistazo a Swing
En este capítulo: • • • • AWT Swing MVC Delegados UI y PLAF

1.1 AWT
AWT (Abstract Window Toolkit) es la parte de Java diseñada para crear interfaces de usuario y para dibujar gráficos e imágenes. Es un conjunto de clases que intentan ofrecer al desarrollador todo lo que necesita para crear una interfaz de usuario para cualquier applet o aplicación Java. La mayoría de los componentes AWT descienden de la clase java.awt.Component como podemos ver en la figura 1.1. (Obsérvese que las barras de menú de AWT y sus ítems no encajan dentro de la jerarquía de Component.)

Figura 1.1 Jerarquía parcial de Components

<<fichero figure1-1.gif>>

1

JFC está compuesto de cinco partes fundamentales: AWT, Swing, Accesibilidad, Java 2D, y Arrastrar y Soltar. Java 2D se ha convertido en una parte más 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 más 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 tecnología se integre más con Swing y AWT en un futuro próximo. De este modo, AWT está en el corazón de JFC, lo que la convierte en una de las librerías más importantes de Java 2.

1.2 Swing
Swing es un extenso conjunto de componentes que van desde los más simples, como etiquetas, hasta los más 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 más una capa encima de AWT que una sustitución del mismo. La figura 1.2 muestra una parte de la jerarquía de JComponent. Si la compara con la jerarquía de Component notará que para cada componente AWT hay otro equivalente en Swing que empieza con "J". La única excepción es la clase de AWT Canvas, que se puede reemplazar con JComponent, JLabel, o JPanel (en la sección 2.8 abordaremos esto en detalle). Asimismo se percatará de que existen algunas clases Swing sin su correspondiente homólogo. La figura 1.2 representa sólo una pequeña fracción de la librería Swing, pero esta fracción son las clases con las que se enfrentará más a menudo. El resto de Swing existe para suministrar un amplio soporte y la posibilidad de personalización a los componentes estas clases definen.

Figura 1.2 Parte de la jerarquía de JComponent

<<fichero figure1-2.gif>>

2

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 noción 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 también lo es, se superpondrá por definición 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, sólo que tenemos que tener cuidado y saber qué situaciones son seguras y cuáles 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 tecnologías trabajen juntas de manera aceptable. La regla más importante a seguir es que no deberíamos colocar componentes pesados dentro de contenedores ligeros, que comúnmente 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 específica de JPopupMenu podemos usar su método setLightWeightPopupEnabled().
Nota: Para JMenus (que usan JPopupMenus para mostrar sus contenidos) tenemos que usar primero el método 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 aplicación, incluyendo menús dentro de menús, etc.

Alternativamente podemos llamar al método estático setDefaultLightWeightPopupEnabled() de JPopupMenu y pasarle un valor false para forzar a todos los menús emergentes de una sesión de Java a ser pesados. Tenga en cuenta que sólo afectará a los menús emergentes creados a partir de que se ha hecho la llamada. Es por eso una buena idea llamar a este método durante la inicialización.

1.2.2 Independencia de la plataforma
La característica más notable de los componentes Swing es que están escritos al 100% en Java y no dependen de componentes nativos, como sucede con casi todos los componentes AWT. Esto significa que un botón Swing y un área de texto se verán y funcionarán idénticamente en las plataformas Macintosh, Solaris, Linux y Windows. Este diseño elimina la necesidad de comprobar y depurar las aplicaciones en cada plataforma destino.
Nota: Las únicas excepciones a esto son los cuatro componentes pesados de Swing que son subclases directas de clases de AWT, que dependen de componentes nativos: JApplet, JDialog, JFrame, y JWindow. Ver capítulo 3.

1.2.3 Vistazo al paquete Swing
javax.swing

Contiene la mayor parte de los componentes básicos de Swing, modelos de componente por defecto, e interfaces. (La mayoría de las clases mostradas en la Figura 1.2 se encuentran en este

3

swing.filechooser Clases e interfaces que dan soporte al componente JFileChooser. javax.html.beans.) javax.swing. javax.basic Consiste en la implementación del Basic look-and-feel. No se trata de una implementación normal de look-andfeel ya que no define ni el aspecto ni el comportamiento de ningún componente. por una razón u otra.html Esta extensión del paquete text contiene soporte para componentes de texto HTML. crean subclases e implementan las clases de este paquete.paquete. Actualmente Java 2 no viene con ninguna implementación de multiplexing look-and-feel (de todos modos. acciones de editor y personalización del teclado.swing.swing. Soporta un alto grado de personalización sin requerir mejoras de look-and-feel.text.parser Soporte para analizar gramaticalmente HTML. javax. javax.tree Clases e interfaces que dan soporte al componente JTree.colorchooser Clases e interfaces que dan soporte al componente JColorChooser. Éstas están orientadas a desarrolladores que.swing. Más bien ofrece la capacidad de combinar varios look-and-feels para usarlos simultáneamente.swing. como metal. Es el único look-and-feel que viene con Swing y que no está diseñado para ser consistente con una plataforma específica.) javax. incluyendo soporte para documentos con o sin estilo.swing. javax.swing.event y java. (Este paquete también contiene alguna clase privada interesante sin documentar.rtf Contiene soporte para documents RTF. javax. javax. javax.) javax. (El soporte de HTML está siendo ampliado y reescrito completamente mientras escribimos este libro.text Clases e interfaces usadas por los componentes de texto.multi Este es el Multiplexing look-and-feel.swing.swing. usado para selección de colores. Es por ello que la cobertura que le damos es muy limitada. javax.swing.swing. Observe que los bordes pueden ser compartidos por cualquier número de componentes Swing.plaf Contiene el API del comportamiento y aspecto conectable usado para definir componentes de interfaz de usuario personalizados. las vistas de estos documentos. usado para selección de ficheros. Este componente se usa para mostrar y manejar datos que guardan alguna jerarquía.undo 4 . Este componente se usa para manejar datos en forma de hoja de cálculo. motif y basic. no pueden usar uno de los look-and-feel existentes.swing.awt. encima del cual se construyen los lookand-feels que provee Swing.metal Metal es el look-and-feel por defecto de los componentes Swing.text. Normalmente deberemos usar las clases de este paquete si queremos crear nuestro look-and-feel personal.table Clases e interfaces para dar soporte al control de JTable. Las implementaciones de look-and-feel. resaltado. Los componentes Swing también soportan eventos y oyentes definidos en java. javax. ya que no son componentes por si mismos.swing.text.border Clases e interfaces que se usan para definir estilos de bordes específicos. Soporta un alto grado de personalización sin requerir mejoras de look-and-feel. se rumorea que el equipo de Swing esta trabajando en un audio look-and-feel mientras escribimos estas líneas). Un ejemplo típico podría ser un look-and-feel de audio combinado con metal o motif. javax. La mayoría de las clases de este paquete son abstractas.plaf.plaf. javax.event El paquete contiene todos los oyentes y eventos específicos de Swing.plaf.swing.

1. En la figura 1. tales como el teclado o el ratón. En su lugar el modelo enviará notificaciones o broadcasts (lo que conocemos como eventos).El paquete undo contiene soporte para implementar y manejar la funcionalidad deshacer/rehacer.3. La vista es responsable de mantener actualizada la representación en pantalla y debe hacerlo recibiendo mensajes indirectos del modelo o mensajes directos del controlador. la vista muestra el color correcto de un componente. necesitamos comprender como se diseñó originalmente su funcionamiento. aquellos valores como el estado pulsado/no pulsado de un botón.3 esta comunicación indirecta se representa con líneas de puntos. por ejemplo. Los componentes Swing están basados en una versión más moderna de este diseño. 1. Figura 1.gif>> 1. 1.3. Un modelo puede ser responsable de comunicación indirecta con la vista y el controlador. etc.1 Modelo El modelo es el responsable de conservar todos los aspectos del estado del componente. Por ejemplo. y el renderizado de la fuente deseada. tanto si el componente sobresale como si está hundido (en el caso de un botón). Esto es el “aspecto(look)” del componente. los datos de un carácter de un componente de texto y como esta estructurado. Nota: La separación en tres partes descrita aquí se usa en la actualidad solamente en un pequeño número de conjuntos de componentes de interfaz de usuario. entre los que destaca VisualWorks. y un controlador.3.2 Vista La vista determina la representación visual del modelo del componente. El controlador es el “comportamiento(feel)” del componente. Esto incluye.3 La arquitectura Modelo-Vista-Controlador <<fichero figure1-3. y determina que acciones se ejecutan cuando se usa el 5 . una vista.3 Controlador El controlador es responsable de determinar si el componente debería reaccionar a algún evento proveniente de dispositivos de entrada. Por indirecta queremos decir que el modelo no ‘conoce’ su vista y controlador--no mantiene referencias hacia ellos. Antes de que abordemos como trabaja MVC en Swing.3 Arquitectura MVC MVC es una descomposición orientada a objeto del diseño de interfaces de usuario bien conocida que data de finales de los 70. Los componentes se descomponen en tres partes: un modelo.

3. Figura 1. El modelo se actualiza y lo notifica mediante un mensaje. Más específicamente. que se visualizan con iconos y colores personalizados. El controlador puede recibir mensajes directos desde la vista.4 muestra un grupo de componentes que usan dos interfaces de usuario diferentes. e indirectos desde el modelo. Si el controlador determina que el usuario ha pulsado el ratón debe enviar un mensaje a la vista. pero que se están usando dos implementaciones diferentes de look-and-feel (diferentes vistas y controladores -.como veremos más adelante). árboles y listas despegables (JComboBox). 1. que será recibido por la(s) vista(s).4 Malachite y Windows look-and-feels de los mismos componentes <<fichero figure1-4. supongamos que tenemos un checkbox seleccionado en nuestro interfaz. Por ejemplo. De está manera. Lo más importante de esta figura es que los componentes mostrados son los mismos. estos componentes permiten definir nuestros propios editor y visualizador de celdas. que se usan para aceptar y mostrar datos específicos respectivamente.5 muestra las columnas de una tabla que contiene datos del mercado de valores. para decirle que debería actualizarse basándose en el nuevo estado del modelo. La figura 1.componente.4 Controlador y vista personalizados Una de las principales ventajas de la arquitectura MVC es la posibilidad de personalizar el “aspecto(look)” y el “comportamiento(feel)” de un componente sin modificar el modelo. Si la vista determina que la pulsación ha sido en el checkbox envía un mensaje al modelo.gif>> Algunos componentes Swing ofrecen también la posibilidad de personalizar partes específicas del componente sin afectar al modelo. La Figura 1. Veremos como sacar provecho de esta funcionalidad en nuestro estudio de las listas. tablas. 6 . permitiéndonos tener varias vistas y controladores manipulando un mismo modelo. el modelo no está ligado a una vista o un controlador específico.

o un intervalo múltiple (discontinuo). Diseñaremos e implementaremos nuestros propios modelos de datos para JComboBox. Guarda: Una colección de objetos. JTabbedPane.5 Visualización personalizada <<fichero figure1-5. Por ejemplo. ListModel Usado por: JList. Permite seleccionar sólo uno. JSlider. ComboBoxModel Usado por: JComboBox. y con que componentes se usan: BoundedRangeModel Usado por: JProgressBar. JPopupMenu. Podemos también asociar el mismo modelo de datos con más de un componente (como ya comprobamos viendo MVC).gif>> 1. MutableComboBoxModel Usado por: JComboBox. TableColumnModel. con una breve descripción de los datos para cuyo almacenamiento están diseñados. min. JTree. JMenuItem. Guarda: Un vector (u otra colección alterable) de objetos y un objeto seleccionado. dos JTextAreas pueden guardar su texto en el mismo modelo de documento.5 Modelos personalizados Otra gran ventaja de la arquitectura MCV de Swing es la posibilidad de personalizar y reemplazar el modelo de datos de un componente. Abajo hemos listado algunas definiciones de interfaces de modelos Swing. Value y extent tienen que estar entre los valores de min y max. extent. Extent es siempre <= max y >= value. podemos construir nuestro propio modelo de documento de texto que preste especial atención a la escritura de una fecha o un número de teléfono de una manera determinada. JScrollBar. JList. JTable. Guarda: Una colección de objetos y un objeto seleccionado. Guarda: Uno o más índices de selecciones de la lista o de ítems de la tabla. Por ejemplo. SingleSelectionModel Usado por: JMenuBar. ButtonModel Usado por: Todas las subclases de AbstractButton. Guarda: Un booleano que determina si el botón está seleccionado (armado) o no (desarmado). max. y más ampliamente a lo largo de nuestro repaso a los componentes de texto. ListSelectionModel Usado por: JList. 7 .Figura 1. Guarda: 4 enteros: value. un intervalo simple. mientras que están usando dos vistas diferentes de esa información.3.

ni la C. la anchura total de todas las columnas. etc. No todos los componentes Swing tienen modelos. ¿cómo encaja el componente por si mismo dentro del definición de MVC?. Document Usado por: Todos los componentes de texto.4 Delegados UI y PLAF Casi todos los conjuntos de componentes modernos combinan la vista y el controlador. imágenes. Guarda: Objetos que se pueden mostrar en un árbol. Smalltalk/V. JFC Swing ha sido el último en añadirse a este grupo. y un indicador de selección de columna. TreeSelectionModel Usado por: JTree. De hecho. Componentes simples o complejos. Guarda: Una colección de objetos TableColumn. aquellos que se usan como contenedores. la anchura entre cada columna. Esto quiere decir que MVC no es totalmente rígido en Swing. y otros tipos de contenido. 8 . como C++. y otro para guardar los datos). un modelo de selección. No es ni la M. Guarda: Una matriz de objetos. Por esta razón. TreeModel Usado por: JTree.e. etc. no necesitan separar los modelos. Las implementaciones tienen que ser capaces de distinguir entre las hojas y el resto de nodos. Guarda: Contenido. la comunicación entre el modelo y el delegado UI es indirecta. algunos componentes Swing tienen más de un modelo (p. TableModel Usado por: JTable. continua y discontinua. permitiendo así tener asociado más de un modelo a un delegado UI. Implementaciones más complejas soportan texto con estilo. Esto se verá más claro cuando progresemos en este capítulo y a lo largo del resto del libro. ni la V. Idealmente. JList usa un modelo para mantener información sobre la selección. JTextField. 1. como veremos en la siguiente sección. tienen que tener modelos. la vista y el controlador. o ahora Java. que no guardan grandes cantidades de información (como JDesktopPane). un conjunto de oyentes para eventos de modelos de una columna. los componentes interactivos como JButton. (p. Sin embargo. El componente se comporta como un mediador entre el/los modelo(s).6. TableColumnModel Usado por: JTable. como JApplet.Guarda: El índice del elemento seleccionado en una colección de objetos perteneciente al implementador. no los tienen.e. Interviews. tanto si se basan en SmallTalk. La vista y el controlador de cada componente están casi siempre separadas en todos los componentes Swing. la arquitectura subyacente de Swing se denomina más acertadamente como modelo-delegado que como modelo-vista-controlador. JFrame. Permite selección simple. Ejemplos de ello son MacApp. JLayeredPane. aunque puede ocupar el lugar de una o incluso todas estas partes si lo diseñamos para ello. Normalmente es texto (caracteres). JTable. JInternalFrame. Guarda: Un Color. y los widgets X/Motif que se usan en IBM Smalltalk. Guarda: Las filas seleccionadas. Podemos verlo en la Figura 1. componentes embebidos). Entonces. y viceversa. JDesktopPane. y los objetos deben estar organizados jerárquicamente. Swing empaqueta todos los controladores y vistas de un componente dentro de un objeto denominado delegado UI. ColorSelectionModel Usado por: JColorChooser.

JComponent c).1 La clase ComponentUI Todos los delegados UI descienden de una clase abstracta que se llama ComponentUI. Observe que a cada método se le pasa como parámetro un JComponent.6 Model-delegate architecture <<fichero figure1-6. paint(Graphics g.MetalButtonUI por defecto. Para obligar a usar un delegado UI específico podemos usar el método setUI() del componente (observe que setUI() está declarado como protected en JComponent porque sólo tiene sentido en subclases de JComponent): 9 . Esta instancia se usa para ser compartida entre componentes del mismo tipo (p. Métodos de ComponentUI: static ComponentUI CreateUI(JComponent c) Este se implementa normalmente para que devuelva una instancia compartida del delegado UI que define la subclase apropiada de ComponentUI. para avisar al delegado UI cuando ocurran cambios en el estado que requieran que se actualice la vista.e.4. JComponent c) Coge toda la información necesaria del componente y posiblemente de su(s) modelo(s) para dibujarlo correctamente.Figura 1. Los métodos de ComponentUI describen los fundamentos de la comunicación entre un delegado UI y un componente.gif>> 1.) installUI(JComponent c) Instala el ComponentUI en el componente especificado. getMinimumSize(JComponent c) Devuelve el tamaño mínimo del componente especificado por el ComponentUI. getMaximumSize(JComponent c) Devuelve el tamaño máximo del componente especificado por el ComponentUI. uninstallUI(JComponent c) Borra este ComponentUI y cualquier oyente añadido por installUI() del componente especificado y/o de su(s) modelo(s).plaf.swing. update(Graphics g. Esto añade normalmente oyentes al componente y/o a su(s) modelo(s). JComponent c) Si el componente es opaco debería pintar su fondo y entonces llamar a paint(Graphics g. Todos los JButtons que usan el Metal look-and-feel comparten la misma instancia estática del delegado UI definido en javax.metal. getPreferredSize(JComponent c) Devuelve el tamaño preferido del componente especificado por el ComponentUI.

basic descienden de ellas para implementar el Basic look-and-feel. 1.plaf.plaf. extiende todas las clases abstractas de javax.sun. Nota: La clase JComponent define métodos para asignar delegados UI porque las declaraciones de métodos no implican código específico de un componente.motif.plaf.plaf.java. (Observe que el Basic look-and-feel no se puede usar directamente ya que BasicLookAndFeel es una clase abstracta.java.swing.JButton m_button = new JButton().metal.swing.MotifLookAndFeel Metal (por defecto): javax.createUI(m_button)). tenemos simplemente que llamar al método setLookAndFeel() de UIManager.multi. De todos modos.swing. La mayor parte de los delegados UI se construyen de manera que conocen un componente y su(s) modelo(s) sólo mientras llevan a cabo tareas de dibujo o de vista-controlador.2 Pluggable look-and-feel Swing incluye varios conjuntos de delegados UI. El multiplexing look-and-feel. pero no viene con Java 2 y se debe descargar separadamente. al uso con look-and-feels de Accesibilidad. y la clase UIManager (que maneja los lookand-feels instalados) los usa para acceder a la tabla UIDefaults del look-and-feel actual (que entre otras cosas contiene los nombres de las clases de los delegados UI del look-and-feel correspondientes a cada componente Swing).) Hay tres implementaciones de pluggable look-and-feel que descienden de Basic lookand-feel: Windows: com. Éste es un conjunto de delegados UI que se usan como base para construir el resto de clases de look-and-feel.plaf. y está enfocado pero no limitado. MetalLookAndFeel.plaf se componen de clases abstractas que derivan de ComponentUI. Todos los paquetes look-and-feel contienen una clase que desciende la clase abstracta javax.swing. etc. El código siguiente se puede usar para llevar esto a cabo en tiempo de ejecución: 10 . m_button.e.sun. y las clases del paquete javax. Para cambiar el look-and-feel actual de una aplicación.swing. pasándole el nombre completo del LookAndFeel que vamos a usar. Swing evita normalmente asociar delegados UI a un componente determinado (a causa de la instancia estática). Esto no es posible con modelos de datos porque no hay un interface de modelo del que todos ellos desciendan (p.MetalLookAndFeel Hay también un MacLookAndFeel que simula las interfaces de usuario de Macintosh.WindowsLookAndFeel CDE\Motif: com.LookAndFeel: BasicLookAndFeel. Las librerías de los Windows y Macintosh pluggable look-and-feel sólo se soportan en la plataforma correspondiente.plaf. El paquete javax. javax. Cada conjunto contiene implementaciones de ComponentUI para casi todos los componentes Swing y podemos llamar a estos conjuntos una implementación de look-and-feel o pluggable look-and-feel (PLAF).windows. Está diseñado para permitir que combinaciones de lookand-feels se usen simultáneamente.swing.setUI((MalachiteButtonUI) MalachiteButtonUI. El trabajo de cada delegado UI multiplexado es manejar cada uno de sus delegados UI hijos.4. WindowsLookAndFeel. nada nos impide asignar el nuestro propio como demuestra el código anterior.swing. Esas son los puntos centrales de acceso a cada paquete de look-and-feel. no hay una clase base como ComponentUI para los modelos Swing).MultiLookAndFeel. Las usamos cuando cambiamos el look-and-feel actual.swing. Por esta razón los métodos para asignar modelos se definen en las subclases de JComponent que sea necesario.

BasicLabelUI y MultiLabelUI descienden ambas de LabelUI y se encuentran en los paquetes basic y multi respectivamente.updateComponentTreeUI(myJFrame). SwingUtilities. Figura 1. el Multi look-and-feel. pero no hemos mencionado nada acerca de las clases específicas de los delegados UI que derivan de ComponentUI. Sin embargo. y es simplemente un medio para permitir instalar un número arbitrario de delegados UI en un componente determinado. } catch (Exception e) { System.MotifLookAndFeel"). y Windows están construidos encima de las versiones de Basic.7 debería enfatizar el hecho de que Swing suministra un gran numero clases de delegados UI.gif>> Se espera que la mayoría de las implementaciones de look-and-feel extiendan las clases definidas en el paquete basic. Todas las clases abstractas del paquete javax.println("Could not load LookAndFeel"). Motif. La figura 1.setLookAndFeel( "com. es la única de las implementaciones que no desciende de Basic. 1. así como a modificar y trabajar con los look-and-feels existentes.plaf descienden de ComponentUI y se corresponden con un componente Swing determinado. Por ejemplo. Si quisiéramos crear una implementación completa de pluggable look-and-feel.motif.4. LabelUI desciende de ComponentUI y es el delegado base usado por JLabel. 11 .swing. Los nombres de estas subclases siguen el esquema general de añadir un prefijo con el nombre del look-andfeel al nombre de la superclase.try { UIManager.3 ¿Dónde están los delegados UI? Hemos hablado de ComponentUI.err. Estas clases son extendidas por implementaciones concretas como los paquetes basic y multi.7 muestra la jerarquía de LabelUI. El nombre de cada clase sigue con el esquema general (sin la “J”) añadiéndole el sufijo “UI”.sun. o las usen directamente.java.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. La figura 1.swing. Los delegados UI de Metal.plaf. y de los paquetes LookAndFeel donde se encuentran las implementaciones.7 Jerarquía de LabelUI <<fichero figure1-7. En el capítulo 21 aprenderemos cosas sobre este proceso. queda claro que supondría un gran esfuerzo y llevaría bastante tiempo. } SwingUtilities. Por ejemplo.

En la sección 2.1 Cambiando el tamaño y la posición de JComponent y sus propiedades 2. Se puede vetar un cambio en el código de manejo de eventos de un VetoableChangeListener lanzando una PropertyVetoException. Mecánicas de Swing En este capítulo: • Cambiando el tamaño y la posición de JComponent. y Actions SwingUtilities 2. Entre las cinco características que debe soportar un JavaBean se encuentra un conjunto de propiedades y sus métodos de acceso asociados. Una propiedad que no tienen ningún evento asociado a un cambio en su valor se llama una propiedad simple. KeyStrokes. Una propiedad restringida (constrained property) es aquella para la que se lanzan PropertyChangeEvents justo antes de que ocurra un cambio en su estado. Podemos registrar nuestros PropertyChangeListeners para escuchar PropertyChangeEvents a través del método addPropertyChangeListener() de JComponent.Capítulo 2.1 Propiedades Todos los componentes Swing cumplen la especificación de los JavaBeans. y sus métodos de acceso. son normalmente de la forma setPropertyname(). Una propiedad ligada (bound property) es aquella para la que se lanzan PropertyChangeEvents después de un cambio en su estado.1.7 veremos esto en detalle. y sus propiedades • • • • • • • • • • • • • Manejo y lanzamiento de eventos Multitarea Temporizadores Los servicios de AppContext Interior de los temporizadores y TimerQueue JavaBeans Fuentes. Colores. Gráficos y texto Usando el área de recorte de Graphics Depuración de Gráficos Pintado y validación Manejo del foco Entrada de teclado. Una propiedad es una variable global. si tiene alguno. getPropertyname() o isPropertyname(). (Sólo hay una clase en 12 . Podemos resgistrar VetoableChangeListeners que escuchen a PropertyChangeEvents por medio del método addVetoableChangeListener() de JComponent.

beans. Peligro: Las propiedades cliente pueden parecer una forma fantástica de añadir soporte al cambio de propiedades para componentes personalizados. o se suelta (ver capítulo 5). las propiedades de cambio son menos poderosas que las propiedades ligadas y que las restringidas. Lo consigue sacrificando la seguridad entre los hilos. PropertyChangeSupport propiedad. Estas son básicamente pares clave/valor que se guardan en una Hashtable facilitada por todos los componentes Swing.event) que es una subclase casi idéntica de PropertyChangeSupport. tenemos que hacer lo siguiente: miComponente.Swing con propiedades restringidas: JInternalFrame).getClientProperty("minombre"). miValor). deberíamos usar esta versión más eficiente. no es asunto de Swing si se siguen consistentemente las reglas generales de la multitarea (porque todo el procesamiento de eventos debería llevarse a cabo en un solo hilo-el hilo de despacho de eventos). Un JButton. Para añadir una propiedad a la Hashtable de propiedades cliente de un componente. Otro nuevo aspecto en el estilo de las propiedades que introduce Swing es la noción de propiedades cliente (client properties). Nota: Todos estos oyentes y eventos están definidos en el paquete java. 13 . Por esta razón. se envía un PropertyChangeEvent a todos los PropertyChangeListeners registrados. en lugar de PropertyChangeSupport. Swing introduce un nuevo tipo de propiedad que podemos llamar de cambio (change property). envía eventos de cambios todas las veces que se arma (se pulsa por primera vez). De manera similar. Los PropertyChangeEvent’s llevan consigo tres segmentos de información: nombre de la antiguo. el valor Swing introduce una nueva clase llamada SwingPropertyChangeSupport (definida en javax. La diferencia es que SwingPropertyChangeSupport se ha construido para que sea más eficiente. Para recuperar una propiedad cliente: miObjeto = miComponente. Un ChangeEvent sólo lleva consigo un segmento de información: la fuente del evento. pero están más extendidas. y el nuevo. como veremos más tarde en este capítulo. a falta de un nombre dado. y se usa a menudo como un sitio donde guardar datos sin tener que construir una nueva subclase. una instancia de VetoableChangeSupport se puede usar para manejar el envío de todos los PropertyChangeEvents correspondientes a cada propiedad restringida.swing. Usamos ChangeListeners para escuchar ChangeEvents que se lanzan cuando cambia el estado de estas propiedades. que.putClientProperty("minombre". Por lo tanto. por ejemplo. Esto permite añadir y borrar propiedades en tiempo de ejecución.awt. pero se nos recomienda explícitamente no hacerlo: “El diccionario clientProperty no está pensado para soportar un alto grado de extensiones de JComponent y no se debería considerar como una alternativa a la creación de subclases cuando se diseña un nuevo componente. a todos los oyentes registrados. si confiamos en que nuestro código ha sido construido de manera segura respecto a los hilos. Nota: No hay equivalente en Swing para VetoableChangeSupport porque sólo hay cuatro propiedades restringidas en Swing--todas definidas en JInternalFrame. se presiona.”API Las propiedades cliente son ligadas: cuando una de ellas cambia. Los Beans pueden usar instancias de para manejar el lanzamiento de PropertyChangeEvents correspondientes a cada propiedad ligada.

2 Cambiando el tamaño y la posición Como JComponent desciende de java. "outline"). Concretamente son estas: JTree.isRollover Un Boolean que sirve para determinar si un botón de la barra de herramientas muestra un borde grabado sólo cuando el puntero del ratón se encuentra entre sus límites y ningún borde cuando no (Boolean.TRUE) o no (Boolean.dragMode".lineStyle Un String que se usa para especificar si las relaciones entro los nodos se muestran como líneas angulosas (“Angled”). 2.por defecto) o sólo las partes superior e izquierda (Boolean.TRUE). le recomendamos que se acostumbre a esto. hereda todas las funcionalidades de posición y tamaño a las que estamos acostumbrados.por defecto). setMinimumSize(). Por ejemplo. Cada uno de los métodos setXX()/getXX() acepta/devuelve una instancia de Dimension.putClientProperty("minombre".1.FALSE -.isPalette Un Boolean que especifica si se usa un borde muy fino (Boolean.Container.putClientProperty("JDesktopPane. En Java 2 FCS no se usa esta propiedad. Si un administrador de disposición presta atención o no a estos tamaños 14 . inspeccionando el código fuente de Swing. así como cualquier otro tipo de evento. A no ser que esté usando Swing para interfaces simples.por defecto). máximo y mínimo de un componente disponemos de los siguientes métodos: setPreferredSize(). o se usa siempre un borde grabado (Boolean.TRUE). líneas horizontales que definen los límites de las celdas (“Horizontal” -. getMaximumSize() Usados durante el posicionamiento para especificar los límites superiores de las dimensiones del componente.FALSE -. JDesktopPane usa una propiedad cliente para controlar la visualización del contorno mientras arrastramos JInternalFrames (esto funcionará sin importar el L&F que se esté usando): miDesktop.FALSE -. JInternalFrame. getMinimumSize() Usados durante el posicionamiento para especificar los límites inferiores de las dimensiones del componente. Cinco componentes Swing tienen propiedades cliente especiales a las que solo el Metal L&F presta atención. JSlider.por defecto). getPreferredSize() El tamaño deseable de un componente. Nota: Puede localizar que propiedades tienen tienen eventos de cambio asociados con ellas. Aprenderemos más de lo que significan estos tamaños dependiendo de cada administrador de disposición en el capítulo 4. Lo usan la mayoría de los administradores de disposición (Layout Managers) para dimensionar los componentes. Para manejar el tamaño preferido.isFreeStanding Un Boolean que se usa para especificar si JScrollbar tendrá un borde (Boolean.por defecto). o no se muestran líneas (“None”).isFilled Un Boolean que especifica si la parte más baja de un deslizador (JSlider) debe estar rellena (Boolean.FALSE .TRUE) o el borde normal (Boolean.Para borrar una propiedad cliente le asignamos un valor null: miComponente. null). JScrollBar.awt. JToolBar. setMaximumSize().

awt.300. JDesktopPane.0: 0.depende solamente de la implementación de dicho administrador.x. int dimAltura = dim.120. o que sólo preste atención a uno. usamos normalmente setBounds() cuando añadimos un JInternalFrame a un JDesktopPane. y puede tomar tanto parámetros de tipo Rectangle (java. Rectangle contiene cuatro propiedades accesibles públicamente que describen su posición y su tamaño: int int int int recX = rec2.120. Este método está sobrecargado. recAltura = rec2.setBounds(120. Por ejemplo.height. Rectangle rec = new Rectangle(120. la anchura y las coordenadas x e y. La alineación horizontal o vertical se puede especificar con valores reales (float) entre 0. Por ejemplo.300). Dimension dim = miComponente.0 y 1.getSize(). Es perfectamente factible construir un administrador que simplemente los ignore todos. El tamaño se puede recuperar también como una instancia de Rectangle o de Dimension: Rectangle rec2 = miComponente.height.width. El tamaño de un componente se puede obtener al estilo de AWT: int h = miComponente. y JComponent).setBounds(rec).5 significa el centro. podemos determinar la posición de un componente dentro de su contenedor mediante el método setLocation(int x. Estas coordenadas se pueden obtener también usando los métodos getX() y getY().width. y más cercanos a 1.y. int w = miComponente. Verá que setBounds() no pasará por encima de ninguna de las políticas de posicionamiento activas a causa de un administrador de disposición de un contenedor padre.Rectangle) como cuatro parámetros de tipo int que represente la altura.0 significan derecha o abajo.getBounds(). Dimension contiene dos propiedades accesibles públicamente que describen su tamaño: int dimAnchura = dim. El dimensionado de los componentes en un contenedor es específico de cada administrador de disposición.getWidth().getHeight(). JComponent también mantiene una alineación. Adicionalmente. estas dos formas son equivalentes: miComponent. int y).300). Por esta razón una llamada a setBounds() puede parecer ignorada en determinadas situaciones porque intentó hacer su trabajo. Las coordenadas que una instancia de Rectangle devuelve usando su método getBounds() representan la situación de un componente dentro de su padre. recY = rec2.0 significan izquierda o arriba. setBounds() se usa normalmente para manejar componentes hijos en contenedores sin administrador de disposición (como JLayeredPane. Los correspondientes métodos de 15 . valores más cercanos a 0. recAnchura = rec2.300. pero el componente fue obligado a volver a su tamaño original por el administrador de disposición (los administradores de disposición siempre tienen la última palabra determinando el tamaño de un componente). El método setBounds() de JComponent se puede usar para asignar a un componente el tamaño y la posición dentro de su contenedor padre. miComponent.

Todos los modelos por defecto mantienen también oyentes y una EventListenerList..2 Manejo y lanzamiento de eventos Los eventos ocurren en cualquier momento que se pulsa una tecla o un botón del ratón. Cuando se reciben eventos. la instancia de Class asociada al evento (usada para identificar el tipo de evento) se añade a un vector EventListenerList. XXListener. así que todas sus subclases lo heredan.awt.2.arrayCopy() en cada adición o borrado. java.swing.swing. debemos registrar oyentes en el objeto destino. se recorre la lista y se envían eventos a todos los oyentes de un tipo adecuado. siempre hay un método definido en cada interface al que se le pasa el XXEvent correspondiente como parámetro.event y por supuesto. La forma en la que los componentes reciben y procesan los eventos no ha cambiado desde el JDK1. Estos valores se usan sólo en contenedores que se manejan mediante BoxLayout o OverlayLayout. los de javax.event. YYListener. seguida del oyente. e información acerca del estado de la fuente del evento antes y después de que éste se generase. Como vimos en el último capítulo. Algunos de estos nuevos tipos de eventos de Swing son específicos del componente.swing. La mayoría de los destinos de eventos permiten tener registrados cualquier número de oyentes. JComponent define sus EventListenerList como un campo protegido llamado listenerList. Los componentes Swing pueden generar diferentes tipos de eventos.awt.EventListenerList EventListenerList es un vector de pares XXEvent/XXListener.1. cualquier instancia de un oyente se puede registrar para recibir eventos de cualquier número de fuentes de éstos.2 Hilo de despacho de eventos (Event-dispatching thread) Todos los eventos se procesan por los oyentes que los reciben dentro del hilo de despacho de eventos 16 . y javax. Como estos pares se guardan en un vector en lugar de en una colección modificable (por eficiencia). incluyendo los de java.1 La clase javax. para recibir la notificación de eventos. Igualmente. Como el vector está ordenado de la forma XXEvent. Las fuentes de eventos son normalmente componentes o modelos. Las clases que soportan la notificación de XXEvents implementan generalmente el interface XXListener. Los componentes Swing manejan la mayoría de sus oyentes directamente a través de listenerList. etc. pero hay también clases de objetos diferentes que pueden generar eventos. los métodos para añadir y borrar oyentes de una EventListenerList sincronizan el acceso al vector cuando lo manipulamos. Un oyente es una implementación de alguna de las clases XXListener (donde XX es un tipo de evento) definidas en los paquetes java.event. JComponent y cada uno de sus descendientes usa una EventListenerList para mantener sus oyentes. las clases que soportan XXEvents ofrecen métodos fireXX() protegidos (protected) que se usan para construir objetos de eventos y para enviarlos a los manejadores de eventos para su proceso. Todos los tipos de eventos se representan por un objeto. YYEvent.7). identifica la fuente del evento.2. y tienen soporte para registrar y cancelar el registro de estos oyentes a través del uso de los métodos addXXListener() y removeXXListener() respectivamente. se crea un nuevo vector usando el método System. Cuando se añade un oyente a un componente Swing o a un modelo. 2. Para seguridad entre procesos. Esta estrategia permite unas rutinas de manejo de eventos muy eficientes (ver sección 2.event. y a menudo lleva información adicional acerca de la clase específica de evento que se trata. 2. 2. Normalmente. un oyente correspondiente a un determinador tipo de evento está siempre el siguiente en el vector. Como mínimo. que como mínimo.beans.event.7. setAlignmentY(float f).JComponent son: setAlignmentX(float f).

(una instancia de java.awt.EventDispatchThread). Todo el dibujo y posicionamiento de componentes debería 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 visualización de un componente en una aplicación 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 petición toma su turno para ejecutar el código 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 método fireXX() desde un hilo de ejecución separado es inseguro. Tenemos que estar seguros también de que el código de manejo de eventos se puede ejecutar rápidamente. En otro caso toda la cola de eventos del sistema se bloquearía esperando a que terminase el proceso de un evento, el repintado, o el posicionamiento, y nuestra aplicación parecería bloqueada o congelada.

2.3 Multitarea
Para ayudar a asegurarnos que todo nuestro código de manejo de eventos se ejecuta sólo dentro del hilo de despacho de eventos, Swing provee una clase de mucha utilidad, que entre otras cosas, nos permite añadir objetos Runnable a la cola de eventos del sistema. Esta clase se llama SwingUtilities y contiene dos métodos en los que estamos interesados aquí: invokeLater() e invokeAndWait(). El primer método añade un Runnable a la cola de eventos del sistema y vuelve inmediatamente. El segundo método añade un Runnable y espera a que sea despachado, entonces vuelve una vez que termina. La sintaxis básica de cada una es la siguiente:
Runnable trivialRunnable = new Runnable() { public void run() { hazTrabajo(); // hace algún trabajo } }; SwingUtilities.invokeLater(trivialRunnable); try { Runnable trivialRunnable2 = new Runnable() { public void run() { hazTrabajo(); // hace algún trabajo } }; SwingUtilities.invokeAndWait(trivialRunnable2); } catch (InterruptedException ie) { System.out.println("...Espera del hilo interrumpida!"); } catch (InvocationTargetException ite) { System.out.println( "...excepción 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 rápidamente como cualquier otro código de manejo de eventos. En los dos ejemplo de arriba, si el método hacerTrabajo() hiciese alguna cosa que le llevase un largo tiempo (como cargar un fichero grande) veríamos que la aplicación se congelaría hasta que la carga finalizase. En los casos que conlleven mucho tiempo como este, deberíamos usar nuestro propio hilo separado para mantener la sensibilidad de la aplicación.

17

El código siguiente muestra la forma típica de construir nuestro propio hilo que haga un trabajo costoso en tiempo. Para actualizar de manera segura el estado de algún componente dentro de este hilo, tenemos que usar invokeLater() o invokeAndWait():
Thread trabajoDuro = new Thread() { public void run() { hacerTrabajoPesado(); // hace algún trabajo costoso en tiempo SwingUtilities.invokeLater( new Runnable () { public void run() { actualizaComponentes(); // actualiza el estado de lo(s) componente(s) } }); } }; trabajoDuro.start(); Nota: se debería 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 podría necesitar durante la operación.

Esto soluciona el problema de la sensibilidad, y añade código relativo al componente al hilo de despacho de eventos, pero no se puede considerar aún amigable al usuario. Normalmente el usuario debería ser capaz de interrumpir una tarea costosa en tiempo. Si estamos esperando una conexión a una red, no queremos esperar indefinidamente si el destino no existe. En casi todas las circunstancias el usuario debería tener la opción de interrumpir nuestro hilo. El pseudocódigo siguiente nos muestra una manera típica 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 algún punto, esto supone cambiar // el estado del componente tenemos que usar // invokeLater aquí porque este es un hilo // separado. // // Tenemos como mínimo que hacer una cosa de // las siguientes: // 1. Chequear periódicamente Thread.interrupted() // 2. Dormir o esperar periódicamente 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 botón // 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 interrupción es chequear periódicamente el estado llamando a Thread.interrupted(). Esto se usa para construir y mostrar diálogos 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 cálculo intensivo, para esperar algún mensaje o el establecimiento de una conexión de red, etc.
Referencia: Los miembros del equipo de Swing han escrito algún artículo sobre como utilizar hilos con Swing, y han construido una clase llamada SwingWorker que hace el manejo del tipo de multitarea descrito aquí más conveniente. Ver http://java.sun.com/products/jfc/tsc/archive/tech_topics_arch/threads/threads.html

2.3.1 Casos especiales
Hay algunos casos especiales en los cuales no necesitamos que código que afecte al estado de componentes se ejecute en el hilo de despacho de eventos: 1. Algunos métodos en Swing, aunque pocos y distantes entre sí, están marcados como seguros respecto a los hilos y no necesitan consideración especial. Algunos métodos que son seguros respecto a los hilos pero que no están marcados son: repaint(), revalidate(), e invalidate(). 2. Un componente se puede construir y manipular de la forma que queramos, sin tener cuidado con los hilos, siempre que no se haya tenido en cuenta (realized) (lo que quiere decir que no se haya mostrado o que no haya encolado una petición de repintado). Los contenedore de más alto nivel (JFrame, JDialog, JApplet) se tienen en cuenta una vez que se ha llamado a setVisible(true), show(), o pack() en ellos. Observe también que se considera que un componente se tiene en cuenta tan pronto como se añade a un contenedor que se tiene en cuenta. 3. Cuando trabajamos con applets Swing (JApplets) todos los componentes se pueden construir y manipular sin prestar atención a los hilos hasta que se llama al método start(), lo que sucede después del método init().

19

La primera envía el Runnable al método postRunnable() de SystemEventQueueUtilities.SystemEventQueueUtilities [privada de paquete] Cuando SwingUtilities recibe un objeto Runnable a través de invokeLater(). una clase interna de SystemEventQueueUtilities. } }. RunnableTarget es una subclase de Component y su único propósito es actuar como fuente y destino de RunnableEvents.EVENT_ID. y define su propio ID del evento como un int estático -.3 ¿Cómo funcionan invokeLater() e invokeAndWait()? clase javax. } } 2. Si el Runnable se recibe a través de invokeAndWait().” Lo primero que hace el método postRunnable() es comunicarse con la cola privada SystemEventQueue. Aquí tenemos una plantilla de método seguro respecto a los hilos. RunnableEvent contiene también una instancia estática de un RunnableTarget.. Dentro de este método lo primero que se hace es ver si en efecto el evento pasado es 20 . junto con una referencia al objeto de bloqueo. de manera que el hilo no avanzará hasta que el objeto sea notificado--por ello “invoke and wait (invocar y esperar).. para obtener un referencia a la cola de eventos del sistema.RESERVED_ID_MAX.RESERVED_ID_MAX + 1000.3. También sobrecarga el método protegido de Component processEvent() para recibir RunnableEvents. La clase RunnableEvent es una subclase de AWTEvent.invokeLater(llamaAhacerTrabajoSeguro). Al constructor de RunnableEvent se le pasan un Runnable y un Object. Se envuelve entonces el Runnable en una instancia de RunnableEvent. En otro caso.2 ¿Cómo construimos nuestros métodos para que sean seguros respecto a los hilos? Esto es realmente fácil. construimos un Object que usaremos como bloqueo en una sección crítica (p.EVENT_ID). (Vea que siempre que definimos nuestros propios eventos debemos usar un ID mayor que el valor de AWTEvent. lo pasa postRunnable() de una clase llamada inmediatamente al método SystemEventQueueUtilities. otra clase interna privada más.e.isEventDispatchThread()) { // // hacer todo el trabajo aquí. SwingUtilities.swing.2.3. Este bloque contiene dos instrucciones. ¿Cómo hace esto RunnableTarget? Su constructor habilita los eventos con un ID que concuerde con el ID del RunnableEvent: enableEvents(RunnableEvent. otra clase interna privada.) El EVENT_ID de RunnableEvent es AWTEvent. se comprueba primero que el hilo actual no sea el hilo de despacho de eventos. que representa al objeto de bloqueo (null si se llamó a invokeLater()) como parámetros. (Sería fatal permitir invocar a invokeAndWait() desde el hilo de despacho de eventos) Si este es el caso se lanza un error. un bloque sincronizado). // } else { Runnable llamaAhacerTrabajoSeguro = new Runnable() { public void run() { hacerTrabajoSeguro(). La segunda espera al objeto de bloqueo. que podemos usar para garantizar que el código de este método se ejecuta sólo en el hilo de despacho de eventos: public void hacerTrabajoSeguro() { if (SwingUtilities.

6). cuando se despacha la petición de repintado en el hilo de despacho de eventos. El contructor RunnableEvent llama al constructor de su superclase (AWTEvent) pasándole una instancia de RunnableTarget como la fuente del evento. Entonces el evento es despachado en algún punto del hilo de despacho de eventos enviándolo al método processEvent() de RunnableTarget. Una vez que se ha creado la instancia de RunnableEvent. Este método ha sido construido para que localice cualquier RunnableEvent (guardado en el Vector) asociado con un determinado RunnableCanvas. tenemos dos posibles caminos dependiendo de si nos estamos ejecutando como un applet o como una aplicación: Aplicaciones: Como tenemos acceso directo a la cola de eventos del sistema de AWT simplemente ponemos el RunnableEvent y volvemos. 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.) Volvamos de nuevo a RunnableEvent.invokeAndWait(). Los ActionListeners se pueden registrar para que reciban estos eventos tal y como los registramos en botones. Una vez que el Runnable termina. Para crear un Timer simple que lance ActionEvents cada segundo podemos hacer algo como lo siguiente: 21 . un RunnableCanvas añade una petición de repaint(). el Runnable que les pasamos es pasado al método SystemEventQueueUtilities. un RunnableCanvas (una clase interna privada que desciende de java. lo notificamos a ese objeto. Resumiendo: cuando llamamos a invokeLater() o a invokeAndWait(). o en otros componentes. Esto ocurrirá sólo si no estamos ejecutándonos como un applet.una instancia de RunnableEvent. el cual lo envía entonces al método processRunnableEvent(). Este método 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.postRunnable() junto al objeto de bloqueo al que está esperando el hilo que les invoca (si fue un invokeAndWait()). que despierta al hilo invocante y hemos acabado. pero funciona). Para abreviar una tarea muy complicada. Si lo es. El método paint() apropiado de RunnableCanvas es llamado como se esperaba. como veremos en la sección 2. en lugar de añadir manualmente un evento a la cola de eventos del sistema. el método postRunnable() (en el que hemos estado todo este tiempo) comprueba si ha obtenido el acceso a la cola de eventos del sistema. y EVENT_ID como el ID del evento. Entonces. entramos en un bloque sincronizado en el objeto de bloqueo de forma que nadie más puede acceder al objeto mientras ejecutamos el Runnable. ya que los applets no tienen acceso directo a la cola de eventos del sistema. En este punto. 2.Canvas) invisible se mantiene para cada applet y se guarda en una Hashtable estática usando el hilo invocante como clave. Un Vector de RunnableEvents se mantiene también y.swing. Si no se ha usado bloqueo (se llamó a invokeLater()) el Runnable se ejecuta y hemos terminado.4 Temporizadores clase javax.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. es pasado al método processRunnableEvent() de SystemEventQueueUtilities (esto ocurre una vez que el RunnableEvent ha sido despachado de la cola de eventos del sistema. Recuerde que este es el mismo objeto de bloqueo al que está esperando el hilo que invocó a SwingUtilities. Si se ha usado un bloqueo (se llamó a invokeAndWait()). Mantiene también referencias al Runnable y al objeto de bloqueo. y lo ejecute (algo rebuscado.awt.

6). Entonces construimos un nuevo Timer pasando el tiempo entre eventos en milisegundos. import javax. En cualquier momento podemos llamar a stop() para detener el Timer y start() para reiniciarlo (start() no hace nada si ya se está ejecutando). así como a la forma en la que las aplicaciones manejan sus clases de servicio (también usando AppContext).start(). Cuando ejecute este código verá que se muestra “Swing is powerful!!” en la salida estándar cada segundo. Esto puede ser útil durante sobrecargas. El método restart() es sólo una abreviatura para llamar a stop() y start() secuencialmente. Como no hay ejecutándose una GUI el programa saldrá inmediatamente. Podemos poner el retraso de un Timer usando su método setDelay() y decirle si debe repetirse o no usando el método setRepeats(). Podemos llamar a restart() en un Timer para que empiece de nuevo todo el proceso. sólo lanzará una acción cuando se inicie (o si ya se está ejecutando). Observe que Timer no lanza un evento justo cuando se inicia. y un ActionListener al que enviárselos. Finalmente llamamos al método start() de Timer para activarlo. Si no siente curiosidad por como comparte Swing las clases de servicio. } } En primer lugar configuramos un ActionListener para que reciba ActionEvents. act). Timer tim = new Timer(1000.*. Si queremos que el Timer lance un evento justo cuando se inicia debemos poner el retraso inicial a 0 usando su método setInitialDelay().awt. El método setCoalesce() permite que varios Timer de lanzamiento de eventos se combinen en uno. class TimerTest { public TimerTest() { ActionListener act = new ActionListener() { public void actionPerformed(ActionEvent e) { System. while(true) {}. Sin embargo.swing. hay mucho más por detrás que merece un poco de atención. el retraso (delay). puede saltarse la siguiente sección. Los Timers son fáciles de usar y a menudo se pueden utilizar como una herramienta conveniente para construir nuestros propios hilos.*. cuando el hilo de la TimerQueue (que veremos más adelante) no tiene suficiente tiempo de proceso para manejar todos sus Timers. no significa que sea necesario para entender los detalles. echaremos un vistazo al servicio de mapeo de clases de Swing (SecurityContext-to-AppContext) para applets. aunque nos referiremos de vez en cuando a AppContext. 22 . Esto es a causa de su retraso inicial (initial delay) que por defecto equivale al tiempo que se le pasa al constructor. Antes de que veamos a fondo como trabajan los Timers. y entonces se detendrá. tim. por lo tanto ponemos un bucle para permitir al Timer que siga con su trabajo indefinidamente (explicaremos por qué es necesario esto en la sección 2.event. Una vez que hemos hecho que un Timer no se repita. } public static void main( String args[] ) { new TimerTest(). } }.import java.out.println("Swing is powerful!!").

Podemos usar entonces los métodos put(). existe un AppContext separado para cada SecurityContext que corresponde a la base del código del applet. así es como se hace: El método estático AppContext.5 Los servicios de AppContext clase sun. Usando distintas EventQueues en cada AppContext. directamente recuperables por la clase de servicio. No profundizaremos en los detalles de SecurityContexts o ClassLoaders aquí. tendrán que compartir necesariamente un SecurityContext.getAppContext() determina el AppContext correcto a usar dependiendo de si se está ejecutando una aplicación o una applet. ¿Pero. a los únicos eventos principales que la app tendría acceso sería a los suyos. más robustos podrán ser.AppContext [específica de la plataforma] Peligro: AppContext no está pensada para ser usada por cualquier desarrollador.awt. No obstante. applets de diferentes bases de código no pueden acceder al AppContext del otro. Cuando se pide un servicio por primera vez. tenemos que implementar nuestros propios métodos como sigue: 23 . De esta forma. En su lugar. cada uno usando código de un directorio diferente. y la clase AppContext está diseñada para aprovecharse de esto permitiendo que haya tan sólo una instancia de ella misma por cada dominio de seguridad. borrar o recuperar servicios? AppContext no está pensado para que sea accedido por desarrolladores. Los servicios registrados con un AppContext se pueden acceder sólo desde apps seguras (trusted apps). Esto consiste en crear una nueva instancia de si mismo y añadirla al mapeo clave/valor del AppContext. los dos deberán tener asociado con ellos un SecurityContext distinto. Una instancia compartida (shared instance) es una instancia de una clase que se puede obtener normalmente usando un método estático definido en esa clase. AppContext es una tabla de servicio de una aplicación o de un applet (diremos “app” para abreviar) que es única para cada sesión de Java (applet o aplicación). y a cada instancia se le denomina como un servicio. Si al contrario. ¿cómo accedemos a nuestro AppContext para añadir. ya que AppContext no forma parte del núcleo del API. pero podemos si realmente lo necesitamos. Una razón por la cual estas instancias compartidas se registran con un AppContext en lugar de ser implementadas como instancias estáticas normales. get() y remove() del AppContext devuelto para manejar las instancias compartidas.. Para los applets. Para lograr esto. y esto garantizaría que nuestro código no sería certificado como 100% puro nunca. Cada AppContext mantiene una Hashtable de instancias compartidas disponibles para el dominio de seguridad asociado. mientras que las clases que proveen directamente instancias estáticas 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). pero es suficiente decir que se pueden usar por los SecurityManagers para indicar dominios de seguridad. Otra razón para ello es la robustez. se ejecutan en espacios de nombres que son diferenciados por los ClassLoaders. por qué es esto importante? Vamos allá. 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 contraseñas. (Por esto hay sólo una EventQueue por cada AppContext) Entonces. Por ejemplo. Cuantos menos applets interactúen con otros de manera indocumentada. es por propósitos de seguridad. Por ejemplo. se han cargado desde la misma base de código. éste registra su instancia compartida con su AppContext asociado..2. La abordamos aquí sólo para facilitar una mejor comprensión de como las clases de servicio de Swing trabajan entre bastidores. Las aplicaciones Java no tienen SecurityContexts. ya que no es parte del API de Java 2. si tenemos dos applets en la misma página.

es añadido a la TimerQueue compartida.AppContext.java): static void appContextPut(Object key. Normalmente este comportamiento es el deseable.LAFState (todas serán abordadas en algún punto de este libro).e.4 saldría inmediatamente si no ponemos un bucle. Estos son los métodos usados por las clases de servicio de Swing.invokeLater().TimerQueue [privada de paquete] Un Timer es un objeto que contiene un pequeño Runnable capaz de despachar ActionEvents a una lista de ActionListeners (guardados en una EventListenerList). Object value) static Object appContextGet(Object key) De todas formas. Todas las instancias de Timer se manejan por la instancia compartida de TimerQueue (registrada con AppContext). Si el Timer que ha expirado está en modo de repetición. Un TimerQueue es una clase de servicio cuyo trabajo es manejar todas las instancias de Timer en una sesión de Java.getAppContext(). Object value) { sun. Esto sucede cuando se llama a TimerQueue.put(key. La clase TimerQueue provee el método estático sharedInstance() para recuperar el servicio TimerQueue de AppContext.6 Interior de los temporizadores y TimerQueue class javax.sharedInstance() por primera vez (cuando se inicia el primer Timer de una sesión de Java).swing.getAppContext().AppContext. } private static void appContextPut(Object key.getAppContext(). ToolTipManager. 24 . } private static void appContextRemove(Object key) { sun. Es también 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. Nota: La razón real por la que el ejemplo de Timer de la sección 2. el tiempo que queda para lanzar el próximo evento). value). Espera continuamente a que expire el Timer con el tiempo más cercano.remove(key).AppContext.awt. Los demonios son hilos de servicio y cuando la JVM sólo tiene ejecutándose demonios terminará ya que asume que no se está haciendo trabajo real.awt. Siempre que un nuevo Timer se crea y se inicia. que mantiene una lista de Timers ordenados por el tiempo en el que expiran (p. Los eventos de un Timer se envían siempre al hilo de despacho de eventos de manera segura respecto a los hilos enviando su objeto Runnable a SwingUtilities. es que la TimerQueue es un demonio. no podemos acceder a ellos porque son privados del paquete.awt. La TimerQueue es un demonio (daemon) que se inicia inmediatamente en la instanciación. se añade de nuevo a la lista el lugar apropiado basándose en su retraso. RepaintManager. esta funcionalidad está implementada como tres métodos estáticos de SwingUtilities (vea el código fuente de SwingUtilities. Una vez que esto ocurre envía una señal al Timer para que envíe ActionEvents a todos sus oyentes. Alguna de las clases de servicio de Swing que registran instancias compartidas con AppContext son: EventQueue. y finalmente borra el que ha expirado. } En Swing. entonces asigna un nuevo Timer como cabeza de la lista. 2. TimerQueue.get(key). FocusManager y UIManager. Object value) static void appContextRemove(Object key.private static Object appContextGet(Object key) { return sun.

Un vector de MethodDescriptors: provee información sobre los métodos accesibles externamente de un bean (incluiría por ejemplo a todos los métodos públicos). los beans soportan diferentes tipos de propiedades.7. tal como un nombre para que se visualice. Introspector provee métodos estáticos para generar un objeto BeanInfo que contenga toda la información que se pueda descubrir de un bean determinado. Un objeto BeanInfo divide toda la información del bean en varios grupos.7. Segundo. necesitamos comprender y apreciar el hecho de que cada componente Swing sea un JavaBean. 2. set. Las propiedades ligadas y restringidas son 25 .2.Introspector.2 Introspección La introspección es la facultad de descubrir los métodos. el bean no hará nada. • • 2. como un botón. Estos objetos se pueden usar para construir instancias de Method y Class correspondientes a los métodos de acceso y a los tipos de las clases respectivamente de la propiedad. El código siguiente recupera toda la información que se puede descubrir de un bean: BeanInfo myJavaBeanInfo = Introspector.3 Propiedades Como vimos en la sección 2. de un bean. y la información de los eventos. a no ser que especifiquemos en que superclase debe detenerse la introspección (podemos especificar la profundidad de una introspección).beans. Estos se pueden usar.7. Un vector de EventSetDescriptors: provee información sobre el conjunto de eventos que un bean lanza.”API “Una Field provee información de un único campo de una clase o interface y acceso dinámico al mismo. estamos asumiendo un conocimiento básico del API Java Reflection: “Instancias de Class representan clases e interfaces en una aplicación Java ejecutándose.1. para recuperar los métodos asociados a oyentes de eventos del bean como instancias de Method.1.”API 2. y/o is. Un vector de PropertyDescriptors: provee información sobre las propiedades que un bean mantiene. Nota: Si es familiar con el modelo de componentes JavaBeans puede que quiera saltar a la siguiente sección.getBeanInfo(myJavaBean).1 El modelo de componentes JavaBeans La especificación de JavaBeans identifica cinco rasgos que todos los beans deben ofrecer. La primera cosa que hay que hacer es pensar en un componente simple. Esto incluye información sobre todas las superclases del bean.”API “Un Method provee información de un único método de una clase o interface y acceso al mismo. Esta información se usa para construir una instancia de Method para cada método. y aplicar lo que aquí veamos a este componente. Propiedades simples son variables que cuando se modifican. junto con las clases y mecanismos que los hacen posibles. Revisaremos estos rasgos aquí.7 La arquitectura JavaBeans Como en este libro estamos interesados en crear aplicaciones Swing. Esto se consigue usando la clase java. las propiedades. entre otras cosas. algunos de los cuales son: • • Un BeanDescriptor: provee información general descriptiva. y que pueden accederse mediante los métodos get.

7.) Las clases que necesiten un procesamiento especial durante la serialización tienen que implementar los siguientes métodos privados: private void writeObject(java. transferencia de datos. debería enviar un PropertyChangeEvent. Cuando va a cambiar una propiedad restringida. etc. Otros objetos pueden escuchar estos eventos para procesarlos como corresponda (lo que guia la comunicación). Actualmente la persistencia a largo plazo no se recomienda. (Vea la documentación del API o el tutorial de Java para más información sobre la serialización. y es posible que cambie en futuras versiones. que contiene el nombre de la propiedad. para RMI. todas las subclases tienen que implementar estos métodos si requieren un procesamiento especial. JComponent implementa readObject() y writeObject() como privados.5 Comunicación Los Beans están diseñados para enviar eventos que notifican a todos los oyentes registrados con él cuando cambia de valor una propiedad ligada o una restringida. y el valor nuevo. el bean mandará eventos de notificación a todos los oyentes. 2.variables que cuando se modifican.7. y se pueden modificar en tiempo de ejecución (o en tiempo de diseño). Observe que el mecanismo de serialización por defecto será invocado para serializar todas las subclases porque se trata de métodos privados. 2. 26 .7.e. Sin embargo. Un método isXX() corresponde normalmente a la obtención de un propiedad booleana (ocasionalmente los métodos getXX() se usan para esto también). no hay ningún problema en implementar persistencia a corto plazo (p. permitiendo que éste sea vetado.6 Persistencia Todos los JavaBeans tienen que implementar el interface Serializable (directa o indirectamente) para permitir la serialización de su estado en un almacenamiento persistente (almacenamiento que existe después de que termine el programa).io. el valor anterior de la propiedad. el bean debería lanzar un PropertyChangeEvent antes de que ocurra el cambio. Si un método getXX() o isXX() está disponible se dice que la propiedad asociada es legible. En el momento que una propiedad ligada cambia. 2. Asociados con las propiedades están los métodos setXX()/getXX() e isXX() de los beans.ObjectInputStream in) Estos métodos se llaman para escribir o leer una instancia de esta clase de un stream. La comunicación es la unión básica que mantiene unido a un GUI interactivo.). las herramientas de diseño pueden aprovechar este conocimiento para permitir una personalización más poderosa en la etapa de diseño. Las apps se construyen registrando oyentes de bean a bean.io.4 Personalización Las propiedades de un bean están expuestas a través de sus métodos setXX()/getXX() e isXX().) Nota: Como en la primera versión de Java 2. Todos los objetos se guardan salvo los que se declaran como transient. Esta notificación tiene la forma de un objeto de evento.ObjectOutputStream out) y private void readObject(java. Como podemos usar la introspección para recoger información sobre el envío y el recibo de eventos de cualquier bean. Los JavaBeans se usan comúnmente 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 métodos de acceso). Si un método setXX() está disponible se dice que su propiedad asociada es escribible. (Observe que JComponent implementa directamente este interface.

// Propiedades private Font m_beanFont.io. // Sólo se necesita un ChangeEvent ya que el único estado del evento // es la propiedad fuente. // Maneja todos los VetoableChangeListeners protected VetoableChangeSupport m_vetoer = new VetoableChangeSupport(this).Las clases que quieren tener un control total sobre su serialización y deserialización deberían implementar el interface Externalizable.java ver \Chapter1\1 import import import import import javax. 12). Font. ligadas.7 Un JavaBean simple basado en Swing El siguiente código nos muestra como construir un JavaBean basado en Swing con propiedades simples. java. java. private Color m_beanColor. javax. private int m_beanValue. java. m_beanDimension = new Dimension(150.*. private String m_beanString. protected EventListenerList m_listenerList = new EventListenerList().100).*. 27 . // Esto hará que aprecie las clases XXSupport.ITALIC. public static final String BEAN_COLOR = "Color".awt.*. public class BakedBean extends JComponent implements Externalizable { // Nombres de la propiedad (sólo para propiedades ligadas o restringidas) public static final String BEAN_VALUE = "Value".*. Verá esto en montones de código Swing. Este interface define dos métodos: public void writeExternal(ObjectOutput out) public void readExternal(ObjectInput in) Estos métodos se invocarán cuando writeObject() y readObject() (vistos anteriormente) sean invocados para llevar a cabo alguna serialización/deserialización.*.swing. private Dimension m_beanDimension. restringidas y de cambio.beans. El código: BakedBean. siempre que configuremos // los métodos fireXX para que miren correctamente en esta lista.BOLD | Font.7. public BakedBean() { m_beanFont = new Font("SanSerif". 2. // Esto puede manejar todos los tipos de oyentes.event.swing. protected transient ChangeEvent m_changeEvent = null. // // // // // simple simple ligada restringida de cambio // Maneja todos los PropertyChangeListeners protected SwingPropertyChangeSupport m_supporter = new SwingPropertyChangeSupport(this). La fuente de los eventos generados // es siempre "this".

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

// Procesa los oyentes del último al primero.writeObject(m_beanColor). out. out.stateChanged(m_changeEvent). l).writeInt(m_beanValue). i-=2) { if (listeners[i]==ChangeListener. } public void setMinimumSize(Dimension dim) { m_beanDimension = dim. i>=0.add(ChangeListener. 29 .removeVetoableChangeListener(l). ((ChangeListener)listeners[i+1]). } public void removeVetoableChangeListener( VetoableChangeListener l) { m_vetoer. } public void addPropertyChangeListener( PropertyChangeListener l) { m_supporter.writeObject(m_beanDimension).class. } // 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. l). } } } public void writeExternal(ObjectOutput out) throws IOException { out. protected void fireStateChanged() { Object[] listeners = m_listenerList.remove(ChangeListener.addPropertyChangeListener(l).class) { if (m_changeEvent == null) m_changeEvent = new ChangeEvent(this).getListenerList(). } public void addVetoableChangeListener( VetoableChangeListener l) { m_vetoer.addVetoableChangeListener(l). out.writeObject(m_beanFont).length-2.class.public Dimension getPreferredSize() { return m_beanDimension. } public void removeChangeListener(ChangeListener l) { m_listenerList. // Verá esto a menudo en código Swing. } // Este es el código típico de despacho de EventListenerList. } public void removePropertyChangeListener( PropertyChangeListener l) { m_supporter. avisando // a los que estén interesados en este evento for (int i = listeners.removePropertyChangeListener(l). } public Dimension getMinimumSize() { return m_beanDimension.

gif>> En el capítulo 18 (sección 18.out. y m_beanValue con nuestro editor de propiedades y se ha serializado al disco.readObject()).add(new BakedBean()).out. } public void readExternal(ObjectInput in) throws IOException. Lo que realmente muestra la Figura 2.readObject()).println("Color change vetoed.readObject()). m_beanColor.9) construiremos en entorno completo de edición de propiedades de JavaBeans. y ChangeListeners.1 muestra BakedBean siendo ejecutado como una aplicación.writeObject(m_beanString). } public static void main(String[] args) { JFrame frame = new JFrame("BakedBean"). m_beanColor. // Usar el tamaño preferido para el mínimo.. m_beanFont..pack().1 BakedBean en nuestro editor personal de JavaBeans <<fichero figure2-1. Soporta persistencia implementando el interface Externalizable y los métodos writeExternal() y readExternal() para controlar su propia serialización (observe que el orden en el que se escriben y se leen los datos coincide). } } BakedBean tiene representación visual (no es obligatorio para un bean).getContentPane(). Al BakedBean mostrado se le han modificado las propiedades m_beanDimension. La Figura 2.readInt()). y m_beanString. setMinimumSize(getPreferredSize()). Tiene las propiedades: m_beanValue.setVisible(true). frame. } catch (PropertyVetoException pve) { System. setPreferredSize((Dimension)in. Y. serializado y deserializado usando este entorno ya que todos ellos cumplen las especificaciones de los JavaBeans 30 . Figura 2. frame. sin tener que hacer nada especial. try { setBeanColor((Color)in.2 es una instancia de ese BakedBean después de que ha sido deserializado (cargado desde el disco). La Figura 2. Observe que cualquier componente Swing puede ser creado. y soporta comunicación permitiendo el registro de PropertyChangeListeners.readObject()). Utilizar un método para mostrar BakedBean en un frame no está dentro de la funcionalidad de JavaBeans. modificado. frame. soporta introspección. setBeanValue(in.2 muestra una instancia de BakedBean en este entorno. VetoableChangeListeners. m_beanDimension. } setBeanString((String)in."). ClassNotFoundException { setBeanFont((Font)in. BakedBean soporta personalización mediante sus métodos setXX() y getXX().

Estos dispositivos se representan como instancias de la clase GraphicsDevice.getAvailableFontFamilyNames().PLAIN es el estilo (que en este caso es negrita (bold) y cursiva (italic)).getFontList() se ha desaconsejado en Java 2 y este código debería actualizarse. Nota: En código para el JDK1. Es interesante.BOLD | Font. Colores. FONT. las fuentes son muy fáciles de crear: m_beanFont = new Font("SanSerif". Podemos especificar el tamaño de la fuente con un int en el constructor de Font. que un GraphicsDevice puede estar en la máquina local o en una remota. Normalmente cada GraphicsConfiguration de un GraphicsDevice representa un modo de operación diferente (por ejemplo resolución y número de colores).1.BOLD. abstract class java.Bold | Font. Una GraphicsConfiguration describe características específicas del dispositivo asociado.Figura 2.ITALIC.8 Fuentes. Font.GraphicsEnvironment Como vimos anteriormente en BakedBean. 12).2 BakedBean en nuestro editor de propiedades de JavaBeans personal <<fichero figure2-2.gif>> 2. Font. El método Toolkit.1 Fuentes clase java. Cada GraphicsDevice tiene un conjunto de objetos GraphicsConfiguration asociados con él. Gráficos y texto 2. y 12 es el tamaño. Nota: Java 2 introduce un nuevo. Font.awt. getLocalGraphicsEnvironment().ITALIC. podemos preguntar al GraphicsEnvironment local: GraphicsEnvironment ge = GraphicsEnvironment. Usando Java 2. 31 .awt. para obtener una lista de los nombres de fuentes disponibles en tiempo de ejecución. String[] fontNames = ge.8.PLAIN. En este código "SanSerif" es el nombre de la fuente. para obtener la lista de nombres de fuentes había que usar el código siguiente: String[] fontnames = Toolkit.getFontList(). La clase Font define tres contantes estáticas de tipo int para indicar el estilo: Font. como pantallas o impresoras. poderoso y completo mecanismo para comunicarse con dispositivos que pueden dibujar gráficos.getDefaultToolkit().Font.

TextAttribute (esta clase está definida en el paquete java. Las subclases de GraphicsEnvironment deben tener tres métodos para obtener arrays de Fonts e información de Font: Font[] getAllFonts(): obtiene todas las Fonts disponibles en tamaño de un punto. GraphicsEnvironment también tiene métodos estáticos para recuperar GraphicsDevices y la instancia local de GraphicsEnvironment.8.font que es nuevo en Java 2. Podríamos pensar que. Color. float g. En su lugar debemos usar uno de los siguientes métodos de instancia de Font para conseguir una nueva Font: deriveFont(float size) deriveFont(int style) deriveFont(int style.GraphicsEnvironment es una clase abstracta que describe una colección de GraphicsDevices. Un Map es un objeto que mapea claves a valores (no contiene los objetos involucrados) y los atributos a los que nos referimos aquí son parejas clave/valor como se describe en los documentos del API de java.2 Colores La clase Color tiene varias instancias estáticas de Color para ser usadas por comodidad (p. etc. reflejado y recortes. float b) Color(int r. String[] getAvailableFontFamilyNames(): obtiene los nombres de todas las familias de fuentes disponibles.text. podemos usar los métodos típicos de acceso getXX()/setXX() para cambiar su nombre. escalado.awt.e. rotaciones.). y que se considera parte de Java 2D -. Podemos construir también un Color usando. String[] getAvailableFontFamilyNames(Locale l): obtiene los nombres de todas las familias de fuentes disponibles usando el Locale (soporte a la internationalización) especificado. 2. no podemos usar los métodos setXX().blue. Nota: AffineTransforms se usan en el mundo de Java 2D para llevar a cabo cosas como translaciones.ver capítulo 23). Es mucho más eficiente y conveniente obtener los nombres de fuentes disponibles y usarlos para construir Fonts que obtener el array de objetos Font. int b) 32 . float size) deriveFont(Map attributes) deriveFont(AffineTransform trans) deriveFont(int style. estilo y tamaño. dado un objeto Font. entre otros. Para encontrar que Fonts están disponibles para el sistema en el que se está ejecutando nuestro programa. int g. los siguientes constructores: Color(float r. Color. AffineTransform trans) Normalmente estaremos interesados solamente en los tres primeros métodos. Podemos usar los métodos getXX() para obtener esta información de una Font: String getName() int getSize() float getSize2D() int getStyle Sin embargo. debemos usar esta instancia local de GraphicsEnvironment como vimos antes.yellow. Bueno. sólo habríamos acertado a medias.

Ambos tienen un cuarto parámetro que representa el valor alpha del Color.8. Como JComponent es una subclase de Component. Nota: La propiedad de opacidad de un componente Swing. El primero permite especificar los valores de rojo. el fondo de la etiqueta no se dibujará. Todo lo que tenemos que hacer es escribir una subclase y sobreescribir el método paintComponent().0 a 1. controlada mediante setOpaque(). En lugar de modificar un objeto Color es más normal que creemos uno nuevo. usa los métodos update() y paint() por diferentes razones. hay un montón de métodos de acceso getXX() pero no de setXX(). La siguiente sección muestra un ejemplo simple de canvas.1 los reconocerán. De todos modos. float b. frames internos.3 Gráficos y texto clase abstracta java. y aquellos familiarizados con el JDK1. float g. pero su comportamiento es impredecible a causa de errores internos de redondeo y sugerimos no usarlos. El valor alpha controla directamente la transparencia. Los segundos dos métodos son nuevos en Java 2.FontMetrics Dibujar en Swing es muy distinto a hacerlo en AWT. Así es como tomamos control del dibujado de nuestros simples componentes personalizados. int b. como con las Fonts. pero basta con decir aquí que cualquier subclase de JComponent que quiera tener el control de su propio dibujado debería sobrescribir el método paintComponent() y no el método paint(). clase abstracta java. no está relacionada directamente con la transparencia de Color. es bastante fácil construir un JComponent que actúe como nuestro propio canvas ligero. int g. pero no están directamente relacionadas. que muestra como usar el valor alpha para mostrar alguna superficie transparente.awt. componentes de texto. Observe que. Con Swing.0 o 0 significa totalmente transparente. Color(0. etc.Color(float r. Dentro de ese método podemos hacer todo nuestro pintado.0. En AWT sobrescribimos normalmente el método paint() de Component para dibujar y el método update() para otras cosas como implementar nuestro propio doble buffer o rellenar el fondo antes de llamar a paint(). Veremos este proceso en la sección 2. Esto funcionará para cualquier componente ligero que Swing provea como etiquetas. De hecho. Seremos capaces de ver a través de ella sólo porque el color es transparente.11. Nota: La clase Color tiene los métodos estáticos brighter() y darker() que devuelven un Color más claro (brighter) o más oscuro (darker) que el Color especificado. 2. esto 33 . Si quitamos la opacidad. El segundo toma estos valores como ints de 0 a 255.0.Graphics. Por ejemplo.150)) Los límites de la etiqueta se pintarán sólo porque es opaca.255. cuyo fondo se ha puesto a un verde transparente (p. Por defecto es 1. Especificando un valor alpha podemos usar el Color resultante como fondo de un componente para hacerlo transparente. azul y verde como floats de 0. si tenemos una JLabel opaca.e. int a) Normalmente usamos los dos primeros métodos. debería empezar siempre su método paintComponent() con una llamada a super. Ambas cosas se tienen que usar conjuntamente para crear componentes transparentes.awt.0 o 255 que corresponde a completamente opaco. Sabiendo esto. no se invoca nunca al método update() para nada. el dibujo de componentes es mucho más complejo. Hay cinco pasos adicionales en el pintado que normalmente se desarrollan dentro del método paint(). Por supuesto habrá cuestiones específicas de cada componente involucradas (como hacer transparentes el borde y la barra de título de un frame interno transparentes). float a) Color(int r.paintComponent(). 0. Adicionalmente.

awt.java ver \Chapter1\2 import java. y es conveniente que mire los documentos del API. Dentro del método paintComponent() tenemos acceso al objeto Graphics de ese componente (a menudo denominado el contexto gráfico del componente) que podemos utilizar para pintar superficies y dibujar líneas y texto.*. class TestFrame extends JFrame { public TestFrame() { 34 . La clase Graphics define una gran cantidad de métodos que se usan para estos propósitos. import javax.*.3 Demostración de Graphics en un canvas ligero. Nota: La clase de awt Canvas se puede reemplazar por una versión simplificada de la clase JCanvas que definiremos en el siguiente ejemplo.no se debería intentar con los componentes Swing normales porque los delegados UI están a cargo de su dibujado (veremos como personalizar el dibujado en el delegado UI al final del capítulo 6.gif>> El Código: TestFrame.swing. El código siguiente muestra como construir una subclase de Component que pinta un ImageIcon y algunas superficies y texto usando diferentes Fonts y Colors. <<fichero figure2-3. algunas completamente opacos y otros parcialmente transparentes (vimos una funcionalidad parecida pero menos interesante en BakedBean). Figura 2. y durante el capítulo 21).

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

120+(h/4)). w = fm. pero una cosa que deberíamos saber es como obtener información útil sobre las fuentes y el texto dibujado al usarlas. (Mostraremos como extender este ejemplo para trabajar con el área de recorte para una mayor eficiencia en la próxima sección).120-(w/2). h = fm.200+(h/4)).drawString("powerful!!".getFontMetrics().getAscent(). dibujados en la Font actual asociada con el objeto Graphics. h = fm.400).setFont(m_pFont).setFont(m_bFont). Sólo el dibujo hecho dentro de los límites del área de recorte será dibujado en el momento. Nota: Todos los componentes Swing tienen doble buffer por defecto.getFontMetrics(). getMinimumSize().getAscent(). de forma que pudimos dibujarlos centrados en los círculos. g. Estamos viendo su estructura.stringWidth("is").getAscent(). para que algunos administradores de disposición puedan dimensionar este componente (de otra manera alguno pondría su tamaño a 0x0). } // Algunos administradores de disposición necesitan esta información public Dimension getPreferredSize() { return new Dimension(400. Dentro del método paint() de un componente. Si estamos construyendo nuestro propio canvas ligero no tenemos que preocuparnos por el doble buffer. g.// Negrita. Esto implica el uso de la clase FontMetrics. } public Dimension getMinimumSize() { return getPreferredSize(). 12-puntos "is" g. Es siempre una buena práctica la sobreescritura de estos métodos cuando se implementan componentes personalizados. Este no es el caso con un Canvas de AWT.stringWidth("powerful!!"). 36 . En el ejemplo anterior.getFontMetrics().drawString("Swing".280-(w/2). 36-puntos "Swing" g.200-(w/2). la manipulación de Fonts y Font es muy compleja. // Normal. La clase Graphics usa lo que se llama el área de recorte (clipping area). // Negrita 24-puntos "powerful!!" g. fm = g. Como mencionamos antes. FontMetrics fm = g. y getMaximumSize(). } } Observe que sobrescribimos los métodos de JComponent getPreferredSize().stringWidth("Swing"). } public Dimension getMaximumSize() { return getPreferredSize(). fm = g.setFont(m_biFont). h = fm.drawString("is". FontMetrics nos permitió determinar la anchura y la altura de tres Strings. La razón por la que se usa el área de recorte es por eficiencia: no hay motivo para pintar regiones invisibles cuando no tenemos que hacerlo. Cursiva. g. esta es la región de la vista del componente que se está repintando. Podemos obtener el tamaño y la posición de estos límites llamando a getClipBounds() que nos devuelve una instancia de Rectangle describiéndola. w = fm. w = fm.280+(h/4)).

El significado de base (baseline). Nuestro programa dibujará el texto “Swing” con una fuente de 36 puntos.4 Usando FontMetrics <<fichero figure2-4.4 ilustra algunas de las informaciones más comunes que podemos obtener de un objeto FontMetrics. y se la restamos a 200 para centrar el texto horizontalmente. como se mostró anteriormente): int w = fm.200-(w/2). Dibujamos líneas en su subida. FontMetrics fm = g. Aquí escribiremos un programa simple que demuestra estos problemas. Suponga que queremos centrar el texto “Swing” en 200. La subida es la distancia de la base hasta lo más alto de la mayoría de las letras de la fuente. Creamos entonces la instancia de FontMetrics llamando a getFontMetrics() en nuestro objeto Graphics: g. g.getFontMetrics(). fm.200.4 no es una forma exacta de documentar FontMetrics. De todas formas. La razón por la que dividimos la subida para cuatro NO está probablemente muy clara.setFont(m_biFont). Para centrarlo verticalmente obtenemos la subida de la fuente actual. y altura (height) debería quedar claro con el diagrama. Figura 2.200+(h/4)). la dividimos para dos. int h = fm. subida (ascent). Observe que cuando usamos g. FontMetrics ofrece varios métodos para obtener esta información y otras más detalladas. y bajada.gif>> Para obtener una instancia de FontMetrics llamamos primero a nuestro objeto Graphics para que use la Font que queremos examinar usando el método setFont().getAscent(). las coordenadas especificadas representan la posición de la base del primer carácter.La Figura 2.5 muestra el resultado. base. subida/2. Obtenemos la anchura de “Swing” en la fuente actual. La figura 2. Aquí está el código que deberíamos usar (asumiendo que hemos recuperado el objeto FontMetrics. como la anchura de un String dibujado en la Font asociada. 37 . La Figura 2. Ahora es el momento de acometer un error común que ha llegado con Java 2. bajada (descent).drawString("Swing".drawString() para dibujar texto. subida/4. la dividimos para cuatro y se la añadimos a 200. parece que hay unos pocos problemas con FontMetrics en Java 2 FCS.stringWidth("Swing"). Estas es la forma de la que hemos visto documentadas estas cosas en el tutorial Java y en casi todos los demás sitios. Una operación típica cuando dibujamos texto es centrarlo en un punto determinado. negrita y monospaced.

gif>> El Código: TestFrame.setColor(Color.Figura 2.50). mainFrame.*.java Ver \Chapter1\2\fontmetrics import java.drawLine(10.pack().190.black).50). Font.190. getContentPane().add(new JCanvas()). import javax. } 38 .50-(h/4)).getDescent(). // dibujar la línea de Base g.50-(h/2).BOLD.drawLine(10. } public static void main( String args[] ) { TestFrame mainFrame = new TestFrame(). FontMetrics fm = g.drawLine(10.190.190.50. // Prueba también: Ñ Ö Ü ^ // dibujar la línea de Subida g.50-(h/4).50. public void paintComponent(Graphics g) { g. // dibujar la línea de Subida/4 g.50-h). } } class JCanvas extends JComponent { private static Font m_biFont = new Font("Monospaced". mainFrame.190.swing.drawString("Swing". // dibujar la línea de Bajada g.awt. // dibujar la línea de Subida/2 g.setVisible( true ).5 La realidad cuando se trabaja con FontMetrics en Java 2 <<fichero figure2-5. int h = fm.50-h. class TestFrame extends JFrame { public TestFrame() { super( "Lets get it straight!" ).getFontMetrics().drawLine(10. g.50+fm. // Negrita 36-puntos "Swing" g.drawLine(10.50+fm. 36).50-(h/2)).*.setFont(m_biFont).getAscent().getDescent()).

Aún así. El método Toolkit.getDefaultToolkit().java ver \Chapter1\3 public void paintComponent(Graphics g) { super. int clipy = r. tamaños. int clipx = r. Si no. mantenemos un contador local que se incrementa cada vez que se pinta una de nuestros ítems. // obtener área de recorte Rectangle r = g. strings e imágenes se pinte solamente si el área de recorte intersecciona con el rectángulo que lo limita.x. La forma más fiable de centrar el texto verticalmente que hemos encontrado es utilizar base + subida/4. pero es importante comprender como implementar esta funcionalidad. y que la bajada es siempre menor. int clipw = r.100). y podría ser útil que trabajase con ellas y las verificase una a una. 0.getClipBounds(). 2.public Dimension getPreferredSize() { return new Dimension(200. (Estas intersecciones son bastante fáciles de calcular. y con caracteres con marcas diacríticas como Ñ. // para int w = int h = int d = usarse a continuación 0. Nota: En el JDK1.5. será mejor que use un mecanismo de centrado diferente para su texto que debería ser simple de determinar mediante la experimentación con esta aplicación.getFontMetrics está desaconsejado en Java 2 y este código debería ser actualizado. Ö.9 Usando el área de recorte de Graphics Podemos usar el área de recorte para optimizar el dibujo de componentes. o Ü. 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 versión 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. Esto puede que no mejore notablemente la velocidad de dibujado de componentes simples como nuestro JCanvas anterior. // contador int c = 0. Modificamos ahora JCanvas para que cada una de nuestras superficies. } } Le aconsejamos que pruebe este programa con diferentes tipos de fuente. ya que todo el sistema de dibujo de Swing está basado en este concepto (veremos más sobre esto en la siguiente sección). Al finalizar el método paintComponent() mostramos el número total de ítems que se pintaron.) Adicionalmente.paintComponent(g). 39 .getFontMetrics(myfont). 0. A continuación está nuestro método optimizado paintComponent() de JCanvas (con contador): El Código: JCanvas.y. para obtener una instancia de FontMetrics había que hacer lo siguiente: FontMetrics fm = Toolkit.1.width. se puede usar también base + bajada y dependiendo de la fuente que se use puede ser más ajustado. Observará que la subida es siempre mucho mayor de lo que normalmente está documentado que sería.

240).setColor(Color.240.120).fillOval(140. c++.fillOval(160.g.black).setColor(Color.clipy. } // dibujar el círculo verde transparente si está dentro del área de // recorte if (clipx + clipw > 140 && clipx < 260 && clipy + cliph > 140 && clipy < 260) { g. w = fm.120.int cliph = r.paintIcon(this. g.setColor(m_tGreen).setColor(m_tBlue).white).getFontMetrics().0.setColor(m_tRed).yellow). } // 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.120.cliph).clipw. c++.280-(w/2). c++. // dibujar el círculo amarillo si está dentro del área de recorte if (clipx <= 240 && clipy <= 240) { g. g.paintIcon(this.220.120-(w/2). } // dibujar el cuadrado azul transparente si está dentro del área de // recorte if (clipx + clipw > 220 && clipx < 380 && clipy + cliph > 60 && clipy < 180) { g.240).160. h = m_flight.240.fillRect(60. c++.height.setFont(m_biFont). // dibujar sólo el área de recorte g. g.120-(h/2)). g. g.getIconHeight().setColor(Color.fillRect(220.fillRect(clipx.getIconWidth(). } g. } w = m_flight.120. g.magenta). FontMetrics fm = g.120).g. g. } // dibujar el cuadrado rojo transparente si está dentro del área de // recorte if (clipx + clipw >= 60 && clipx <= 180 && clipy + cliph >= 220 && clipy <= 340) { g. 40 .140.fillOval(0.setColor(Color. c++. c++. // 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.stringWidth("Swing"). } // dibujar el círculo magenta si está dentro del área de recorte if (clipx + clipw >= 160 && clipx <= 400 && clipy + cliph >= 160 && clipy <= 400) { g.280-(h/2)).60.120). c++.

y lo hace bastante eficientemente.out. Mantenga la consola a la vista de forma que pueda monitorizar cuantos ítems se dibujan en cada repintado. con doble buffer y trasparencia. c++. Esto es lo que hace JComponent. c++.getDescent().println("# items repainted = " + c + "/10").120-(w/2).drawString("powerful!!".getFontMetrics().getDescent(). d = fm. h = fm.getDescent(). Su salida debería mostrar algo como lo siguiente (por supuesto.stringWidth("is").280+(h/4)).getAscent().stringWidth("powerful!!").getFontMetrics(). // 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.280-(w/2). } Pruebe a ejecutar este ejemplo desplazando otra ventana de su escritorio sobre partes del JCanvas. d = fm. 2. } g. que probablemente se superponen. } System. // Normal. d = fm. 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. probablemente verá otros números 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 difícil.11.120+(h/4)). w = fm.10 Depuración de gráficos La depuración de gráficos nos ofrece la posibilidad de observar todas las operaciones de pintado durante 41 . pero imagine como sería optimizar un contenedor con un número variable de hijos. // Negrita.200-(w/2).setFont(m_bFont). 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.200+(h/4)). fm = g. Aprenderemos un poco más sobre como se hace esto en la sección 2.h = fm.getAscent().drawString("is". c++. fm = g.getAscent(). Cursiva. } g. h = fm.drawString("Swing".setFont(m_pFont). Pero primero terminaremos con nuestro vistazo de alto nivel a los gráficos introduciendo una funcionalidad nueva de Swing muy poderosa: la depuración de gráficos. w = fm.

Estos valores se pueden modificar con los siguientes métodos estáticos de DebugGraphics: setFlashTime(int flashTime) setFlashCount(int flashCount) setFlashColor(Color flashColor) Si no deshabilitamos el doble buffer mediante el RepaintManager (que veremos en la siguiente sección) no veremos el pintado en el momento en que ocurre: RepaintManager.y con cualquier cosa relacionada.logStream(). DebugGraphics.usando el operador binario | ) los cuatro valores estáticos definidos en DebugGraphics. Los valores por defecto son: 250ms como intervalo.. try { debugStream = new PrintStream( new FileOutputStream("JCDebug.println("can't open JCDebug.1 Opciones de la depuración de gráficos 1. soporta la depuración de gráficos.el dibujo de un componente y de todos sus hijos. DebugGraphics.println("\n===> paintComponent ENTERED <==="). setDoubleBufferingEnabled(false). y las jerarquías de componentes -.out. Esto se consigue con un cambio lento. Nota: Desactivar el doble buffer en el RepaintManager tiene el efecto de ignorar la propiedad doubleBuffered de todos los componentes. e imprimiendo en él: PrintStream ps = DebugGraphics. 4 destellos."). Si en algún punto tenemos que redirigir la salida de nuevo hacia la salida estándar: DebugGraphics. Por defecto estos mensajes se envían a la salida estándar (la consola -. Este método recibe un PrintStream como parámetro. } DebugGraphics. y color rojo.10. Podemos insertar cualquier cadena obteniendo el stream de salida con el método estático logStream() de DebugGraphics.currentManager(null). que se puede activar/desactivar con el método setDebugGraphicsOptions() de JComponent. de un determinado color y con un intervalo especificado.setLogStream(debugStream).txt")). ps. Este método recibe un int como parámetro que corresponde normalmente a uno de (o una combinación de bits -.LOG_OPTION: Envía mensajes describiendo todas las operaciones de pintado cuando ocurren.out).txt. Para enviar la salida a un fichero haríamos algo como lo siguiente: PrintStream debugStream = null. y por tanto todos los componente Swing. 2. Pero podemos cambiar el destino con el método estático setLogStream() de DebugGraphics. 2. Si está habilitada la depuración de gráficos.FLASH_OPTION: Cada operación de pintado produce un número determinado de destellos.setLogStream(System. } catch (Exception e) { System. usando distintos destellos para indicar la región que se está pintando. 42 .out). Se intenta ayudar a encontrar problemas con el dibujo. la disposición.System. JComponent. el objeto Graphics que se usa cuando se pinta es una instancia de DebugGraphics (una subclase de Graphics).

El valor de graphicsID representa el número de instancias de DebugGraphics que se han creado durante la vida de la aplicación (p.awt. La forma más simple de evitar esto es definir un delegado UI trivial (vacío).b=0] Las llamadas a los métodos de Graphics se añadirán al registro cuando está opción esté habilitada. 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. Todas las operaciones se imprimen con la siguiente sintaxis: "Graphics" + (isDrawingBuffer() ? "<B>" : "") + "(" + graphicsID + "-" + debugOptions + ")" Todas las líneas empiezan con “Graphics”. La depuración de gráficos no funcionará para cualquier componente cuyo UI sea null. 2. Los valores de graphicsID y de debugOptions se ponen entre paréntesis y separados con un “-”. Con un valor alpha de 0 el color del destello no se verá nunca. con el registro y los destellos habilitados. La línea anterior se generó al hacerse una llamada a setColor(). la depuración de gráficos no hará nada. es un contador de tipo int estático).Color[r=0. Por defecto. vemos una salida parecida a esta para todas las operaciones: Graphics(1-3) Setting color: java. y BUFFERED_OPTION = 7 Por ejemplo. DebugGraphics. se usa un color rojo para los destellos. como hicimos anteriormente con JCanvas. 4. Esto no supone necesariamente un defecto de diseño ya que nada nos impide usar un color completamente transparente para los destellos.2 Advertencias sobre la depuración de gráficos Hay varios asuntos con los que hay que tener cuidado cuando usamos la depuración de gráficos: 1. Como tenemos que tener un delegado UI. En 2 FCS esta opción no es funcional. se añade “<B>”. definimos una subclase trivial de ComponentUI e implementamos su método createUI() para que devuelva una instancia estática de si mismo: 43 . 2. De todos modos. ésta se rellena con ese color rojo del destello y no se borra (simplemente se pinta encima).e. FLASH_OPTION. en la mayoría de los casos es fácil de seguir lo que se está dibujando si ponemos flashTime y flashCount de forma que haya bastante tiempo entre operaciones. El único problema de esto es que no veremos ningún destello.Peligro: Escribir un registro a un fichero sobrescribirá el mismo cada vez que se reinicialice el stream. 2.3 Usando la depuración de gráficos Ahora habilitaremos la depuración de gráficos en nuestro ejemplo de JCanvas de las dos últimas secciones. Si lo está. Veremos como hacer esto con un ejemplo más tarde.10. si ha creado una subclase directa de JComponent sin un delegado UI. se fusionará con el rojo de abajo (o con cualquiera que sea el color del destello).g=255. DebugGraphics no limpia cuando termina. Por tanto.BUFFERED_OPTION: Se supone que desplegará un frame mostrando el dibujado tal como ocurre en el buffer invisible si está habilitado el doble-buffer. 3. Cuando se marca una región. El método isDrawingBuffer() nos dice si está habilitado el buffer. DebugGraphics. En cambio.NONE_OPTION: Apaga la depuración de gráficos. Esto supone un problema porque el dibujo transparente no se mostrará transparente.10.

public JCanvas() { super.*. creamos una instancia de JCanvas y habilitamos la depuración de gráficos con las opciones LOG_OPTION y FLASH_OPTION. simplemente llamamos a super. No se ha hecho ningún otro cambio en el código paintComponent() de JCanvas. Configuramos también una variable de tipo PrintStream en JCanvas y la usamos para añadir unas pocas líneas propias al stream de registro durante el método paintComponent (para guardar cuando empieza y termina el método).createUI(this)) desde el constructor de JCanvas.setUI(EmptyUI. getContentPane().swing.*.setFlashColor(new Color(0.plaf.9 intacto private PrintStream ps.*. el número de éstos a 2. javax. El Código: TestFrame. TestFrame. DebugGraphics. } 44 . mainFrame. class TestFrame extends JFrame { public TestFrame() { super( "Graphics demo" ). java.0.setFlashTime( 100 ). ponemos el intervalo de los destellos a 100ms. javax. Deshabilitamos el doble buffer con RepaintManager.add(jc). DebugGraphics. RepaintManager.*. public static ComponentUI createUI(JComponent c) { return sharedInstance.io. setDoubleBufferingEnabled(false). DebugGraphics.0)).java ver \Chapter1\4 import import import import java.setUI(EmptyUI. En nuestra aplicación de prueba.setDebugGraphicsOptions(DebugGraphics. JCanvas jc = new JCanvas().setVisible( true ).class EmptyUI extends ComponentUI { private static final EmptyUI sharedInstance = new EmptyUI(). } public static void main( String args[] ) { TestFrame mainFrame = new TestFrame().createUI(this)).pack().awt. mainFrame.setFlashCount( 2 ). } } Para asociar adecuadamente este delegado UI con JCanvas.LOG_OPTION | DebugGraphics. y usamos un color totalmente transparente.swing. jc. } } class JCanvas extends JComponent { // Código de la sección 2.FLASH_OPTION).currentManager(jc).0.

y=220.width=120.b=0] Graphics(0-3) Setting font: javax.plaf.Rectangle[x=160.height=240] Graphics(1-3) Drawing image: sun. width=400.swing.width=120.FontUIResource[family=dialog.Color[r=255.g=255.Color[r=0.awt. // Todo el código de pintado intacto ps.Point[x=258.b=0] Graphics(1-3) Filling oval: java.public void paintComponent(Graphics g) { super.awt.g=255.Color[r=255.awt.awt.b=255] Graphics(1-3) Filling oval: java.windows.name=Dialog.println("\n===> paintComponent ENTERED <===").awt.Rectangle[x=60.println("\n# items repainted = " + c + "/10"). Cuando se ejecuta este ejemplo se verá la siguiente salida en su consola (suponiendo que no tape la región visible de JCanvas cuando se pinta por primera vez): Graphics(0-3) Enabling debug Graphics(0-3) Setting color: javax.g=0.ColorUIResource[r=0.b=0] Graphics(1-3) Filling oval: java.swing.y=140.paintComponent(g).9 intacto } class EmptyUI extends ComponentUI { private static final EmptyUI sharedInstance = new EmptyUI(). ps.y=257] Graphics(1-3) Setting color: java.g=0.y=0.logStream().awt.g=0.size=12] ===> paintComponent ENTERED <=== Graphics(1-3) Setting color: java.name=Mono 45 .Rectangle[x=0.b=255] Graphics(1-3) Filling rect: java.awt.Rectangle[x=140.WImage@32a5625a at: java.println("===> paintComponent FINISHED <===\n"). ps.Color[r=255.awt.awt.b=0] Graphics(1-3) Filling rect: java. la depuración de gráficos nos ofrece una mejor información para verificar correctamente como funciona nuestra optimización del área de recorte (de la última sección).Font[family=monospaced.y=0.plaf.width=240.y=60. width=240.height=120] Graphics(1-3) Setting color: java.Color[r=0.height=240] Graphics(1-3) Setting color: java.width=120.bolditalic.WImage@32a5625a at: java.Rectangle[x=220.awt.Point[x=98.awt. ps = DebugGraphics.awt.Color[r=255. } // Código de la sección 2.height=120] Graphics(1-3) Setting color: java.awt. style=plain. } } Poniendo LOG_OPTION.y=97] Graphics(1-3) Drawing image: sun.awt.Rectangle[x=0.awt.Color[r=0.b=0] Graphics(1-3) Setting font: java.awt.windows.awt.awt.g=255.height=400] Graphics(1-3) Setting color: java. public static ComponentUI createUI(JComponent c) { return sharedInstance.b=255] Graphics(1-3) Filling rect: java.g=0.height=120] Graphics(1-3) Setting color: java.y=160.g=0.

size=36] Graphics(1-3) Drawing string: "Swing" at: java. y como especificar el doble buffer de componentes individuales con el método setDoubleBuffered() de JComponent.style=bolditalic. este objeto Graphics se pasará a ellos para usarlo para pintar. Como veremos en el capítulo 3. 2. Como vimos en la última sección. todos sus hijos se pintarán también usando doble buffer. el doble buffer consiste en crear una Image y obtener su objeto Graphics para usarlo en todos los métodos de pintado. ¿cómo trabaja? El doble buffer es la técnica de pintar en una pantalla invisible en lugar de hacerlo directamente en un componente visible.Point[x=195.Font[family=Arial. JRootPane es el componente Swing de más alto nivel en cualquier ventana (lo que incluye a JInternalFrame -. si estamos usando doble-buffer en un componente.size=12] Graphics(1-3) Drawing string: "is" at: java.awt. la imagen resultante se pinta en la pantalla (lo que sucede relativamente deprisa). Hay unos pocos temas que merecen más atención aquí antes de que veamos los detalles de los procesos de pintado y validación.5).y=129] Graphics(1-3) Setting font: java.style=plain.Point[x=65. Si está buscando información sobre como sobreescribir y usar sus propios métodos de pintado.8. los desarrolladores debían implementar su propio doblebuffer para reducir el parpadeo. y sólo hay normalmente una instancia de RepaintManager para cada applet o aplicación (RepaintManager es una clase de servicio que registra una instancia compartida de si misma con AppContext--ver sección 2.aunque no sea realmente una ventana).Point[x=228. Si el componente que vamos a pintar tiene hijos. vaya a la sección 2.11 Pintado y validación En el corazón del mecanismo de pintado y validación de JComponent está RepaintManager. Por tanto.spaced. Si es relativamente nuevo en Java o Swing le recomendamos que se la salte . lo hace interceptando las peticiones de repaint() y revalidate().y=203] Graphics(1-3) Setting font: java. todos sus hijos lo estarán haciendo también (lo tengan habilitado o no) porque dibujaran en el mismo objeto Graphics.name=SanSerif. Nota: Esta sección contiene una explicación relativamente exhaustiva de los más complejos mecanismos subyacentes de Swing. como deshabilitarlo en el RepaintManager. Vea que sólo hay una pantalla invisible para cada RepaintManager.style=bold. Por tanto.y=286] # items repainted = 10/10 ===> paintComponent FINISHED <=== 2.awt. Habilitando el doble buffer en JRootPane.awt.1 Doble buffer Hemos mencionado el doble buffer.Font[family=serif. y así sucesivamente. combinando cualquier petición cuando sea posible. y enviándolas a invokeLater(). Es RepaintManager el que es responsable de enviar peticiones de pintado y validación a la cola de eventos del sistema de AWT para su despacho.awt. Al final. no sorprende mucho encontrar esta funcionalidad en todos los componentes Swing Internamente.11. Pero.size=24] Graphics(1-3) Drawing string: "powerful!!" at: java. Para personalizar el dibujo de los delegados UI vea el capítulo 21. envolviéndolas en objetos Runnable. Cuando se usan componentes AWT. Estaba claro que el doble buffer debía ser una funcionalidad interna a causa de su extendido uso. Para resumir.awt.name=Serif.bold. RepaintManager también ofrece un control global sobre el doble buffer de todos los 46 .

ellos no cambiarán el tamaño o la posición si no es a causa de alguna influencia exterior (p. llamar a este método es equivalente a preguntarle a un componente: ¿es posible que uno de tus componentes hijos se superponga a los otros? Si lo es.11. tenemos que hacer un recorrido completo del árbol para determinar que componentes tienen que ser refrescados. Por tanto. y JDesktopPane (una subclase de JLayeredpane). Podemos modificar esta propiedad llamando al método setOpaque(). sólo JRootPane. ¿Qué significa para un componente el ser transparente? Técnicamente significa que su método isOpaque() devolverá false. JScrollPane. Repintar este tipo de contedor es mucho más complicado que repintar otro contenedor que sepamos que no permite la superposición. asumimos que no tenemos que considerar una situación como esta.componentes. Si se ha sobreescrito isOptimizedDrawingEnabled() para que devuelva true. JViewport. Básicamente. pero veremos que cuando está puesta a false se incrementa la carga de trabajo de todo el mecanismo de pintado. Lo que la opacidad significa en este contexto. Esto puede parecer sorprendente al principio. pero pueden hacerlo. es que un componente pintará todos los pixels dentro de sus límites. deberíamos dejar siempre esta propiedad a true. Lo importante no es que pase raramente o que 47 . El método isOptimizedDrawingEnabled() de JComponent está sobreescrito para que devuelva true para casi todas las subclases de JComponent excepto: JLayeredPane. Es por ello. En situaciones como esta. por ejemplo. Generalmente está puesto a true. o 'optimizado'. tiene que haber alguna manera de determinar si posicionar ese componente afectará a algún otro. Para ayudarle a que se convenza. JLayeredPane. los componentes situados completamente debajo de otros se pueden ver aún a través de éstos. como está por defecto para la mayoría de los componentes (Este valor lo pone normalmente un delegado UI). y JTextField devuelven true. Si está a false. No importa lo grande que hagamos algo dentro de un JRootPane. que el dibujo es más eficiente. Puede observar que la creación de nuevas líneas cambiará su tamaño (dependiendo del posicionamiento).2 Dibujo optimizado No hemos visto el hecho de que los componentes puedan superponer a cualquier otro en Swing todavía. A no ser que estemos construyendo un componente que no tiene que rellenar toda su región rectangular (como haremos en el capítulo 5 con los botones poligonales).setDoubleBufferingEnabled(true). Adicionalmente. 2. 2. o JTextField. intente añadir un componente de texto multi-línea en un contenedor sin ponerlo dentro de un panel de scroll. no se garantiza que pase esto. llamar a este método es equivalente a preguntar: si posiciono tu contenido de nuevo. pero aparentemente es cierto que estos componentes son los únicos componentes Swing cuyo contenido puede ser posicionado de forma exitosa. es un contenedor que permite que cualquier número de componentes se superponga a cualquier otro. Básicamente. de cualquier parte de nuestra jerarquía de componentes. otra forma de garantizar que todos los componentes usen doble buffer es llamar a: RepaintManager. Este tipo de componentes no son necesariamente hermanos (están en el mismo contenedor) porque podemos tener varios contenedores no opacos puestos uno sobre otro. ¿puedes garantizarme que ninguno de tus padres o hermanos se verá afectado desfavorablemente (tendrá que ser posicionado de nuevo)? Por defecto.3 El interior de la validación Una petición de revalidate() se genera cuando un componente tiene que ser situado de nuevo. JScrollPane. en cualquier situación (suponiendo que no hay componentes pesados).e. como los componentes pueden ser transparentes. sin afectar a los padres o hermanos. se pueda superponer a los demás. un hermano o un padre).11. entonces hay un montón de trabajo de repintado más que hacer para tener en cuenta el hecho de que cualquier número de componentes.currentManager(null). Cuando se recibe una petición de un determinado componente. El método isValidateRoot() de JComponent devuelve false para la mayoría de los componentes. principalmente a causa de la posibilidad de que los componentes sean transparentes.

Esta clase maneja también todo el doble buffer en Swing y mantiene una Image sencilla para este propósito. Es suficiente que sepa que los cell renderers no siguen el esquema de pintado y validación que hemos visto en esta sección.11. ¿dónde se usa este método? Un componente o su padre se revalida normalmente cuando el valor de una propiedad cambia y el tamaño.currentManager(null). posiblemente usando uno para cada componente. y JTable son especiales ya que están envueltos en instancias de CellRendererPane y todas las peticiones de validación y repintado no se propagan por la jerarquía. lo que no necesitaremos hacer casi nunca. Veremos que RepaintManager depende de este método para despachar peticiones de validación. (Esta definición existe para que la usen subclases que quieran trabajar con más de un RepaintManager. la posición o el posicionamiento interno del componente se ven afectados. 2. Por padres queremos decir contenedores padre. Se chequea entonces la visibilidad del componente resultante. De todos modos. En otro caso RepaintManager recorre su árbol hasta que alcanza el componente raíz. Por tanto. no importa lo que le pasemos. Este método recibe un Component como parámetro. sino que puede pasar. podemos modificar el mismo manualmente usando el método setDoubleBufferMaximumSize() de RepaintManager. a no ser que creemos específicamente nuestra propia instancia de RepaintManager. Intercepta todas las peticiones repaint() y revalidate().) RepaintManager existe para dos propósitos: para proveer revalidación y repintado eficientes.11. 2. (El resto de funcionalidades de RepaintManager se verá a lo largo de esta sección donde sean aplicables. Después 48 . Vea el capítulo 17 para saber más CellRendererPane y la causa de este comportamiento.4 RepaintManager Como sabemos. Este es el único tipo de situación sobre la que isValidateRoot() nos avisará. De hecho el componente que se pasa a este método no se usa en ningún sitio del método (vea el código fuente de RepaintManager. por lo que se puede usar un valor null de forma segura.5 Revalidación RepaintManager mantiene un Vector de componentes que tienen que ser validados. Aún así. JTree. se envía el componente fuente al método addInvalidComponent() y se chequea su propiedad “validateRoot” usando isValidateRoot(). Esto sucede recursivamente en los padres del componente hasta que isValidateRoot() devuelve true.java). Llamando recusivamente a isValidateRoot() en el padre de un componente Swing hasta que obtengamos true. El tamaño máximo de esta Image es por defecto el tamaño de la pantalla. normalmente hay sólo una instancia en uso de una clase de servicio para cada applet o aplicación. un Window o Applet. Por tanto. Nota: Por hermanos queremos decir componentes del mismo contenedor. Si alguno de los contenedores padre no es visible no hay razón para revalidarlo. RepaintManager chequea entonces el Vector de componentes invalidos y si en él no está aún el componente lo añade. Cuando quiera que una petición revalidate es interceptada. todo el repintado es manejado por la instancia compartida que está registrada con AppContext.) Nota: Los cell renderers que se usan en componentes como JList. si es que hay alguno.se pueda prevenir. Normalmente la obtenemos llamando al método estático currentManager() de RepaintManager: myRepaintManager = RepaintManager. terminaremos con el ascendiente más cercano de ese componente que nos garantice que su validación no afectará a sus padres o hermanos.

int height) public void repaint(Rectangle r) public repaint() // heredado de java. se crea una nueva. int width.awt. Cada componente tendrá como máximo una región sucia en esta tabla en un momento determinado. El primer método envía los parametros de la región sucia al método addDirtyRegion() de RepaintManager. Para componentes pequeños y simples esto está bien. seguido de paintDirtyRegions().11. Si es mayor que 0x0. Cuando se añade una región sucia. El segundo método repaint() mostrado anteriormente llama directamente al primero. La única razón por la que está ahí es para sobreescribir el método repaint() correcto de java. Note: Recuerde que se debería llamar a validateInvalidComponents() sólo dentro del hilo de despacho de eventos. se comprueba el tamaño de la región y del componente.awt. Si no hay ninguna. El segundo recibe la misma información encapsulada en una instancia de Rectangle. RepaintManager pasa entonces la raíz al método queueComponentWorkRequest() de SystemEventQueueUtilities (vimos esta clase en la sección 2. El método paintDirtyRegions() es mucho más complicado. int x. ya que sincroniza el acceso para evitar la adición de componentes invalidos durante la ejecución). se comprueba la visibilidad del componente fuente y sus ancestros.de que se añada con éxito. Se marca entonces como pendiente y dejamos el bloque sincronizado. Entonces sincronizamos el acceso a esa ComponentWorkRequest. El método validateInvalidComponents() básicamente comprueba el Vector de RepaintManager que contiene los componentes que necesitan validación.Container: public void repaint(long tm. y se hereda la versión sin argumentos de repaint() que existe en java. la ponemos en la tabla de peticiones de trabajos si es nueva. si son todos visibles. la anchura y la altura de esa región. No importa el valor que use para él. int y. una Window o un Applet. La misma regla se aplica para paintDirtyRegions(). Cuando se ejecuta finalmente en el hilo de despacho de eventos notifica a RepaintManager que ejecute validateInvalidComponents(). RepaintManager contiene una Hashtable de regiones sucias.6 Repintado JComponent define dos métodos repaint().invokeLater(). y comprobamos si está pendiente (p. Nota: El parámetro long del primer método repaint() no representa absolutamente nada y no se usa. y. Si no está pendiente. la enviamos a SwingUtilities.e. se encuentra navegando por el árbol (de forma parecida a como sucede 49 . En el caso de que tenga una anchura o una altura <= 0 el método vuelve y no pasa nada. pero para los más grandes y complejos esto no es eficiente. Nunca llame a este método desde cualquier otro hilo. 2.awt.3). y llama a validate() en cada uno de ellos.Container Si llama a la versión sin argumentos se repinta todo el componente. su componente raíz. (Este método es en la actualidad un poco más cuidadoso de lo que describimos aquí. Este método comprueba si ya hay un ComponentWorkRequest (esta es una clase privada estática en SystemEventQueueUtilities que implementa Runnable) que corresponda a esta raíz guardada en la tabla de peticiones de trabajos. es suficiente con saber que este método pinta todas las regiones que lo precisen de todos los componentes que se mantengan en RepaintManager.Component. Por ahora. y veremos alguno de esos detalles más adelante. si ha sido añadida a la cola de eventos del sistema de AWT). Si ya hay una. Los otros dos métodos reciben los límites de la región que debe repintarse (la región sucia) como parámetro. simplemente tomamos una referencia a ella. Los parámetros de tipo int del primer método corresponden a las coordenadas x e y. usando addDirtyRegion().

De esta forma todos los límites de la región sucia se minimizan de forma que sólo se mantiene la región visible. De hecho. De ser así. El único trabajo del método paint() de JComponent es el manejo de las áreas de recorte. Finalmente.java para más detalles). (Observe que collectDirtyComponents() tiene en cuenta la transparencia. Al final de cada iteración se llama a paintImmediately() en el componente sucio asociado. es bastante raro que maneja algo de él directamente. Se pregunta a la Hashtable de regiones sucias si ya tiene guardada una región sucia de nuestro componente. Llamando al método collectDirtyComponents() de RepaintManager para cada uno. translaciones. y continúa trabajando con isOpaque() para determinar el componente padre final y sus límites para invocar a paint(). 50 . comprueba si está habilitado el doble buffer (en cuyo caso utiliza la pantalla invisible del objeto Graphics asociado a la Image del RepaintManager).computeUnion() conveniente se usa para combinar la nueva región sucia con la anterior. hacen un repintado artístico incluso más espectacular. El método paintDirtyRegions() continúa iterando a través de una Enumeration de componentes sucios. y redirigiendo la Hashtable de regiones sucias de RepaintManager hacia una nueva vacía. Esto se hace en una sección crítica de forma que no se pueden añadir regiones sucias mientras sucede el intercambio. Este método mira en todos los ancestros del componente sucio especificado y comprueba cualquier superposición con su región sucia usando el método SwingUtilities. el método paintDirtyRegions() entra en un bucle. al contrario que las implementaciones AWT de paint(). RepaintManager pasa la raíz al método queueComponentWorkRequest() de SystemEventQueueUtilities. devuelve su valor. por si ha sido movido. que pinta en ese momento todas las regiones sucias minimizadas en su posición correcta (veremos esto más adelante).El resto de este método es bastante largo y complicado por lo que concluiremos con un resumen del código más significativo (ver RepaintManager. Entonces recorre los padres no opacos del componente (usando isOpaque()) y aumenta los límites de la región a repintar adecuadamente hasta que alcanza un padre opaco: 1.) Este método empieza creando una referencia local a la Hashtable de regiones sucias de RepaintManger. (Recuerde que debería llamarse sólo dentro del hilo de despacho de eventos. se llama al método privado _paintImmediately() y se le pasa la nueva región calculada.11. Esto completa el método paintDirtyRegions(). El método paint() de JComponent. pero primero tenemos que saber como se invoca a paint(). un Rectangle. Si el padre alcanzado es una subclase de JComponent. Este método interroga al método isOptimizedDrawing(). pero todavía tenemos que ver el aspecto más importante de todo el proceso: el pintado. no maneja todo el pintado de un componente. Como sabemos por nuestro repaso al proceso de repintado. ocultado o eliminado desde que se hizo la petición. y el privado al que llama. y el método SwingUtilities. y pintar piezas de Image que se usan por RepaintManager para doble buffer. RepaintManager es responsable de llamar a un método llamado paintImmediately() en todos los componentes para pintar su región sucia (recuerde que hay siempre una sola región sucia para cada componente porque RepaintManager las combina inteligentemente). 2.en addInvalidateComponent()). Ahora podemos hablar un poco sobre el método paintDirtyRegions() que resumimos anteriormente. Lo que sucede a partir de aquí es idéntico a lo que vimos para la revalidación (ver más arriba). El método update() no se usa actualmente en ningún componente Swing. Primero comprueba si el componente destino es visible. pero se provee por compatibilidad. Este bucle calcula la intersección final de cada componente sucio con su región sucia. El resto del trabajo se delega en otros métodos.Veremos en breve cada uno de estos métodos y el orden en que las operaciones de pintado suceden.computeIntersection().7 Pintar JComponent incluye un método update() que simplemente llama a paint(). Este método.) Una vez que se ha hecho esto para cada componente sucio.

Este método trabaja con el objeto Graphics de la pantalla invisible y su área de recorte para generar llamadas al método paint() del padre (pasándole el objeto Graphics usando un área de recorte distinta cada vez). llama a update() en ese delegado UI y sale. que normalmente llamará al método paint() de java. El método paint() comprueba si el doble buffer está habilitado y si se llamó a este método desde paintWithBuffer() (ver más arriba): 1. manejo del área de recorte. Si está completamente oscurecida. y paintChildren() en ese orden. Sólo se puede instanciar pasándole un objeto Graphics existente. (En este caso específico. Ésta no está en los documentos del API porque es privada de paquete. De todas formas. usa el objeto Graphics resultante para dibujar directamente al componente visible. 2.) 51 . si está habilitada la depuración de gráficos se usará una instancia de DebugGraphics para todo el pintado. Si está habilitado el doble buffer. por el hecho de que implementa un interface llamado GraphicsWrapper. sólo hace falta llamar a paintChildren(). se llama a paintComponent(). porque comprueba algunos flags que no explicaremos. Si está habilitado el doble buffer y no se llamó desde paintWithBuffer(). se envian los límites de la región al método repaint() de ese padre. el método paint() no usará ningún buffer internamente ya que sabe. Si no lo tiene simplemente sale. El método update() de un delegado UI es normalmente responsable del pintado del fondo de un componente. y operaciones de tipo deshacer.awt. y entonces llama a paint().) 2. Este método reenviará entonces el tráfico a todos los métodos paint() de sus hijos ligeros. paint() comprueba si el área de recorte del objeto Graphics actual está totalmente oscurecida por algún componente hijo. que es también privado de paquete. Si no está habilitado el doble buffer. que se está teniendo cuidado con el proceso de buffer en algún otro sitio. Si no lo está. y una Stack (pila) de objetos Graphics que se usa para caché. El método paintComponent() comprueba si el componente tiene un delegado UI instalado. A.) B. Si lo tiene. si es opaco. se llama a paintComponent(). se hace simplemente una llamada al paint() del padre. y paintChildren() en ese orden. Si lo está sólo es necesario llamar a paintChildren(). reciclaje. usará el objeto Graphics de pantalla invisible de la Image asociada con RepaintManager durante el resto de este método. Si el padre no es un JComponent.A. Esta funcionalidad se ha hecho incluso más explícita. paintBorder(). (Veremos lo que hacen estos tres métodos dentro de poco. llama a paintWithBuffer() (otro método privado). El método paint() de un delegado UI es el que pinta el contenido del componente correspondiente. Una mirada rápida al código de pintado de JComponent muestra un gran uso de una clase llamada SwingGraphics. Si se llamó a paint() desde paintWithBuffer() o si no está habilitado el doble buffer. (Veremos como personalizar los delegados UI extensamente a lo largo de este texto. SwingGraphics funciona actualmente como un envoltorio para todas las instancias de Graphics usadas durante el proceso de pintado. antes de hacer esto se asegura de que todos los hijos ligeros a los que notifica no están completamente cubiertos por el área de recorte actual del objeto Graphics que se pasó. paintBorder(). Si no lo está. Parece ser una clase muy útil para manejar translaciones personalizadas. Después de cada llamada a paint(). Comprobará entonces si el área de recorte del objeto Graphics actual está completamente oscurecida por los componentes hijo.Component. ¡En todos los casos hemos alcanzado finalmente el método paint() de JComponent! Dentro del método paint() de JComponent.

12 Manejo del foco Cuando se sitúan componentes Swing dentro de un contenedor Swing. El método paint() llamado en cada hijo iniciará esencialmente el proceso de pintado del hijo desde la parte 2 de arriba.) requestFocusEnabled: especifica si una pulsación de ratón dará el foco a ese componente. ya que el camino del foco se maneja para 52 . C. Para resumir. y no se puede cambiar con un método típico de acceso setXX(). nextFocusableComponent: esta especifica el componente al que se transfiere el foco cuando se pulsa la tecla TAB. 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 método paintComponent() y llamemos inmediatamente a super. Por defector esta propiedad es true (para la mayoría de los componentes). y siempre es una buena práctica evitar hacerlo. Sólo se puede cambiar sobreescribiendo el método isManagingFocus() y devolviendo el valor booleano apropiado. FocusManager utiliza cinco propiedades de JComponent para tratar cuando el foco alcanza a éste o le abandona: focusCycleRoot: esta especifica si el componente contiene un ciclo de foco propio. Si contiene un ciclo de foco. (Observe que cuando el foco alcanza a un componente a través de una pulsación de ratón se llama a su método requestFocus(). Esto no afecta al trabajo del FocusManager . el camino del foco del teclado es. El método paintBorder() pinta simplemente el borde del componente si lo tiene. y este proceso se repetirá hasta que no existan más hijos o no necesiten ser pintados. focusTraversable: esta especifica si el foco se puede transferir al componente por el FocusManager a causa de un desplazamiento del foco en el ciclo. busca por todos los componentes hijo y determina si se debería invocar a paint() en éstos usando el área de recorte de Graphics actual. De esta forma daremos al delegado UI la oportunidad de que dibuje el componente primero. que continuará transfiriendo el foco al componente como parte del ciclo del foco. y cambiar el foco de un componente al siguiente del ciclo se logra usando la tecla TAB o CTRL-TAB. Sólo se puede cambiar sobreescribiendo el método isFocusCycleRoot() y devolviendo el valor booleano apropiado. por defecto. y no se puede cambiar con un método típico de acceso setXX(). managingFocus: esta especifica si los KeyEvents correspondientes a un cambio de foco serán enviados al componente o interceptados y consumidos por el FocusManager. Para moverse en la dirección inversa a través del ciclo usamos SHIFT-TAB o CTRLSHIFT-TAB. Por defecto está propiedad es false (para la mayoría de los componentes). Por defecto esta propiedad es false (para la mayoría de los componentes). el método isOpaque() y el método isOptimizedDrawingEnabled().B. Nos referimos normalmente a este camino como el ciclo del foco. Sólo se puede cambiar sobreescribiendo el método isFocusTraversable() y devolviendo el valor booleano apropiado. Por defecto esta propiedad es true (para la mayoría de los componentes). Sobreescribiendo requestFocus() podemos responder a peticiones de foco de manera específica para cada componente. El método paintChildren() está un poco más implicado en el proceso. y no se puede cambiar con un método típico de acceso setXX(). de izquierda a derecha y de arriba a abajo. el foco entrará en este componente y se moverá a través de su ciclo de foco hasta que se envíe fuera de ese componente manualmente o mediante código. Sobreescribir el método paint(). o cualquier otro de los métodos mencionado anteriormente será rara vez necesario. 2. y se puede cambiar con el método setRequestFocusEnabled() de JComponent .paintComponent(). Por defecto está puesto a null. El ciclo se controla por una instancia de la clase abstracta FocusManager.

12.1 FocusManager clase abstracta javax.5). el KeyEvent se consume normalmente y se mueve el foco hacia delante o hacia atrás usando los métodos focusNextComponent() o focusPreviousComponent() respectivamente. que recibe dos Components como parámetros y determina en primer lugar cual de ellos está situado más cerca de la parte de arriba del contenedor para que sea la raíz del ciclo del foco.DefaultFocusManager DefaultFocusManager desciende de FocusManager y define los tres métodos requiridos. En otro caso devolverá false. y podemos comprobar si está habilitado o no en un momento determinado usando el método estático isFocusManagerEnabled(). Los siguientes tres métodos abstractos se tienen que definir en las subclases: focusNextComponent(Component aComponent): se debería llamar para desplazar el foco al siguiente componente en el ciclo del foco cuya propiedad focusTraversable sea true.FocusManager Esta clase define la responsabilidad de determinar como se mueve el foco de un componente a otro. o bien consumir un KeyEvent enviado al componente. Podemos deshabilitar el servicio actual FocusManager usando el método estático disableFocusManager().2 DefaultFocusManager clase javax. Si ambos está situados a la misma altura este método determinará cual de ellos está más a la izquierda.”API 2. Los métodos getFirstComponent() y getLastComponent() devuelven el primer componente y el último que recibirán el foco en el ciclo del foco de un determinado contenedor. Nota: “FocusManager recibirá los eventos de teclado KEY_PRESSED. y los métodos getComponentBefore() y getComponentAfter() se definen para devolver el componente anterior y posterior respectivamente. Para acceder a FocusManager usamos su método estático getCurrentManager(). todos los demás eventos se deberían consumir también.swing. Se devolverá un valor de true si el primer componente pasado debe obtener el foco antes que el segundo. o bien para permitirle ser procesado por el componente. 2. que recibirán el foco después de un determinado componente en el ciclo del foco. Para asignar un nuevo FocusManager usamos el método estático setCurrentManager(). 53 . KeyEvent anEvent): se debería llamar para.swing. Si este es el caso. El método más importante en esta clase es compareTabOrder().12. así como varios métodos adicionales. Si se consume un evento. FocusManager es una clase de servicio cuya instancia compartida se guarda en la tabla de servicio AppContext (ver 2.nosotros por el servicio FocusManager. Los métodos focusNextComponent() y focusPreviousComponent() desplazan el foco como se esperaba. focusPreviousComponent(Component aComponent): se debería llamar para desplazar el foco al anterior componente en el ciclo del foco cuya propiedad focusTraversable sea true. KEY_RELEASED y KEY_TYPED. processKeyEvent(Component focusedComponent. Asignando un componente como el nextFocusableComponent potenciará el mecanismo de foco de FocusManager. Esto se consigue pasando el componente al método setNextFocusableComponent() de JComponent. Este método se usa normalmente para determinar si una pulsación de teclado corresponde a un desplazamiento en el foco.

y en que orden ocurre ésta). En caso contrario. los identificadores FOCUS_LOST y FOCUS_GAINED para distinguir entre sus dos tipos de eventos. en un cuadro de texto la operación podría ser añadir un carácter específico al contenido del documento). TAB y SHIFT-TAB moverán el cursor en su lugar (ver capítulos 11 y 19). podemos escuchar cambios del foco en un componente adjuntando una instancia del interface java.FocusListener.FocusEvent como parámetro: focusGained(FocusEvent e): este método recibe un FocusEvent cuando se da el foco a un componente al que se le ha añadido este oyente. Cuando el foco vuelve a esta ventana. pulsar y soltar una tecla del teclado): KEY_PRESSED: este tipo de evento de tecla se genera cuando una tecla del teclado se pulsa. cada uno de los cuales ocurre por lo menos una vez cada activación de teclado (p. Si estos eventos corresponden a un desplazamiento del foco (p.awt. La tecla que se ha pulsado queda especificada por la propiedad keyCode y un código virtual de la tecla se puede obtener con el método getKeyCode() de KeyEvent.ComponentEvent y define.El método processKeyEvent() intercepta KeyEvents enviados al componente que posee actualmente el foco. y SHIFT-CTRL-TAB) se consumen y se cambia el foco adecuadamente.awt. CTRL-TAB y SHIFT-CTRL-TAB se pueden usar para desplazar el foco fuera de componentes de texto. cada uno de los cuales recibe una instancia de java. SHIFT-TAB.VK_ENTER. focusLost(FocusEvent e): este método recibe un FocusEvent cuando se pierde el foco en un componente al que se le ha añadido este oyente. KeyStrokes.e. Hay tres tipos de eventos KeyEvent. Un evento FOCUS_LOST ocurrirá correspondiendo a una pérdida de foco temporal o permanente. 2. estos eventos se envían al componente para ser procesados (ver sección 2. que 54 . entre otros.13. al contrario que la mayoría de los eventos. Como es lógico. (más adelante veremos exactamente como tener notificación de la entrada de teclado.13). y Actions 2.e. o despachando algún KeyEvent que cause un cambio de foco en el método processKeyEvent() de FocusManager.3 Escuchando cambios del foco Como con los componentes AWT.1 Escuchando la entrad de teclado Se lanzan KeyEvents por un componente siempre que el componente tiene el foco y el usuario pulsa una tecla. 2. Las pérdidas ocurren cuando otra aplicación u otra ventana recibe el foco.awt. KeyEvent define numerosas constantes estáticas de tipo int. Un código virtual de tecla se usa para informar de la tecla exacta del teclado que ha causado el evento. los KeyEvents se despachan antes de que la operación correspondiente tome parte (p.13 Entrada de teclado.12. o mediante código al invocar a requestFocus() en otro componente. FocusListener define dos métodos. el componente que perdió el foco lo obtendrá de nuevo. TAB. Nota: Por defecto. CTRLTAB. Podemos consumir estos eventos usando el método consume() antes de que se manejen más adelante por asociaciones de teclas u otros oyentes.e. Para escuchar estos eventos en un componente particular podemos añadir KeyListeners usando el métodos addKeyListener(). podemos añadir o borrar implementaciones de FocusListener a cualquier componente Swing usando los métodos addFocusListener() y removeFocusListener() de Component respectivamente. KeyEvent desciende de InputEvent y. tal como KeyEvent. Observe que el FocusManager siempre intercepta los eventos de teclado. y un evento FOCUS_GAINED se despachará en ese momento. Las pérdidas permanentes de foco ocurren cuando el foco se mueve a causa de una pulsación en otro componente de la misma ventana. FocusEvent desciende de java.

si se pulsa CTRL-C. Podemos usar el método isActionKey() de KeyEvent para obtener el valor de esta propiedad. ALT.KeyStroke con ActionListeners (normalmente instancias de javax. como veremos más adelante. 55 . La mayoría de las teclas con representación Unicode. isAltDown(). e incluyó la funcionalidad de interceptar eventos de teclado sin tener en cuenta el componente que tenga el foco. y podemos comprobar específicamente cual de estas teclas estaba pulsada en el momento en que se lanzó evento usando isShiftDown(). El conjunto de teclas que muestran este comportamiento. y InputEvent.). etc. Este es un valor de tipo int que es el resultado de un or binario entre InputEvent.CHAR_UNDEFINED--p. isControlDown(). (Observe que el orden en el que se lanzan depende del orden en el que se pulsan. Nunca contiene una propiedad keyCode correspondiente a la tecla pulsada. las teclas de función de un teclado normal de PC). InputEvent. El int devuelto por getKeyCode() correspondiente a pulsar CTRL será un KeyEvent. el carácter devuelto por getKeyChar() correspondiente a pulsar la tecla “C” será ‘c’.) KeyEvent también tiene un propiedad keyChar que especifica la representación Unicode del carácter pulsado (si no hay representación Unicode se usa KeyEvent. El equipo de Swing se percató de esto. y a menudo tediosa. Por ejemplo. a pesar de que se usa el mismo keyCode en ambas situaciones--p. una significativa. y getKeyChar() devolverá simplemente ‘’ en este caso. CTRL. Si estaba pulsado SHIFT cuando se pulsó la tecla “C”. Podemos obtener el carácter keyChar correspondiente a un KeyEvent usando el método getKeyChar().swing. KEY_TYPED: este tipo de eventos se lanzan en algún momento entre un evento KEY_PRESSED y un evento KEY_RELEASED.empiezan con el prefijo “VK. el int devuelto por getKeyCode() correspondiente a pulsar la tecla “C” será un KeyEvent. el valor VK_C se será devuelto por getKeyCode() esé pulsada o no la tecla SHIFT cuando se pulsa la tecla “C”. Cada KeyEvent mantiene un conjunto de modificadores que especifica el estado de las teclas SHIFT. no se puede controlar y depende de la plataforma. Igualmente. Podemos obtener este valor con getModifiers().” que significa Virtual Key (tecla virtual) (ver los documentos del API de KeyEvent para una lista completa). y isMetaDown(). Observe que para teclas sin representación Unicode (como RE PAG.swing. los eventos KEY_RELEASED son idénticos a los eventos KEY_PRESSED (aunque. 2. no se lanzará el evento KEY_TYPED.e. cuando se mantienen pulsadas durante un rato.Action).13.VK_C. A causa de esto. y se devolverá 0 siempre que se llame a getKeyCode() en un evento de este tipo. Observe también que no hay keyChar asociado con teclas como CTRL. ocurren mucho menos a menudo). y META. (Observe que se devuelven distintos keyChars para mayúsculas y minúsculas.VK_CTRL. KeyEvent también contiene la propiedad booleana actionKey que especifica si la tecla que lo ha lanzado corresponde a una acción que debería ejecutar la aplicación (true) o si son datos que se usan normalmente para cosas como la adición de contenido a un componente de texto (false).META_MASK (dependiendo de que teclas están pulsadas en el momento del evento). PRINT SCREEN. InputEvent.ALT_MASK. y el porcentaje en que lo hacen. generarán repetidos KEY_PRESSED y KEY_TYPED (en este orden). Salvo por esta diferencia.CTRL_MASK.2 KeyStrokes El uso de KeyListeners para manejar la entrada de teclado componente por componente era necesario antes de Java 2.) KEY_RELEASED: este tipo de evento de teclado se genera cuando se suelta una tecla. Por ejemplo. el carácter devuelto por getKeyChar() correspondiente a la tecla “C” será ‘C’. se lanzarán dos eventos KEY_PRESSED. Esta funcionalidad está implementada usando asociaciones de instancias de la clase javax. cantidad de tiempo se gastaba planificando y depurando operaciones de teclado.SHIFT_MASK.e.

int condition). y una propiedad booleana que especifica si se debería activar en una pulsación de tecla (false -. un valor modifiers (idéntico al de KeyEvent -. Cuando se registra un KeyStroke usando el método registerKeyboardAction(). Cada instancia de KeyStroke encapsula un keyCode de KeyEvent (ver anteriormente).WHEN_FOCUSED: sólo se llamará al correspondiente ActionListener si el componente con el que está registrado este KeyStroke tiene el foco.por defecto) o cuando se suelta la tecla (true). InputEvent. int modifiers) getKeyStroke(int keyCode. JComponent.getRootPane(). Las propiedades keyCode. Sólo se puede registrar un 56 .e. y no es necesario que estos métodos develvan siempre una instancia completamente nueva): getKeyStroke(char keyChar) getKeyStroke(int keyCode. La clase KeyStroke ofrece cinco métodos estáticos para crear objetos KeyStroke (observe que todos los objetos KeyStrokes están escondidos. boolean onKeyRelease) getKeyStroke(String representation) getKeyStroke(KeyEvent anEvent) El último método devolverá un KeyStroke con las propiedades correspondientes a los atributos del KeyEvent. para asociar la invocación de un ActionListener a la pulsación de ALT-H sin importar el componente que tenga el foco en un JFrame determinado.4) en lugar de en el componente.ALT_MASK. keyChar. Por ejemplo.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT: sólo se llamará al correspondiente ActionListener si el componente con el que está registrado este KeyStroke es ancestro (contiene) del componente que tiene el foco. JFrame. JComponent. El parámetro int especifica bajo que condiciones se considera valido el KeyStroke: JComponent. o algún otro componente pesado) que tiene el foco. false). Cada JComponent mantiene una propiedad cliente de tipo Hashtable que contiene todos los KeyStrokes asociados. El parámetro ActionListener tiene que estar definido de forma que su método actionPerformed() haga las operaciones necesarias cuando se intercepte entrada de teclado correspondiente al parámetro KeyStroke. se añade a esta estructura.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT). myKeyStroke. KeyStroke stroke.getKeyStroke(KeyEvent. int modifiers. 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. podemos hacer lo siguiente: KeyStroke myKeyStroke = KeyStroke. Observe que las acciones de teclado registradas con esta condición se manejan en una instancia de la clase privada de servicio KeyBoardManager (ver 2. Para registrar una combinación KeyStroke/ActionListener con un JComponent podemos usar su método registerKeyBoardAction(ActionListener action. myJFrame. JDialog.13.ver anteriormente). JComponent.Nota: A las acciones de teclado registradas se les conoce normalmente como aceleradores de teclado.registerKeyBoardAction( myActionListener. JApplet. JWindow.VK_H.WHEN_IN_FOCUSED_WINDOW: sólo se llamará al correspondiente ActionListener si el componente con el que está registrado este KeyStroke está en algún lugar de la ventana de más alto nivel (p.

y podemos obtener su correspondiente propiedad de condición con el método getConditionForKeyStroke(). métodos de conversión. los KeyStrokes definidos con la condición WHEN_FOCUSED tienen esa oportunidad. entonces KeyboardManager pasa el evento a todas las JMenuBars de la ventana actual y les da a sus aceleradores la oportunidad de procesar el evento. que se dividen en siete grupos: métodos de cálculo. Si ninguno de éstos maneja el evento. el nuevo seobreescribirá al anterior. Si los oyentes no lo consumen y el componente con el foco es un JTextComponent. una clase de servicio que es privada de paquete (observe que al contrario de la mayoría de las clases de servicio de Swing. 2.5). entonces se hace que el JComponent llame a super. Todos estos métodos son estáticos y se describen muy brevemente en esta sección (para una comprensión más avanzada vea el 57 . Estos son sólo 2 de los 36 métodos de utilidad genérica definidos en SwingUtilities.13. métodos de accesibilidad. El FocusManager tiene la primera oportunidad de procesarlo. 2. Podemos obtener un array de KeyStrokes correspondientes a las asociaciones guardadas en esta Hashtable usando el método getRegisteredKeyStrokes() de JComponent. el componente navega por sus contenedores padre (hasta que alcanza un JRootPane) buscando KeyStrokes que estén definidas con la condición WHEN_ANCESTOR_OF_FOCUSED_COMPONENT. Un KeyMap es una lista de asociaciones Action/KeyStroke y JTextComponent soporta múltiples niveles de este tipo de mapeo.swing. Este proceso continúa hasta que se procesa el evento o se alcanza la ventana de máximo nivel. KeyboardManager no registra su instancia compartida con AppContext -. se envía a KeyboardManager. comprobamos si el foco reside en un JInternalFrame (porque es el único RootPaneContainer que puede estar dentro de otro componente Swing).ActionListener para cada KeyStroke. 2. y métodos de disposición/dibujo/UI. métodos de recuperación. Nota: Los componentes de texto son especiales porque usan una resolución jerárquica mediante KeyMaps. se recorre la jerarquía de KeyMap (ver capítulos 11 y 19 para más detalles sobre KeyMaps). métodos para los botones del ratón. métodos relacionados con la multitarea y los eventos. y si ya hay un ActionListener para un determinado KeyStroke.processKeyEvent() que da la oportunidad a muchos KeyListeners de procesar el evento. Si es el caso.3 vimos dos métodos de la clase SwingUtilities que se usaban para ejecutar código en el hilo de despacho de eventos. Dado un objeto KeyStroke podemos obtener su correspondiente ActionListener con el método getActionForKeyStroke() de JComponent.14 SwingUtilities clasa javax. A menudo usamos instancias de Action cuando registramos acciones de teclado. Si el evento no se ha manejado después de que se haya alcanzado el contenedor de máximo niveI.ver sección 2. en este momento las asociaciones de teclado registradas con el componente que tiene el foco tienen una oportunidad. Primero.13. Si no se encuentra ninguno de éstos. Ver capítulos 11 y 19. y podemos anular todas las asociaciones con el método resetKeyboardActions(). Si el FocusManager no lo quiere. KeyboardManager busca componentes con KeyStrokes registrados con la condición WHEN_IN_FOCUSED_WINDOW y les envía el evento.3 Actions Una instancia de Action es básicamente una implementación conveniente de ActionListener que encapsula una Hashtable de propiedades ligadas semejante a la de las propiedades cliente de JComponent (ver capítulo 12 para más detalles sobre el trabajo con implementaciones de Action y sus propiedades). nos trasladamos al padre del JInternalFrame.4 El flujo de la entrada de teclado Cada KeyEvent se despacha primero al componente con el foco. Si el evento aún no es manejado. Si no se consume el evento.SwingUtilities En la sección 2.

Component destination): devuelve un MouseEvent nuevo con destination como fuente y las coordenadas x e y convertidas al sistema de coordenadas de destination (asumiendo en ambos casos que destination no sea null). 2. int y. Rectangle computeUnion(int x. Point aPoint. Rectangle computeIntersection(int x. Point convertPoint(Component source. Rectangle dest): devuelve la unión de dos áreas rectangulares. Si destination es null las coordenadas se convierten al sistema de coordenadas de source. int width. Si uno de los componentes es null se usa el sistema de coordenadas del otro.14.código fuente de SwingUtilities.3).3 Métodos de accesibilidad Accessible getAccessibleAt(Component c. 2.2 Métodos de conversión MouseEvent convertMouseEvent(Component source.java). int height. Rectangle b): devuelve true si el Rectangle a contiene completamente al Rectangle b. void convertPointFromScreen(Point p. String str): devuelve la achura del String de acuerdo al objeto FontMetrics (ver 2. computeStringWidth(FontMetrics fm. Component destination): devuelve un Point que representa aPoint convertido al sistema de coordenadas del componente destination como si se hubiese generado en el componente source. El parámetro de tipo Rectangle se modifica y se devuelve como resultado del cálculo de forma que no se tiene que instanciar un nuevo Rectangle. void convertPointToScreen(Point p. El parámetro de tipo Rectangle se modifica y se devuelve como resultado del cálculo de forma que no se tiene que instanciar un nuevo Rectangle. int x.8. La primera región se define con los parámetros de tipo int y la segunda por con el parámetro de tipo Rectangle.1 Métodos de cálculo Rectangle[] computeDifference(Rectangle rectA. Component destination): devuelve un Rectangle convertido del sistema de coordenadas del componente source al sistema de coordenadas del componente destination.14. y se pone source como fuente del evento. MouseEvent sourceEvent. Si ambos son null el MouseEvent devuelto es idéntico al evento que se pasa. Point convertPoint(Component source. Este método se comporta de forma similar a convertPoint(). Component destination): este método funciona igual que el anterior método convertPoint() excepto que recibe parámetros de tipo int que representan las coordenadas del Point a convertir en lugar de una instancia de Point. int width. Component c): convierte el Point dado en coordenadas de la pantalla al sistema de coordenadas del Component dado. y si ambos son null el Point devuelto es idéntico al Point pasado. int height. 2. int y. Rectangle aRectangle. La primera región se define con los parámetros de tipo int y la segunda por con el parámetro de tipo Rectangle. Rectangle rectB): devuelve las regiones rectangulares que representan la porción de rectA que no intersecciona con rectB. Point p): devuelve el componente 58 . Rectangle convertRectangle(Component source. Component c): convierte el Point dado en el sistema de coordenadas del Component dado al sistema de coordenadas de la pantalla. Rectangle dest): devuelve la intersección de dos áreas rectangulares.14. isRectangleContainingRectangle(Rectangle a. inty.

Component comp): devuelve el ancestro más cercano del Component dado con el nombre que le pasamos. Si el Component no es un Container este método termina inmediatamente. int i): devuelve el i-ésimo hijo Accessible del Component dado. boolean isDescendingFrom(Component allegedDescendent.Accessible. o si el Component dado no implementa Accessible. (Observe que cada Component tiene una propiedad name que se puede asignar y recuperar usando los métodos setName() y getName() respectivamente. Window windowForComponent(Component c): devuelve el primer ancestro de c que es una Window.Accessible en el determinado Point del sistema de coordenadas del Component dado (se devolverá null si no se encuentra ninguno). En otro caso se devuelve null.0). Component allegedAncestor): devulve true si allegedAncestor contiene a allegedDescendent. int x. Container getAncestorNamed(String name.14. Component getDeepestComponentAt(Component parent. En otro caso este método devuelve el último ancestro que es un Applet. int y): devuelve el hijo más profundo del Component dado que contiene el punto (x.5 Métodos relacionados con la multitarea y los eventos Ver sección 2. void invokeLater(Runnable obj): envía el Runnable a la cola de despacho de eventos y 59 . Si no hay tal componente se devuelve null. Rectangle getLocalBounds(Component c): devuelve un Rectagle que representa los límites de un Component determinado en su propio sistema de coordenadas (de este modo siempre empieza en 0. Component getRoot(Component c): devuelve el primer ancestro de c que es una Window. Se devolverá -1 si el padre es null o no implementa Accessible. c): devuelve el número 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. AccessibleStateSet getAccessibleStateSet(Component c): devuelve el conjunto de AccessibleStates que no están activos para el Component dado. void invokeAndWait(Runnable obj): envía el Runnable a la cola de despacho de eventos y bloquea el hilo actual. Observe que un componente Accessible es aquel que implementa el interface javax.14. JRootPane getRootPane(Component c): devuelve el primer JRootPane que es padre de c. En otro caso se devuelve null. o c si es un JRootPane.y) en términos del sistema de coordenadas del Component dado.accessibility.) Container getAncestorOfClass(Class c.3 para más información sobre estos métodos. int getAccessibleChildrenCount(Component Accessible que contiene el Component dado. Component comp): devuelve el ancestro más cercano del Component dado que es una instancia de c. 2.4 Métodos de recuperación Component findFocusOwner(Component c): devuelve el componente contenido dentro del Component dado (o el Component dado) que tiene el foco. Accessible getAccessibleChild(Component c. 2. En otro caso devuelve null.

”) en lugar del texto que no cabría. Rectangle viewR. las condiciones de alineamiento y las posiciones del texto dentro del Rectangle viewR . int h): pinta el Component dado en el contexto gráfico dado. y JTable para mostrar correctamente el comportamiento de "sello de goma" (rubber stamp).. String layoutCompoundLabel(JComponent c.14. String text. void paintComponent(Graphics g. Rectangle textR. int verticalTextPosition. Los Rectangles textR e iconR se modifican para reflejar la nueva disposición. Rectangle iconR. int verticalAlignment.7 Métodos de disposición/dibujo/UI String layoutCompoundLabel(FontMetrics fm. 60 . int w. usando el rectángulo definido por los cuatro parámetros de tipo int como área de recorte. JTree.continúa. pero recibe el componente destino para comprobar si el la orientación del texto se debe tener en cuenta (ver el artículo “Component Orientation in Swing: How JFC Components support BIDI text” en the Swing Connection para más información sobre orientación: http://java. icon icon. int horizontalTextPosition. int textIconGap): este método es idéntico al anterior. y se devuelve el String resultante de esta disposición. Esta es la misma metodología que usan los pintores de componentes de JList. Si se determina que el texto de la etiqueta no cabrá dentro de este Rectangle.6 Métodos para los botones del ratón boolean isLeftMouseButton(MouseEvent): devuelve true si el MouseEvent corresponde a una pulsación del botón izquierdo del ratón. Rectangle iconR. Este comportamiento se logra mediante el uso de un CellRendererPane (ver capítulo 17 para más información sobre esta clase y por qué se usa para envolver los pintores). Rectangle textR. void paintComponent(Graphics g. int x. int textIconGap): Este método se usa normalmente por el delegado UI de JLabel para posicionar texto y/o un icono usando el FontMetrics. Rectangle viewR.com/products/jfc/tsc/tech_topics/bidi/bidi. Component c.html). boolean boolean isMiddleMouseButton(MouseEvent): devuelve true si el MouseEvent corresponde a una pulsación del botón de en medio del ratón. y a c. FontMetrics fm. int horizontalTextPosition. int horizontalAlignment. Container p. que actualicen su delegado UI para que correspondan a los actuales UIManager y UIDefaults (ver capítulo 21). Component c. se usan puntos suspensivos (“. boolean isEventDispatchThread(): devuelve true si el hilo actual es el hilo de despacho de eventos. int y. El Container se usa para que actúe como el padre del Component de forma que cualquier petición de validación o repintado que sucedan en ese componente no se propaguen por el árbol de ancestros del componente al que pertenece el contexto gráfico dado. String text. pero recibe un Rectangle como parámetro en lugar de cuatro ints. Rectangle r): funciona de forma idéntica al método anterior. int verticalTextPosition. Container p..14. void updateComponentTreeUI(Component c): notifica a todos los componentes que contiene c. int verticalAlignment. isRightMouseButton(MouseEvent): devuelve corresponde a una pulsación del botón derecho del ratón. int horizontalAlignment. 2. icon icon.sun. true si el MouseEvent 2.