Capítulo Capítulo 6: Interfac 6: Interfac 6: Interfaces de Usuario.

Anuncio
6-. Interfaces de Usuario.
Capítulo 6: Interfaces
Interfaces de
Usuario.
- 77 -
6-. Interfaces de Usuario.
6. Interfaces de Usuario.
En el proceso de interacción persona-ordenador, la Interfaz Gráfica de Usuario
(GUI), es el artefacto que permite la interacción amigable con el sistema informático.
La interfaz gráfica de usuario (en inglés Graphical User Interface, GUI) es un tipo de
interfaz de usuario que utiliza un conjunto de imágenes y objetos gráficos (iconos,
ventanas, tipografía) para representar la información y acciones disponibles en la
interfaz. En este capítulo comentaremos las diferentes herramientas que se pueden
utilizar para realizar interfaces de usuario; comenzaremos por describir el paquete
AWT (Abstract Window Toolkit) de java; a continuación describiremos con mucho
detalle el paquete Java Swing, que es el que ha sido utilizado finalmente para
implementar la interfaz. Por último hablaremos de SWT, otra tecnología alternativa
para la realización de interfaces gráficas que está adquiriendo cada vez más
importancia.
6.1-. Paquete Java.awt.
El AWT o Abstract Window Toolkit es un conjunto de herramientas que permite
el desarrollo de aplicaciones gráficas basadas en ventanas. El AWT contiene numerosas
clases y métodos que permiten crear y gestionar dichas ventanas. Aunque el propósito
principal del AWT es suministrar a las applets el soporte necesario para que trabajen
con ventanas, también se puede utilizar para crear ventanas independientes que se
ejecuten en un entorno gráfico independiente, como puede ser Windows 95/98/NT. Una
descripción completa del AWT podría fácilmente ocupar un libro entero, es por eso que
aquí sólo se darán unas nociones básicas que permitan entender su funcionamiento,
limitaciones, y el por qué de la elección de Swing para el desarrollo de este proyecto.
Esta descripición pretende también realizar una pequeña introducción a las librerías de
creación de interfaces gráficas.
Java.awt es el paquete que maneja ventanas e interfaces gráficas, independientes
(abstractas) del entorno operativo. Es un paquete muy importante, y está disponible en
todas las plataformas Java.
Antes de seguir, es necesario aclarar a qué nos referimos con GUI. Si estamos
utilizando un programa, es muy probable que en la parte superior exista un menú.
Típicamente tendremos los elementos “Archivo”, “Ver”, “Ayuda”, etc. Aparte de esto,
es muy probable que el programa disponga también de una serie de iconos. Por ejemplo,
una carpeta para abrir un documento, un disquette para guardar un documento, una
impresora que pulsaremos si queremos imprimir, etc. Todo esto constituye la interfaz
gráfica de usuario o GUI. Este es el fin del paquete java.awt: dar el soporte necesario
para crear un GUI. Es un paquete que permite por ejemplo definir botones y cajas de
texto. Es más, dado que es un paquete Java, mantiene como es lógico la característica
principal de este lenguaje de programación: la portabilidad. Definiremos pues botones o
etiquetas portables a Windows, OS/2, Xwindows, o cualquier otro entorno gráfico,
siempre usando el mismo código fuente, evidentemente.
Java AWT (Abstract Windows Toolkit) es la librería visual más antigua de java,
pero hoy en día es mucho más utilizada la librería Swing. El AWT define ventanas en
- 79 -
6-. Interfaces de Usuario.
función de una jerarquía de clases que añade funcionalidad y carácter específico con
cada nivel. Las dos ventanas más comunes son las que derivan de panel (utilizadas por
las applets) y las que derivan de Frame (marco), que permiten crear ventanas estándar.
La mayor parte de la funcionalidad de estas ventanas la heredan de sus superclases. Para
poder entender esto es fundamental describir una jerarquía de clases que relacione
ambas clases. De forma gráfica, esta jerarquía podría representarse como sigue:
Component
Container
Menu Container
Interface
Window
Panel
Frame
Ilustración 3: Jerarquía de clases para Panel y Frame.
En la parte superior de la jerarquía de clases del AWT se encuentra la clase
Component. Es una clase abstracta que encapsula todos los atributos de un componente
visual. Todos los elementos del GUI que se visualizan en pantalla e interactúan con el
usuario son subclases de Component. A continuación viene la clase Container, que es
una subclase abstracta de Component y que contiene una serie de métodos adicionales
que permiten que otros objetos Component aniden dentro de él. También se pueden
anidar objetos Container dentro de otros objetos Container, puesto que también son
instancias de Component. Esto hace que sea un sistema de contenidos completamente
jerárquico.
La clase Panel es una subclase concreta de Container que no añade ningún
método. Es la superclase de Applet. Básicamente, un objeto Panel es una ventana que
no contiene barra de título, ni barra de menú, ni bordes. Esta es la razón por la que no se
pueden ver estos elementos cuando una applet se ejecuta en un navegador. Cuando se
visualiza con el visualizador de applets, es el visualizador el que proporciona el título y
el borde.
La clase Window crea una ventana de nivel superior que no está contenida en
ningún otro objeto y se encuentra directamente sobre el escritorio. En general, no se
pueden crear objetos de la clase Window directamente. En su lugar se crean objetos
Frame. La clase Frame (marco) es una subclase de Window y tiene una barra de título,
una barra de menú, bordes y esquinas para cambiar el tamaño.
Por último, y aunque no aparezca en la jerarquía de clases mostrada anteriormente,
hay que hablar de la clase Canvas. Esta clase encapsula una ventana vacía sobre la que
se puede dibujar. Es, digamos, una superficie de dibujo.
- 80 -
6-. Interfaces de Usuario.
Aparte de estas clases de alto nivel, existen otras clases que permiten al usuario
interactuar con la aplicación de muy diversas formas. Se denominan “controles” y el
AWT permite los siguientes tipos de controles:
- Etiquetas.
- Botones.
- Cuadros de comprobación.
- Menús de opciones.
- Listas.
- Barras de desplazamiento.
- Edición de texto.
Estos controles son de también subclases de Component. Excepto las etiquetas,
que son controles pasivos, el resto de controles generan eventos cuando el usuario actúa
sobre ellos. Por ejemplo, si el usuario pulsa un botón, se genera un evento que identifica
el botón pulsado. Estos eventos hay que gestionarlos. El modelo de gestión de eventos
del AWT es el mismo que utiliza Swing y será explicado un poco más tarde.
No vamos a explicar mucho más sobre el AWT ya que el proyecto ha sido
desarrollado utilizando el paquete Swing. Sin embargo, es bueno realizar esta pequeña
introducción al AWT para ir entrando en materia e ir comprendiendo un poco qué nos
vamos a encontrar en el paquete Swing.
6.2-. Paquete Javax.swing.
6.2.1-. Origen.
Para comenzar a hablar de Swing, vamos a ver su origen, y para ello nombraremos
el JFC. JFC es la abreviatura de Java Foundation Classes, que comprende un grupo de
características para ayudar a construir interfaces gráficas de usuario (GUIs). El JFC
incluye una serie de características, como pueden ser:
- Los componentes Swing: hablaremos a continuación de ellos. Para comenzar,
diremos que incluye prácticamente todo lo que había en el AWT y más. Por
ejemplo, tenemos desde botones hasta SplitPanes (que dividen la pantalla en
zonas) o tablas.
- Soporte de Aspecto y Comportamiento Conectable: Le ofrece a cualquier
componente Swing una amplia selección de aspectos y comportamientos (look and
feel). Por ejemplo, el mismo programa puede usar el Aspecto y Comportamiento
Java o el Aspecto y Comportamiento Windows.
- API de Accesibilidad: Permite tecnologías asistivas como lectores de pantalla o
displays Braille para obtener información desde el interfaz de usuario.
Estas tres primeras características del JFC fueron implementadas sin ningún
código nativo, es decir, utilizando únicamente la API definido en el JDK 1.1. Cómo
resultado, se convirtieron en una extensión del JDK 1.1. Esta versión fue liberada como
JFC 1.1, que algunas veces es llamada 'Versión Swing'. La API del JFC 1.1 es conocido
como la API Swing. Una curiosidad sobre el nombre: "Swing" era el nombre clave del
proyecto que desarrolló los nuevos componentes. Aunque no es un nombre oficial,
frecuentemente se usa para referirse a los nuevos componentes y al API relacionado.
- 81 -
6-. Interfaces de Usuario.
Está inmortalizado en los nombres de paquete de la API Swing, que empiezan con
"javax.swing". En el JDK 1.2 se incluyeron otras características, como el Java 2D API
que permite a los desarrolladores incorporar fácilmente gráficos 2D de alta calidad,
texto e imágenes, o por ejemplo el soporte de Drag and Drop para “arrastrar y soltar”
entre aplicaciones Java y aplicaciones nativas.
Dada la importancia de Swing, hoy en día no se plantea el tener un JDK sin las
capacidades que proporciona este paquete.
6.2.2-. API Swing.
Swing consta de 16 paquetes, cada uno de los cuales tiene un propósito concreto
que detallamos brevemente a continuación:
-
javax.swing: Es el paquete de más alto nivel, que contiene los componentes,
adaptadores, los modelos por defecto de los componentes, y las interfaces para
todos los modelos.
-
javax.swing.border: Clases e interfaces que se usan para definir estilos de bordes
específicos. Observe que los bordes pueden ser compartidos por cualquier número
de componentes Swing, ya que no son componentes por sí mismos.
-
javax.swing.colorchooser: Contiene clases de soporte para el componente
seleccionador de color.
-
javax.swing.event: Contiene los tipos de eventos y listeners (oyentes) específicos
de Swing. Además, los componentes Swing pueden generar sus propios eventos.
-
javax.swing.filechooser: Contiene clase de soportes para el componente
seleccionador de ficheros.
-
javax.swing.plaf: (PLAF = pluggable look-and-feel) contiene las clases de
Interfaz de Usuario que implementan los diferentes look-and-feel para los
componentes. Éstas están orientadas a desarrolladores que, por una razón u otra,
no pueden usar uno de los look-and-feel existentes.
-
javax.swing.plaf.basic: Consiste en la implementación del Basic look-and-feel,
encima del cual se construyen los look-and- feels que provee Swing. Normalmente
deberemos usar las clases de este paquete si queremos crear nuestro look-and-feel
personal.
-
javax.swing.plaf.metal: Metal es el look-and-feel por defecto de los componentes
Swing. Es el único look-and-feel que viene con Swing y que no está diseñado para
ser consistente con una plataforma específica.
-
javax.swing.plaf.multi: Es el Multiplexing look-and-feel. No se trata de una
implementación normal de look-and-feel ya que no define ni el aspecto ni el
- 82 -
6-. Interfaces de Usuario.
comportamiento de ningún componente. Más bien ofrece la capacidad de
combinar varios look-and-feels para usarlos simultáneamente.
-
javax.swing.table: Contiene las clases e interfaces para soportar el componente
tabla.
-
javax.swing.text: Contiene las clases de soporte para la edición de texto.
-
javax.swing.text.html: Contiene las clases de soporte para crear editores de texto
HTML.
-
javax.swing.text.html.parser: Soporte para analizar gramaticalmente HTML.
-
javax.swing.text.rtf: Contiene soporte para documentos RTF.
-
javax.swing.tree: Clases e interfaces que dan soporte al componente tree. Este
componente se usa para mostrar y manejar datos que guardan alguna jerarquía.
-
javax.swing.undo: El paquete undo contiene soporte para implementar y manejar
la funcionalidad deshacer/rehacer.
6.2.3-. Diferencias entre Swing y AWT.
Para empezar con diferencias sencillas, nos fijaremos en una diferencia sintáctica
que salta a la vista: se pueden identificar los componentes Swing porque sus nombres
empiezan por “J”. Por ejemplo, la clase del AWT que crea un botón se denomina
Button, y la clase Swing se llama JButton. Los componentes AWT están en el paquete
java.awt, mientras que los componentes Swing están en el paquete javax.swing.
Si profundizamos un poco más, podemos ver otras diferencias importantes. La
mayor diferencia entre los componentes AWT y los componentes Swing es que éstos
últimos están implementados sin nada de código nativo. Esto significa que los
componentes Swing pueden tener más funcionalidad que los componentes AWT,
porque no están restringidos a las características presentes en cada plataforma. Esto
también significa que un botón Swing y un área de texto se verán y funcionarán
idénticamente en cualquier plataforma (Macintosh, Solaris, Linux, Windows, etc.).
Las capacidades que tienen los componentes Swing son bastante más importantes
que las que ofrecen los componentes del AWT. Por citar algunos ejemplos:
-
-
Los botones y las etiquetas Swing pueden mostrar imágenes en lugar de o además
del texto.
Se pueden añadir o modificar fácilmente los bordes dibujados alrededor de casi
cualquier componente Swing.
Se puede modificar fácilmente el comportamiento o la apariencia de un
componente Swing llamando a métodos o creando una subclase que herede del
componente en cuestión.
Los componentes Swing no tienen por qué ser rectangulares. Por ejemplo, los
botones pueden ser redondos.
- 83 -
6-. Interfaces de Usuario.
Otra característica importante de Swing es que se puede especificar el Aspecto y
Comportamiento (Look & Feel) que utilice el GUI de nuestro programa (Java o Metal /
Motif / Windows) en función de nuestras preferencias. Por el contrario, los
componentes AWT siempre tienen el aspecto y comportamiento de la plataforma nativa.
Otro detalle interesante es que los componentes Swing con estado usan modelos
para mantener el estado. Por ejemplo, un JSlider (barra de desplazamiento) usa un
objeto BoundedRangeModel para contener su valor actual y un rango de valores legales.
La gran ventaja que esto ofrece es que los modelos se configuran automáticamente, por
eso no tenemos que tratar con ellos, a menos que queramos tomar ventaja de la potencia
que pueden ofrecernos.
Existen una serie de reglas a la hora de utilizar componentes AWT y/o
componentes Swing. Estas reglas son especialmente importantes si se van a utilizar
ambos tipos de componentes simultáneamente.
-
-
-
-
Como regla general, los programas no deberían usar componentne de “peso
pesado” junto con componentes Swing. Los componentes de peso pesado incluyen
todos los componentes AWT listos para usar (como Menu y ScrollPane) y todos
los componentes que desciendan de las clases Canvas y Panel del AWT. Esta
restricción existe porque cuando un componente Swing (u otro componente de
“peso ligero”) se solapa con componentes de peso pesado, éste último siempre se
dibuja encima.
Los componentes Swing no son de “thread seguro”. Si se modifica un componente
Swing visible desde cualquier lugar que no sea el manejador de eventos, hay que
seguir unos pasos especiales para hacer que la modificación se ejecute en el thread
de despacho de eventos. Esto no es ningún problema para la mayoría de los
programas Swing, ya que el código que modifica los componentes normalmente se
encuentra en los manejadores de eventos y estos se ejecutan en el thread de
despacho de eventos. (Veremos estos conceptos de eventos y manejador de
eventos con más detalle en apartados posteriores.).
La herencia de contenidos de cualquier ventana o applet que contenga
componentes Swing debe tener un contenedor de alto nivel Swing como raíz del
árbol. Por ejemplo, una ventana principal debería ser implementada como un
ejemplar de JFrame en vez de como un ejemplar de Frame.
No se añaden directamente los componentes a un contenedor de alto nivel como
un JFrame. En su lugar, se añaden los componentes a un contenedor (llamado
“panel de contenido”) que a su vez está contenido por el JFrame.
6.2.4-. Vista rápida por el código de un programa Swing.
Antes de entrar en más detalle sobre los elementos del paquete Swing, vamos a
echar un vistazo al código de una aplicación Swing. En los siguientes apartados se darán
explicaciones completas sobre los tópicos que aquí se introduzcan, sin embargo es
interesante empezar por tener una visón global.
Veamos la ventana de la aplicación con la que vamos a tratar:
- 84 -
6-. Interfaces de Usuario.
Ilustración 4: Ejemplo de Aplicación Swing.
El funcionamiento es bien sencillo: cada vez que el usuario pulsa el botón, la
etiqueta de la ventana se actualiza. Para llegar a este resultado hay que realizar una serie
de acciones. Comentamos a continuación los detalles más notorios del código
correspondiente a este ejemplo.
-
IMPORTAR PAQUETES SWING:
Los programas Swing necesitan los elementos del paquete principal de Swing que
se importa mediante la línea:
import javax.swing.*;
Sin embargo, no es este el único paquete que necesitaremos por norma general. Es
muy común que los programas Swing también necesiten clases de los paquetes
principales del AWT, ya que Swing utiliza el modelo de eventos de este último. Para
poder utilizarlos se añaden las líneas siguientes:
import java.awt.*;
import java.awt.event.*;
-
ELEGIR EL ASPECTO Y COMPORTAMIENTO:
Como ya dijimos, el aspecto y comportamiento de nuestro programa es
configurable. Para poder elegir el Look and Feel que deseemos tendremos que tratar con
el conocido como “UIManager” o “delegado UI” (UI significa User Interface). Swing
incluye varios conjuntos de delegados UI. Cada conjunto contiene implementaciones de
UI para casi todos los componentes Swing y podemos llamar a estos conjuntos una
implementación de look-and-feel o pluggable look-andfeel (PLAF).
Hay tres implementaciones de pluggable look-and-feel que descienden de Basic
look-and- feel y son:
- Windows: com.sun.java.swing.plaf.windows.WindowsLookAndFeel.
- CDE\Motif: com.sun.java.swing.plaf.motif.MotifLookAndFeel.
- Metal\Multiplataforma (look and feel por defecto): javax.swing.plaf.metal.
MetalLookAndFeel.
-
MacLookAndFeel: Existe también este look-and-feel que simula las interfaces de
usuario de Macintosh, pero no viene con Java 2 y hay que descargarlo por
separado.
Otro detalle a tener en cuenta es que las librerías de los Windows y Macintosh
pluggable look-and-feel sólo se soportan en la plataforma correspondiente.
Realizar el cambio del look-and-feel de una aplicación es bien sencillo.
Únicamente hay que llamar al método setLookAndFeel() de UIManager, pasándole el
- 85 -
6-. Interfaces de Usuario.
nombre completo del LookAndFeel que vamos a usar. El código que se muestra a
continuación se puede usar para llevar esto a cabo en tiempo de ejecución:
try {
UIManager.setLookAndFeel(
"com.sun.java.swing.plaf.motif.MotifLookAndFeel");
SwingUtilities.updateComponentTreeUI(myJFrame);
}catch (Exception e) {
System.err.println("Could not load LookAndFeel");
}
SwingUtilities.updateComponentTreeUI(……) informa a todos los hijos del
componente especificado que el look-and-feel ha cambiado y que necesitan reemplazar
sus delegados UI por los del tipo especificado.
-
CONFIGURAR EL CONTENEDOR DE ALTO NIVEL:
Es fundamental saber que todo programa que presente un GUI Swing contiene al
menos un contenedor de alto nivel. Para la mayoría de los programas, los contenedores
de alto nivel Swing son ejemplares de JFrame, JDialog o JApplet en el caso de las
applets. Un contenedor de alto nivel existe principalmente para proporcionar el soporte
que necesitan los componentes Swing para realizar su dibujado y manejo de eventos.
Para ir conociendo un poco los elementos Swing podemos decir que un objeto JFrame
(frame significa marco en Inglés), implementa una ventana principal, mientras que un
objeto JApplet implementa un área de pantalla de un applet dentro de una ventana del
navegador.
A continuación podemos ver un ejemplo de código en el que tenemos como
contenedor de alto nivel un JFrame, y en el que se especifica que cuando el usuario
cierre el frame, la aplicación finalice.
public class SwingApplication {
...
public static void main(String[] args) {
...
JFrame frame = new JFrame("SwingApplication");
//...crear los componentes que irán en el marco...
//...incluirlos en un contenedor llamado contents...
frame.getContentPane().add(contents,BorderLayout.CENTER);
//Política de cierre de ventana
frame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
//Termina de configurar el frame y lo muestra.
frame.pack();
frame.setVisible(true);
}
- 86 -
6-. Interfaces de Usuario.
}
Como dijimos anteriormente, esto es una ligera introducción para que nos vayan
sonando los conceptos. Muchos de ellos serán ampliamente desarrollados más adelante.
-
CONFIGURAR LOS BOTONES Y LAS ETIQUETAS:
Dado que esto es un ejemplo sencillo, la aplicación sólo contiene un botón y una
etiqueta. El resto de aplicaciones Swing tienen por norma general bastante más
elementos, desde SplitPanes hasta CheckBoxes, pasando por TabbedPanes (paneles de
pestañas) y otros componentes. Veremos diversos componentes más adelante, pero de
momento nos centramos en los del ejemplo. El código que inicializa el botón es el
siguiente:
JButton button = new JButton("I'm a Swing button!");
button.setMnemonic('i');
button.addActionListener(this);
En la primera línea se crea el botón con el texto “I´m a Swing button!”. A
continuación se añade un mnemónico al botón. Este mnemónico se utiliza para poder
simular el click del ratón. En el caso del Look & Feel Metal, o en el Windows,
simularíamos el click del ratón mediante el tecleo de Alt+i (i para este ejemplo ya que
es la letra elegida como mnemónico en el código). La tercera línea añade el manejador
de eventos, veremos más adelante qué son y para qué sirven los eventos.
El código para la etiqueta lo podemos ver a continuación:
// en el lugar de declaración de las variables de instancia::
private static String labelPrefix = "Number of button clicks: ";
private int numClicks = 0;
//en el código de inicialización del GUI:
final JLabel label = new JLabel(labelPrefix + "0
");
//en el manejador de eventos de las pulsaciones de botón:
label.setText(labelPrefix + numClicks);
Las primeras líneas son fáciles de interpretar, y la creación de la etiqueta es
bastante similar a la del botón. Sólo nos quedaría añadir al manejador de eventos la
última línea en la que numClicks es una variable contador que irá acumulando el
número de clicks que realicemos.
-
AÑADIR COMPONENTES A LOS CONTENEDORES:
Si volvemos a mirar la imagen de la aplicación de ejemplo, vemos que la etiqueta
y el botón están dentro de un marco o Frame. Sin embargo, los componentes no se
pueden añadir directamente al frame, sino que hay que añadirlos antes a un contenedor.
En este caso se ha utilizado un JPanel.
JPanel pane = new JPanel();
- 87 -
6-. Interfaces de Usuario.
pane.setBorder(BorderFactory.createEmptyBorder(30, 30, 10, 30));
pane.setLayout(new GridLayout(0, 1));
pane.add(button);
pane.add(label);
Primero creamos el JPane, para a continuación decir qué tipo de borde y que
controlador de distribución queremos. El borde simplemente proporciona un espacio en
blanco alrededor del panel. Los números son medidas en pixels.
En la tercera línea entra en juego el controlador de distribución. Éste fuerza al
contenido del panel a dibujarse en una sola columna en este caso, por haber elegido
GridLayout. Las dos últimas líneas añaden el botón button y la etiqueta label al panel.
Esto significa que tanto el botón como la etiqueta serán controlados por el controlador
de distribución que hemos asignado al panel, y será éste quien determine el tamaño y
posición de cada componente.
-
MANEJAR EVENTOS:
En el código que hemos visto hasta ahora hemos encontrado dos manejadores de
eventos. Uno maneja las pulsaciones de botón y otro maneja los eventos de cierre de la
ventana o frame. Desarrollaremos este punto con detalle en capítulos posteriores, pero
veamos el código del ejemplo que corresponde al manejo de eventos (en este caso se
definen en clases internas anónimas):
//Manejador de eventos para el botón .
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
numClicks++;
label.setText(labelPrefix + numClicks);
}
});
//Manejador de eventos para el cierre de ventana.
frame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
-
TRATAR CON LOS THREADS:
El programa de ejemplo es de thread seguro. Una vez q que su GUI es visible, en
el manejador de eventos sólo ocurre manipulación del GUI (actualizar la etiqueta).
Como el manejador de eventos se ejecuta en el mismo thread que realiza todo el manejo
de eventos y pintado de la aplicación, no existe la posibilidad de que dos threads
intenten manipular el GUI a la vez. Sin embargo, es fácil introducir problemas de
threads en un programa Swing. Más adelante veremos algunos apartados sobre los hilos
en Swing.
- 88 -
6-. Interfaces de Usuario.
6.2.5-. Estructura básica de una aplicación Swing.
En la aplicación de ejemplo hemos presentado algún componente Swing. En esta
sección presentaremos con más detalles esos componentes así como otros componentes
muy utilizados en Swing.
Los elementos que utilizamos en el ejemplo eran: un frame, o ventana principal
(ejemplar de la clase JFrame), un panel, algunas veces llamado “pane” (clase JPanel),
un botón (clase JButton) y una etiqueta (clase JLabel). Con este ejemplo vamos a poder
estudiar bien la anatomía general de un programa Swing.
El frame es un contenedor de alto nivel. Como ya explicamos anteriormente,
existe principalmente para proporcionar espacio para que se dibujen otros componentes
Swing. En un programa Swing debe haber siempre un Contenedor de Alto Nivel para
dar soporte al resto de componentes. Los contenedores de alto nivel más utilizados son:
- javax.swing.JFrame: una ventana independiente.
- javax.swing.JApplet: un applet
- Diálogos: ventanas de interacción sencilla con el usuario, como por ejemplo:
o javax.swing.JOptionPane:
ventana
de
diálogo
tipo
SI/NO,
SI/NO/ACEPTAR/CANCELAR, etc.
o javax.swing.JFileChooser: ventana para elegir un archivo.
o javax.swing.JColorChooser: ventana para elegir colores.
El panel es un contenedor intermedio. Los Contenedores Intermedios tienen el
único cometido de simplificar el posicionamiento de los otros componentes, en nuestro
ejemplo del botón y la etiqueta. JPanel es un contenedor intermedio que no proporciona
ninguna característica especial. Otros contenedores intermedios, como los paneles
desplazables (JScrollPane) y los paneles con pestañas (JTabbedPane), juegan un papel
más visible e interactivo en el GUI de un programa.
Por último llegamos a los Componentes Atómicos, que serían el botón y la
etiqueta de nuestro ejemplo. Son componentes que existen no para contener otros
componentes Swing como era el caso de los Contenedores Intermedios y de Alto Nivel,
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), checkBoxes para elegir opciones (JCheckBox), campos de texto
(JTextField), y tablas (JTable).
Veamos un diagrama de árbol que nos muestra la anatomía del programa. Es
importante comentar que si añadimos una ventana -- por ejemplo, un diálogo -- la nueva
ventana tendría su propio árbol de contenidos, independiente del mostrado en esta
figura.
JFrame (contenedor de alto nivel)
|
(Panel de contenido)
|
JPanel (contenedor intermedio)
|
+----------------+
|
|
JButton
JLabel
- 89 -
6-. Interfaces de Usuario.
Incluso el programa Swing más sencillo tiene múltiples niveles en su árbol de
contenidos. La raíz del árbol de contenidos es siempre un contenedor de alto nivel que
proporciona espacio para que los componentes Swing descendentes se dibujen. Todo
contenedor de alto nivel contiene un contenedor intermedio conocido como “panel de
contenido”. Cómo regla general, el panel de contenido contiene todos los componentes
visibles en el GUI de la ventana. Sin embargo, existe una gran excepción a esta regla y
es la siguiente: si el contenedor de alto nivel tiene una barra de menú (JMenuBar),
entonces ésta se sitúa en un lugar especial fuera del panel de contenido.
Como curiosidad comentar que se puede ver el árbol de contenidos de cualquier
frame o diálogo, pulsando el borde para seleccionarlo y pulsando a continuación
Control-Shift-F1. Se escribirá una lista con el árbol de contenidos en el flujo de salida
estándar.
Hemos dicho que unos componentes contienen a otros pero, ¿cómo se añaden los
componentes a un contenedor? Pues es algo tan sencillo como utilizar el método add
(añadir en inglés) en sus distintas formas. Este método tendrá siempre como argumento
al menos el componente añadir, aunque dependiendo del método add que utilicemos
podemos encontrar otros argumentos adicionales que especifiquen por ejemplo, el lugar
de colocación del elemento a añadir. Concluyamos esta explicación con código:
frame = new JFrame(...);
pane = new JPanel();
button = new JButton(...);
label = new JLabel(...);
pane.add(button);
pane.add(label);
frame.getContentPane().add(pane, BorderLayout.CENTER);
Con el código anterior creamos el contenedor de alto nivel (frame), el intermedio
(pane) y los elementos (button y label). Añadimos al panel el botón y la etiqueta
mediante pane.add(…), y a continuación lo añadimos al contendor de alto nivel el panel.
Más concretamente, la última línea especifica que el panel debería estar en el centro
(CENTER) de su contenedor (el panel de contenido).
6.2.6-. Control de Distribución.
Las ilustración 5 muestra los GUIs de cinco programas, cada uno de ellos con
cinco botones. Los botones son idénticos, y el código de los programas es también
prácticamente idéntico. Entonces, ¿por qué parecen tan diferentes? Simplemente porque
usan diferentes controladores de distribución para controlar el tamaño y posición de los
botones.
- 90 -
6-. Interfaces de Usuario.
Ilustración 5: Controladores de Distribución.
Control de Distribución es el proceso de determinar el tamaño y posición de los
componentes. Por defecto, cada contenedor tiene un controlador de distribución, es
decir, un objeto que realiza el control de la distribución de los componentes que se
alojen dentro del contenedor. Los componentes pueden indicarle al controlador sus
preferencias de tamaño y alineamiento, pero es finalmente el controlador el que decidirá
la posición final del componente, es él quien tiene la última palabra.
Existen cinco tipos de layout estándares en Java Swing:
-
BorderLayout
BoxLayout
FlowLayout
GridLayout
GridBagLayout
En la figura anterior se muestra cómo cada uno de ellos mostraría diferentes
componentes. Existe una sexta clase, CardLayout, que es un controlador de distribución
de carácter general que se utiliza en combinación con otros controladores de
distribución.
Anteriormente hablamos del método add. Siempre que se use el método add para
poner un componente en un contenedor, debemos tener en cuenta el controlador de
distribución del contenedor con el que estamos tratando. En general los controladores de
distribución colocarán los componentes en el orden que se han ido añadiendo. Sin
embargo, algunos controladores como BorderLayout requieren que especifiquemos la
posición relativa del componente en el contenedor, usando un argumento extra para el
método add. También hay otros como GridBagLayout que requieren elaborados
- 91 -
6-. Interfaces de Usuario.
procesos de configuración. Generalmente, sólo se tendrá que seleccionar el controlador
de distribución de dos tipos de contenedores: paneles de contenido (que usan
BorderLayout por defecto) y JPanel (que usa FlowLayout por defecto).
Tras esta introducción a los Controladores de Distribución vamos a ver algunas de
las tareas más comunes para ellos:
-
SELECCIONAR EL CONTROLADOR DE DISTRIBUCIÓN:
Se puede cambiar el controlador que utiliza un contenedor por defecto. Sólo hay
que llamar al método setLayout del contenedor. Por ejemplo, para hacer que un JPanel,
que por defecto usa FlowLayout, utilice BorderLayout habría que escribir lo siguiente:
JPanel pane = new JPanel();
pane.setLayout(new BorderLayout());
Aunque es recomendable utilizar controladores de distribución, se puede realizar
la distribución sin ellos. Seleccionando una propiedad de distribución del contenedor
inicializada a nulo, podemos hacer que el contenedor no use ningún controlador de
distribución. Esta técnica se denomina “posicionamiento absoluto” y con ella se puede
especificar el tamaño y posición de cada componente dentro del contenedor. Una
desventaja del posicionamiento absoluto es que no se ajusta bien cuando se
redimensiona el contenedor de alto nivel. Tampoco se ajusta bien a las diferencias
entres usuarios y sistemas, ni a los diferentes tamaños de fuente.
-
PROPORCIONAR CONSEJOS SOBRE UN COMPONENTE:
Como ya hemos comentado, un componente puede decirle al controlador de
distribución sus preferencias de tamaño y alineamiento, pero es el controlador de
distribución el que decide en última instancia qué hacer. Para personalizar estas
preferencias del componente podemos configurar sus tamaños máximo, mínimo y
preferido mediante los métodos de selección de tamaño del componente que son
setMinimumSize, setPreferredSize y setMaximumSize. Sin embargo esto no tiene por
qué dar el resultado que desearíamos: actualmente, el único controlador de distribución
en la plataforma Java que presta atención a la petición de tamaño máximo del
componente es BoxLayout.
También existen métodos para especificar preferencias de alineamiento
(setAlignmentX y setAlignmentY). Al igual que para el tamaño, BoxLayout es el único
controlador de distribución que presta atención a los consejos de alineamiento.
-
PONER ESPACIO ENTRE COMPONENTES:
Hay tres factores que influyen en la cantidad de espacio entre componentes
visibles en un GUI:
o El controlador de distribución: Algunos ponen espacio entre componentes
automáticamente, otros no. Algunos permiten especificar la cantidad de
espacio que queremos dejar entre los componentes.
o Los Componentes Invisibles: Son componentes de peso ligero que ocupan
espacio en el GUI, pero no realizan ningún dibujo. Podemos por tanto crear un
componente invisible si queremos dejar un espacio en nuestro GUI.
o Bordes Vacíos: Es otra de las opciones para añadir espacio entre componentes.
Consiste en algo tan sencillo como añadir bordes vacíos. Los mejores
candidatos para los bordes vacíos son los que típicamente no tienen bordes,
- 92 -
6-. Interfaces de Usuario.
como los paneles y las etiquetas. Sin embargo, hay algunos otros
componentes, como los paneles desplazables, que no funcionan bien con
bordes en algunas implementaciones del Aspecto y Comportamiento, debido a
la forma en que implementan su código de dibujo.
-
CÓMO OCURRE EL CONTROL DE DISTRIBUCIÓN:
Mostramos un ejemplo de secuencia de control de distribución para un frame
(JFrame). Es interesante leerlo por tener una idea de cómo funciona por dentro nuestro
GUI, pero realmente son detalles de bajo nivel de los que, por lo general, no tendremos
que ocuparnos nosotros.
1. Después de que el GUI esté construido, se llama al método pack sobre el
JFrame. Esto especifica que el frame debería ser de su tamaño preferido.
2. Para encontrar el tamaño preferido del frame, el controlador de distribución
añade el tamaño de los lados del frame al tamaño preferido del componente
directamente contenido por el frame (en nuestro ejemplo inicial este elemento
sería el JPanel). Esto es: la suma del tamaño preferido del panel de contenido
más el tamaño de la barra de menú del frame, si existe.
3. El controlador de disposición del panel de contenido es responsable de
imaginarse el tamaño preferido del panel de contenido. Por defecto, este
controlador de disposición es un objeto BorderLayout. Sin embargo, asumamos
que lo hemos reemplazado con un objeto GridLayout que se ha configurado para
crear dos columnas. Lo interesante de Gridlayout es que fuerza a que todos los
componentes sean del mismo tamaño, e intenta hacerlos tan anchos como la
anchura preferida del componente más ancho, y tan altos como la altura
preferida del componente más alto. Primero, el controlador Gridlayout pregunta
al panel de contenido por su insets (el tamaño del borde del panel de contenido),
si existe. Luego, el controlador de Gridlayout le pregunta a cada componente del
panel de contenido sus tamaños preferidos, anotando la mayor anchura preferida
y la mayor altura preferida. Por último calcula el tamaño preferido del panel de
contenido.
4. Cuando a cada botón se le pide su tamaño preferido, el botón primero
comprueba si el usuario ha especificado un tamaño preferido. Si es así, reporta
este tamaño. Si no es así, le pregunta a su Aspecto y Comportamiento el tamaño
preferido por defecto.
El resultado final es que para determinar el mejor tamaño de un frame, el sitema
determina los tamaños de los contenedores en la parte inferior del árbol de contenidos.
De forma similar ocurren los cálculos cuando se redimensiona el frame.
6.2.7-. Manejo de Eventos.
Cada vez que el usuario teclea un carácter o pulsa un botón del ratón, ocurre un
evento. Cualquier componente puede ser notificado del evento. Todo lo que tiene que
hacer es implementar el interfaz apropiado y ser registrado como “oyente de evento” del
evento fuente apropiado.
Existen muchos tipos de eventos diferentes en Swing. En la siguiente tabla se
muestran algunos ejemplos:
- 93 -
6-. Interfaces de Usuario.
Acción que resulta en el evento
Tipo de oyente
El usuario pulsa un botón, presiona Return mientras teclea en un
ActionListener
campo de texto, o elige un ítem de un menú.
El usuario selecciona una ventana principal.
WindowListener
El usuario pulsa un botón del ratón mientras el cursor está sobre
MouseListener
un componente.
El usuario mueve el cursor sobre un componente.
MouseMotionListener
El componente se hace visible.
ComponentListener
El componente obtiene el foco del teclado.
FocusListener
Cambia la tabla o la selección de una lista.
ListSelectionListener
Tabla 6: Eventos y Oyentes Asociados en Swing.
Cada evento está representado por un objeto que ofrece información sobre el
evento e identifica la fuente (es decir, quién produjo dicho evento). Las fuentes de los
eventos normalmente son componentes, pero otros tipos de objetos también pueden ser
fuente de eventos.
Cada fuente de eventos puede tener varios oyentes registrados, es decir, varios
objetos a la vez pueden estar escuchando una fuente de eventos, y serán notificados
cuando un se produzca un evento en esta fuente. Inversamente, un oyente puede
registrarse con varias fuentes de eventos. Mostramos gráficamente lo explicado:
Objeto evento
/----> oyente de evento
Fuente de evento -----------------------------------> oyente de evento
\----> oyente de evento
Objeto evento
/----> fuente de evento
Oyente de evento -----------------------------------> fuente de evento
\----> fuente de evento
-
COMO IMPLEMENTAR UN MANEJADOR DE EVENTOS:
Todo manejador de eventos requiere tres partes de código.
1. La clase del manejador: debe o bien implementar una interfaz de oyente, o
bien descender de una clase que implementa una interfaz del oyente en
cuestión. Por ejemplo:
public class MyClass implements ActionListener {
- 94 -
6-. Interfaces de Usuario.
2. El código que registra sobre uno o más componentes un ejemplar de la clase
de manejo de eventos de un oyente. Por ejemplo:
someComponent.addActionListener(instanceOfMyClass);
En este código se está registrando una instancia de la clase de manejo de
eventos (MyClass, definida en el punto 1) sobre el componente de nombre
someComponent.
3. En el caso en que en el primer punto hayamos decidido implementar el
interfaz, ahora es necesaria la implementación de los métodos del interfaz
oyente. Por ejemplo:
public void actionPerformed(ActionEvent e) {
...//código que reaccione a la acción...
}
Veamos un ejemplo en el que iremos indicando los pasos descritos anteriormente.
Imaginemos un programa que tiene botones (JButton). El usuario pulsará el botón de la
pantalla (o bien las teclas equivalentes al botón). Para poder detectar esta pulsación, el
programa debe tener algún objeto que implemente el interfaz ActionListener (que es el
correspondiente al evento “pulsación de botón”). Este será nuestro “manejador” u
“oyente de action” (PASO 1).
El programa debe registrar este manejador como un oyente de Action del botón
(PASO 2). Cuando el usuario pulse el botón, se producirá el evento Action, y será
notificado a todos los elementos que están registrados a este tipo de evento de esta
fuente, en este caso nuestro manejador. Esto resulta en una llamada al método
ActionPerformed del oyente de action, que en este caso particular es el único método
del interfaz ActionListener. El único argumento del método es un objeto ActionEvent,
que representa al evento ocurrido y nos proporciona información tanto del evento como
de su fuente.
Es decir, cuando el usuario pulsa un botón, se produce un evento ActionEvent y
los oyentes de action del botón son notificados. Gráficamente:
ActionEvent
button ---------------------------------> action listener
Los manejadores de eventos pueden ser ejemplares de cualquier clase.
Frecuentemente, se implementan usando clases internas anónimas. Son clases sin
nombre definidas dentro de otras clases. Aunque las clases internas puedan hacer el
código algo más confuso y parezcan difíciles de leer, realmente hacen el código mucho
más fácil de entender, una vez que se han utilizado. Manteniendo una implementación
de un manejador de eventos cerca de donde se registra el manejador de eventos, las
clases internas ayudan tanto al programador como a los que siguen su código a
encontrar fácilmente la implementación completa del manejador de eventos.
-
LOS THREADS Y EL MANEJO DE EVENTOS:
El código de manejo de eventos se ejecuta en un sólo thread: el thread de despacho
de eventos. Esto asegura que todo manejador de eventos se terminará de ejecutar antes
- 95 -
6-. Interfaces de Usuario.
de ejecutar otro. Es más, el código de dibujo también se realiza el thread de despacho de
eventos y sigue la misma norma. Esto significa que, mientras se está ejecutando por
ejemplo el método actionPerformed de la interfaz ActionListener citada anteriormente,
el GUI del programa permanecerá congelado: no se dibujará nada ni tampoco se
responderá a otros eventos como pulsaciones de ratón.
Hay que tener cuidado con el manejo de eventos. El código de manejo de eventos
debería poder ejecutar cada pulsación, cada click de ratón, cada evento en resumen. De
otro modo el rendimiento del programa se verá empobrecido. Si fuese necesario realizar
una larga operación como consecuencia de un evento, la opción más acertada es
arrancar un nuevo thread que realice dicha operación.
-
LOS ADAPTADORES:
La mayoría de los interfaces de oyentes, al contrario que ActionListener,
contienen más de un método. Por ejemplo, el interface MouseListener contiene cinco
métodos: mousePressed, mouseReleased,
mouseEntered, mouseExited,
y
mouseClicked. Incluso si sólo te importan las pulsaciones, si tu clase implementa
directamente la interfaz MouseListener, entonces debes implementar los cinco métodos
de MouseListener. Aquellos métodos de eventos que no te interesan pueden tener los
cuerpos vacíos. Aquí hay un ejemplo:
public class MyClass implements MouseListener {
...
someObject.addMouseListener(this);
...
// Definiciones vacías de métodos:
public void mousePressed(MouseEvent e) { }
public void mouseReleased(MouseEvent e) { }
public void mouseEntered(MouseEvent e) { }
public void mouseExited(MouseEvent e) { }
public void mouseClicked(MouseEvent e) {
//Supongamos que solo nos interesa este evento.
//La implementación iría aquí.
}
}
Desafortunadamente, la colección de cuerpos de métodos vacíos resultante puede
resultar dura de leer y de mantener. Para ayudarnos a evitar este emborronamiento del
código con cuerpos de métodos vacíos, el AWT y Swing proporcionan una clase adapter
por cada interfaz de oyente con más de un método. Por ejemplo, la clase MouseAdapter
implementa el interfaz MouseListener. Una clase adaptador implementa versiones
vacías de todos los métodos del interfaz.
Para usar un adaptador se crea una subclase, en vez de implementar directamente
una interfaz de oyente. Por ejemplo, extendiendo la clase MouseAdapter, nuestra clase
hereda definiciones vacías para los métodos que contiene MouseListener. Por ejemplo:
public class MyClass extends MouseAdapter {
...
someObject.addMouseListener(this);
- 96 -
6-. Interfaces de Usuario.
...
public void mouseClicked(MouseEvent e) {
//Implementación del manejador para este evento.
}
}
¿Qué pasa si no queremos que nuestras clases de manejo de eventos desciendan de
una clase adaptador? Por ejemplo, supongamos que escribimos un applet, y queremos
que nuestra subclase Applet contenga algún método para manejar eventos de ratón.
Como el lenguaje Java no permite la herencia múltiple, nuestra clase no puede
descender de las clases Applet y MouseAdapter a la vez. La solución es definir una
clase interna (una clase dentro de nuestra subclase Applet) que descienda de la clase
MouseAdapter, como se muestra a continuación.
//Ejemplo de utilización de clase interna.
public class MyClass extends Applet {
...
someObject.addMouseListener(new MyAdapter());
...
class MyAdapter extends MouseAdapter {
public void mouseClicked(MouseEvent e) {
//Implementación del manejador para este evento.
}
}
Otro ejemplo de uso de clases internas, en este caso con una clase interna
anónima:
public class MyClass extends Applet {
...
someObject.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
//Implementación del manejador para este evento.
}
});
...
}
}
Las clases internas funcionan bien incluso si nuestro manejador de eventos
necesita acceder a ejemplares de variables privadas de la clase que la encierra, aunque
algunos compiladores no permiten el acceso a dichas variables privadas. Un atajo es
eliminar el especificador private de la declaración del ejemplar de la variable. Siempre
que no declaremos una clase interna como static, se podrá referir a ejemplares de
variables y métodos como lo hace el resto de código que contiene la clase.
- 97 -
6-. Interfaces de Usuario.
6.2.8-. Dibujo.
En principio no es necesario conocer todos los detalles de dibujo del GUI pues el
programador no tiene que encargarse prácticamente de nada con respecto a este aspecto.
Sin embargo, puede ser interesante tener ciertas nociones para, en caso de que nuestros
componentes no se dibujen correctamente, ser capaces de entender qué hay de erróneo
en nuestra implementación. También es útil lo que se explica a continuación si
queremos crear código de dibujo personalizado para un componente.
-
CÓMO FUNCIONA EL DIBUJO:
Cuando un GUI necesita dibujarse a si mismo (al iniciarlo, en respuesta a la vuelta
de un ocultamiento, o porque necesite reflejar un cambio en su estado), empieza con el
componente más alto del árbol de contenidos que necesite ser redibujado, y va
descendiendo. Todo esto lo controla el sistema de dibujo del AWT, y se ha hecho más
eficiente mediante el manejador de dibujo de Swing que utiliza un doble buffer.
Al igual que el código de manejo de eventos, el código de dibujo se ejecuta en el
thread del despacho de eventos. Mientras se esté manejando un evento no ocurrirá
ningún dibujo. De forma similar, si la operación de dibujado tarda mucho tiempo, no se
manejará ningún evento durante ese tiempo
Los componentes Swing generalmente se redibujan a sí mismos siempre que es
necesario. Por ejemplo, cuando llamamos al método setText de un componente, el
componente debería redibujarse automáticamente a sí mismo, y si es necesario,
redimensionarse. Si no lo hace así es un bug. El atajo es llamar al método repaint sobre
el componente para pedir que el componente se ponga en la cola para redibujado.
Los programas sólo deberían dibujarse cuando el sistema de dibujo se lo diga.
Esto es así porque cada ocurrencia de dibujo de un componente debe ejecutarse sin
ningún tipo de interrupción. Si no se hace así, podríamos tener resultados impredecibles
e indeseados, como por ejemplo un botón que se dibuje medio pulsado y medio
liberado.
Para acelerar y mejorar el rendimiento, el dibujo Swing usa doble-buffer por
defecto. ¿En qué consiste este doble buffer? Se realiza el dibujo en un buffer fuera de
pantalla y luego se lanza a la pantalla una vez finalizado. Otra opción para ayudar al
rendimiento es hacer un componente Swing opaco. Así, el sistema de dibujo de Swing
conocerá lo que no tiene que pintar detrás del componente. Para hacer opaco un
componente Swing, se llama al método setOpaque(true) sobre el componente.
Los componentes no-opacos de Swing puede parecer que tienen cualquier forma,
aunque su área de dibujo disponible es siempre rectangular. Por ejemplo, un botón
podría dibujarse a sí mismo dibujando un octógono relleno. El componente detrás del
botón, (su contenedor, comúnmente) sería visible, a través de las esquinas de los lados
del botón. El botón podría necesitar incluir código especial de detección para evitar que
ocurra un evento Action cuando el usuario pulsa en las esquinas del botón.
-
UN EJEMPLO DE DIBUJO:
Si volvemos a nuestra aplicación de ejemplo, que no contenía nada más que un
botón y una etiqueta, podemos describir cómo ocurre su dibujo.
- 98 -
6-. Interfaces de Usuario.
1. El contenedor de alto nivel, JFrame, se dibuja as sí mismo.
2. El panel de contenido primero dibuja su fondo, que es un rectángulo sólido de
color gris. Luego le dice al JPanel que se dibuje el mismo. El rectángulo del
panel de contenido realmente no aparece en el GUI finalizado porque está
oscurecido por el JPanel. Es importante que el panel de contenido sea opaco.
De otro modo, resultará en dibujados confusos. Como un JPanel es opaco,
podemos hacer que sea el panel de contenido (utilizando getContentPane().
add). Esto simplifica considerablemente el árbol de contenidos y el dibujado,
eliminado un contenedor innecesario.
3. El JPanel primero dibuja su fondo, un rectángulo sólido de color gris. Luego
dibuja su borde. El borde es un EmptyBorder, que no tendrá efecto excepto
para incrementar el tamaño del JPanel reservando algún espacio extra en los
laterales del panel como ya explicamos cuando hablamos del espacio entre
componentes. Finalmente, el panel le pide a sus hijos, es decir, a los
componentes que descienden de él en el árbol que se dibujen a sí mismos.
4. Para dibujarse a sí mismo, el JButton dibuja su rectángulo de fondo si es
necesario y luego dibuja el texto que contiene. Si el botón tiene el foco del
teclado, significa que cualquier cosa que se teclee va directamente al botón
para su procesamiento (el botón realiza algún dibujado característico para
aclarar que tiene el foco, específico del Aspecto y Comportamiento que se esté
utilizando).
5. Para dibujarse a sí misma, la etiqueta JLabel dibuja su texto.
Resumiendo, un componente se dibuja a si mismo antes de dibujar a los
componentes que contenga. De este modo nos aseguramos que una zona del fondo de
un JPanel sólo queda dibujada si no tiene ningún componente hijo que la cubra. La
siguiente figura ilustra el orden en que cada componente que desciende de JComponent
se dibuja a sí mismo.
1. fondo
(si es opaco)
2. dibujo
3. borde
personalizado
(si existe)
(si existe)
4. hijos
(si existen)
.............
.............
.............
.............
.............
.............
.............
....().......
.............
.............
.............
.............
=============
=...()......=
=.---------.=
=.|JButton|.=
=.---------.=
=============
=============
=...()......=
=...........=
=...........=
=...........=
=============
Ilustración 6: Orden en el dibujo de una aplicación Swing.
6.2.9-. Los threads y Swing.
Hasta ahora hemos nombrado el thread de despacho de eventos. También hemos
hablado de “programas de thread seguro”. Vamos a intentar aclarar un poco todo esto.
Si nuestro programa es un applet, lo más seguro es construir el GUI en el método init.
Por el contrario, si nuestro programa es una aplicación, podemos usar el siguiente
patrón común para estar seguros con respecto a los hilos:
- 99 -
6-. Interfaces de Usuario.
//Ejemplo de hilo seguro
public class MyApplication {
public static void main(String[] args) {
JFrame f = new JFrame(...);
...//Añadir componentes al frame aquí.
f.pack();
f.setVisible(true);
//No realizar más trabajo para la GUI aquí.
}
...
//Toda la manipulación de la GUI (setText, getText, etc.)se realiza
//en manejadores de eventos como por ejemplo actionPerformed().
...
}
Existe una regla para tratar con los hilos en Swing:
“Una vez que se haya realizado un componente Swing, todo el código que pudiera
afectar o depender del estado de ese componente debería ejecutarse en el thread de
despacho de eventos.”
Esta regla puede parecer complicada a primera vista, pero para la mayoría de
programas sencillos no tenemos que preocuparnos de los threads. Es importante definir
un par de términos antes de continuar la exposición: “realizado” y “thread de despacho
de eventos”.
Realizado significa que el método paint del componente haya sido o podría ser
llamado. Un componente Swing que sea una ventana de alto nivel se realiza habiendo
llamado a uno de estos métodos sobre ella: setVisible(true), show, o pack. Una vez que
una ventana se ha realizado, todos los componentes que contiene están realizados. Otra
forma de realizar un componente es añadirlo a un componente que ya esté realizado.
El thread de despacho de eventos es el thead que ejecuta el código de dibujo y
de manejo de eventos. Por ejemplo, los métodos paint y actionPerformed se ejecutan
automáticamente en el thread de despacho de eventos. Otra forma de ejecutar código en
el thread de despacho de eventos es usar el método invokeLater de SwingUtilities
Como para casi toda regla, existen algunas excepciones. Existen algunos métodos
que se denominan “de thread seguro”. Podemos ver qué métodos son éstos en la
documentación de la API Swing, ya que los métodos de “thread seguro” están marcados
con dicho texto.
Podemos añadir otra excepción que dice: “La GUI de una aplicación
frecuentemente puede ser construida y mostrada en el thread principal.”. Mientras no se
haya realizado ningún componente, no hay problema en construir y mostrar un GUI en
el hilo main. De hecho, en general se puede construir (pero no mostrar) un GUI en
cualquier thread, mientras no se hagan llamadas que se refieran o afecten a los
componentes ya realizados. Veámoslo en el ejemplo de hilo seguro anterior.
1. El ejemplo construye el GUI en el thread principal.
2. Los componentes del GUI son realizados por la llamada a pack.
3. Según la regla, “una vez realizado un componente Swing, todo el código que
pudiera afectar o depender del estado de ese componente debería ejecutarse en
el thread de despacho de eventos.” Es decir, la llamada a setVisible, con la que
- 100 -
6-. Interfaces de Usuario.
son mostrados los componentes del GUI, es técnicamente insegura ya que los
componentes han sido realizados con la llamada a pack y se está ejecutando
este setVisible fuera del thread de despacho de eventos. Sin embargo, como el
programa no ha hecho visible el GUI todavía, es sumamente contrario a que
una llamada a paint ocurra antes de que retorne setVisible.
4. El thread principal no ejecuta código GUI después de llamar a setVisible. Esto
significa que el código del GUI se mueve del thread principal al thread de
despacho de eventos, y el ejemplo es, en la práctica, de thread seguro.
Si queremos ejecutar código en el thread de despacho de eventos, pero no desde
algún método de un Oyente, sino desde otros lugares de nuestro programa, podemos
utilizar unos métodos de la clase SwingUtilities:
- invokeLater: Pide que algún código se ejecute en el thread de despacho de
eventos. Este método retorna inmediatamente, sin esperar a que el código sea
ejecutado.
- invokeAndWait: Actúa igual que invokeLater, excepto en que este método espera
a que el código se ejecute. Como regla, deberíamos usar invokeLater en vez de
este método.
Si no hablamos de aplicaciones sino de Applets, la técnica para construir un GUI
de hilo seguro consiste en construir y mostrar el GUI en el método init. Los navegadores
existentes no dibujan el applet hasta después de que hayan sido llamados los métodos
init y start. Así, construir el GUI en el método init del applet es seguro, siempre que no
se llame a show() o setVisible(true) sobre el objeto applet actual. Por supuesto, los
applets que usan componentes Swing deben ser implementados como subclases de
JApplet, y los componentes deben ser añadidos al panel de contenido del JApplet, en
vez de directamente al JApplet, es decir, el procedimiento es similar al explicado para
las aplicaciones. Al igual que para cualquier applet, nunca deberíamos realizar
inicialización que consuma mucho tiempo en los métodos init o start; en su lugar
deberíamos arrancar un thread que realice las tareas que consuman tiempo.
6.2.10-. Más características Swing.
-
Características adquiridas de JComponent: Todos los componentes Swing cuyo
nombre empieza por J, salvo los contenedores de alto nivel, descienden de la clase
JComponent y por tanto obtienen muchas características de esta clase. Por
ejemplo, la posibilidad de tener bordes, tooltips (texto de ayuda que aparece al
dejar el ratón quieto sobre un componente durante algunos segundos) y Look &
Feel configurable.
-
Iconos: Muchos componentes Swing, principalmente los botones y las etiquetas,
pueden mostrar algún tipo de dibujo o imagen. Para ello se utilizan objetos de la
clase Icon.
-
Actions: Estos objetos permiten compartir datos y estados entre dos o más
componentes que puedan generar eventos de tipo Action. Un ejemplo: un botón e
- 101 -
6-. Interfaces de Usuario.
un item de menú que realicen la misma función pueden ser implementados con
este tipo de objeto.
-
Aspecto y Comportamiento Conectable: Un programa, aunque sea muy sencillo,
puede tener uno o varios Aspectos y Comportamientos diferentes. Es más,
podemos permitir que el usuario los determine, o determinarlos nosotros
directamente mediante la programación.
-
Soporte para tecnologías asistivas: Ya hemos nombrado esta característica Swing.
No entraremos mucho más en detalle de lo visto hasta ahora, pero merece la pena
recordar que las tecnologías asistivas, como por ejemplo un lector de pantalla,
también pueden usar la API Swing. La utilización de tecnologías asistivas puede
expandir sin duda el mercado de nuestro programa.
-
Modelos de Datos y Estados separados: La mayoría de los objetos Swing que no
son contenedores tienen un “modelo”. Por ejemplo, un botón (JButton) tiene un
modelo (ButtonModel) que almacena el estado del botón: su mnemónico de
teclado, si está activado/seleccionado/pulsado, etc. Otros componentes como JList
tienen múltiples modelos: un ListModel para almacenar los contenidos de la lista y
un ListSelectionModel que sigue la pista de la selección actual de la lista. En
general no es necesario conocer los modelos, de hecho hay muchas veces en las
que ni se utilizan. Sin embargo, existen porque ofrecen la posibilidad de trabajar
con componentes más eficientemente y de compartir fácilmente datos y estados
entre componentes.
6.2.11-. Sobre los componentes de texto en Swing.
Dado que la aplicación que se desarrolla en este proyecto fin de carrera es una
herramienta para la edición de un tipo concreto de documento, debemos prestar especial
atención a los Componentes de Texto de Swing en esta memoria.
Los componentes de texto muestran algún texto y opcionalmente permiten que el
usuario lo edite. Los programas necesitan componentes de texto para tareas dentro del
rango del sencillo (introducir una palabra y pulsar Enter) al complejo (mostrar y editar
texto con estilos y con imagenes embebidas en un lenguaje asiático). Los paquetes
Swing proporcionan cinco componentes de texto y proporcionan clases e interfaces para
conseguir los requerimientos más complejos. Sin importar sus diferentes usos o
capacidades, todos los componentes de texto Swing descienden de la misma superclase,
JTextComponent, que proporciona una base poderosa y ampliamente configurable
para la manipulación de texto. La ilustración 7 muestra una clasificación de los distintos
componentes de texto Swing. Así mismo, mostramos una figura (ilustración 8) de una
sencilla aplicación en la que se pueden observar estos componentes.
- 102 -
6-. Interfaces de Usuario.
Ilustración 7: Clasificación de los Componentes de Texto Swing.
Ilustración 8: Aplicación con Componentes de Texto Swing.
El siguiente cuadro define los tres grupos existentes de componentes de texto.
Grupo
Controles de
Texto
Plano
Descripción
Conocidos simplemente como campos de texto, los
controles de texto pueden mostrar y editar sólo una
línea de texto y están basados en action como los
botones. Se utilizan para obtener una pequeña
cantidad de información textual del usuario y toman
algunas acciones después de que la entrada se haya
completado.
JTextArea, el único componentes de texto plano de
Swing, puede mostrar y editar múltiples líneas de
texto. Aunque un área de texto puede mostrar texto
en cualquier fuente, todo el texto está en la misma
fuente. Toda la edición de los componentes de texto
plano se consigue a través de la manipulación directa
del texto con el teclado y el ratón, por esto los
componentes de texto plano son más fáciles de
- 103 -
Clases Swing
JTextField
y su subclase
JPasswordField
JTextArea
6-. Interfaces de Usuario.
Formateado
configurar y utilizar que los componentes de texto
formateado. También, si la longitud del texto es
menor de unas pocas páginas, podemos fácilmente
utilizar setText y getText para recuperar o modificar
el contenido del componente con una simple llamada
a un método.
Un componente de texto formateado puede mostrar y
editar texto usando más de una fuente. Algunos
componentes de texto formateado permiten incluir
imágenes e incluso componentes. Típicamente se
tendrán que hacer más programación para usar y
configurar componentes de texto formateado, porque
muchas de sus funcionalidades no están disponibles a
través de la manipulación directa con el ratón y el
teclado. Una característica manejable y fácil de usar
proporcionada por JEditorPane es que puede ser
cargado con texto formateado desde una URL
JEditorPane
y su subclase
JTextPane
Tabla 7: Grupos de Componentes de Texto.
Veamos ahora algunas reglas de uso de los Componentes de Texto.
JTextComponent es la base para los componentes de texto Swing y proporciona estas
características, personalizables para todos sus descendientes:
-
Un modelo separado, conocido como Document, para manejar el contenido del
componente.
Una vista separada, que se encarga de mostrar el componente en la pantalla.
Un controlador separado, conocido como un editor kit, que puede leer y escribir
texto e implementa capacidades de edición con comandos action.
Mapas de teclas personalizados.
Soporte para repetir/deshacer infinito.
Cursores conectables y oyentes de cambios de cursor.
Como hemos dicho, y al igual que muchos otros componentes Swing, un
componente de texto separa su contenido de su vista. El contenido de un componente es
manejado por su documento, el cual contiene el texto, soporte para edición, y notifica a
los oyentes los cambios en el texto. Un documento es un ejemplar de una clase que
implementa el interfaz Document o su subinterfaz StyledDocument. Podemos
personalizar un documento, dándole las características que necesitemos (por ejemplo,
hacer que limite el número de caracteres que puede contener). También hemos dicho
que un documento notifica sus cambios a los oyentes interesados. Se utiliza un oyente
de Document (DocumentListener) para reaccionar cuando se inserta o se elimina texto
de un documento, o cuando cambia el estilo de alguna parte del texto.
Todos los componentes de Texto Swing soportan comandos de edición estándar
como cortar, copiar, pegar y la inserción de caracteres. Cada comando de edición está
representada e implementado por un objeto Action. Esto hace sencillo el asociar un
comando con un componente GUI, como un ítem de menú o un botón, y construir un
GUI alrededor de un componente de texto. Un componente de texto usa un objeto
EditorKit para crear y manejar estas acciones. Además de manejar un conjunto de
- 104 -
6-. Interfaces de Usuario.
acciones para un componente de texto, un kit de editor también sabe leer y escribir
documentos en un formato particular. El paquete text de Swing proporciona estos tres
kits de editor:
- DefaultEditorKit: Lee y escribe texto sin estilo. Proporciona un conjunto básico
de comandos de edición. Los demás kits de editor descienden de este.
- StyledEditorKit: Lee y escribe texto con estilo y proporciona un conjunto de
acciones mínimo para texto con estulo. Esta clase es una subclase de
DefaultEditorKit y es el kit de editor usado por defecto por JTextPane.
- HTMLEditorKit: Lee, escribe y edita HTML. Esta es una subclase de
StyledEditorKit.
6.3-. SWT.
6.3.1-. Introducción.
No sólo existen AWT y Swing para la creación de GUIs. Otro representante
importante es SWT. Describiremos a continuación qué es, por qué surge y sobre todo
por qué no se ha utilizado para el desarrollo de este proyecto.
El Standard Widget Toolkit (SWT) ha sido creado por IBM como reemplazo de
AWT y Swing. De manera más precisa, el SWT es un conjunto de widgets (widgets son
controles o componentes) para desarrolladores Java que ofrece una API portable y una
integración muy ligada con la interfaz gráfica de usuario nativa al sistema operativo de
cada plataforma. Así, cada plataforma debe adaptar el SWT para sus gráficos nativos.
Esto parece que entra en contradicción con la filosofía de Java de ser independiente de
la plataforma, pero ofrece ventajas en la unicidad del aspecto del GUI diseñado sea cual
sea la plataforma sobre la que se ejecute. Hasta el momento los entornos a los que se ha
portado SWT son Windows, Linux GTK, Linux Motif, Solaris Motif, AIX Motif,
HPUX Motif, Photon QNX, y Mac OS X.
6.3.2-. Evolución de los gráficos en Java.
Los gráficos en Java han tenido un largo y exitoso desarrollo. Se empezó como ya
sabemos con el paquete básico AWT, viéndose ampliado por el paquete Swing.
Actualmente está empezando a hablarse de SWT, y parece que pueda llegar a
imponerse. Ahondemos un poco en esta evolución:
- AWT: Primer acercamiento de Java al desarrollo de interfaces gráficas (GUI).
Permite mostrar ventanas con controles variados como cajas de texto y botones.
Las GUIs con AWT son fáciles de desarrollar, y usan los controles propios del
sistema operativo en el que se programa, por ejemplo, en windows aparecerá una
ventana de texto típica de windows, en Mac, una ventana con sus respectivas
características Mac, etc. El problema que se presenta es que algunos sistema
operativos difieren de otros en el conjunto de controles, por lo que Sun sólo
implementó los controles comunes a los sistemas operativos a los que se dirige
- 105 -
6-. Interfaces de Usuario.
-
-
Java, que por lo general, es un conjunto reducido y simple respecto de los
realmente disponibles.
SWING: Introducido posteriormente, motivado por la creciente demanda de los
desarrolladores de tener una implementación no nativa, esto es, independiente de
la plataforma, de controles de más alto nivel como árboles, tablas y texto. Con esto
se gana en funcionalidad, pero con el inconveniente de hacer las aplicaciones
Swing demasiado específicas de Java. Sun añadió una emulación look and feel
para aproximar el aspecto de las aplicaciones Swing al sistema operativo sobre las
que se ejecuta, pero no abarca las nuevas versiones de los sistemas operativos
(Windows Me, 2000 en adelante, por ejemplo). Además, al estar implementado
sobre Java y no de forma nativa en el sistema operativo, los tiempos de respuesta
de las interfaces Swing son sensiblemente más lentas que las nativas.
SWT (Standard Widget Toolkit): Ofrece un conjunto de elementos que hacen uso
directo de los controles nativos a través de la Interfaz Nativa de Java (JNI). Si los
controles no están ofrecidos por el sistema operativo, SWT crea los suyos propios
según las necesidades. Esto significa que se necesita código nativo para poder
funcionar en cada sistema operativo, pero IBM ha sido capaz de adaptar SWT a un
buen número de sistemas. Es muy destacable la importancia de SWT porque es el
entorno gráfico de desarrollo que viene con Eclipse y debe ser utilizado en los
plugins desarrollados para el mismo. Eclipse, como ya sabemos, es la herramienta
que se ha utilizado para el desarrollo de este proyecto.
6.3.3-. Fundamentos de SWT.
SWT se compone esencialmente de tres elementos:
1. En el nivel inferior se encuentra una librería nativa que interacciona
directamente con el sistema operativo. Es la denominada JNI, que hemos
nombrado anteriormente. Al ser la parte más dependiente de la plataforma,
debe ser portada en función de la misma.
2. Por encima de la anterior capa está la clase Display, la interfaz por la que el
SWT se comunica con la interfaz gráfica.
3. El nivel superior lo forma la clase Shell, representa el tipo de ventana de más
alto nivel y que es donde se alojan los widgets, controles o componentes. El
shell es la parte de la interfaz que está directamente controlada por el sistema
de ventanas del sistema operativo. La clase Shell es hija de la clase Display, en
cuyo caso es la ventana o marco principal a partir de la cual se construye el
resto de la interfaz, o bien hija de otro shell, siendo el ejemplo más común las
ventanas de diálogo.
No vamos a entrar en detalles de código de SWT. Sin embargo, para tener una
ligera idea, finalizaremos con un ejemplo: el típico “Hello World”.
import org.eclipse.swt.widgets.*;
public class HelloWorld {
- 106 -
6-. Interfaces de Usuario.
public static void main(String[] args) {
Display display = new Display();
Shell shell = new Shell(display);
shell.setText("Hola Mundo");
shell.setSize(200, 100);
shell.open();
while (!shell.isDisposed() {
if (!display.readAndDispatch())
display.sleep();
}
display.dispose();
}
}
Comentemos el código. Se identifican las partes de las que hablamos
anteriormente.
Display display = new Display();
Display representa la pantalla y su conexión con el sistema gestor de ventanas del
sistema operativo en el que nos encontramos. El objeto display contendrá a su vez una
lista de objetos shell. El shell es una ventana abierta sobre la pantalla. Es la clase raíz de
los componentes y controles. Añadimos un shell a un display con el código:
Shell shell = new Shell(display);
shell.open();
El siguiente fragmento de código es el denominado “bucle de eventos”. Ha de ser
programado explícitamente y su función es detectar y ejecutar eventos. Es la forma en
que liberamos la CPU para otros menesteres ajenos a la interfaz gráfica cuando no se
han producido eventos a los que dedicar ciclos de proceso para atender:
while (!shell.isDisposed())
{
if (!display.readAndDispatch())
display.sleep();
}
Por último observamos que los recursos del sistema han de ser liberados por el
programador (display.dispose();). La liberación del objeto display conlleva la
finalización de todos los shells hijos contenidos.
Esto no es más que una pequeña introducción. Para ahondar en el tema se puede ir
a la web del proyecto Eclipse, www.eclipse.org/swt.
- 107 -
6-. Interfaces de Usuario.
6.3.4-. Ventajas y desventajas de SWT.
Por último, es importante comentar las ventajas y desventajas de este paquete, y
explicar por qué se ha elegido Swing para la realización del proyecto. Hoy en día hay
una lucha importante entre defensores y detractores de Swing y SWT. Podríamos
destacar algunos aspectos:
-
-
-
-
No elija SWT por su fidelidad a la plataforma, ya que esto no es una gran virtud.
Normalmente los usuarios no tiene problema en utilizar interfaces alejados de su
S.O. nativo, siempre que estos sean buenos y fáciles de usar. Véase el ejemplo
de Firefox, Winamp, etc.
Se utiliza un planteamiento de “mínimo común denominador” de las plataformas
soportadas. Es decir, puede haber una característica muy típica de Windows pero
que no esté disponible sólo porque ésta no existe en Motif.
Si las necesidades del proyecto se alejan de las de las necesidades del proyecto
Eclipse la dificultad aumenta ya que SWT fue creado como soporte para dicho
proyecto y está orientado hacia él, dejando de lado características que Eclipse no
utiliza.
SWT tiene muchos fallos. Es una plataforma relativamente joven y se nota.
Para el que ya conoce Swing, elegir SWT por pensar que Swing es “feo” es un
gran error. Se pueden hacer aplicaciones realmente atractivas en Swing.
Aparte de los puntos anteriores, a la hora de decidir entre Swing y SWT para
realizar el proyecto se tuvo en cuenta la escasa cantidad de documentación que se puede
encontrar de este último, dado que es una tecnología joven y emergente. Por estas
razones, la elección fue Swing.
6.4-. Conclusiones.
Llegados a este punto, realicemos una breve recapitulación. Hemos descrito en
este capítulo las herramientas más importantes para la realización de interfaces gráficas
de usuario, prestando especial atención al paquete Java Swing. Hemos visto la gran
cantidad de elementos que éste nos permite implementar y sus ventajas frente al AWT.
También hemos hablado de la tecnología SWT. Las explicaciones presentadas en este
capítulo servirán para comprender mejor la herramienta implementada. Pero antes de
pasar a la descripción de dicha herramienta es necesario hablar de los Analizadores
XML, tema que abordaremos en el siguiente capítulo.
- 108 -
Documentos relacionados
Descargar