desarrollo de aplicaciones visuales - sing-group.orglipido/dojos/swing/swing.pdf · o general, como...

34
Desarrollo de aplicaciones visuales Daniel González Peña 17.1 Fundamentos Los lenguajes de programación de propósito general, como lo es Java, disponen de librerías para la creación de interfaces gráficas de usuario (GUI, Graphical User Interface). Estas librerías suelen ser un conjunto de componentes o widgets (botones, áreas de texto, listas desplegables, menús, etc.) que constituyen lo que se conoce comúnmente como toolkits. Sin embargo, la existencia de uno o varios toolkits para un lenguaje de programación, no implica que sean estándar, ni que se incluyan por defecto con el lenguaje. Así por ejemplo, las librerías GTK+, Qt, wxWindows, VCL/Kylix asociadas todas ellas a los lenguajes C/C++, son soluciones tanto libres como propietarias que no están asociadas de forma estándar a estos lenguajes. JFC (Java Foundation Classes), es un framework para la creación de interfaces gráficas de usuario en Java. Está formada por: AWT (Abstract Window Toolkit). Permite la integración de los programas con el sistema de ventanas subyacente, incluyendo APIs para el “Drag and Drop”. Capítulo Java 2D. Permite la creación de gráficos 2D, tratamiento de imágenes, texto e impresión. Componentes Swing. Extiende a AWT incluyendo un conjunto de componentes (toolkit) para la creación de interfaces visuales. Además posee un sistema extensible para el manejo del aspecto y comportamiento (Pluggable Look and Feel). Accesibilidad. Permite la creación de aplicaciones accesibles para usuarios con alguna discapacidad. Internacionalización. Permite la creación de aplicaciones que pueden interactuar con los usuarios utilizando su propio lenguaje y convenciones (fechas, moneda, etc.). Es preciso destacar que los componentes Swing se incluyeron más tarde que AWT. Hasta ese momento se utilizaban otros componentes visuales (botones, áreas de texto, listas desplegables…) que formaban parte de AWT. En AWT, cada componente se renderiza y es controlado directamente por un componente nativo de la plataforma en la que se esté ejecutando el programa en cada momento. Un componente AWT es, en realidad, un “envoltorio” de un componente nativo. Por el contrario, los componentes Swing son 100% Java y no requieren la creación de un componente nativo asociado a cada componente Swing, si no que se dibujan directamente mediante Java 2D, sin que el sistema subyacente sea consciente de su existencia en pantalla. Una consecuencia directa de esta diferencia de diseño es que una aplicación AWT tendrá un aspecto muy parecido al resto de aplicaciones del sistema operativo donde se ejecute y, por el contrario, una aplicación Swing podrá mantener un aspecto uniforme independientemente de la plataforma donde se ejecute. AWT, la solución más antigua, ha sido muy criticada y actualmente se aconseja el uso de los componentes Swing frente a los de AWT (una forma típica de distinguirlos es que los componentes Swing comienzan por “J” y los AWT no, p. ej.: JBUTTON frente a BUTTON). Swing se distribuye por defecto con el JDK a partir de su versión 1.2. Se considera una tecnología más orientada a objetos, más comprensible, más avanzada, menos difícil de utilizar y menos difícil de depurar que AWT. Sin embargo, en términos estrictos, Swing no es un reemplazo total de

Upload: phungdat

Post on 30-Sep-2018

246 views

Category:

Documents


2 download

TRANSCRIPT

Page 1: Desarrollo de aplicaciones visuales - sing-group.orglipido/dojos/swing/swing.pdf · o general, como lo es Java, disponen de librerías para la creación de interfaces gráficas de

Desarrollo de aplicaciones visuales Daniel González Peña

17.1 Fundamentos

Los lenguajes de programación de propósito general, como lo es Java, disponen de

librerías para la creación de interfaces gráficas de usuario (GUI, Graphical User

Interface). Estas librerías suelen ser un conjunto de componentes o widgets

(botones, áreas de texto, listas desplegables, menús, etc.) que constituyen lo que se

conoce comúnmente como toolkits. Sin embargo, la existencia de uno o varios

toolkits para un lenguaje de programación, no implica que sean estándar, ni que se

incluyan por defecto con el lenguaje. Así por ejemplo, las librerías GTK+, Qt,

wxWindows, VCL/Kylix asociadas todas ellas a los lenguajes C/C++, son

soluciones tanto libres como propietarias que no están asociadas de forma estándar

a estos lenguajes.

JFC (Java Foundation Classes), es un framework para la creación de interfaces

gráficas de usuario en Java. Está formada por:

• AWT (Abstract Window Toolkit). Permite la integración de los

programas con el sistema de ventanas subyacente, incluyendo APIs para

el “Drag and Drop”.

Capítulo • Java 2D. Permite la creación de gráficos 2D, tratamiento de imágenes,

texto e impresión.

• Componentes Swing. Extiende a AWT incluyendo un conjunto de

componentes (toolkit) para la creación de interfaces visuales. Además

posee un sistema extensible para el manejo del aspecto y comportamiento

(Pluggable Look and Feel).

• Accesibilidad. Permite la creación de aplicaciones accesibles para

usuarios con alguna discapacidad.

• Internacionalización. Permite la creación de aplicaciones que pueden

interactuar con los usuarios utilizando su propio lenguaje y convenciones

(fechas, moneda, etc.).

Es preciso destacar que los componentes Swing se incluyeron más tarde que AWT.

Hasta ese momento se utilizaban otros componentes visuales (botones, áreas de

texto, listas desplegables…) que formaban parte de AWT. En AWT, cada

componente se renderiza y es controlado directamente por un componente nativo

de la plataforma en la que se esté ejecutando el programa en cada momento. Un

componente AWT es, en realidad, un “envoltorio” de un componente nativo. Por

el contrario, los componentes Swing son 100% Java y no requieren la creación de

un componente nativo asociado a cada componente Swing, si no que se dibujan

directamente mediante Java 2D, sin que el sistema subyacente sea consciente de su

existencia en pantalla. Una consecuencia directa de esta diferencia de diseño es que

una aplicación AWT tendrá un aspecto muy parecido al resto de aplicaciones del

sistema operativo donde se ejecute y, por el contrario, una aplicación Swing podrá

mantener un aspecto uniforme independientemente de la plataforma donde se

ejecute.

AWT, la solución más antigua, ha sido muy criticada y actualmente se aconseja el

uso de los componentes Swing frente a los de AWT (una forma típica de

distinguirlos es que los componentes Swing comienzan por “J” y los AWT no, p.

ej.: JBUTTON frente a BUTTON). Swing se distribuye por defecto con el JDK a partir de

su versión 1.2. Se considera una tecnología más orientada a objetos, más

comprensible, más avanzada, menos difícil de utilizar y menos difícil de depurar

que AWT. Sin embargo, en términos estrictos, Swing no es un reemplazo total de

Page 2: Desarrollo de aplicaciones visuales - sing-group.orglipido/dojos/swing/swing.pdf · o general, como lo es Java, disponen de librerías para la creación de interfaces gráficas de

AWT, si no que es un complemento. Es habitual que en un programa Swing se

importen paquetes de AWT para el manejo de eventos, el control de la distribución

y otras tareas.

Frente a AWT y Swing como alternativas estándar para la creación de interfaces

gráficas, existe otra tercera tecnología, más reciente, denominada SWT (Standard

Widget Toolkit). Es software libre y ha sido desarrollada por IBM y ahora es

mantenida por la Fundación Eclipse (http://www.eclipse.org). SWT se puede ver

como un compromiso entre AWT y Swing, en el sentido de que es un API de alto

nivel como Swing a la vez que está implementada “envolviendo” los diferentes

componentes nativos en clases Java, como hace AWT.

Este capítulo trata sobre el desarrollo de aplicaciones visuales con Swing cuyo

aspecto se parece al que muestra la Figura 17.1 cuando se utiliza look and feel por

defecto.

Figura 17.1. Aplicación Swing con el aspecto “Metal”

Obviamente no se van a tratar en profundidad todos los aspectos que puede

proporcionar este framework tan amplio, mostrando una por una todas las API de

cada uno de los componentes, si no que se hará alusión a la mayoría de ellos

mostrando ejemplos básicos de su funcionamiento.

17.2 Aspectos generales de las aplicaciones Swing

17.2.1 Vista rápida de un programa Swing

Antes de entrar en detalle sobre los componentes Swing, se hará una breve

introducción sobre la estructura típica de un programa Swing. Para introducir estos

conceptos básicos se indicarán los pasos a seguir para implementar una aplicación

con el aspecto que muestra la Figura 17.2.

Figura 17.2. Aplicación simple Swing

Los pasos básicos a seguir son:

1. Importar paquetes Swing.

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

2. Elegir el aspecto y el comportamiento (Look and Feel).

public static void main(String [] args) { try { UIManager.setLookAndFeel( UIManager.getCrossPlatformLookAndFeelClassName()); } catch (Exception e) { } //Crea y muestra el GUI... }

El fragmento de código anterior se utilizaría el aspecto multi-plataforma, de forma

que la aplicación se vería igual independientemente del sistema operativo que se

esté usando. Este aspecto se conoce comúnmente como “Metal”.

3. Configurar el contenedor de alto nivel.

AApplliiccaacciioonnSSwwiinnggDDeemmoo..jjaavvaa

Page 3: Desarrollo de aplicaciones visuales - sing-group.orglipido/dojos/swing/swing.pdf · o general, como lo es Java, disponen de librerías para la creación de interfaces gráficas de

Todo programa que presente una interfaz Swing contiene al menos un contenedor

Swing de alto nivel. Para la mayoría de los programas, los contenedores de alto

nivel Swing son instancias (directas o que heredan) de JFRAME, JDIALOG, o (para los

applets) JAPPLET. Cada objeto JFRAME implementa una ventana secundaria. Cada

objeto JAPPLET implementaría un área de pantalla de un applet dentro de una

ventana de navegador. Un contenedor de alto nivel Swing proporciona el soporte

que necesitan los componentes Swing para realizar su dibujado y su manejo de

eventos.

public class SwingApplication { ... public static void main(String[] args) { ... JFrame frame = new JFrame("AplicacionSwingDemo"); //...crear los componentes que iran en la ventana... //...agruparlos en un contenedor llamado contents... frame.getContentPane().add(contents, BorderLayout.CENTER); //Terminar de configurar la ventana y mostrarla. frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.pack(); frame.setVisible(true); } }

El fragmento de código anterior utiliza la clase JFRAME directamente creando una

instancia de la misma para luego añadirle componentes utilizando su referencia.

Sin embargo, lo habitual es crear una clase que hereda de alguno de los

contenedores de alto nivel y configurarla en su constructor, tal y como muestra el

siguiente fragmento alternativo:

public class MiVentana extends JFrame { public MiVentana(){ super("AplicacionSwingDemo"); //...crear los componentes que iran en la ventana... //...agruparlos en un contendor llamado contents this.getContentPane().add(contents, BorderLayout.CENTER); //Terminar de configurar la ventana y mostrarla. this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this.pack(); } } ... public class Aplicacion{ public static void main(String[] args) { ... MiVentana frame = new MiVentana(); frame.setVisible(true); } }

4. Configurar los botones y las etiquetas.

La aplicación ejemplo únicamente contiene un botón y una etiqueta. Para la

creación del botón bastaría con las siguientes líneas:

JButton button = new JButton("I'm a Swing button!"); button.setMnemonic('i'); button.addActionListener(this);

La primera línea crea el botón. La segunda selecciona la letra ‘i’ como mnemónico

que el usuario puede utilizar para simular un clic del botón. Por ejemplo, si se usa

el Look And Feel “Metal”, tecleando Alt+i resulta en un clic del botón. La tercera

línea registra un manejador de eventos para el clic del botón. Los manejadores de

eventos se tratarán más adelante.

Para iniciar la etiqueta sería:

JButton button = new JLabel(“Numero de clics: 0”);

5. Añadir componentes a contenedores.

Suele ser habitual que los componentes se agrupen en paneles de forma que sea

más sencillo distribuirlos finalmente en la ventana mediante las técnicas de control

de la distribución que se tratarán más adelante. En el ejemplo, tanto el botón como

la etiqueta se agruparán en un panel distribuidos en una rejilla de una única

columna y dos filas, yendo primero el botón y luego la etiqueta.

JPanel pane = new JPanel(); pane.setLayout(new GridLayout(0, 1)); pane.add(button); pane.add(label);

6. Manejar eventos.

Se establecerá un manejador de eventos que atienda la pulsación sobre el botón que

incrementará una variable que cuenta el número de pulsaciones que se han

realizado desde el inicio de la aplicación.

button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { numClicks++; label.setText(labelPrefix + numClicks); } });

El manejo de eventos se tratará en detalle más adelante.

17.2.2 Jerarquía de componentes

Para entender mejor el funcionamiento de Swing es necesario saber que un

programa Swing puede ser visto como un árbol de componentes. Siguiendo con el

ejemplo de la Figura 17.2 se han utilizado los siguientes componentes:

Page 4: Desarrollo de aplicaciones visuales - sing-group.orglipido/dojos/swing/swing.pdf · o general, como lo es Java, disponen de librerías para la creación de interfaces gráficas de

• Un frame o ventana principal (JFRAME).

• Un panel, algunas veces llamado pane (JPANEL).

• Un botón (JBUTTON).

• Una etiqueta (JLABEL).

El frame es un contenedor de alto nivel. Existe principalmente para proporcionar

espacio para que se dibujen otros componentes Swing. Los otros contenedores de

alto nivel más utilizados son los diálogos (JDIALOG) y los applets (JAPPLET).

El panel es un contenedor intermedio. Su único propósito es simplificar el

posicionamiento del botón y la etiqueta. Otros contenedores intermedios, como los

paneles desplazables (JSCROLLPANE) y los paneles con pestañas (JTABBEDPANE),

típicamente juegan un papel más visible e interactivo en el GUI de un programa.

El botón y la etiqueta son componentes atómicos, es decir, componentes que

existen no para contener otros componentes Swing, sino como entidades auto-

suficientes que representan bits de información para el usuario. Frecuentemente,

los componentes atómicos también obtienen entrada del usuario. El API Swing

proporciona muchos componentes atómicos, incluyendo combo-boxes

(JCOMBOBOX), campos de texto (JTEXTFIELD) y tablas (JTABLE).

La Figura 17.3 muestra un diagrama con el árbol de contenidos de la ventana

mostrada en la Figura 17.2.

Figura 17.3. Árbol de contenidos de una aplicación Swing

El diagrama muestra todos los contenedores creados o usados por el programa,

junto con los componentes que contienen. Si se añadiese una ventana (p. ej.: un

diálogo), la nueva ventana tendría su propio árbol de contenidos, independiente del

mostrado en la Figura 17.3.

17.3 Manejo de eventos

Cada vez que el usuario teclea un carácter o pulsa un botón del ratón, ocurre un

evento (Event). Cualquier objeto puede ser notificado del evento. Únicamente debe

implementar la interfaz apropiada y ser registrado como un oyente (Listener) del

evento apropiado. Los objetos registrados como oyentes se denominan también

manejadores de eventos. Los componentes Swing pueden generar muchas clases

de evento.

Cada evento está representado por un objeto que ofrece información sobre el

evento e identifica la fuente. Las fuentes de los eventos normalmente son

componentes Swing, pero otros tipos de objetos también pueden ser fuente de

eventos. Para atender a un mismo evento sobre una misma fuente se pueden

registrar múltiples oyentes, tal y como muestra la Figura 17.4.

Figura 17.4. Oyentes atendiendo a un evento

Algunos ejemplos típicos de interfaces que definen oyentes y los eventos asociados

se muestran en la Tabla 17.1.

Tabla 17.1. Eventos Swing más habituales

Acción que resulta en el evento Interfaz oyente Objeto Evento

Page 5: Desarrollo de aplicaciones visuales - sing-group.orglipido/dojos/swing/swing.pdf · o general, como lo es Java, disponen de librerías para la creación de interfaces gráficas de

El usuario pulsa un botón, presiona Return mientras se teclea en un campo de texto o elige un ítem de menú.

ACTIONLISTENER ACTIONEVENT

El usuario elige un frame (ventana principal). WINDOWLISTENER WINDOWEVENT El usuario pulsa un botón del ratón mientras el cursor está sobre un componente.

MOUSELISTENER MOUSEEVENT

El usuario mueve el cursor sobre un componente.

MOUSEMOTIONLISTENER MOUSEEVENT

El componente se hace visible. COMPONENTLISTENER COMPONENTEVENT El componente obtiene el foco del teclado. FOCUSLISTENER FOCUSEVENT Cambia la selección de una lista. LISTSELECTIONLISTENER LISTSELECTIONEVENT Cuando se selecciona o deselecciona un elemento de una lista, un check box, un radio button, etc.

ITEMLISTENER ITEMEVENT

17.3.1 Creación de manejadores de eventos

Para implementar un manejador de eventos se deben seguir tres pasos:

1. Definir una clase manejadora de eventos indicando el tipo de oyente que es, en

función del evento que desea atender. Ello se indica, o bien implementando

directamente la interfaz adecuada, o bien heredando de una clase que hereda

de dicha interfaz. Por ejemplo:

public class MiManejador implements ActionListener {

2. Implementar los métodos necesarios del manejador que serán invocados cada

vez que se produzca el evento. Por ejemplo:

public void actionPerformed(ActionEvent e) { System.out.println(“Se produjo un evento”); }

3. Registrar una instancia del manejador como oyente sobre uno o más

componentes. Esto se realiza invocando a métodos que comienzan por ADD*,

por ejemplo:

MiManejador manejador = new MiManejador(); boton.addActionListener(manejador);

Suele ser frecuente que los manejadores de eventos se implementen “en línea”

como clases anónimas y no como una clase aparte. El siguiente fragmento de

código es equivalente a los tres pasos anteriores:

boton.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent e){ System.out.println(“Se produjo un evento”); } });

17.3.2 Clases adaptadoras

Algunas interfaces de oyentes declaran más de un método. Sin embargo, se puede

estar interesado en definir sólo un subconjunto de ellos. Por ese motivo existen

clases adaptadoras (Adapter) que implementan las interfaces de los oyentes

implementando todos los métodos (sin código). De este modo se podría

implementar un oyente heredando de una clase adaptadora y redefiniendo

únicamente el o los métodos de interés.

Por ejemplo, si se desea capturar el evento de que el ratón pasa por encima de un

componente, se debe crear una clase que implemente MOUSELISTENER. Esta interfaz

define cinco métodos, pero únicamente interesa el método

MOUSEENTERED(MOUSEEVENT E). Sin embargo se está obligado a implementar siempre

todos los métodos de una interfaz. Por ello existe la clase MOUSEADAPTER que ya

implementa esta interfaz con lo que la mejor opción sería que el manejador de

eventos heredase de esta clase y redefiniese únicamente dicho método.

La Tabla 17.2 muestra algunas interfaces oyente y sus métodos, junto con las

clases adaptadoras que las implementan.

Tabla 17.2. Interfaces de oyente, clases adaptadoras y métodos

Interfaz Clase Adaptadora Métodos ACTIONLISTENER ninguna ACTIONPERFORMED CARETLISTENER ninguna CARETUPDATE CHANGELISTENER ninguna STATECHANGE COMPONENTLISTENER COMPONENTADAPTER COMPONENTHIDDEN

COMPONENTMOVED COMPONENTRESIZED COMPONENTSHOWN

CONTAINERLISTENER CONTAINERADAPTER COMPONENTADDED COMPONENTREMOVED

DOCUMENTLISTENER ninguna CHANGEDUPDATE INSERTUPDATE REMOVEUPDATE

FOCUSLISTENER FOCUSADAPTER FOCUSGAINED FOCUSLOST

INTERNALFRAMELISTENER INTERNALFRAMEADAPTER INTERNALFRAMEACTIVATED INTERNALFRAMECLOSED INTERNALFRAMECLOSING INTERNALFRAMEDEACTIVATED INTERNALFRAMEDEICONIFIED INTERNALFRAMEICONIFIED INTERNALFRAMEOPENED

ITEMLISTENER ninguna ITEMSTATECHANGED

Page 6: Desarrollo de aplicaciones visuales - sing-group.orglipido/dojos/swing/swing.pdf · o general, como lo es Java, disponen de librerías para la creación de interfaces gráficas de

KEYLISTENER KEYADAPTER KEYPRESSED KEYRELEASED KEYTYPED

LISTSELECTIONLISTENER ninguna VALUECHANGED MOUSELISTENER MOUSEADAPTER

MOUSEINPUTADAPTER* MOUSECLICKED MOUSEENTERED MOUSEEXITED MOUSEPRESSED MOUSERELEASED

MOUSEMOTIONLISTENER MOUSEMOTIONADAPTER MOUSEINPUTADAPTER*

MOUSEDRAGGED MOUSEMOVED

UNDOABLEEDITLISTENER ninguna UNDOABLEEDITHAPPENED WINDOWLISTENER WINDOWADAPTER WINDOWACTIVATED

WINDOWCLOSED WINDOWCLOSING WINDOWDEACTIVATED WINDOWDEICONIFIED WINDOWICONIFIED WINDOWOPENED

17.3.3 API Action

Suele ser habitual que en una aplicación visual existan varios componentes que

permiten el acceso a la misma funcionalidad. Por ejemplo, en un editor de texto, la

función de “Copiar” se puede alcanzar con el menú “Edición -> Copiar”, pulsando

un botón en la barra de herramientas o mediante un menú contextual. En esos casos

se recomienda el uso de un objeto ACTION para implementar la función.

Un objeto ACTION es un ACTIONLISTENER que proporciona, además de las funciones

propias de un manejador de eventos, un modo centralizado para el manejo del

estado de los componentes que generan los eventos que atiende el objeto ACTION,

como pueden ser botones de barra de herramientas, elementos de un menú, botones

comunes y campos de texto. Por estado se entiende, entre otros: el texto que

muestra el componente, el icono, el mnemónico y el estado

habilitado/deshabilitado.

Para asociar un objeto ACTION a un componente se utiliza el método SETACTION.

Cuando se invoca este método ocurre lo siguiente:

• El estado del componente se actualiza para pasar a utilizar el del objeto

ACTION. Por ejemplo, si al objeto ACTION se le había asociado un texto y un

icono, entonces el texto y el icono del componente pasan a ser los que se

hayan indicado para el objeto ACTION.

• El objeto ACTION se registra como un ACTIONLISTENER del componente.

• Si el estado del objeto ACTION cambia, el estado del componente cambia

automáticamente. Por ejemplo, si se cambia el estado de

habilitado/deshabilitado del objeto ACTION, todos los componentes que

tienen asociado este objeto ACTION cambiarán su estado de

habilitado/deshabilitado.

La Figura 17.5 muestra una aplicación que usa varias ACTION todas ellas colocadas

tanto en un menú (más adelante se verá cómo crear menús), como en botones (más

adelante se verá cómo crear botones).

Figura 17.5. Ejemplo de uso de la API Action

A continuación se verá en diferentes fragmentos de código, cómo se puede obtener

la aplicación de la Figura 17.5.

El siguiente fragmento muestra cómo crear un botón de una barra de herramientas

y un elemento de menú que realizan la misma función:

Action leftAction = new LeftAction(); //El codigo de LeftAction se muestra despues ... button = new JButton(leftAction) ... menuItem = new JMenuItem(leftAction);

Para crear un objeto ACTION, normalmente se crea una subclase de ABSTRACTACTION y

se instancia. En la subclase, se debe implementar el método ACTIONPERFORMED que

atiende al evento de acción. Además es en el constructor de esta subclase donde se

AAccttiioonnDDeemmoo..jjaavvaa

Page 7: Desarrollo de aplicaciones visuales - sing-group.orglipido/dojos/swing/swing.pdf · o general, como lo es Java, disponen de librerías para la creación de interfaces gráficas de

describe la acción, es decir, texto, icono, mnemónico y estado de

habilitado/deshabilitado. Para ello se utiliza el método PUTVALUE de ABSTRACTACTION.

El siguiente ejemplo muestra el código de una clase ACTION concreta denominada

LEFTACTION.

leftAction = new LeftAction("Go left", anIcon, "This is the left button.", new Integer(KeyEvent.VK_L)); ... class LeftAction extends AbstractAction { public LeftAction(String text, ImageIcon icon, String desc, Integer mnemonic) { super(text, icon); putValue(SHORT_DESCRIPTION, desc); putValue(MNEMONIC_KEY, mnemonic); } public void actionPerformed(ActionEvent e) { displayResult("Action for first button/menu item", e); } }

El método PUTVALUE recibe como primer parámetro una constante de tipo STRING que

identifica la propiedad concreta. Los componentes a los que se le asigne la acción

(botones, menús, etc.) obtendrán automáticamente estos valores de la acción e

invocarán métodos propios de forma automática. No todos los componentes usan

todas las propiedades. La Tabla 17.3 muestra las propiedades que se pueden

definir, junto con los componentes que las leerán y el propósito que tienen.

Tabla 17.3. Propiedades de las Acciones

Propiedad Clase que se auto-configura (método llamado)

Propósito

ACCELERATOR_KEY JMENUITEM (SETACCELERATOR) La combinación de teclas que activa la acción.

ACTION_COMMAND_KEY ABSTRACTBUTTON, JCHECKBOX, JRADIOBUTTON

(SETACTIONCOMMAND)

La cadena de texto asociada a este comando.

LONG_DESCRIPTION ninguno Una descripción larga de la acción. Puede ser utilizada para una ayuda contextual.

MNEMONIC_KEY ABSTRACTBUTTON, JMENUITEM, JCHECKBOX, JRADIOBUTTON

(SETTEXT)

El mnemónico de la acción. Es decir, una tecla que activa la acción cuando está visible al usuario.

NAME ABSTRACTBUTTON, JMENUITEM, JCHECKBOX, JRADIOBUTTON

(SETTEXT)

El nombre de la acción.

SHORT_DESCRIPTION ABSTRACTBUTTON, JCHECKBOX, JRADIOBUTTON

(SETTOOLTIPTEXT)

Una descripción corta de la acción.

SMALL_ICON ABSTRACTBUTTON, JMENUITEM

(SETICON) El icono de la acción a utilizar en la barra de herramientas o en un botón.

17.4 Componentes Swing

La API Swing está formada en su mayor parte por los denominados componentes

Swing. Generalmente un componente Swing es una clase (que comienza por ‘J’) y

que representa una entidad que se dibuja en la pantalla. Además de la clase central

del componente, existen clases asociadas a cada componente que se deben conocer

y que se irán viendo en cada caso.

Cada ventana de un programa Swing utiliza al menos un contenedor de alto nivel,

un contenedor intermedio (panel de contenido al menos) y varios componentes

ligeros.

17.4.1 Contenedores de alto nivel

En general, cada aplicación tiene al menos un árbol de contenidos encabezado por

un objeto frame (JFRAME). Cada applet debe tener un árbol de contenido encabezado

por un objeto JAPPLET. Cada ventana adicional de una aplicación o un applet tiene

su propio árbol de contenido encabezado por un frame o dialógo (JFRAME/JDIALOG).

17.4.1.1 Frames

La mayoría de las aplicaciones Swing presentan su GUI principal dentro de un

JFRAME o JAPPLET, un contenedor Swing de alto nivel que proporciona ventanas para

aplicaciones y applets. Un frame tiene decoraciones como un borde, un título, y

botones para cerrar y minimizar la ventana. Un programa típico simplemente crea

un frame, añade componentes al panel de contenido, y quizás añade una barra de

menú. Sin embargo, a través de su panel raíz, JFRAME proporciona soporte para una

mayor personalización.

Para crear una ventana que sea dependiente de otra ventana, es decir, que, por

ejemplo, desaparezca cuando la otra ventana se minimiza, se utiliza un diálogo

(JDIALOG) en vez de un frame. Para crear una ventana que aparece dentro de otra

ventana se utiliza un frame interno (JINTERNALFRAME). Todos ellos se verán más

adelante.

17.4.1.1.1 Añadir componentes a frames

Page 8: Desarrollo de aplicaciones visuales - sing-group.orglipido/dojos/swing/swing.pdf · o general, como lo es Java, disponen de librerías para la creación de interfaces gráficas de

Para incluir componentes en un JFRAME, se añaden a su panel de contenido. Las dos

técnicas más comunes para añadir componentes al panel de contenido de un frame

son.

• Crear un contenedor como un JPANEL, JSCROLLPANE, o un JTABBEDPANE, y

añadirle componentes, luego utilizar JFRAME.SETCONTENTPANE para

convertirlo en el panel de contenido del frame. El siguiente fragmento de

código utiliza esta técnica. El código crea un panel desplazable para

utilizarlo como panel de contenido del frame. Hay una tabla en el panel

desplazable:

public class TableDemo extends JFrame { public TableDemo() { super("TableDemo"); MyTableModel myModel = new MyTableModel(); JTable table = new JTable(myModel); table.setPreferredScrollableViewportSize(new Dimension(500, 70)); //Crear el panel de scroll y añadirle la tabla. JScrollPane scrollPane = new JScrollPane(table); //Añadir el panel de scroll al frame. setContentPane(scrollPane);

• Utilizar JFRAME.GETCONTENTPANE para obtener el panel de contenido del

frame. Añadir componentes al objeto devuelto. El siguiente fragmento de

código utiliza esta técnica:

...//crear los componentes... //obtener el panel de contenido y añadirle los componentes creados: Container contentPane = getContentPane(); // utilizar un manejador de distribución que respete los tamaños contentPane.setLayout(new BoxLayout(contentPane, BoxLayout.Y_AXIS)); contentPane.add(Box.createRigidArea(new Dimension(0, 10))); contentPane.add(controls); contentPane.add(Box.createRigidArea(new Dimension(0, 10))); contentPane.add(emptyArea);

• A partir de la versión 1.5 del JDK no es necesario obtener el panel de

contenido con GETCONTENTPANE para añadirle componentes. Ahora se puede

hacer directamente con el método ADD de JFRAME. En versiones anteriores

este método lanzaba una excepción indicando que no se debía usar

directamente.

El controlador de distribución por defecto para el panel de contenido de un frame

es BORDERLAYOUT (los controladores de distribución se ven más adelante). Cómo se

ha visto en los ejemplos anteriores, se puede invocar al método SETLAYOUT sobre el

panel de contenidos para cambiar su controlador de distribución.

17.4.1.2 Diálogos

Muchas clases Swing soportan diálogos, es decir, ventanas que son más limitadas

que los frames. Todo diálogo depende de un frame. Cuando el frame se destruye,

también se destruyen sus diálogos. Cuando el frame es minimizado, sus diálogos

dependientes también desaparecen de la pantalla. Cuando el frame es maximizado,

sus diálogos dependientes vuelven a la pantalla. Swing proporciona

automáticamente este comportamiento.

Para crear un diálogo, simple y estándar se utiliza JOPTIONPANE. Para crear diálogos

personalizados, se utiliza directamente la clase JDIALOG, la cual se utiliza de forma

muy similar a JFRAME. Otras dos clases, JCOLORCHOOSER y JFILECHOOSER, también

suministran diálogos estándar. Para mostrar un diálogo de impresión se utiliza el

método GETPRINTJOB de la clase TOOLKIT. Se hará especial hincapié en JOPTIONPANE y

JFILECHOOSER por ser los más utilizados.

Un diálogo puede ser modal. Cuando un diálogo modal es visible, bloquea las

entradas del usuario en todas las otras ventanas del programa. Todos los diálogos

que proporciona JOPTIONPANE son modales. Para crear un diálogo no modal,

debemos utilizar directamente la clase JDIALOG, que permite crear tanto diálogos

modales como no modales. Incluso si utilizamos JOPTIONPANE para implementar un

diálogo, estamos utilizando JDIALOG indirectamente.

17.4.1.2.1 Trabajo con JOptionPane

Utilizando JOPTIONPANE se pueden crear muchos tipos de diálogos. Para ello se crea

y se muestra el diálogo utilizando uno de los métodos SHOWXXXDIALOG de

JOPTIONPANE como los que se muestran en la Tabla 17.4.

Tabla 17.4. Tipos de diálogos estándar con JOptionPane

Método Descripción SHOWMESSAGEDIALOG Muestra un diálogo modal con un botón, etiquetado "Aceptar". Se

puede especificar fácilmente el mensaje, el icono y el título que mostrará el diálogo. Para crear un diálogo de este tipo bastaría por ejemplo con: JOptionPane.showMessageDialog(frame, "Esto es un mensaje de

Page 9: Desarrollo de aplicaciones visuales - sing-group.orglipido/dojos/swing/swing.pdf · o general, como lo es Java, disponen de librerías para la creación de interfaces gráficas de

alerta.", “Atención”, JOptionPane.WARNING_MESSAGE); Como se puede observar existe un parámetro para indicar el tipo de mensaje, por el cual Swing asignará un icono adecuado (las opciones son: ERROR_MESSAGE, INFORMATION_MESSAGE, WARNING_MESSAGE, QUESTION_MESSAGE, o

PLAIN_MESSAGE). Ejemplos de este tipo de diálogo se muestran en la Figura 17.6.

SHOWCONFIRMDIALOG Muestra un diálogo modal con dos o tres botones, etiquetados "Sí" y "No" o también “Sí”, “No” y “Cancelar”. Para crear un diálogo de este tipo bastaría por ejemplo con: int opcion = JOptionPane.showConfirmDialog(frame,"Deseas formatear tu disco duro?", "Pregunta", JOptionPane.YES_NO_OPTION); Como se puede observar existe un parámetro para indicar si habrá dos o tres botones utilizando las constantes YES_NO_OPTION o YES_NO_CANCEL_OPTION. Un ejemplo de este tipo de diálogo se muestra en la Figura 17.7.

SHOWINPUTDIALOG Muestra un diálogo modal que obtiene una cadena del usuario. El diálogo de entrada puede mostrar o bien un campo de texto para que el usuario teclee en él, o bien un combo-box no editable, desde el que el usuario puede elegir una de entre varias cadenas. Para crear un diálogo de este tipo bastaría por ejemplo con: Object[] possibilities = {"c++", "java", "pascal"}; String s = (String)JOptionPane.showInputDialog( frame, "El mejor lenguaje de programación es...", "Diálogo de Prueba", JOptionPane.PLAIN_MESSAGE, null,possibilities, "java");

Este tipo de diálogo permite indicar una serie de posibilidades al usuario apareciendo de ese modo un combo-box. Si en lugar de indicar posibilidades se pasa NULL, se proporcionará un área de texto para que el usuario introduzca cualquier cadena. Un ejemplo de este tipo de diálogo se muestra en la Figura 17.8.

SHOWOPTIONDIALOG Muestra un diálogo para que el usuario pulse alguno de sus botones al igual que showConfirmDialog. La diferencia es que permite personalizar los botones que se le ofrecen al usuario a través de un array (showConfirmDialog sólo permitía YES_NO_OPTION y de YES_NO_CANCEL_OPTION). Para crear un diálogo de este tipo bastaría por ejemplo con: Object[] posibilities = {"Como texto plano","Como HTML", "Como PDF", "Cancelar"} int opcion = JOptionPane.showOptionDialog( frame, "Enviar el e-mail como:", "Diálogo de Prueba", JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, null, possibilities, "Como HTML");

Este tipo de diálogo mostrará una serie de botones con las diferentes opciones indicadas. Un ejemplo de este tipo de diálogo se muestra en la Figura 17.9.

Todos los métodos mostrados en la Tabla 17.4 están sobrecargados de forma que si

no se desea utilizar algún parámetro (por ejemplo, no se quiere indicar el icono, el

título, etc.), existirá una variante del método para el que no se necesite incluir

dicho parámetro, tomando entonces un valor por defecto. Además existe una

versión SHOWINTERNALXXXDIALOG de cada uno de los métodos de la Tabla 17.4 que

muestra el diálogo como un frame interno a otro.

Figura 17.6. Diálogos estándar creados con showMessageDialog

Figura 17.7. Diálogo creado con showConfirmDialog

Figura 17.8. Diálogo creado con showInputDialog

JJOOppttiioonnPPaanneeDDeemmoo..jjaavvaa

Page 10: Desarrollo de aplicaciones visuales - sing-group.orglipido/dojos/swing/swing.pdf · o general, como lo es Java, disponen de librerías para la creación de interfaces gráficas de

Figura 17.9. Diálogo creado con showOptionDialog

Todos los métodos lanzan un diálogo modal, el cual bloquea el hilo que llama a

dicho método y que no continúa hasta que el usuario introduce una opción. En ese

momento se obtiene el valor de retorno que contiene lo que el usuario ha indicado

(excepto en el caso de SHOWMESSAGEDIALOG, en el que no retorna nada). Para

SHOWCONFIRMDIALOG y SHOWOPTIONDIALOG existen definidas unas constantes que

representan los distintos valores, como por ejemplo, NO_OPTION, YES_OPTION,

CANCEL_OPTION, etc que se pueden usar como sigue:

int opcion = JOptionPane.showConfirmDialog(frame,"Deseas formatear tu disco duro?", "Pregunta", JOptionPane.YES_NO_OPTION); if (opcion == JOptionPane.YES_OPTION){ discoDuro.formatear(); }

17.4.1.2.2 Trabajo con JFileChooser

La clase JFILECHOOSER proporciona un diálogo para elegir un fichero de una lista. Un

selector de ficheros es un componente que podemos situar en cualquier lugar de la

interfaz gráfica de nuestro programa. Sin embargo, normalmente los programas los

muestran en diálogos modales porque las operaciones con ficheros son sensibles a

los cambios dentro del programa. La clase JFILECHOOSER permite mostrar fácilmente

un diálogo modal que contiene un selector de ficheros.

Los selectores de ficheros se utilizan comúnmente para dos propósitos:

• Para presentar una lista de ficheros que pueden ser abiertos por la

aplicación.

• Para permitir que el usuario seleccione o introduzca el nombre de un

fichero a guardar.

Es preciso destacar que el selector de ficheros ni abre ni guarda ficheros.

Simplemente presenta un diálogo para elegir un fichero de una lista. El programa

es responsable de hacer las operaciones que estime oportunas con el fichero, como

abrirlo o guardarlo.

Como la mayoría de los programas necesitan un selector para abrir o guardar

ficheros, la clase JFILECHOOSER proporciona los métodos convenientes para mostrar

estos tipos de selectores de ficheros en un diálogo. La Figura 17.10 muestra el

aspecto de un diálogo para escoger un fichero para abrirlo.

Figura 17.10. Diálogo para guardar un fichero con JFileChooser

El código que crea y muestra el selector de apertura de ficheros es el siguiente

JFileChooser filechooser = new JFileChooser(); int returnVal = filechooser.showOpenDialog(frame); //frame es una instancia de JFrame

Por defecto, un selector de ficheros que no haya sido mostrado anteriormente

muestra todos los ficheros en el directorio del usuario. Se puede especificar un

directorio inicial utilizando uno de los otros constructores de JFILECHOOSER, o se

puede seleccionar el directorio directamente con el método SETCURRENTDIRECTORY.

El siguiente fragmento de código ilustra cómo se obtendría un diálogo para escoger

un fichero para guardar, además de cómo obtener el fichero escogido por el usuario

para hacer el tratamiento que se considere oportuno.

private JFileChooser filechooser = new JFileChooser(); ... int returnVal = filechooser.showSaveDialog(FileChooserDemo.this); if (returnVal == JFileChooser.APPROVE_OPTION) { File file = filechooser.getSelectedFile(); System.out.println(“Guardando el fichero: “+ file.getName()); guardarEn(file); // metodo propio que guarda cierta información en el fichero escogido }

Page 11: Desarrollo de aplicaciones visuales - sing-group.orglipido/dojos/swing/swing.pdf · o general, como lo es Java, disponen de librerías para la creación de interfaces gráficas de

Suele ser interesante tener una única instancia de JFILECHOOSER (por eso en el

ejemplo se muestra como un atributo de una clase) ya que utilizando el mismo

selector de ficheros para abrir y grabar ficheros, el programa consigue los

siguientes beneficios:

• El selector recuerda el directorio actual entre usos, por eso los diálogos de

abrir y grabar comparten el mismo directorio actual.

• Sólo se tiene que personalizar un selector de ficheros, y dicha

personalización se aplicará a las dos versiones, la de apertura y la de

guardado.

• La apertura del diálogo a partir de la segunda vez es más rápida si no se

instancia cada vez.

Cómo se ha visto en los fragmentos de código anteriores, los métodos

SHOWXXXXDIALOG devuelven un entero que indica si el usuario ha seleccionado un

fichero. Podemos utilizar el valor de retorno para determinar si realizar o no la

operación requerida.

Mediante el método GETSELECTEDFILE sobre el selector de ficheros se puede obtener

un ejemplar de FILE, que representa el fichero elegido. El ejemplo obtiene el

nombre del fichero y lo utiliza en un mensaje. Podemos utilizar otros métodos del

objeto FILE para obtener información sobre él.

Una personalización muy común de este tipo de diálogos es el filtrado de ficheros a

escoger. De forma básica se podría indicar si se desea presentar al usuario

únicamente directorios, únicamente ficheros o ambos. Para ello se utiliza el método

SETFILESELECTIONMODE que recibe un parámetro cuyos valores pueden ser:

FILES_ONLY para sólo ficheros, DIRECTORIES_ONLY, para sólo directorios y

FILES_AND_DIRECTORIES, para ambos. Sin embargo, también es frecuente que sea

necesario hacer un filtrado más avanzado a nivel de ficheros, por ejemplo para

aceptar únicamente ficheros con una extensión determinada. Para ello se debe

heredar de la clase FILEFILTER la cual representa un filtro de ficheros con únicamente

dos métodos a implementar:

• BOLEAN ACCEPT(FILE). Debe devolver cierto si el fichero pasado como

parámetro puede ser escogido por el usuario o falso si ese fichero debe ser

filtrado y no se debe mostrar.

• STRING GETDESCRIPTION(). Debe devolver una cadena explicativa del filtro, p.

ej: “Ficheros de Imágenes”.

JFILECHOOSER acepta varios filtros, por lo que se irán añadiendo con

ADDCHOOSABLEFILEFILTER. El siguiente fragmento de código muestra un ejemplo de la

implementación de una subclase de FILEFILTER y de su uso:

class ImageFilter extends FileFilter{ public boolean accept(File f) { if (f.isDirectory()) { return true; } String s = f.getName(); int i = s.lastIndexOf('.'); if (i > 0 && i < s.length() - 1) { String extension = s.substring(i+1).toLowerCase(); if (“tiff”.equals(extension) || “tif”.equals(extension) || “gif”.equals(extension) || “jpeg”.equals(extension) || “jpg”.equals(extension)) { return true; // la extension es de un fichero de imagen } else { return false; // la extension que tiene no es de imagen } return false; // no tiene extension } public String getDescription(){ return “Ficheros de Imagenes”; } } ... // Utilizar el filtro para el JFileChooser filechooser.addChoosableFileFilter(new ImageFilter());

Existen personalizaciones avanzadas, menos comunes, de JFILECHOOSER que

permiten incluir componentes dentro del propio diálogo para por ejemplo incluir

vistas previas de los ficheros que el usuario va escogiendo.

17.4.2 Contenedores intermedios

Los contenedores intermedios son componentes Swing que sólo existen para

contener otros componentes. Técnicamente, las barras de menús pertenecen a esta

categoría, pero se describen en un apartado propio.

JJFFiilleeCChhoooosseerrDDeemmoo..jjaavvaa

Page 12: Desarrollo de aplicaciones visuales - sing-group.orglipido/dojos/swing/swing.pdf · o general, como lo es Java, disponen de librerías para la creación de interfaces gráficas de

Los contenedores intermedios más utilizados son los paneles. Implementados con

la clase JPANEL, los paneles no añaden casi ninguna funcionalidad más allá de las

que tienen los objetos JCOMPONENT. Normalmente se usan para agrupar

componentes, porque los componentes están relacionados o sólo porque agruparlos

hace que la distribución sea más sencilla. Un panel puede usar cualquier

controlador de distribución, y se les puede dotar de bordes fácilmente.

Otros cuatro contenedores Swing proporcionan más funcionalidad. Un JSCROLLPANE

proporciona barras de desplazamiento alrededor de un sólo componente. Un

JSPLITPANE, o panel dividido, es un componente que a su vez permite anidar a dos

componentes separados por una barra vertical u horizontal móvil. Un JTABBEDPANE,

o panel de solapas, muestra sólo un componente a la vez, permitiendo fácilmente

cambiar entre componentes. Un JTOOLBAR, o barra de herramientas, contiene un

grupo de componentes (normalmente botones) en una fila o columna, y

opcionalmente permite al usuario arrastrar la barra de herramientas a diferentes

localizaciones.

El resto de los contenedores intermedios Swing son incluso más especializados.

JINTERNALFRAME, o frame interno, se parece a los JFRAME y tienen un API muy

similar, pero al contrario que los aquéllos deben aparecer dentro de otras ventanas.

JROOTPANE proporciona soporte avanzado a los contenedores de alto nivel.

JLAYEREDPANE permite soportar ordenación en el eje Z de componentes.

17.4.2.1.1 Trabajo con JScrollPane

Un JSCROLLPANE proporciona una vista desplazable de un componente ligero.

Cuando el estado de la pantalla real está limitado, se utiliza un SCROLLPANE para

mostrar un componente que es grande o cuyo tamaño puede cambiar

dinámicamente.

El código para crear un JSCROLLPANE puede ser mínimo. La Figura 17.11 muestra un

programa que utiliza un JSCROLLPANE para ver una salida de texto (JTEXTAREA).

Figura 17.11. Un JScrollPane moviendo un JTextArea

El siguiente fragmento de código muestra cómo se obtendría el programa de la

Figura 17.11.

JTextArea textArea = new JTextArea(5,30); JScrollPane scrollPane = new JScrollPane(textArea); this.add(scrollPane, BorderLayout.CENTER); //this es un JFrame

El programa proporciona el área de texto como argumento al constructor del

JSCROLLPANE estableciendo dicha área de texto como el cliente del panel desplazable

que se encargará de: crear las barras de desplazamiento cuando son necesarias,

redibujar el cliente cuando el usuario se mueve sobre él, etc.

Típicamente se utiliza JSCROLLPANE para el desplazamiento de áreas de texto

(JTEXTAREA), imágenes (IMAGEICON), listas (JLIST), tablas (JTABLE), árboles (JTREE), etc.

Si lo que se necesita es proporcionar desplazamiento básico para un componente

ligero como estos, no se necesitan más conceptos que los vistos hasta ahora. Sin

embargo, un JSCROLLPANE es un objeto altamente personalizable. Se puede

determinar bajo qué circunstancias se mostrarán las barras de desplazamiento.

También se puede decorar con una fila de cabecera, una columna de cabecera y

esquinas. Finalmente se puede crear un cliente de desplazamiento que avisa al

panel desplazable sobre el comportamiento de desplazamiento como los

incrementos de unidad y de bloques.

17.4.2.1.2 Trabajo con JSplitPane

Un JSPLITPANE contiene dos componentes de peso ligero, separados por un divisor.

Arrastrando el divisor, el usuario puede especificar qué cantidad de área pertenece

a cada componente. Un JSPLITPANE se utiliza cuando dos componentes contienen

información relacionada y queremos que el usuario pueda cambiar el tamaño de los

JJSSccrroollllPPaanneeDDeemmoo..jjaavvaa

Page 13: Desarrollo de aplicaciones visuales - sing-group.orglipido/dojos/swing/swing.pdf · o general, como lo es Java, disponen de librerías para la creación de interfaces gráficas de

componentes en relación a uno o a otro. Un uso común de un JSPLITPANE es para

contener una lista para seleccionar un elemento y además una visión de la elección

actual, como por ejemplo sería un programa de correo que muestra una lista con

los mensajes y el contenido del mensaje actualmente seleccionado de la lista.

La Figura 17.12 muestra un programa que utiliza JSPLITPANE.

Figura 17.12. Ejemplo de uso de JSplitPane

El siguiente fragmento de código muestra cómo se trata con JSPLITPANE para crear el

programa de la Figura 17.12.

... // crear una lista desplazable llamada listScrollPane // crear una imagen desplazable llamada listScrollPane ... JSplitPane splitPane = new JSplitPane(JsplitPane.HORIZONTAL_SPLIT); splitPane.setLeftComponent(listScrollPane); splitPane.setRightComponent(pictureScrollPane); splitPane.setOneTouchExpandable(true); // tener botones para ampliar y reducir totalmente // Dar el tamaño mínimo de los components del JSplitPane Dimension minimumSize = new Dimension(100, 50); listScrollPane.setMinimumSize(minimumSize); pictureScrollPane.setMinimumSize(minimumSize); // Dar la posicion inicial y tamaño de la barra divisoria splitPane.setDividerLocation(150); splitPane.setDividerSize(10);

El constructor utiliza SETLEFTCOMPONENT y SETRIGHTCOMPONENT para situar la lista y la

etiqueta de imagen en el JSPLITPANE. Si el JSPLITPANE tuviera orientación vertical, se

podría utilizar SETTOPCOMPONENT y SETBOTTOMCOMPONENT en su lugar.

JSPLITPANE utiliza el tamaño mínimo de sus componentes para determinar hasta

dónde puede el usuario mover el divisor. Un JSPLITPANE no permite que el usuario

haga un componente más pequeño que su tamaño mínimo moviendo el divisor. Sin

JJSSpplliittPPaanneeDDeemmoo..jjaavvaa

embargo se pueden utilizar los “botones explandibles” para ocultar un componente,

si se han activado previamente con el método SETONETOUCHEXPANDABLE.

También se puede controlar con programación la posición de la barra divisoria con

los métodos SETDIVIDERLOCATION. El método SETRESIZEWEIGHT permite indicar qué

proporción de tamaño extra se asigna a cada parte cuando se amplia el tamaño

disponible para el JSPLITPANE.

17.4.2.1.3 Trabajo con JTabbedPane

Con la clase JTABBEDPANE, o panel de solapas, se pueden colocar varios

componentes (normalmente objetos JPANEL) compartiendo el mismo espacio. El

usuario puede elegir qué componente ver seleccionando la pestaña del componente

deseado.

Para crear un panel de solapas, simplemente se instancia un JTABBEDPANE, se crean

los componentes que se desean mostrar, y finalmente se añaden al JTABBEDPANE

utilizando el método ADDTAB.

La Figura 17.13 muestra una aplicación que usa JTABBEDPANE.

Figura 17.13. Ejemplo de uso de JTabbedPane

El fragmento de código necesario para crear la aplicación de la Figura 17.13 es el

siguiente.

JJTTaabbbbeeddPPaanneeDDeemmoo..jjaavvaa

Page 14: Desarrollo de aplicaciones visuales - sing-group.orglipido/dojos/swing/swing.pdf · o general, como lo es Java, disponen de librerías para la creación de interfaces gráficas de

ImageIcon icon = new ImageIcon(getClass().getResource("/icons/load.png")); JTabbedPane tabbedPane = new JTabbedPane(); Component text1 = new JTextArea("Contenido de la Solapa 1"); JScrollPane scroll1 = new JScrollPane(text1); tabbedPane.addTab("Uno", icon, scroll1, "No hace nada"); Component text2 = new JTextArea("Contenido de la Solapa 2"); JScrollPane scroll2 = new JScrollPane(text2); tabbedPane.addTab("Dos", icon, scroll2, "Tampoco hace nada"); tabbedPane.setSelectedIndex(0);

17.4.2.1.4 Trabajo con JToolBar

Un objeto JTOOLBAR crea una barra de herramientas para colocar, normalmente

botones, aunque se pueden colocar cualquier tipo de componentes dispuestos en

una fila o una columna. Normalmente las barras de herramientas proporcionan

acceso a funcionalidades que también se encuentran en ítems de menús, siendo

interesante unificar dichas funcionalidades con la API Action, tratada

anteriormente.

La Figura 17.14 muestra una aplicación que contiene una barra de herramientas

sobre un área de texto.

Figura 17.14. Ejemplo de uso de JToolBar

Por defecto, el usuario puede arrastrar la barra de herramientas a un lateral distinto

de su contenedor o fuera de su propia ventana. La Figura 17.15 muestra cómo

aparece la aplicación después de que el usuario haya arrastrado la barra de

herramientas al lateral derecho de su contenedor y fuera de la ventana. Para hacer

que el arrastre de la barra de herramientas funcione correctamente, la barra debe

estar en un contenedor que use BORDERLAYOUT (ver apartado de controladores de

distribución), y el contenedor sólo debe tener otro componente que esté situado en

el centro.

Figura 17.15. Diferentes ubicaciones de JToolBar

El siguiente código implementa la barra de herramientas.

public JToolBarDemo() { ... JToolBar toolBar = new JToolBar(); addButtons(toolBar); JPanel contentPane = new JPanel(); contentPane.setLayout(new BorderLayout()); contentPane.add(toolBar, BorderLayout.NORTH); ... } protected void addButtons(JToolBar toolBar) { JButton button = null; // primer boton button = new JButton(new ImageIcon("icons/left.png ")); ... toolBar.add(button); // segundo boton button = new JButton(new ImageIcon("icons/center.png")); ... toolBar.add(button); // tercer boton button = new JButton(new ImageIcon("icons/right.png")); ... toolBar.add(button); }

Existen además otros métodos interesantes que proporciona JTOOLBAR. El método

SETFLOATABLE permite establecer si la barra se puede o no desplazar a otras

JJTToooollBBaarrDDeemmoo..jjaavvaa

Page 15: Desarrollo de aplicaciones visuales - sing-group.orglipido/dojos/swing/swing.pdf · o general, como lo es Java, disponen de librerías para la creación de interfaces gráficas de

ubicaciones. El método ADDSEPARATOR añade un hueco entre el último componente

añadido y los sucesivos que se vayan añadiendo.

Como ya se ha comentado, JTOOLBAR permite la colocación de todo tipo de

componentes tal y como se muestra en la Figura 17.16. Es preciso destacar que

para que los componentes queden alineados verticalmente al centro, se debe

utilizar el método SETALIGNMENTY de cada componente.

Figura 17.16. Diferentes componentes en una JToolBar

El siguiente fragmento de código muestra cómo se han ido añadiendo los

componentes hasta obtener la Figura 17.16.

button1.setAlignmentY(CENTER_ALIGNMENT); button2.setAlignmentY(CENTER_ALIGNMENT); button3.setAlignmentY(CENTER_ALIGNMENT); button4 = new JButton("Another button"); button4.setAlignmentY(CENTER_ALIGNMENT); toolBar.add(button1); toolBar.add(button2); toolBar.add(button3); toolBar.addSeparator(); toolBar.add(button4); //fifth component is NOT a button! JTextField textField = new JTextField("A text field");... textField.setAlignmentY(CENTER_ALIGNMENT); toolBar.add(textField);

17.4.2.1.5 Trabajo con JInternalFrame y JDesktopFrame

Con la clase JINTERNALFRAME, se puede mostrar un frame como una ventana dentro

de otra ventana. Para crear un frame interno que parezca un diálogo sencillo, se

pueden utilizar los métodos SHOWINTERNALXXXDIALOG de JOPTIONPANE, como se

JJTToooollBBaarrDDeemmoo22..jjaavvaa

explicó anteriormente. Con JINTERNALFRAME se pueden crear las denominadas

aplicaciones MDI (Multiple Document Interface).

Normalmente, los frames internos se muestran dentro de un JDESKTOPPANE que es

una subclase de JLAYEREDPANE al que se le ha añadido el API para manejar el

solapamiento de múltiples frames internos.

La Figura 17.17 muestra una aplicación que tiene dos frames internos dentro de un

frame normal.

Figura 17.17. Dos JInternalFrame dentro de un JFrame

El siguiente fragmento muestra cómo se trabaja con JINTERNALFRAME para conseguir

la aplicación de la Figura 17.17.

class FrameInterno extends JInternalFrame{ static int openFrameCount = 0; static final int xOffset = 30, yOffset = 30; public FrameInterno() { super("Internal Frame #" + (++openFrameCount), true, //resizable true, //closable true, //maximizable true);//iconifiable // Introducir componentes en el internal frame como si fuera un frame normal...

JJIInntteerrnnaallFFrraammeeDDeemmoo..jjaavvaa

Page 16: Desarrollo de aplicaciones visuales - sing-group.orglipido/dojos/swing/swing.pdf · o general, como lo es Java, disponen de librerías para la creación de interfaces gráficas de

// Establecer la posicion del frame setLocation(xOffset*openFrameCount, yOffset*openFrameCount); setSize(200,200); } } class JInternalFrameDemo extends JFrame{ JDesktopPane desktop; public JInternalFrameDemo(){ super("JIntenalFrameDemo"); desktop = new JDesktopPane(); //a specialized layered pane createFrame(); // Primera ventana createFrame(); // Otra mas setContentPane(desktop); desktop.setPreferredSize(new Dimension(300,300)); } protected void createFrame() { FrameInterno frame = new FrameInterno(); frame.setVisible(true); desktop.add(frame); try { frame.setSelected(true); } catch (java.beans.PropertyVetoException e2) {} } ... }

17.4.3 Componentes ligeros

17.4.3.1 JLabel

JLABEL permite mostrar texto no seleccionable e imágenes. Es útil cuando se

necesita crear un componente que muestre un texto sencillo o una imagen. La

Figura 17.18 muestra el aspecto que tendrían tres etiquetas, la primera con texto e

icono, la segunda con sólo texto y una tercera que sólo es el icono.

Figura 17.18. Ejemplo de JLabel

El siguiente fragmento de código muestra cómo se podría obtener la aplicación de

la Figura 17.18.

ImageIcon icon = new ImageIcon("icons/load.png"); JLabel label1 = new JLabel("Etiqueta con texto e icono", icon, JLabel.CENTER); //Posicion del texto label1.setVerticalTextPosition(JLabel.BOTTOM);

JJLLaabbeellDDeemmoo..jjaavvaa

label1.setHorizontalTextPosition(JLabel.CENTER); JLabel label2 = new JLabel("Etiqueta con solo texto"); JLabel label3 = new JLabel(icon); JPanel panel = new JPanel(); //Añadir las etiquetas al JPanel. panel.add(label1); panel.add(label2); panel.add(label3);

17.4.3.2 JButton

Para crear un botón en Swing, se debe instanciar una de las muchas subclases de la

clase ABSTRACTBUTTON. Esta sección explica el API básico que define la clase

ABSTRACTBUTTON y lo que todos los botones Swing tienen en común. Como la clase

JBUTTON desciende de ABSTRACTBUTTON este apartado muestra a nivel general cómo

funcionan los botones.

La Tabla 17.5 muestra las subclases de ABSTRACTBUTTON definidas en Swing.

Tabla 17.5. Tipos de AbstractButton

Clase Descripción JBUTTON Un botón común. JCHECKBOX Un check box. JRADIOBUTTON Un bóton de radio, normalmente, dentro de un grupo: RADIOGROUP. JMENUITEM Un ítem de menú. JTOGGLEBUTTON Un botón común, pero con estado de selección.

La Figura 17.19 muestra el aspecto de una aplicación con tres botones. Se incluyen

iconos en los botones con el texto colocado en sitios diferentes.

Figura 17.19. Ejemplo de JButton

El siguiente fragmento de código muestra cómo se obtiene la aplicación de la

Figura 17.19.

ImageIcon leftButtonIcon = new ImageIcon("icons/load.png");

JJBBuuttttoonnDDeemmoo..jjaavvaa

Page 17: Desarrollo de aplicaciones visuales - sing-group.orglipido/dojos/swing/swing.pdf · o general, como lo es Java, disponen de librerías para la creación de interfaces gráficas de

ImageIcon middleButtonIcon = new ImageIcon("icons/apply_small.png"); ImageIcon LEFTButtonIcon = new ImageIcon("icons/next.png"); final JButton b1 = new JButton("Desactivar boton del medio", leftButtonIcon); b1.setVerticalTextPosition(AbstractButton.CENTER); b1.setHorizontalTextPosition(AbstractButton.LEFT); b1.setMnemonic('d'); final JButton b2 = new JButton("Boton del medio", middleButtonIcon); b2.setVerticalTextPosition(AbstractButton.BOTTOM); b2.setHorizontalTextPosition(AbstractButton.CENTER); b2.setMnemonic('m'); final JButton b3 = new JButton("Habilitar el boton del medio", LEFTButtonIcon); //Posicion por defecto CENTER, LEFT. b3.setMnemonic('e'); b3.setEnabled(false); //Listen for actions on buttons 1 and 3. b1.addActionListener(new ActionListener(){ public void actionPerformed(java.awt.event.ActionEvent e) { b2.setEnabled(false); b1.setEnabled(false); b3.setEnabled(true); } }); b3.addActionListener(new ActionListener(){ public void actionPerformed(java.awt.event.ActionEvent e) { b2.setEnabled(true); b1.setEnabled(true); b3.setEnabled(false); } });

El modo de implementar el manejo de eventos depende del tipo de botón utilizado

y de cómo se utiliza. Generalmente, se implementa un ACTIONLISTENER, que es

notificado cada vez que el usuario pulsa el botón, Para un JCHECKBOX normalmente

se utiliza un ITEMLISTENER, que es notificado cuando el JCHECKBOX es seleccionado o

deseleccionado.

17.4.3.3 JCheckBox

JCHECKBOX permite la creación de botones tipo check box. Swing también soporta

check boxes en menús, utilizando la clase JCHECKBOXMENUITEM (se ve en el apartado

de menús). Como JCHECKBOX y JCHECKBOXMENUITEM descienden de ABSTRACTBUTTON,

los check boxes tienen todas las características de un botón normal como se

explicó en apartado anterior. Por ejemplo, podemos especificar imágenes para ser

utilizadas en los checkboxes.

Además tienen un estado de selección que cambia cada vez que el usuario pulsa el

botón. Mediante programación se puede seleccionar con SETSELECTED y consultar

cuando se desee con ISSELECTED.

Los check boxes son similares a los botones de radio (se ve en apartado siguiente),

pero su modelo de selección es diferente, por convención. Cualquier número de

check boxes en un grupo (BUTTONGROUP) permite que ninguno, alguno o todos

puedan ser seleccionados. Por otro lado, en un grupo de botones de radio, sólo

puede haber uno seleccionado.

La Figura 17.20 muestra una aplicación que utiliza JCHECKBOX para simular un

pequeño carrito de compra. Ilustra cómo se pueden capturar el evento de selección

de los check boxes, además de acceder al estado de los mismos para informar al

usuario de qué ítems tiene seleccionados.

Figura 17.20. Ejemplo de JCheckBox

El siguiente fragmento de código muestra cómo se puede obtener la aplicación de

Figura 17.20.

patatasCheck = new JCheckBox("Patatas"); patatasCheck.setMnemonic('P'); patatasCheck.setSelected(true); lechugaCheck = new JCheckBox("Lechuga"); lechugaCheck.setMnemonic('L'); lechugaCheck.setSelected(false); tomatesCheck = new JCheckBox("Tomates"); tomatesCheck.setMnemonic('T'); tomatesCheck.setSelected(false); MiListener listener = new MiListener(); patatasCheck.addItemListener(listener); lechugaCheck.addItemListener(listener); tomatesCheck.addItemListener(listener); ... class MiListener implements ItemListener { public void itemStateChanged(ItemEvent e){ String mensaje = "";

JJCChheecckkBBooxxDDeemmoo..jjaavvaa

Page 18: Desarrollo de aplicaciones visuales - sing-group.orglipido/dojos/swing/swing.pdf · o general, como lo es Java, disponen de librerías para la creación de interfaces gráficas de

if (e.getStateChange() == e.SELECTED){ mensaje = "Has añadido: " + ((JCheckBox)e.getItemSelectable()).getText(); }else{ mensaje = "Has eliminado: " + ((JCheckBox)e.getItemSelectable()).getText(); } mensaje +="\n\nCesta actual: "; if (patatasCheck.isSelected()){ mensaje+="\n"+patatasCheck.getText(); } if (lechugaCheck.isSelected()){ mensaje+="\n"+lechugaCheck.getText(); } if (tomatesCheck.isSelected()){ mensaje+="\n"+tomatesCheck.getText(); } JOptionPane.showMessageDialog(JCheckBoxDemo.this, mensaje); //JCheckBoxDemo.this es una referencia al JFrame } };

17.4.3.4 JRadioButton

Los botones de radio suelen aparecer en un grupo que, por convención, sólo uno de

ellos puede estar seleccionado. Swing soporta botones de radio con las clases

JRADIOBUTTON y BUTTONGROUP. Para poner un botón de radio en un menú, se utiliza la

clase JRADIOBUTTONMENUITEM. Otras formas de presentar una entre varias opciones

son los combo boxes y las listas. Los botones de radio tienen un aspecto similar a

los check boxes, pero, por convención, los check boxes no tienen límites sobre

cuantos ítems pueden estar seleccionados a la vez.

BUTTONGROUP es quien se encarga de que cuando un botón de radio se selecciona,

automáticamente se deseleccionen los restantes del mismo grupo.

Como JRADIOBUTTON desciende de ABSTRACTBUTTON, los botones de radio Swing

tienen todas las características de los botones normales vistos anteriormente. Por

ejemplo, se puede especificar la imagen mostrada por un botón de radio.

La Figura 17.21 muestra una aplicación que utiliza JRADIOBUTTON dentro de un

BUTTONGROUP.

Figura 17.21. Ejemplo de JRadioButton

El siguiente fragmento de código muestra cómo se puede obtener la aplicación de

la Figura 17.21. En el caso de los botones de radio el oyente más utilizado es el

ACTIONLISTENER, igual que para los botones normales (JBUTTON).

patatasRadio = new JRadioButton("Patatas"); patatasRadio.setMnemonic('P'); patatasRadio.setSelected(true); lechugaRadio = new JRadioButton("Lechuga"); lechugaRadio.setMnemonic('L'); lechugaRadio.setSelected(true); tomatesRadio = new JRadioButton("Tomates"); tomatesRadio.setMnemonic('T'); tomatesRadio.setSelected(true); //Meterlos en un grupo que permite la seleccion unica ButtonGroup grupo = new ButtonGroup(); grupo.add(patatasRadio); grupo.add(lechugaRadio); grupo.add(tomatesRadio); MiListener listener = new MiListener(); patatasRadio.addActionListener(listener); lechugaRadio.addActionListener(listener); tomatesRadio.addActionListener(listener); ... class MiListener implements ActionListener { public void actionPerformed(ActionEvent e){ JOptionPane.showMessageDialog(JRadioButtonDemo.this, "Has seleccionado "+((JRadioButton)e.getSource()).getText()); //JRadioButtonDemo.this es una referencia al JFrame } };

JJRRaaddiiooBBuuttttoonnDDeemmoo..jjaavvaa

Page 19: Desarrollo de aplicaciones visuales - sing-group.orglipido/dojos/swing/swing.pdf · o general, como lo es Java, disponen de librerías para la creación de interfaces gráficas de

17.4.3.5 JComboBox

Un JCOMBOBOX permite crear una lista desplegable y un campo de texto. El usuario

puede teclear un valor o elegirlo desde una lista. Un JCOMBOBOX editable ahorra

tiempo de entrada proporcionando atajos para los valores más comúnmente

introducidos. Un JCOMBOBOX no editable desactiva el tecleo pero aún así permite al

usuario seleccionar un valor desde una lista. Esto proporciona un espacio

alternativo a un grupo de botones de radio o una lista.

A un JCOMBOBOX se le puede añadir cualquier objeto, mediante ADDITEM, que será

mostrado en forma de cadena de texto obtenida a partir de su método TOSTRING. Esto

es muy útil para tener referenciados en el propio combo objetos más complejos del

dominio de la aplicación que se esté manejando, de forma transparente. El objeto

seleccionado en cada momento en el combo se obtiene con el método

GETSELECTEDITEM.

El modo de capturar el evento en un JCOMBOBOX suele ser con el oyente

ACTIONLISTENER de forma que cada vez que el usuario modifica el elemento

seleccionado del combo se produce el evento. También se podrían capturar con

ITEMLISTENER de forma que se lanza el evento cuando un elemento del combo se

selecciona o ha dejado de ser seleccionado, por lo que cuando el usuario cambia el

elemento seleccionado se producen dos eventos: uno por el que se selecciona y

otro por el que deja de estar seleccionado.

La Figura 17.22 muestra una aplicación que utiliza JCOMBOBOX. Cada vez que se

modifica la selección del combo, se muestra un mensaje con el texto del elemento

que se acaba de seleccionar.

Figura 17.22. Ejemplo de JComboBox

El siguiente fragmento de código muestra cómo se podría obtener la aplicación de

la Figura 17.22.

JComboBox box = new JComboBox(); box.addItem("Patatas"); box.addItem("Lechuga"); box.addItem("Tomates"); box.setEditable(true); box.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent e){ JComboBox box = (JComboBox) e.getSource(); JOptionPane.showMessageDialog(JComboBoxDemo.this, "Has seleccionado "+box.getSelectedItem()); } });

17.4.3.6 JList

Un JLIST le presenta al usuario un grupo de ítems para elegir. Los ítems pueden ser

cualquier OBJECT. Una lista puede tener muchos ítems, o podría crecer hasta

tenerlos, por eso suelen situarse dentro de paneles desplazables (JSCROLLPANE).

Para crear un JLIST se puede utilizar uno de sus constructores que reciben como

parámetro los elementos de la lista, bien mediante un VECTOR, un array de OBJECT o

un LISTMODEL. Sea cual sea el constructor que se use, siempre se crea un LISTMODEL

para la lista, que siempre podrá ser accedido mediante el método GETMODEL. Para

obtener un elemento concreto de la lista se usa LISTMODEL. Si además se desea

modificar la lista una vez creada, se puede usar como modelo DEFAULTLISTMODEL

para construir la lista y luego añadir o eliminar elementos sobre el modelo.

Lo habitual es acceder a los elementos seleccionados de la lista. Para ello se

proporcionan los métodos GETSELECTEDINDEX o GETSELECTEDINDICES para obtener la o

las posiciones de los elementos seleccionados actualmente; y GETSELECTEDVALUE y

GETSELECTEDVALUES para obtener el o los elementos seleccionados.

El usuario puede seleccionar elementos de la lista. Para ello existen varios modos

de selección configurables mediante el método SETSELECTIONMODE. Las opciones

son:

Modo Descripción SINGLE_SELECTION Sólo un ítem de la lista puede ser seleccionado. Cuando el

usuario selecciona un ítem, cualquier ítem anteriormente seleccionado se deselecciona primero.

JJCCoommbbooBBooxxDDeemmoo..jjaavvaa

Page 20: Desarrollo de aplicaciones visuales - sing-group.orglipido/dojos/swing/swing.pdf · o general, como lo es Java, disponen de librerías para la creación de interfaces gráficas de

SINGLE_INTERVAL_SELECTION Se puede seleccionar varios ítems contiguos. Cuando el usuario empieza una nueva selección, cualquier ítem anteriormente seleccionado se deselecciona primero.

MULTIPLE_INTERVAL_SELECTION El valor por defecto. Se pueden seleccionar cualquier combinación de ítems. El usuario debe deseleccionar explícitamente los ítems.

Es posible capturar el evento del cambio de selección mediante la implementación

de un oyente LISTSELECTIONLISTENER.

La Figura 17.23 muestra una aplicación que utiliza una JLIST modificable.

Figura 17.23. Ejemplo de JList

El siguiente fragmento de código muestra cómo se puede obtener la aplicación de

la Figura 17.23.

// Se utiliza DefaultListModel porque se quiere modificar despues la lista DefaultListModel model = new DefaultListModel(); model.addElement("Item 1"); model.addElement("Item 2"); model.addElement("Item 3"); // Creacion de la lista final JList lista = new JList(model); lista.setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION); // Boton de añadir JButton botonAñadir = new JButton("Añadir uno mas"); botonAñadir.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent e){ DefaultListModel model = (DefaultListModel) lista.getModel(); model.addElement("Otro mas"); } }); //Etiquetas para mostrar datos

JJLLiissttDDeemmoo..jjaavvaa

final JLabel cantidadSeleccionada = new JLabel("Tienes seleccionados 0 elementos"); final JLabel unElemento = new JLabel(" "); // Capturar eventos cuando se modifica la seleccion en la lista lista.addListSelectionListener(new ListSelectionListener(){ public void valueChanged(ListSelectionEvent e){ if (e.getValueIsAdjusting()){ return; }else{ // Actualizar las etiquetas cantidadSeleccionada.setText("Tienes seleccionados "+lista.getSelectedIndices().length+" elementos"); if (lista.getSelectedIndices().length==1){ unElemento.setText("El elemento seleccionado es: "+lista.getSelectedValue().toString()); }else{ unElemento.setText(" "); } } } });

17.4.3.7 Construcción de Menús

Un menú proporciona una forma de ahorrar espacio y permitir al usuario elegir una

entre varias opciones. Otros componentes con los que el usuario puede hacer una

elección incluyen combo boxes, listas, botones de radio y barras de herramientas.

Si alguno de los ítems de un menú realiza una acción que está duplicada en otro

ítem de menú o en un botón de una barra de herramientas, sería interesante utilizar

la API Action que se trata más adelante.

Los menús son únicos en que, por convención, no se sitúan con los otros

componentes en el UI. En su lugar, aparecen en una barra de menú o en un menú

contextual. Una barra de menú contiene uno o más menús, y tiene una posición

dependiente de la plataforma (normalmente debajo de la parte superior de la

ventana). Un menú contextual es un menú que es invisible hasta que el usuario

realiza una acción del ratón específica de la plataforma, como pulsar el botón

derecho del ratón sobre un componente. Entonces el menú contextual aparece bajo

el cursor.

Las clases que permiten la creación de menús son:

• JMENUBAR. Se utiliza para que los menús aparezcan en una barra de menús

en un JFRAME.

• JMENU. Agrupa a un conjunto de ítems de menú. Se utiliza tanto para los

botones directos en la barra de menús, como para los menús internos que

contengan submenús.

Page 21: Desarrollo de aplicaciones visuales - sing-group.orglipido/dojos/swing/swing.pdf · o general, como lo es Java, disponen de librerías para la creación de interfaces gráficas de

• JMENUITEM, JCHECKBOXMENUITEM, JRADIOBUTTONMENUITEM. Son los elementos

hoja de los menús.

• JPOPUPMENU. Se utiliza para crear menús contextuales. Al igual que JMENU

se le añaden recursivamente JMENUITEM y JMENU, sin embargo proporciona

el método SHOW que lo muestra en una posición cualquiera del JFRAME.

La Figura 17.24 muestra los componentes Swing que implementan cada parte de

los menús de una aplicación.

Figura 17.24. Ejemplo de menús

El siguiente fragmento de código muestra cómo se puede obtener la aplicación de

la Figura 17.24.

JMenuBar menuBar = new JMenuBar(); JMenu menu = new JMenu("Un Menu"); menuBar.add(menu); JMenuItem menuItem = new JMenuItem("Un Item con Solo Texto"); menu.add(menuItem); menuItem = new JMenuItem("Both text and icon", new ImageIcon(JFrame.class.getResource("/icons/load.png"))); menu.add(menuItem); menuItem = new JMenuItem(new ImageIcon(JFrame.class.getResource("/icons/load.png"))); menu.add(menuItem); //un grupo de radio buttons

JJMMeennuuDDeemmoo..jjaavvaa

menu.addSeparator(); ButtonGroup group = new ButtonGroup(); JMenuItem rbMenuItem = new JRadioButtonMenuItem("Un Item con radio button"); rbMenuItem.setSelected(true); group.add(rbMenuItem); menu.add(rbMenuItem); rbMenuItem = new JRadioButtonMenuItem("Otro"); group.add(rbMenuItem); menu.add(rbMenuItem); //un grupo de check boxes menu.addSeparator(); JMenuItem cbMenuItem = new JCheckBoxMenuItem("Un Item con check box"); menu.add(cbMenuItem); cbMenuItem = new JCheckBoxMenuItem("Otro"); menu.add(cbMenuItem); //submenu menu.addSeparator(); JMenu submenu = new JMenu("Un Submenu"); menuItem = new JMenuItem("Un Item en un submenu"); submenu.add(menuItem); menuItem = new JMenuItem("Otro Item"); submenu.add(menuItem); menu.add(submenu); //Build second menu in the menu bar. menu = new JMenu("Otro Menu"); menuBar.add(menu); ... frame.setJMenuBar(menuBar);

Como se puede ver en el código anterior, para configurar una barra de menús para

un JFRAME, se utiliza el método SETJMENUBAR. Para añadir un JMENU a un JMENUBAR,

se utiliza el método ADD(JMENU). Para añadir ítems de menú y submenús a un JMENU,

se utiliza el método ADD(JMENUITEM).

17.4.3.7.1 Manejar Eventos desde los menús

El manejo de eventos en menús es muy similar al de los distintos tipos de botones

y check boxes. Para detectar cuando el usuario selecciona un JMENUITEM, se puede

escuchar por eventos Action (ACTIONEVENT) (igual que se haría para un JBUTTON).

Para detectar cuando el usuario selecciona un JRADIOBUTTONMENUITEM, se puede

escuchar tanto por eventos Action, como por eventos item (ITEMEVENT). Para

JCHECKBOXMENUITEMS, generalmente se escuchan también eventos item.

17.4.3.7.2 Crear un menú contextual

Para traer un menú contextual se usa JPOPUPMENU. El siguiente fragmento de código

muestra cómo se construye un menú contextual.

JPopupMenu popup = new JPopupMenu(); JMenuItem menuItem = new JMenuItem("Un elemento de menu contextual"); popup.add(menuItem); menuItem = new JMenuItem("Otro elemento de menu contextual”); menuItem.addActionListener(this);

Page 22: Desarrollo de aplicaciones visuales - sing-group.orglipido/dojos/swing/swing.pdf · o general, como lo es Java, disponen de librerías para la creación de interfaces gráficas de

popup.add(menuItem); MouseListener popupListener = new PopupListener(); componente.addMouseListener(popupListener); //componente es cualquier componente

Como se puede ver en el código anterior, se debe registrar un oyente de ratón

(MOUSELISTENER) para cada componente al que debería estar asociado el menú

contextual.

El oyente debe detectar las peticiones del usuario para que aparezca el menú

contextual, es decir, detectar si el botón del ratón pulsado es el que, por convenio

en la plataforma es que el que debe desplegar un menú contextual. Por ejemplo en

Windows se usa el botón derecho, sin embargo en Mac OSX se utiliza una

combinación de teclas y el botón del ratón. Es por ello por lo que existe el método

de MOUSEEVENT denominado ISPOPUPTRIGGER. Para visualizar un JPOPUPMENU se utiliza

su método SHOW.

El siguiente fragmento de código muestra cómo se podría codificar el oyente de

ratón desde donde se visualiza el menú contextual.

class PopupListener extends MouseAdapter { public void mousePressed(MouseEvent e) { maybeShowPopup(e); } public void mouseReleased(MouseEvent e) { maybeShowPopup(e); } private void maybeShowPopup(MouseEvent e) { if (e.isPopupTrigger()) { popup.show(e.getComponent(), e.getX(), e.getY()); } } }

17.4.3.8 JTable

JTABLE permite mostrar tablas de datos, y opcionalmente permitir que el usuario los

edite. JTABLE no contiene ni almacena datos; simplemente es una vista de los datos.

La Figura 17.25 muestra una tabla típica mostrada en un JSCROLLPANE.

Figura 17.25. Ejemplo de JTable

El siguiente fragmento de código es suficiente para mostrar la aplicación de la

Figura 17.25:

String[][] data = { {"Maria", "Garcia", "Snowboarding", new Integer(5), new Boolean(false)}, {"Alicia", "Gomez", "Tenis", new Integer(3), new Boolean(true)}, {"Catalina", "Perez", "Atletismo", new Integer(2), new Boolean(false)}, {"Marcos", "Fernandez", "Futbol", new Integer(20), new Boolean(true)}, {"Angela", "Gonzalez", "Natacion", new Integer(4), new Boolean(false)} }; String[] columnNames = {"Nombre", "Primer Apellido", "Deporte", "# de años", "Vegetariano"}; JTable tabla = new JTable(data, columnNames); this.add(new JScrollPane(tabla));

Para obtener fácilmente el valor de una celda se utiliza el método GETVALUEAT de

JTABLE que recibe como parámetros la fila y la columna.

El ejemplo utiliza uno de los constructores de JTABLE que aceptan datos

directamente.

• JTABLE(OBJECT[][] ROWDATA, OBJECT[] COLUMNNAMES).

• JTABLE(VECTOR ROWDATA, VECTOR COLUMNNAMES).

La ventaja de utilizar uno de estos constructores es que es sencillo. Sin embargo,

estos constructores también tienen desventajas.

• Automáticamente hacen todas las celdas editables.

• Tratan igual a todos los tipos de datos. Por ejemplo, si una columna tiene

datos BOOLEAN, los datos podrían mostrarse mejor con un CHECKBOX en la

tabla. Sin embargo, si especificamos los datos como un argumento array o

vector del constructor de JTABLE, nuestro dato BOOLEAN se mostrará como

un STRING. Se pueden ver estas diferencias en las dos últimas columnas de

los ejemplos anteriores.

Page 23: Desarrollo de aplicaciones visuales - sing-group.orglipido/dojos/swing/swing.pdf · o general, como lo es Java, disponen de librerías para la creación de interfaces gráficas de

• Requieren colocar todos los datos de la tabla en un array o vector, lo que

es inapropiado para algunos datos. Por ejemplo, si estamos instanciando

un conjunto de objetos desde una base de datos, se podrían pedir los

objetos bajo demanda, en vez de copiar todos los valores en un array o un

vector.

Para solucionar esos problemas se debe utilizar la interfaz TABLEMODEL. Un JTABLE

obtiene sus datos siempre de un objeto TABLEMODEL. JTABLE pregunta a este objeto

todo lo que necesita saber para dibujarse: número de filas y columnas, cabecera de

columnas, valor en una posición, tipo de objeto que tiene una columna (por defecto

OBJECT), además de delegar la asignación de nuevos valores a las filas y columnas.

Si no se especifica el TABLEMODEL a usar, JTABLE mantiene uno por defecto. Para

facilitar la implementación de esta interfaz, existe la clase ABSTRACTTABLEMODEL que

es una implementación por defecto de dicha interfaz.

El siguiente fragmento de código muestra la implementación de un TABLEMODEL

propio.

class MyTableModel extends AbstractTableModel { Object[][] data = { {"Maria", "Garcia", "Snowboarding", new Integer(5), new Boolean(false)}, {"Alicia", "Gomez", "Tenis", new Integer(3), new Boolean(true)}, {"Catalina", "Perez", "Atletismo", new Integer(2), new Boolean(false)}, {"Marcos", "Fernandez", "Futbol", new Integer(20), new Boolean(true)}, {"Angela", "Gonzalez", "Natacion", new Integer(4), new Boolean(false)} }; String[] columnNames = {"Nombre", "Primer Apellido", "Deporte", "# de años", ”Vegetariano"}; public int getColumnCount() { return columnNames.length; } public int getRowCount() { return data.length; } public String getColumnName(int col) { return columnNames[col]; } public Object getValueAt(int row, int col) { return data[row][col]; } public Class getColumnClass(int c) {

JJTTaabblleeDDeemmoo..jjaavvaa

return getValueAt(0, c).getClass(); } public boolean isCellEditable(int row, int col) { if (col < 2) { return false; } else { return true; } } public void setValueAt(Object value, int row, int col) { data[row][col]=value; } }

El anterior fragmento de código daría como resultado la tabla que se muestra en la

Figura 17.26.

Figura 17.26. Ejemplo de JTable con JTableModel

Como se puede observar en la Figura 17.26, gracias a la información más detallada

que proporciona el TABLEMODEL, sobre todo por el método GETCOLUMNCLASS, el JTABLE

es capaz de mostrar la información más ajustada: los valores enteros se muestran

alineados a la derecha, y los booleanos se muestran con un check box.

La Tabla 17.6 muestra el mapeo que existe entre la clase que devuelve

GETCOLUMNCLASS y una renderización concreta en función de la misma.

Tabla 17.6. Renderizaciones en función de los valores devueltos por getColumnClass

Clase devuelta por getColumnClass

Cómo se renderiza

BOOLEAN Mediante un check box. NUMBER Mediante una etiqueta de texto alineada a la derecha. DOUBLE, FLOAT Igual que con NUMBER, pero la transformación del objeto

numérico a una cadena de texto se hace mediante NUMBERFORMAT que respeta además el locale.

DATE Se renderiza por una etiqueta de texto con la transformación del objeto fecha a una cadena de texto mediate DATEFORMAT.

Page 24: Desarrollo de aplicaciones visuales - sing-group.orglipido/dojos/swing/swing.pdf · o general, como lo es Java, disponen de librerías para la creación de interfaces gráficas de

IMAGEICON, ICON Mediante una etiqueta con icono centrada. OBJECT Mediante una etiqueta de texto que muestra el TOSTRING del

objeto.

17.4.3.9 Componentes de texto

Swing proporciona un conjunto de componentes para el manejo de texto. Van

desde componentes sencillos, para introducir pequeñas cantidades de información,

hasta componentes complejos capaces de dar estilo al texto, visualizar imágenes,

componentes o incluso HTML. Por orden de complejidad, los componentes de

texto principales en Swing son: JTEXTFIELD, JTEXTAREA y JTEXTPANE.

17.4.3.9.1 JTextField

Un JTEXTFIELD es un componente básico que permite al usuario teclear una pequeña

cantidad de texto y dispara un evento ACTION cuando el usuario indique que la

entrada de texto se ha completado (normalmente pulsando Return). Si se necesita

proporcionar un campo para una contraseña, es decir, que no muestre los caracteres

tecleados por el usuario se puede utilizar la clase JPASSWORDFIELD.

El siguiente fragmento de código muestra un ejemplo de uso de este componente

que además responde a la pulsación de la tecla Return.

final JTextField textField = new JTextField(20); //maximo 20 caracteres textField.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent evt) { String text = textField.getText(); JOptionPane.showMessageDialog(this, “Has escrito ”+text); } }); this.add(textField);

La Figura 17.27 muestra el aspecto que tendría la aplicación que utiliza JTEXTFIELD

en el momento de pulsar Return.

JJTTeexxttFFiieellddDDeemmoo..jjaavvaa

Figura 17.27. Ejemplo de JTextField

17.4.3.9.2 JTextArea

Un JTEXTAREA es un componente que muestra un área de texto muestra múltiples

líneas de texto y permite que el usuario edite el texto con el teclado y el ratón. El

texto sólo puede tener una fuente.

La Figura 17.28 muestra el aspecto que tendría un JTEXTAREA (utilizando un

JSCROLLPANE para desplazarla en caso de que haya más texto del que se puede

visualizar).

Figura 17.28. Ejemplo de JTextArea

El siguiente fragmento de código muestra cómo se ha obtenido la aplicación de la

Figura 17.28.

Page 25: Desarrollo de aplicaciones visuales - sing-group.orglipido/dojos/swing/swing.pdf · o general, como lo es Java, disponen de librerías para la creación de interfaces gráficas de

JTextArea textArea = new JTextArea(5, 10); // 5 filas y 10 columnas textArea.setFont(new Font("Serif", Font.ITALIC, 16)); textArea.setText( "Esto es un JTextArea editable " + "iniciada con el método setText. " + "Un area de texto es un componente de texto \"plano\", " + "lo que significa que aunque puede mostrar texto " + "en cualquier fuente, todo el texto debe estar en la misma fuente." ); textArea.setLineWrap(true); textArea.setWrapStyleWord(true); JScrollPane areaScrollPane = new JScrollPane(textArea); areaScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); areaScrollPane.setPreferredSize(new Dimension(250, 250));

En el ejemplo anterior, se establece la fuente del área de texto y, tal y como dice el

ejemplo, únicamente se puede usar una fuente para todo el área.

Las dos siguientes sentencias tratan de la ruptura de líneas. Primero llama a

SETLINEWRAP, que activa la ruptura de líneas. Por defecto, un área de texto no rompe

las líneas. En su lugar muestra el texto en una sola línea y si el área de texto está

dentro de un panel de desplazamiento, se permite a sí mismo desplazarse

horizontalmente. La segunda es una llamada a SETWRAPSTYLEWORD, que indica que

se rompan las líneas entre palabras.

17.4.3.9.3 JTextPane

JTEXTPANE permite la creación de áreas de texto muy personalizables que permiten

visualizar texto con formato avanzado. La propia explicación de este componente

sería muy amplia, por lo que se ha decidido mostrar únicamente un ejemplo

ilustrativo de algunas de sus capacidades.

La Figura 17.29 muestra un ejemplo de una aplicación que usa un JTEXTPANE para

mostrar un texto con diferentes estilos.

JJTTeexxttAArreeaaDDeemmoo..jjaavvaa

Figura 17.29. Ejemplo de JTextPane

El siguiente fragmento de código muestra cómo se puede obtener la aplicación de

la Figura 17.29. Básicamente, se introduce el texto inicial en un array y se crean y

codifican varios estilos (objetos que representan diferentes formatos de párrafos y

caracteres) en otro array. Posteriormente, el código ejecuta un bucle por el array,

inserta el texto en el JTEXTPANE, y especifica el estilo a utilizar para el texto

insertado.

// Crear el Text Pane JTextPane textPane = createTextPane(); JScrollPane paneScrollPane = new JScrollPane(textPane); ... private JTextPane createTextPane() { JTextPane textPane = new JTextPane(); String[] initString = { "This is an editable JTextPane, ", "another ", "styled ", "text ", "component, ", "which supports embedded icons..." + newline, " " + newline, "...and embedded components..." + newline, " " + newline + newline, "JTextPane is a subclass of JEditorPane that ", "uses a StyledEditorKit and StyledDocument, and provides ", "cover methods for interacting with those objects." }; String[] initStyles = { "regular", "italic", "bold", "small", "large", "regular", "icon", "regular", "button", "regular", "regular", "regular" }; initStylesForTextPane(textPane); Document doc = textPane.getDocument(); try {

JJTTeexxttPPaanneeDDeemmoo..jjaavvaa

Page 26: Desarrollo de aplicaciones visuales - sing-group.orglipido/dojos/swing/swing.pdf · o general, como lo es Java, disponen de librerías para la creación de interfaces gráficas de

for (int i=0; i < initString.length; i++) { textPane.setCaretPosition(doc.getLength()); doc.insertString(doc.getLength(), initString[i], textPane.getStyle(initStyles[i])); textPane.setLogicalStyle(textPane.getStyle(initStyles[i])); } } catch (BadLocationException ble) { System.err.println("Couldn't insert initial text."); } return textPane; } protected void initStylesForTextPane(JTextPane textPane) { //Initialize some styles Style def = StyleContext.getDefaultStyleContext(). getStyle(StyleContext.DEFAULT_STYLE); Style regular = textPane.addStyle("regular", def); StyleConstants.setFontSize(def, 14); StyleConstants.setFontFamily(def, "SansSerif"); Style s = textPane.addStyle("italic", regular); StyleConstants.setItalic(s, true); s = textPane.addStyle("bold", regular); StyleConstants.setBold(s, true); s = textPane.addStyle("small", regular); StyleConstants.setFontSize(s, 10); s = textPane.addStyle("large", regular); StyleConstants.setFontSize(s, 16); s = textPane.addStyle("icon", regular); StyleConstants.setAlignment(s, StyleConstants.ALIGN_CENTER); StyleConstants.setIcon(s, new ImageIcon("images/Pig.gif")); s = textPane.addStyle("button", regular); StyleConstants.setAlignment(s, StyleConstants.ALIGN_CENTER); JButton button = new JButton(new ImageIcon("images/sound.gif")); button.setMargin(new Insets(0,0,0,0)); button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { Toolkit.getDefaultToolkit().beep(); } }); StyleConstants.setComponent(s, button); }

17.4.3.10 JTree

Con la clase JTREE, se puede mostrar un árbol de datos. JTREE realmente no contiene

datos, simplemente es un vista de ellos. La Figura 17.30 muestra un ejemplo de un

árbol JTREE.

Figura 17.30. Ejemplo de JTree

Como muestra la figura anterior, JTREE muestra los datos verticalmente. Cada fila

contiene exactamente un ítem de datos o nodo. Cada árbol tiene un nodo raíz

(llamado “Root” en la Figura 17.30, del que descienden todos los nodos). Los

nodos que no pueden tener hijos se llaman nodos hoja (leaf). En el caso de la

Figura 17.17, el look and feel marca los nodos hojas con un círculo y a los nodos

intermedios con una carpeta. Dichos nodos intermedios pueden tener cualquier

número de hijos, o incluso no tenerlos. Normalmente el usuario puede expandir y

contraer los nodos que no son hojas. Por defecto, los nodos que no son hojas

empiezan contraídos.

Cuando se inicializa un árbol, se crea un ejemplar de TREENODE para cada nodo del

árbol, incluyendo el nodo raíz. Cada nodo que no tenga hijos es una hoja. Para

hacer que un nodo sin hijos no sea una hoja, se llama al método

SETALLOWSCHILDREN(TRUE) sobre él.

Normalmente los JTREE suelen incluirse dentro de paneles de desplazamiento

(JSCROLLPANE) tal y como se ha hecho en la aplicación que muestra la Figura 17.31,

la cual implementa un pequeño catálogo de libros.

Figura 17.31. Aplicación que integra JTree para mostrar un catálogo de libros

Page 27: Desarrollo de aplicaciones visuales - sing-group.orglipido/dojos/swing/swing.pdf · o general, como lo es Java, disponen de librerías para la creación de interfaces gráficas de

El código para obtener la aplicación de la Figura 17.31 se muestra en los siguientes

tres fragmentos de código. El primer fragmento, mostrado a continuación, ilustra

cómo se inició el árbol insertando sus elementos.

DefaultMutableTreeNode raiz = new DefaultMutableTreeNode(“The Java Series”); DefaultMutableTreeNode category = null; DefaultMutableTreeNode book = null; category = new DefaultMutableTreeNode("Books for Java Programmers"); raiz.add(category); //Tutorial book = new DefaultMutableTreeNode(new BookInfo("The Java Tutorial: Object-Oriented Programming for the Internet", "tutorial.html")); category.add(book); ... category = new DefaultMutableTreeNode("Books for Java Implementers"); raiz.add(category); //VM book = new DefaultMutableTreeNode(new BookInfo("The Java Virtual Machine Specification", "vm.html")); category.add(book); //Language Spec book = new DefaultMutableTreeNode(new BookInfo("The Java Language Specification", "jls.html")); category.add(book); // Crear el arbol JTree tree = new JTree(raiz);

Como se puede ver en el ejemplo, todos los nodos, incluyendo la raíz son

instancias de DEFAULTMUTABLETREENODE, la cual implementa la interfaz TREENODE y

suele ser suficiente para cualquier árbol. A dicha clase se le pueden ir añadiendo

hijos recursivamente (instancias de MUTABLETREENODE).

Cuando se crea un DEFAULTMUTABLETREENODE, en el constructor se le puede pasar

cualquier objeto que es el que realmente utiliza el programador para guardar la

información del árbol. En el ejemplo se han guardado tanto cadenas de texto (para

nodos intermedios) como un objeto propio de usuario, denominado BOOKINFO y que

guarda información relativa a un libro. El árbol utilizará el método TOSTRING para

obtener la etiqueta que se mostrará en el árbol, por lo cual suele ser muy útil

redefinirlo.

private class BookInfo { public String bookName; public URL bookURL; public BookInfo(String book, String filename) { bookName = book; } //Esto es lo que se mostrará en el árbol. public String toString() {

JJTTrreeeeDDeemmoo..jjaavvaa

return bookName; } }

Finalmente, se ha añadido un oyente de eventos de selección sobre el árbol

(TREESELECTIONLISTENER) para actuar cuando se pincha en un nodo y mostrar así

información relativa al libro que se ha seleccionado. Para obtener dicho

comportamiento bastaría con escribir el siguiente fragmento de código.

tree.addTreeSelectionListener(new TreeSelectionListener() { public void valueChanged(TreeSelectionEvent e) { // Obtener el nodo de nuevo DefaultMutableTreeNode node = (DefaultMutableTreeNode) (e.getPath().getLastPathComponent()); Object nodeInfo = node.getUserObject(); if (node.isLeaf()) { BookInfo book = (BookInfo)nodeInfo; displayURL(book.bookURL); } else { displayURL(helpURL); } } });

Dicho fragmento obtiene el nodo seleccionado a partir del evento, para finalmente

acceder al objeto de usuario (BOOKINFO) y obtener la URL de donde descargar

información adicional del libro.

17.4.3.10.1 Personalización de la presentación

Un árbol usa un único objeto responsable de renderizar todos los nodos el cual

implementa la interfaz TREECELLRENDERER. Por defecto todos los JTREE tienen como

renderizador un objeto de la clase DEFAULTTREECELLRENDERER. Si se quiere hacer una

personalización simple, como cambiar el icono utilizado para las hojas y para los

nodos intermedios, el color de la fuente, el color de selección, se puede configurar

este propio renderizador, tal y como muestra el siguiente fragmento de código.

DefaultTreeCellRenderer renderer = new DefaultTreeCellRenderer(); renderer.setLeafIcon(new ImageIcon("icons/center.png")); tree.setCellRenderer(renderer);

El resultado sería el mostrado en la Figura 17.32.

Page 28: Desarrollo de aplicaciones visuales - sing-group.orglipido/dojos/swing/swing.pdf · o general, como lo es Java, disponen de librerías para la creación de interfaces gráficas de

Figura 17.32. Personalización de los iconos de un JTree

Para un control total sobre el renderizado habría que implementar directamente la

clase TREECELLRENDERER.

17.4.3.10.2 Modificación dinámica de un JTree

Para insertar o eliminar nodos de un árbol se debe utilizar el objeto

DEFAULTTREEMODEL que representa el modelo de datos del árbol. El siguiente

fragmento de código muestra cómo se puede insertar un nuevo nodo colgando de

otro ya existente en el árbol mediante el método INSERTNODEINTO del modelo.

DefaultMutableTreeNode parentNode = null; TreePath parentPath = tree.getSelectionPath(); //el nodo padre sera el que esta seleccionado // obtener el nodo padre if (parentPath == null) { parentNode = rootNode; } else { parentNode = (DefaultMutableTreeNode) (parentPath.getLastPathComponent()); } DefaultMutableTreeNode childNode = new DefaultMutableTreeNode(“nodo hijo”); DefaultTreeModel model = (DefaultTreeModel) tree.getModel(); treeModel.insertNodeInto(childNode, parentNode, parentNode.getChildCount()); // visualizar el nuevo nodo insertado tree.expandPath(new TreePath(parentNode.getPath())); tree.scrollPathToVisible(new TreePath(childNode.getPath()));

De forma similar, para eliminar un nodo se utilizaría el método

REMOVENODEFROMPARENT también perteneciente a DEFAULTTREEMODEL.

17.5 Control de la distribución con Layouts

Un controlador de distribución (Layout Manager) es un objeto que implementa la

interfaz LAYOUTMANAGER y su objetivo es determinar el tamaño y posición de los

componentes Swing dentro de un contenedor. Aunque los componentes pueden

indicar el tamaño y orientación, el manejador de distribución es el que tiene la

última palabra.

El uso de controladores de distribución es muy útil cuando se usa con ventanas

cuyo tamaño puede ser modificado por el usuario o si la aplicación va a ser

visualizada en diferentes sistemas operativos donde el aspecto puede variar. El uso

de controladores de distribución es un capítulo esencial para que el acabado de la

aplicación tenga un mínimo de seriedad.

Swing proporciona cinco controladores de distribución comúnmente utilizados:

FLOWLAYOUT, BORDERLAYOUT, BOXLAYOUT, GRIDBAGLAYOUT y GRIDLAYOUT.

17.5.1 FlowLayout

FLOWLAYOUT es el controlador de distribución por defecto para todos los paneles.

Simplemente coloca los componentes de izquierda a derecha, empezando una

nueva línea si es necesario. La Figura 17.33 muestra una aplicación que utiliza este

controlador de distribución para colocar cinco botones.

Figura 17.33. Aplicación que usa FlowLayout

El siguiente fragmento de código muestra cómo se ha utilizado FLOWLAYOUT para

obtener la aplicación de la Figura 17.33.

// Constructor del JFrame de la aplicacion ... Container contentPane = getContentPane(); contentPane.setLayout(new FlowLayout()); contentPane.add(new JButton("Button 1")); contentPane.add(new JButton("2")); contentPane.add(new JButton("Button 3"));

FFlloowwLLaayyoouuttDDeemmoo..jjaavvaa

Page 29: Desarrollo de aplicaciones visuales - sing-group.orglipido/dojos/swing/swing.pdf · o general, como lo es Java, disponen de librerías para la creación de interfaces gráficas de

contentPane.add(new JButton("Long-Named Button 4")); contentPane.add(new JButton("Button 5"));

17.5.2 BorderLayout

BORDERLAYOUT es el controlador de distribución por defecto para las ventanas, como

JFRAME y JDIALOG. Utiliza cinco áreas para contener los componentes: north, south,

east, west y center (norte, sur, este, oeste y centro). Cuando se redimensiona la

ventana todo el espacio extra se sitúa en el área central. La Figura 17.34 muestra

una aplicación que utiliza este controlador de distribución para colocar cinco

botones.

Figura 17.34. Aplicación que usa BorderLayout

El siguiente fragmento de código muestra cómo se ha utilizado BORDERLAYOUT para

obtener la aplicación de la Figura 17.34.

//Constructor del JFrame de la aplicacion Container contentPane = getContentPane(); // Por defecto el panel tiene BorderLayout // contentPane.setLayout(new BorderLayout()); //innecesario contentPane.add(new JButton("Button 1 (NORTH)"), BorderLayout.NORTH); contentPane.add(new JButton("2 (CENTER)"), BorderLayout.CENTER); contentPane.add(new JButton("Button 3 (WEST)"), BorderLayout.WEST); contentPane.add(new JButton("Long-Named Button 4 (SOUTH)"), BorderLayout.SOUTH); contentPane.add(new JButton("Button 5 (EAST)"), BorderLayout.EAST);

Se debe tener en cuenta que cuando se está utilizando BORDERLAYOUT, el método ADD

del contenedor necesita un parámetro adicional que indica una de las cinco

localizaciones posibles.

BORDERLAYOUT permite definir el espacio entre las cinco zonas en su constructor:

BORDERLAYOUT(INT HORIZONTALGAP, INT VERTICALGAP).

BBoorrddeerrLLaayyoouuttDDeemmoo..jjaavvaa

17.5.3 BoxLayout

BOXLAYOUT coloca los componentes e una única fila o columna. Respeta las

peticiones de máximo tamaño de los componentes y también permite alinearlos. Se

puede ver a BOXLAYOUT como una versión más potente de FLOWLAYOUT.

La Figura 17.35 muestra una aplicación que utiliza este controlador de

distribución. En la parte superior de la ventana un BOXLAYOUT de arriba-a-abajo

sitúa una etiqueta sobre un panel de desplazable. En la parte inferior, un BOXLAYOUT

de izquierda-a-derecha sitúa dos botones uno junto al otro. Un BORDERLAYOUT

combina las dos partes de la ventana y se asegura que cualquier exceso de espacio

será entregado al panel de scroll.

Figura 17.35. Aplicación que usa dos BoxLayout dentro de un BorderLayout

El código necesario para obtener la ventana de la Figura 17.35 se muestra a

continuación.

... // Etiqueta y scroll dispuestos de arriba-a-abajo. JPanel listPane = new JPanel(); listPane.setLayout(new BoxLayout(listPane, BoxLayout.Y_AXIS)); JLabel label = new JLabel("Area de texto: "); listPane.add(label); listPane.add(Box.createRigidArea(new Dimension(0,5))); JScrollPane listScroller = new JScrollPane(new JTextArea("hola", 20,50)); listPane.add(listScroller);

BBooxxLLaayyoouuttDDeemmoo..jjaavvaa

Page 30: Desarrollo de aplicaciones visuales - sing-group.orglipido/dojos/swing/swing.pdf · o general, como lo es Java, disponen de librerías para la creación de interfaces gráficas de

listPane.setBorder(BorderFactory.createEmptyBorder(10,10,10,10)); // Botones dispuestos de izquierda-a-derecha. JPanel buttonPane = new JPanel(); buttonPane.setLayout(new BoxLayout(buttonPane, BoxLayout.X_AXIS)); buttonPane.setBorder(BorderFactory.createEmptyBorder(0, 10, 10, 10)); buttonPane.add(Box.createHorizontalGlue()); buttonPane.add(new JButton("Cancelar")); buttonPane.add(Box.createRigidArea(new Dimension(10, 0))); buttonPane.add(new JButton("Set")); // Unir todo en el panel de contenido con su BorderLayout. Container contentPane = getContentPane(); contentPane.add(listPane, BorderLayout.CENTER); contentPane.add(buttonPane, BorderLayout.SOUTH);

El ejemplo incorpora el uso de la clase BOX que contiene métodos estáticos para la

creación de componentes invisibles para usar como relleno. Existen tres tipos de

componentes invisibles que se pueden generar y que se muestran en la Tabla 17.7.

Tabla 17.7. Componentes invisibles de relleno con Box

Nombre Descripción Cómo se crean Área Rígida (Rigid) Se usa cuando se quiere un espacio

fijo entre dos componentes. BOX.CREATERIGIDAREA(SIZE)

Pegamento (Glue) Se usa para especificar dónde debería ir el exceso de espacio en la distribución. Se puede ver como una goma semi-húmeda que se estira y encoge, que sólo toma el espacio que los demás componentes no utilizan. Por ejemplo, poniendo un pegamento horizontal entre dos componentes en un box de izquierda-a-derecha se logra que todo el espacio sobrante quede entre los dos componentes en vez de a la derecha.

BOX.CREATEHORIZONTALGLUE() BOX.CREATEVERTICALGLUE()

Relleno personalizado (Filler)

Se usa para especificar un componente invisible con los tamaños mínimo, preferido y máximo que se deseen. Por ejemplo, poniendo un relleno personalizado de al menos 5 píxeles entre dos componentes en un BoxLayout de izquierda-a-derecha se obtendría esa separación entre dichos componentes, pero si hay más tamaño extra lo cogería hasta llegar al máximo definido.

NEW BOX.FILLER(MINSIZE, PREFSIZE, MAXSIZE)

La Figura 17.36 muestra el comportamiento que tendrían los rellenos vistos

separando dos componentes frente a los cambios de tamaño del contenedor.

Figura 17.36. Comportamiento de los diferentes rellenos

17.5.4 GridLayout

GRIDLAYOUT coloca los componentes en una parrilla de celdas que tienen el mismo

tamaño, mostrándolos en una sucesión de filas y columnas. Si se redimensiona el

componente, cambia el tamaño de cada celda para que todas sean lo más grande

posible. La Figura 17.37 muestra una aplicación que utiliza este controlador de

distribución para colocar cinco botones.

Figura 17.37. Aplicación que usa GridLayout

Page 31: Desarrollo de aplicaciones visuales - sing-group.orglipido/dojos/swing/swing.pdf · o general, como lo es Java, disponen de librerías para la creación de interfaces gráficas de

El siguiente fragmento de código muestra cómo se ha utilizado GRIDLAYOUT para

obtener la aplicación de la Figura 17.37.

Container contentPane = getContentPane(); contentPane.setLayout(new GridLayout(0,2)); //dos columnas y las filas que sean necesarias contentPane.add(new JButton("Boton 1")); contentPane.add(new JButton("2")); contentPane.add(new JButton("Boton 3")); contentPane.add(new JButton("Boton muy largo 4")); contentPane.add(new JButton("Boton 5"));

El constructor de GRIDLAYOUT utilizado en el ejemplo toma dos parámetros que

indican el número de filas y de columnas que tendrá la parrilla. Uno de ellos (sólo

uno) puede valer cero, indicando que no se desea ninguna restricción. Si se han

indicado valores tanto para filas como para columnas distintos de cero, en caso de

que se añadan más componentes que los que en principio cabrían, prevalecerá el

valor dado para las filas sobre el de las columnas.

17.5.5 GridBagLayout

GRIDBAGLAYOUT es el controlador de distribución más sofisticado y flexible. Alinea

los componentes situándolos en una parrilla de celdas, permitiendo que algunos

componentes ocupen más de una celda. Las filas de la parrilla no tienen porque ser

de la misma altura; de la misma forma las columnas pueden tener diferentes

anchuras. La Figura 17.38 muestra una aplicación que utiliza este controlador de

distribución para colocar cinco botones.

Figura 17.38. Aplicación que usa GridBagLayout

Para indicar el tamaño y la posición característicos de los componentes se hace

individualmente para cada componente utilizando un objeto de la clase

GGrriiddLLaayyoouuttDDeemmoo..jjaavvaa

GRIDBAGCONSTRAINTS que contiene toda la información relativa a la posición y

tamaño de un componente en el contenedor que utiliza GRIDBAGLAYOUT. El proceso

típico de uso de este controlador de distribución se puede ver en el siguiente

fragmento de código.

GridBagLayout gridbag = new GridBagLayout(); GridBagConstraints c = new GridBagConstraints(); JPanel pane = new JPanel(); pane.setLayout(gridbag); // Para cada componente que se va a añadir al contenedor: //...Crear el componente... //...Establecer los atributos para el objeto GridBagContraints... //indicar que se quieren usar esas especificaciones sobre ese componente gridbag.setConstraints(theComponent, c); pane.add(theComponent); //añadir finalmente el componente

Como se puede observar en el fragmento de código anterior, se necesita crear una

instancia de la clase GRIDBAGCONTRAINTS y, cada vez que se añada un componente, se

establecen los detalles en esa instancia, se asocia al componente y, finalmente, se

añade el componente. Se puede reutilizar la misma instancia de GRIDBAGCONTRAINTS

para añadir más componentes ya que una vez que se añade un componente los

valores que tenga en ese momento el objeto GRIDBAGCONTRAINTS son copiados y no

se verán afectados por cambios posteriores.

En una instancia de GRIDBAGCONTRAINTS se pueden indicar los valores que se

muestran en la Tabla 17.8. Todos ellos son atributos públicos.

Tabla 17.8. Valores configurables en GridBagConstraints

Variable Tipo / valores válidos Descripción GRIDX, GRIDY Entero (número de celda)

GRIDBAGCONSTRAINTS.RELATIVE Indica la posición en la parrilla. La esquina superior izquierda es la GRIDX=0 y GRIDY=0. Si se utiliza RELATIVE, el componente se situará una celda a la derecha (para GRIDX) y una abajo (para GRIDY) del componente añadido justo antes.

GRIDWIDTH, GRIDHEIGHT

Entero (número de celdas) GRIDBAGCONSTRAINTS.REMAINDER GRIDBAGCONSTRAINTS.RELATIVE

Indica el número de columnas (GRIDWIDTH) y filas (GRIDHEIGHT) utilizadas por el componente. Si se usa REMAINDER, el componente será siempre el último de esa fila (para GRIDWIDTH) o de esa columna (para GRIDHEIGHT). Si se usa RELATIVE, el componente será el siguiente al último de la fila (para

GGrriiddBBaaggLLaayyoouuttDDeemmoo..jjaavvaa

Page 32: Desarrollo de aplicaciones visuales - sing-group.orglipido/dojos/swing/swing.pdf · o general, como lo es Java, disponen de librerías para la creación de interfaces gráficas de

GRIDWIDTH) o de la columna (para GRIDHEIGHT).

FILL GRIDBAGCONSTANS.NONE GRIDBAGCONSTRAINTS.HORIZONTAL GRIDBAGCONSTRAINTS.VERTICAL GRIDBAGCONSTRAINTS.BOTH

Indica si se debe aprovechar el espacio sobrante cuando la celda del componente dispone de más espacio que el que éste ocupa. Con NONE (por defecto) no se aprovecha el espacio. Con HORIZONTAL se estiraría para aprovechar todo el ancho disponible, con VERTICAL para aprovechar todo el alto disponible, y BOTH para ambos casos.

IPADX, IPADY Entero (pixels). Indica un tamaño adicional al mínimo del componente tanto para el ancho (IPADX) como para el alto (IPADY). Por tanto el tamaño final del componente será su tamaño mínimo más IPADX*2 (ancho) o IPADY*2 (alto). Por defecto es cero.

INSETS Objeto de tipo INSETS: NEW INSETS(ARRIBA, IZQUIERDA, ABAJO, DERECHA).

Indica el espaciado externo mínimo del componente. Es decir, el espacio que hay como mínimo entre el propio componente y el borde de su celda.

ANCHOR GRIDBAGCONSTRAINTS.CENTER GRIDBAGCONSTRAINTS.NORTH GRIDBAGCONSTRAINTS.NORTHEAST GRIDBAGCONSTRAINTS.EAST GRIDBAGCONSTRAINTS.SOUTHEAST GRIDBAGCONSTRAINTS.SOUTH GRIDBAGCONSTRAINTS.SOUTHWEST GRIDBAGCONSTRAINTS.WEST GRIDBAGCONSTRAINTS.NORTHWEST

Indica el anclaje del componente (donde ha de colocarse) cuando se dispone de más espacio en la celda del que ocupa. Por defecto se utiliza CENTER.

WEIGHTX, WEIGHTY

Double entre 0.0 y 1.0. Indica la proporción de tamaño adicional que le corresponde a la columna (weightx) o fila (weighty) del componente cuando se redimensiona el contendor. Por defecto es 0.0, con lo que cuando se aumenta el tamaño del contenedor las filas y columnas no reciben espacio, si no que se queda todo en los bordes externos del contenedor, quedando la rejilla en el centro.

El código necesario para obtener la ventana de la Figura 17.38 se muestra a

continuación.

JButton button; Container contentPane = getContentPane(); GridBagLayout gridbag = new GridBagLayout(); GridBagConstraints c = new GridBagConstraints(); contentPane.setLayout(gridbag); c.fill = GridBagConstraints.HORIZONTAL;

button = new JButton("Button 1"); c.weightx = 0.5; c.gridx = 0; c.gridy = 0; gridbag.setConstraints(button, c); contentPane.add(button); button = new JButton("2"); c.gridx = 1; c.gridy = 0; gridbag.setConstraints(button, c); contentPane.add(button); button = new JButton("Button 3"); c.gridx = 2; c.gridy = 0; gridbag.setConstraints(button, c); contentPane.add(new JButton("Button 3")); button = new JButton("Long-Named Button 4"); c.ipady = 40; // Hacer que el componente sea alto c.weightx = 0.0; c.gridwidth = 3; c.gridx = 0; c.gridy = 1; gridbag.setConstraints(button, c); contentPane.add(button); button = new JButton("Button 5"); c.ipady = 0; //restablecer al valor por defecto c.weighty = 1.0; //pedir todo el tamaño adicional vertical c.anchor = GridBagConstraints.SOUTH; //colocarse siempre abajo c.insets = new Insets(10,0,0,0); //espacio arriba de 10 pixeles c.gridx = 1; // alineado con button 2 c.gridwidth = 2; // ocupa 2 columnas c.gridy = 2; // tercera fila gridbag.setConstraints(button, c); contentPane.add(button);

17.6 Threads y Swing

Una vez iniciada y visible una aplicación visual Swing, el programa es dirigido por

eventos que provocan la ejecución de código de manejo de eventos. Este

comportamiento se consigue gracias a que existe un hilo denominado de despacho

de eventos, encargado de recoger los eventos y de ejecutar el código de manejo de

los mismos presente en los manejadores de eventos. El hilo de despacho de eventos

también se encarga de dibujar los componentes en la pantalla. Por lo tanto en

Swing el código de manejo de eventos y de pintado se ejecuta en un único hilo.

Este diseño asegura que cada manejador de eventos finaliza antes de que se ejecute

el siguiente manejador, además de que el código de pintado nunca se ve

interrumpido por un evento.

Sin embargo, para evitar posibles problemas como interbloqueos, hay que tener

mucha precaución y respetar la siguiente regla:

Page 33: Desarrollo de aplicaciones visuales - sing-group.orglipido/dojos/swing/swing.pdf · o general, como lo es Java, disponen de librerías para la creación de interfaces gráficas de

Los componentes Swing se deben crear, modificar y consultar únicamente en el

hilo de despacho de eventos.

Por norma general el código que accede a los componentes visuales se encuentra

en los manejadores de eventos y, por lo tanto, se ejecuta en el hilo de despacho de

eventos. Se podrá por tanto acceder a los componentes visuales sin ningún tipo de

problema dentro del código de un manejador de eventos. Este caso es el más

habitual y no va en contra de la regla anterior.

Sin embargo, en ocasiones resulta útil el uso de hilos en las aplicaciones visuales:

1. Para realizar una tarea que implica mucho consumo de tiempo y se desea que

la interfaz siga respondiendo al usuario. Si en un manejador de eventos se

incluye código que tarda mucho tiempo en terminar, el hilo de despacho de

eventos permanecerá ejecutando dicho código y, durante ese tiempo, la

interfaz no se actualizará ni recogerá nuevos eventos, permaneciendo

“congelada”. El uso de hilos con este fin satisface la regla: el código de los

manejadores de eventos debe ser lo más rápido posible de forma que la

aplicación responda inmediatamente a todos los eventos del usuario.

2. Para realizar una operación que se ejecute cada cierto intervalo de tiempo.

3. Para esperar por mensajes procedentes de otros programas. Por ejemplo, se

puede mantener un hilo secundario bloqueado en la lectura de un SOCKET a la

espera de algún mensaje desde la otra parte y que la aplicación siga

respondiendo.

Todos los casos anteriores obligan a la utilización de un hilo secundario. El

problema surge si desde ese hilo se trata de acceder a los componentes visuales (p.

ej: al finalizar un cálculo costoso o al recibir un mensaje procedente de una

conexión, se desea modificar el texto de una etiqueta, habilitar botones, mostrar

mensajes, etc.).

El siguiente fragmento de código muestra la creación de un hilo para realizar un

cálculo costoso. El cálculo se hará en un hilo distinto al de despacho de eventos:

public void actionPerformed(ActionEvent e) { // codigo ejecutado en el hilo de despacho de eventos . . .

new Thread(){ public void run(){ // codigo NO ejecutado en el hilo de despacho de eventos calculoCostoso(); // tiempo apreciable por el usuario miAreaDeTexto.setText(“El calculo ha terminado!”); //mal! } }.start(); }

Como se puede observar, existe una línea al final del código del hilo secundario

que accede a un componente visual, lo cual debe evitarse. Lo que se debe

conseguir es que dicha línea se ejecute en el hilo de despacho de eventos. Existen

utilidades para obtener dicho comportamiento:

• Métodos INVOKELATER y INVOKEANDWAIT. Son dos métodos estáticos

pertenecientes a la clase JAVAX.SWING.SWINGUTILITIES. Ambos reciben como

parámetro un objeto que implementa la interfaz RUNNABLE y, por lo tanto,

proporciona un método RUN. INVOKELATER solicita al hilo de despacho de

eventos que ejecute el código del objeto RUNNABLE (método RUN). Por otro

lado, INVOKEANDWAIT se comporta igual que INVOKELATER, con la diferencia

de que la llamada a INVOKELATER retorna inmediatamente, pero la llamada a

INVOKEANDWAIT no retorna hasta que el hilo de despacho de eventos haya

ejecutado el código del objeto RUNNABLE. Se recomienda el uso de

INVOKELATER para evitar posibles interbloqueos. El siguiente fragmento de

código muestra cómo se utilizaría el método INVOKELATER, solventando el

problema que se está tratando:

public void actionPerformed(ActionEvent e) { new Thread(){ public void run(){ calculoCostoso(); // tiempo apreciable por el usuario SwingUtilities.invokeLater(new Runnable(){ public void run(){ // codigo ejecutado en el hilo de despacho de eventos miAreaDeTexto.setText(“El calculo ha terminado!”); } }); } }.start(); }

• Clase JAVAX.SWING.TIMER. Permite ejecutar código en el hilo de despacho de

eventos cada cierto intervalo de tiempo. Para ello se le pasan dos

parámetros en el constructor: uno indica el período de repetición y el otro

es un objeto de tipo ACTIONLISTENER, al cual le será invocado el método

ACTIONPERFORMED cada vez que se cumpla el período. El código dicho

Page 34: Desarrollo de aplicaciones visuales - sing-group.orglipido/dojos/swing/swing.pdf · o general, como lo es Java, disponen de librerías para la creación de interfaces gráficas de

método se ejecutará en el hilo de despacho de eventos. El siguiente

fragmento de código muestra un ejemplo de uso de la clase TIMER:

Timer miTimer = new Timer(ONE_SECOND, new ActionListener(){ public void actionPerformed(ActionEvent evt){ // codigo ejecutado en el hilo de despacho de eventos miAreaDeTexto.setText(“Ha pasado un segundo mas!”); } });