Anda di halaman 1dari 38

Prctica: Interfaces grcas

10

If a program is useless, it will have to be documented. Murphys Laws of Computer Programming #10

Meta
Que el alumno aprenda a crear interfaces grcas.

Objetivos
Al nalizar esta prctica el alumno ser capaz de: entender las interfaces grcas en Java; entender qu son los eventos y crear interfaces grcas.

144

Desarrollo
La mayor parte del mundo utiliza la computadora slo como una herramienta. Escriben trabajos en ellas, calculan sus impuestos, organizan su agenda, platican con alguien del otro lado del mundo, le mandan un correo electrnico al hijo que est estudiando lejos, etc. Para hacer todo eso necesitan aplicaciones sencillas de usar, que les permita realizar su trabajo y pasatiempos sin muchas complicaciones. Cuando se dice que estas aplicaciones deben ser sencillas de usar, muchos coinciden en que la mejor manera de facilitarle la vida al usuario es que el programa se comunique con l a travs de interfaces grcas de usuario, IGUs, o GUIs por las mismas siglas en ingls. (El hecho de que las interfaces grcas nos compliquen la vida a los programadores no parece importarle mucho a los usuarios. . . ) A lo largo de estas prcticas hemos utilizado interfaces grcas sencillas, aunque ustedes mismos no las han programado. En esta prctica veremos cmo se programan las interfaces grcas y discutiremos la caracterstica ms importante de ellas, algo que se ha evitado intencionalmente hasta ahora. Nos referimos al manejo de eventos. Las primeras versiones de Java utilizaban las clases del paquete java.awt para crear interfaces grcas. Las ltimas versiones utilizan las clases del paquete javax.swing, que ser el denitivo una vez que las aplicaciones escritas con AWT sean convertidas a Swing. En esta prctica utilizaremos Swing.

Actividad 10.1 Ve las clases del paquete javax.swing y java.awt en la pgina indicada en este manual.

Componentes
Una interfaz grca en Java est compuesta de componentes alojados en un contenedor. Veamos una pequea interfaz grca que se ha usado a lo largo del curso, la pequea ventana de dilogo que aparece cuando hacemos
c . l e e S t r i n g ( "Mete una cadena:" ) ;

La ventana la puedes ver en la gura 10.1

Interfaces grcas Figura 10.1 Dilogo de leeString

145

En esta ventana de dilogo hay cuatro componentes: Un componente de la clase JDialog, un dilogo. ste es un contenedor de primer nivel. Este tipo de componentes es generalmente una ventana, y los ms usuales son JFrame, JDialog y Applet. Un componente de la clase JPanel, un panel. ste es un contenedor intermedio, y lo utilizaremos para agrupar de manera sencilla otros componentes. Un componente de la clase JLabel, una etiqueta. Las etiquetas generalmente sern slo usadas para mostrar algn tipo de texto, aunque pueden mostrar imgenes (pixmaps). Un componente de la clase JTextField, un campo de texto. Son componentes usados casi siempre para obtener del usuario una cadena de pocos carcteres o de una sola lnea. Los componentes en una interfaz grca estn ordenados jerrquicamente. En el primer nivel est el dilogo, dentro de l est el panel, y dentro del panel estn la etiqueta y la caja de texto, como se muestra en la gura 10.2.
Figura 10.2 Jerarqua de componentes

JDialog JPanel JLabel JTextField

El dilogo, que en este sencillo ejemplo es la ventana principal, sirve ms que nada para poner juntos a los dems componentes. Es un contenedor de primer nivel. El panel es un contenedor intermedio. Su propsito en la vida es ayudarnos a acomodar otros componentes. La etiqueta y la caja de texto son componentes atmicos, componentes que no contienen a otros componentes, sino que por s mismos muestran o reciben informacin del usuario.

146 En el pequeo diagrama de la jerarqua de componentes nos saltamos algunos componentes que hay entre el dilogo y el panel. Sin embargo, la mayor parte de las veces no habr que preocuparse de esos componentes. Para crear esa interfaz grca, el cdigo fundamental es
JDialog JPanel JLabel JTextField dialogo panel etiqueta cajaTexto = = = = new new new new JDialog ( . . . ) ; JPanel ( . . . ) ; JLabel ( . . . ) ; JTextField ( . . . ) ;

panel . add ( e t i q u e t a ) ; panel . add ( c a j a T e x t o ) ; d i a l o g . getContentPane ( ) . add ( panel ) ; d i a l o g . pack ( ) ; d i a l o g . s e t V i s i b l e ( true ) ;

Administracin de trazado
Los componentes se dibujan sobre un contenedor intermedio (como son los objetos de la clase JPanel) utilizando un administrador de trazado (layout manager). La administracin de trazado es de las caractersticas ms cmodas de Swing, ya que prcticamente slo hay que especicar cmo queremos que se acomoden los componentes y Swing se encarga de calcular los tamaos y posiciones para que los componentes se vean bien distribuidos y con un tamao agradable. Sin embargo puede volverse un poco complicado el asunto, ya que hay muchos administradores de trazado, y de acuerdo a cul se escoja, nuestra aplicacin terminar vindose muy distinta. A lo largo de la prctica veremos distintos ejemplos de los administradores de trazado ms comunes.

Eventos
Ya sabemos cmo se ponen algunos componentes en una pequea ventana. Ahora, cmo hacemos para que la interfaz reaccione a lo que el usuario haga? Por ejemplo, la caja de texto de nuestro dilogo debe cerrar el dilogo cuando el usuario presione la tecla ENTER en ella.

Interfaces grcas

147

Esto se logra con el manejo de eventos. Un evento ocurre cuando el usuario genera algn cambio en el estado de un componente: teclear en una caja de texto, hacer click con el ratn en un botn, cerrar una ventana. Si existe un escucha (listener en ingls) para el objeto que emiti el evento, entonces podr ejecutar su manejador del evento. Adems, un objeto puede tener ms de un escucha, cada uno de los cuales ejecutar su manejador del evento si el evento ocurre. Entender cmo se emiten los eventos y cmo se manejan es lo ms complicado de hacer interfaces grcas. Lo dems es seguir algunas recetas para utilizar los distintos tipos de componentes, que son muchos, pero que todos funcionan de manera similar.

Escuchas
Los escuchas son interfaces; son como clases vacas que nos dicen qu tipos de eventos se pueden emitir, pero que nos dejan la libertad de implementar qu hacer cuando los eventos sucedan. Hagamos un ejemplo completo: una pequea ventana con una etiqueta para mensajes y un botn. Primero hagmoslo sin escuchas
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package i c c 1 . ejemplos ; import import import import import import import import j a v a x . swing . JFrame ; j a v a x . swing . J B u t t o n ; j a v a x . swing . JLabel ; j a v a x . swing . JPanel ; j a v a x . swing . B o r d e r F a c t o r y ; j a v a . awt . BorderLayout ; j a v a . awt . Gr i d L ay o u t ; j a v a . awt . C o n t a i n e r ;

public class EjemploBoton { public s t a t i c void main ( S t r i n g [ ] args ) { JFrame frame = new JFrame ( "Ejemplo de Botn" ) ; J B u t t o n boton = new J B u t t o n ( "Haz click" ) ; JLabel e t i q u e t a = new JLabel ( "Esto es una etiqueta" ) ; JPanel panel = new JPanel ( ) ; panel . s e t B o r d e r ( B o r d e r F a c t o r y . createEmptyBorder ( 5 , 5 , 5 , 5 ) ) ; panel . s e t L a y o u t (new G r id L a y ou t ( 0 , 1 ) ) ; Contina en la siguiente pgina

148
Contina de la pgina anterior 22 23 24 25 26 27 28 29 30 } 31 } panel . add ( boton ) ; panel . add ( e t i q u e t a ) ; C o n t a i n e r c = frame . getContentPane ( ) ; c . add ( panel , BorderLayout .CENTER ) ; frame . pack ( ) ; frame . s e t V i s i b l e ( t r u e ) ;

Qu hace el programa? En las lneas 14, 15 y 16 crea los tres principales componentes de nuestra aplicacin: nuestra ventana principal (JFrame), nuestro botn (JButton) y nuestra etiqueta (JLabel). Esos son los componentes que el usuario ver directamente. Lo siguiente que hace el programa es crear un panel (lnea 18). El nico propsito del panel es agrupar a nuestro botn y a nuestra etiqueta en la ventana principal. Lo primero que hace es ponerse un borde de 5 pixeles alrededor [20], y denirse un administrador de trazado [21]; en este caso es de la clase GridLayout, lo que signica que ser una cuadrcula. Ya que le pasamos como parmetros 0 y 1, se entiende que tendr un nmero no determinado de renglones (de ah el 0), y una sola columna. Lo ltimo que hace el panel es aadirse el botn y la etiqueta [22,23]. El programa despus obtiene la ventana contenedora de la ventana principal [25] (todos los contenedores de primer nivel tienen una ventana contenedora), y a aqul le aadimos el panel [26]. Para terminar, la ventana principal se empaca y se hace visible. Durante el proceso de empacado se calculan los tamaos de los componentes, para que automticamente se dena el tamao de la ventana principal.

Actividad 10.2 Escribe la clase EjemploBoton.java y escribe el correspondiente build.xml para que puedas compilarla y correr el ejemplo.

El programa no hace nada interesante (ni siquiera termina cuando se cierra la ventana). El botn puede ser presionado cuantas veces queramos; pero nada ocurre porque no le aadimos ningn escucha. Cuando el usuario hace click en el botn se dispara un evento; pero no hay ningn manejador de evento que se encargue de l. Vamos a escribir un escucha para nuestro ejemplo. Ya que el evento que nos interesa es el click del botn, utilizaremos un escucha de ratn, o mouse listener, que es una interfaz que est declarada como sigue

Interfaces grcas

149

1 package j a v a . awt . event ; 2 public i n t e r f a c e MouseListener extends E v e n t L i s t e n e r { 3 public void mouseClicked ( MouseEvent e ) ; 4 public void mouseEntered ( MouseEvent e ) ; 5 public void mouseExited ( MouseEvent e ) ; 6 public void mousePressed ( MouseEvent e ) ; 7 public void mouseReleased ( MouseEvent e ) ; 8 }

(Aunque se pueden escribir escuchas propios, Java provee ya los escuchas ms utilizados para distintos eventos, por lo que no hay necesidad de hacerlos.) Esto nos dice que los escuchas de ratn ofrecen mtodos para cuando el ratn haga click sobre el componente (mouseClicked), para cuando el ratn se posa sobre el componente (mouseEntered), para cuando el ratn deja de estar sobre el componente (mouseExited), para cuando se presiona el botn del ratn sin soltarlo (mousePressed), y para cuando se suelta el botn del ratn despus de presionarlo (mouseRelease). Si queremos hacer un escucha para nuestro botn, debemos crear una clase que implemente la interfaz MouseListener, diciendo qu debe hacerse en cada caso:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 package i c c 1 . ejemplos ; import j a v a . awt . event . MouseListener ; import j a v a . awt . event . MouseEvent ; import j a v a x . swing . JLabel ; public class MiEscuchaDeRaton implements MouseListener { private i n t contador ; p r i v a t e JLabel e t i q u e t a ; public MiEscuchaDeRaton ( JLabel e t i q u e t a ) { this . etiqueta = etiqueta ; contador = 0; } public void mouseClicked ( MouseEvent e ) { c o n t a d o r ++; e t i q u e t a . s e t T e x t ( "Clicks: "+ c o n t a d o r +"." ) ; } public public public public } void void void void mouseEntered ( MouseEvent e ) { } mouseExited ( MouseEvent e ) { } mousePressed ( MouseEvent e ) { } mouseReleased ( MouseEvent e ) { }

150

Realmente slo implementamos la funcin mouseClicked; las otras no nos interesan. Pero debemos darles una implementacin a todas (aunque sea como en este caso una implementacin vaca), porque si no el compilador se quejar de que no estamos implementando todas las funciones de la interfaz MouseListener. Qu hace este manejador de evento? Slo se hace cargo del evento mouseClicked; cuando el usuario haga click sobre el botn, la etiqueta cambiar su mensaje a Clicks y el nmero de clicks que se hayan hecho. Para que nuestro botn utilice este escucha slo tenemos que agregarlo (modicando la clase EjemploBoton):
15 16 17 18 19 J B u t t o n boton = new J B u t t o n ( "Haz click" ) ; JLabel e t i q u e t a = new JLabel ( "Esto es una etiqueta" ) ; MiEscuchaDeRaton escucha = new MiEscuchaDeRaton ( e t i q u e t a ) ; boton . addMouseListener ( escucha ) ;

Actividad 10.3 Escribe la clase MiEscuchaDeRaton.java y modica la clase de uso EjemploBoton.java para que el botn utilice el escucha. Compila y ejecuta el programa de nuevo.

Adaptadores
Como ocurri arriba, muchas veces no querremos implementar todas las funciones que ofrece un escucha. La mayor parte de las veces slo nos interesar el evento de click sobre un botn y no nos importar el resto. Para esto estn los adaptadores. Los adaptadores son clases concretas que implementan a las interfaces de un escucha, pero que no hacen nada en sus funciones; de esta forma, uno slo hereda al adaptador y sobrecarga la funcin que le interese. Por ejemplo, el adaptador de ratn (mouse adapter) es
1 package j a v a . awt . event ; 2 public class MouseAdapter implements MouseListener { 3 public void mouseClicked ( MouseEvent e ) { } 4 public void mouseEntered ( MouseEvent e ) { } 5 public void mouseExited ( MouseEvent e ) { } 6 public void mousePressed ( MouseEvent e ) { } 7 public void mouseReleased ( MouseEvent e ) { } 8 }

Interfaces grcas

151

No hacen nada sus funciones; pero si nicamente nos interesa el evento del click del ratn slo hay que implementar mouseClicked. Nuestra clase MiEscuchaDeRaton se reducira a
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package i c c 1 . ejemplos ; import j a v a . awt . event . MouseAdapter ; import j a v a . awt . event . MouseEvent ; import j a v a x . swing . JLabel ; public class MiEscuchaDeRaton extends MouseAdapter { private int contador ; p r i v a t e JLabel e t i q u e t a ; public MiEscuchaDeRaton ( JLabel e t i q u e t a ) { this . etiqueta = etiqueta ; contador = 0; } public void mouseClicked ( MouseEvent e ) { c o n t a d o r ++; e t i q u e t a . s e t T e x t ( "Clicks: "+ c o n t a d o r +"." ) ; } }

Como los otros eventos no nos interesan, los ignoramos. No hacemos nada si ocurren. Todos los escuchas de Java tienen un adaptador disponible para facilitarnos la vida.1
Actividad 10.4 Vuelve a modicar MiEscuchaDeRaton.java para que extienda el adaptador de ratn. Compila y ejecuta de nuevo el programa.

Clases internas y annimas


En estas prcticas no hemos mencionado que Java puede tener clases internas; clases declaradas dentro de otras clases
public class A { ... Por supuesto, a menos que el escucha slo tenga un mtodo. No tendra sentido tener un adaptador en ese caso.
1

152
class B { ... } }

La clase B es clase interna de A; slo puede ser vista dentro de A. Adems, los mtodos de la clase B pueden hacer referencia a las variables de clase de la clase A, incluso a las privadas. Esto es posible ya que la clase B es parte de hecho de la clase A. Las clases internas son muy tiles con los escuchas y adaptadores, ya que nada ms nos interesa que los vean dentro de la clase donde estamos creando nuestra interfaz grca. Cuando compilemos el archivo A.java, que es donde viven las clases A y B, se generarn dos archivos: A.class, y A$B.class; este ltimo quiere decir que B es clase interna de A. Podemos entonces crear nuestros escuchas y adaptadores dentro de la clase donde construyamoss nuestra interfaz grca. Pero aun podemos hacer ms: podemos utilizar clases annimas. Las clases annimas son clases creadas al vuelo, que no tienen nombre. Por ejemplo, para nuestro botn podramos aadirle nuestro escucha de ratn al vuelo, de esta manera
16 17 18 19 20 21 22 23 24 f i n a l JLabel e t i q u e t a = new JLabel ( "Esto es una etiqueta" ) ; f i n a l i n t contador = { 0 } ; boton . addMouseListener (new MouseAdapter ( ) { public void mouseClicked ( MouseEvent e ) { contador [ 0 ] = contador [ 0 ] + 1 ; e t i q u e t a . s e t T e x t ( "Clicks: "+ c o n t a d o r . i n t V a l u e ( ) + "." ) ; } });

Nuestra clase EjemploBoton.java quedara as


1 2 3 4 5 6 7 package i c c 1 . ejemplos ; import import import import import j a v a x . swing . JFrame ; j a v a x . swing . J B u t t o n ; j a v a x . swing . JLabel ; j a v a x . swing . JPanel ; j a v a x . swing . B o r d e r F a c t o r y ; Contina en la siguiente pgina

Interfaces grcas

153
Contina de la pgina anterior

8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41

import import import import import

j a v a . awt . BorderLayout ; j a v a . awt . Gr i d L ay o u t ; j a v a . awt . C o n t a i n e r ; j a v a . awt . event . MouseAdapter ; j a v a . awt . event . MouseEvent ;

public class EjemploBoton { public s t a t i c void main ( S t r i n g [ ] args ) { JFrame frame = new JFrame ( "Ejemplo de Botn" ) ; J B u t t o n boton = new J B u t t o n ( "Haz click" ) ; f i n a l JLabel e t i q u e t a = new JLabel ( "Esto es una etiqueta" ) ; f i n a l i n t contador = { 0 } ; boton . addMouseListener (new MouseAdapter ( ) { public void mouseClicked ( MouseEvent e ) { contador [ 0 ] = contador [ 0 ] + 1 ; e t i q u e t a . s e t T e x t ( "Clicks: "+ c o n t a d o r [ 0 ] + "." ) ; } }); JPanel panel = new JPanel ( ) ; panel . s e t B o r d e r ( B o r d e r F a c t o r y . createEmptyBorder ( 5 , 5 , 5 , 5 ) ) ; panel . s e t L a y o u t (new G r id L a y ou t ( 0 , 1 ) ) ; panel . add ( boton ) ; panel . add ( e t i q u e t a ) ; C o n t a i n e r c = frame . getContentPane ( ) ; c . add ( panel , BorderLayout .CENTER ) ; frame . pack ( ) ; frame . s e t V i s i b l e ( t r u e ) ; } }

Actividad 10.5 Vuelve a modicar el archivo EjemploBoton.java para que utilice la clase annima. Complalo y ejectalo de nuevo.

154 Ah estamos creando una clase annima al vuelo al invocar la funcin addMouseListener. Al compilar EjemploBoton.java, la clase annima se compilar en el archivo EjemploBoton$1.class. Con eso nos ahorramos tener que crear una clase completa en su propio archivo, cuando slo nos interesan uno o dos de sus mtodos. Las clases annimas son mucho ms cmodas de utilizar que el tener que crear una clase para cada uno de los eventos de un programa (el nmero de eventos puede aumentar a cientos de ellos). Muchos notarn (y se preguntarn) por qu ahora la etiqueta y el contador son variables nales, y por qu el contador es un arreglo de enteros con un nico elemento. La mayor desventaja de las clases annimas es que no pueden utilizar variables locales declaradas fuera de ellas mismas, a menos que sean nales. En el caso de la etiqueta realmente no importa ya que no modicamos la referencia al objeto (cambiamos su contenido, pero la referencia sigue siendo la misma). En el caso del contador queremos que se modique en cada llamada, y entonces no podemos usar una variable de tipo int , ya que tiene que ser nal. Por eso usamos un arreglo; de nuevo, no modicamos la referencia al arreglo y su tamao (contador), sino un elemento del arreglo (contador[0]). Parte del problema en el ejemplo es que todo el cdigo est en el mtodo main; se puede resolver todo de manera ms elegante si rediseamos un poco:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package i c c 1 . ejemplos ; import import import import import import import import import import import import j a v a x . swing . B o r d e r F a c t o r y ; j a v a x . swing . J B u t t o n ; j a v a x . swing . JFrame ; j a v a x . swing . JLabel ; j a v a x . swing . JPanel ; j a v a . awt . BorderLayout ; j a v a . awt . C o n t a i n e r ; j a v a . awt . G r i d La y o u t ; j a v a . awt . event . MouseAdapter ; j a v a . awt . event . MouseEvent ; j a v a . awt . event . WindowAdapter ; j a v a . awt . event . WindowEvent ;

public class EjemploBoton { private i n t contador ; public EjemploBoton ( ) { contador = 0; Contina en la siguiente pgina

Interfaces grcas

155
Contina de la pgina anterior

22 JFrame frame = new JFrame ( "Ejemplo de Botn" ) ; 23 J B u t t o n boton = new J B u t t o n ( "Haz click" ) ; 24 f i n a l JLabel e t i q u e t a = new JLabel ( "Esto es una etiqueta" ) ; 25 frame . addWindowListener (new WindowAdapter ( ) { 26 27 public void windowClosing ( WindowEvent evento ) { 28 System . e x i t ( 0 ) ; 29 } 30 }); 31 32 boton . addMouseListener (new MouseAdapter ( ) { 33 public void mouseClicked ( MouseEvent e ) { 34 c o n t a d o r ++; 35 e t i q u e t a . s e t T e x t ( "Clicks: "+ c o n t a d o r +"." ) ; 36 } 37 }); 38 39 JPanel panel = new JPanel ( ) ; 40 panel . s e t B o r d e r ( B o r d e r F a c t o r y . createEmptyBorder ( 5 , 5 , 5 , 5 ) ) ; 41 panel . s e t L a y o u t (new G r id L a y ou t ( 0 , 1 ) ) ; 42 panel . add ( boton ) ; 43 44 panel . add ( e t i q u e t a ) ; 45 C o n t a i n e r c = frame . getContentPane ( ) ; 46 47 c . add ( panel , BorderLayout .CENTER ) ; 48 49 frame . pack ( ) ; 50 frame . s e t V i s i b l e ( t r u e ) ; 51 } 52 53 public s t a t i c void main ( S t r i n g [ ] args ) { 54 EjemploBoton e = new EjemploBoton ( ) ; 55 } 56 }

(Aadimos tambin un escucha a la ventana, en las lneas 2630, para que cuando el usuario la cierre, el programa termine). Las clases internas y annimas fueron pensadas mayormente para interfaces grcas, pero podemos usarlas cundo y dnde queramos. Sin embargo nosotros slo las utilizaremos para interfaces grcas (y de hecho es lo recomendable).

156 En los ejercicios de la prctica podrs utilizar clases normales, internas o annimas, dependiendo de cmo lo preeras.

Los componentes de Swing


Swing ofrece varios componentes (herederos casi todos de la clase JComponent) para crear prcticamente cualquier tipo de interfaz grca. Por motivos de espacio veremos slo una breve descripcin de cada uno de los componentes que Swing ofrece, pero se darn descripciones detalladas en el ejemplo completo que se ver ms adelante, y slo para los componentes que se utilicen o que se relacionen con lo que se utilice.

Actividad 10.6 Consulta la documentacin de la clase JComponent en la pgina referida para ello.

Contenedores de primer nivel


Los contenedores de primer nivel son ventanas (excepto los que corresponden a
Applet), la raz en nuestra jerarqua de componentes. Todos tienen un contenedor (heredero de la clase Container), llamada ventana contenedora o content pane. Adems de

la ventana contenedora, todos los componentes de primer nivel tienen la posibilidad de que se les agregue una barra de men. Los contenedores de primer nivel son: JFrame. (Marco) Son ventanas autnomas y que no dependen de ninguna otra ventana. Generalmente sern la ventana principal de nuestras aplicaciones. JDialog. (Dilogo) Son ventanas ms limitadas que los objetos de la clase JFrame. Dependen de otra ventana, que es la ventana padre (parent window) del dilogo. Si la ventana padre es minimizada, sus dilogos tambin lo hacen. Si la ventana padre es cerrada, se cierran tambin los dilogos. Applet. (No tiene sentido en espaol) Emulan a una ventana para correr aplicaciones de Java dentro de un navegador de la WWW. No los veremos. Para manejar los eventos de un contenedor de primer nivel, se utiliza un escucha de ventana o window listener.

Interfaces grcas

157

Actividad 10.7 Consulta en la pgina correspondiente la documentacin de las clases JFrame, JDialog, WindowListener y WindowAdapter. Tambin consulta la documentacin de las clases JOptionPane y JFileChooser. La clase Applet no se utilizar en estas prcticas.

Contenedores intermedios
Los contenedores intermedios nos sirven para agrupar y presentar de cierta manera distintos componentes. Mientras que se pueden realizar varias cosas con ellos, aqu slo daremos una breve descripcin de cada uno de ellos y nos concentraremos en los objetos de la clase JPanel en el ejemplo ms abajo. Panel. Es el contenedor intermedio ms exible y frecuentemente usado. Est implementado en la clase JPanel; los pneles no tienen en s mismos casi ninguna funcionalidad nueva a la que heredan de JComponent. Se usan frecuentemente para agrupar componentes. Un panel puede usar cualquier administrador de trazado, y se le puede denir fcilmente un borde. Ventana corrediza. Provee barras de desplazamiento alrededor de un componente grande o que puede aumentar mucho de tamao. Se implementa en la clase JScrollPane. Ventana dividida. Muestra dos componentes en un espacio jo determinado, dejando al usuario el ajustar la cantidad de espacio dedicada a cada uno de los componentes. Se implementa en la clase JSplitPane. Ventana de carpeta. Contiene mltiples componentes, pero slo muestra uno a la vez. El usuario puede cambiar fcilmente entre componentes. Se implementa en la clase JTabbedPane. Barra de herramientas. Contiene un grupo de componentes (usualmente botones) en un rengln o en una columna, permitindole al usuario el arrastrar la barra de herramientas en diferentes posiciones. Se implementa en la clase JToolBar. Hay otros tres contenedores intermedios, ms especializados: Marco interno. Es como un marco y tiene casi los mismos mtodos, pero debe aparecer dentro de otra ventana. Se implementa en la clase JInternalFrame. Ventana en capas. Provee una tercera dimensin, profundidad, para acomodar componentes. Se debe especicar la posicin y tamao para cada componente. Se implementa en la clase LayeredPane.

158 Ventana raz. Provee soporte detrs de los telones para los contenedores de primer nivel. Se implementa en la clase JRootPane.

Componentes atmicos
Los componentes atmicos sirven para presentar y/o recibir informacin del usuario. Son los eventos generados por estos componentes los que ms nos interesarn al crear nuestras interfaces grcas. Los siguientes son componentes que sirven para recibir informacin del usuario: Botn, Botn de dos estados, Radio botn. Proveen botones de uso simple. Los botones normales estn implementados en la clase JButton. Los botones de dos estados son botones que cambian de estado cuando se les hace click. Como su nombre lo indica, tienen dos estados: activado y desactivado. Estn implementados en la clase JCheckBox. Los botones de radio son un grupo de botones de dos estados en los cuales slo uno de ellos puede estar activado a la vez. Estn implementados en la clase JRadioButton. Caja de combinaciones. Son botones que al hacer click en ellos ofrecen un men de opciones. Estn implementados en la clase JComboBox. Listas. Muestran un grupo de elementos que el usuario puede escoger. Estn implementadas en la clase JList. Mens. Permiten hacer mens. Estn implementados en las clase JMenuBar, JMenu y JMenuItem. Rangos. Permiten escoger un valor numrico que est en cierto rango. Estn implementados en la clase JSlider. Campo de texto. Permiten al usuario escribir una sola lnea de texto. Estn implementados en la clase JTextField. Los siguientes son componentes que slo muestran informacin al usuario, sin recibir ninguna entrada de l: Etiquetas. Muestran texto, un icono o ambos. Implementadas en la clase JLabel. Barras de progreso. Muestran el progreso de una tarea. Implementadas en la clase JProgressBar.

Interfaces grcas

159

Pistas. Muestran una pequea ventana con informacin de algn otro componente. Implementadas en la clase JToolTip. Los siguientes son componentes que presentan informacin con formato, y una manera de editar esa informacin: Selector de color. Una interfaz para seleccionar colores. Implementada en la clase JColorChooser. Selector de archivos. Una interfaz para seleccionar archivos. Implementada en la clase JFileChooser. Tabla. Un componente exible que muestra informacin en un formato de cuadrcula. Implementada en la clase JTable. Soporte para texto. Una serie de componentes para manejo de texto, a distintos niveles y con distintos grados de complejidad. Est en las clases JTextArea, JTextComponent, JTextField, y JTextPane. rbol. Un componente que muestra datos de manera jerrquica. Implementado en la clase JTree.

Un ejemplo paso a paso


Como se dijo al inicio de la prctica, lo ms difcil de las interfaces grcas es el manejo de eventos. Lo dems es aprender a utilizar los distintos componentes de Swing. Para que tengan una referencia concreta, veremos un ejemplo completo donde haremos un editor de texto sencillo.
Figura 10.3 Jerarqua de componentes
JFrame JMenuBar Men: Archivo JMenuItem: Nuevo archivo JMenuItem: Abrir archivo JMenuItem: Guardar archivo JMenuItem: Guardar archivo como... JMenuItem: Quitar programa Men: Ayuda JMenuItem: Acerca de... JPanel JTextArea JLabel

160

Diseo de un editor de texto sencillo


Queremos hacer un editor de texto sencillo en una clase llamada Editor. Podr cargar archivos de texto del disco duro, editarlos y salvarlos de nuevo. La mayor parte de la interfaz grca ser un componente que nos permita mostrar y editar texto. Adems, nuestra interfaz deber tener un men de archivo con opciones para abrir, guardar, salir y crear un nuevo archivo, y otro men de ayuda con informacin del programa. Tambin deberemos tener una etiqueta que le diga al usuario si el archivo actual ha sido o no modicado. El componente que nos permite mostrar y editar texto es un objeto de la clase JTextArea; nuestra ventana principal es un objeto de la clase JFrame. Para acomodar los componentes usaremos un objeto de la clase JPanel. Ya con esto, nuestra jerarqua de componentes sera como se ve en la gura 10.3.

Variables de clase y clases importadas


Usaremos componentes y eventos de Swing, componentes y eventos de AWT, y algunas clases del paquete java.io, para leer y guardar nuestros archivos en disco duro. Tambin importamos la clase Locale, para que el programa hable bien espaol. Veremos algo ms de esto ms adelante. Nuestro paquete ser icc1.ejemplos, como con todos los ejemplos que hemos estado usando; entonces nuestra clase quedara as al inicio:
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package i c c 1 . ejemplos ; import import import import import import import import import import import import j a v a x . swing . JFrame ; j a v a x . swing . JTextArea ; j a v a x . swing . JPanel ; j a v a x . swing . J S c r o l l P a n e ; j a v a x . swing . JLabel ; j a v a x . swing . JMenuBar ; j a v a x . swing . JMenu ; j a v a x . swing . JMenuItem ; j a v a x . swing . JF i l eC h o os e r ; j a v a x . swing . JOptionPane ; j a v a x . swing . BoxLayout ; j a v a x . swing . KeyStroke ; // // // // // // // // // // // // Ventana p r i n c i p a l . rea de t e x t o . Panel . Ventana c o r r e d i z a . Etiqueta . Barra de men . Men . Elemento de men . S e l e c c i o n a d o r de a r c h i v o s . . Di l o g o s . Trazado en c a j a . Teclazo .

import j a v a x . swing . event . DocumentListener ; / / Escucha en e l / / rea de t e x t o . import j a v a x . swing . event . DocumentEvent ; / / Evento en e l / / rea de t e x t o . Contina en la siguiente pgina

Interfaces grcas

161
Contina de la pgina anterior

24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47

import import import import import import

/ / Tipo . / / D i s p o s i t i v o de s a l i d a / / ( pantalla ) . import j a v a . awt . G r a p h i c s C o n f i g u r a t i o n ; / / C o n f i g u r a c i n / / del d i s p o s i t i v o . import j a v a . awt . GraphicsEnvironment ; / / V a l o r e s d e l d i s p o s i t i v o . import import import import import import import import import j a v a . awt . event . WindowAdapter ; / / Escucha en l a ventana . j a v a . awt . event . WindowEvent ; / / Evento en l a ventana . j a v a . awt . event . A c t i o n L i s t e n e r ; j a v a . awt . event . A c t i o n E v e n t ; j a v a . awt . event . KeyEvent ; / / Evento de t e c l a . java . java . java . java . io io io io . File ; . F il e R e a d e r ; . FileWriter ; . IOException ; // // // // Archivo . F l u j o de e n t r a d a . F l u j o de s a l i d a . Excepcin de e n t r a d a / s a l i d a .

j a v a . awt . C o n t a i n e r ; j a v a . awt . Dimension ; j a v a . awt . Rectangle ; j a v a . awt . I n s e t s ; j a v a . awt . Font ; j a v a . awt . GraphicsDevice ;

/ / Contenedor . / / Dimensin . / / Rectngulo .

import j a v a . u t i l . Locale ; / / Para que n u e s t r o programa hable en espa o l .

Qu variables de clase vamos a necesitar? En primer lugar, nuestro componente de texto deber ser una variable de clase para que podamos modicarlo de manera sencilla en todos nuestros mtodos. La etiqueta para decirle al usuario si se ha modicado el texto tambin nos conviene hacerla una variable de clase. Y siempre es conveniente hacer nuestra ventana principal una variable de clase. Usaremos una cadena para saber el nombre del archivo sobre el que estamos trabajando y un booleano para saber si el texto ha sido modicado. Vamos adems a aadir otra cadena para guardar el nombre del archivo (vamos a necesitarlo despus) y denamos constantes para el ancho y alto de nuestra ventana:
47 public class E d i t o r { 48 p r i v a t e JFrame marco ; 49 50 p r i v a t e JTextArea t e x t o ;

/ / Ventana p r i n c i p a l . / / El t e x t o del archivo . Contina en la siguiente pgina

162
Contina de la pgina anterior 51 52 53 54 55 56 57 58 59 60 61 p r i v a t e JLabel private String private String p r i v a t e boolean estado ; / / E l estado d e l a r c h i v o . archivoOriginal ; / / E l nombre d e l a r c h i v o o r i g i n a l . a r c h i v o ; / / E l nombre d e l a r c h i v o a c t u a l . modificado ; / / Tamao de bloque / / de b y t e s . = 640; / / Ancho de l a ventana . = 480; / / A l t u r a de l a ventana .

public s t a t i c f i n a l i n t BLOQUE = 512; public s t a t i c f i n a l i n t ANCHO public s t a t i c f i n a l i n t ALTO

La variable esttica BLOQUE [58], la vamos a usar para leer y escribir del disco duro.

Constructores
Tendremos dos constructores; uno sin parmetros y otro con una cadena como parmetro, que ser el nombre de un archivo a abrir. As podremos iniciar un editor vaco, sin ningn texto, y otro que abra un archivo y muestre su contenido.
65 66 67 public E d i t o r ( ) { this ( null ) ; }

75 76 77 78 79 80 81 82 83

public E d i t o r ( S t r i n g a r c h i v o ) { modificado = false ; archivoOriginal = this . archivo = archivo ; creaVentanaPrincipal ( ) ; i f ( archivo != null ) { abrirArchivoDeDisco ( ) ; } marco . s e t V i s i b l e ( t r u e ) ; }

Los dos constructores funcionan de manera idntica; slo que uno adems de inicializar todo, manda abrir un archivo de texto. Para evitar cdigo duplicado, hacemos que el constructor que no recibe parmetros mande llamar al constructor que recibe una cadena [66] y le pase null para distinguir cundo abrimos el archivo. El constructor inicializa en false la variable que nos dice si el archivo est modicado [76], inicializa las variables con el nombre del archivo [77] (que puede ser null),

Interfaces grcas

163

y manda crear la ventana principal [78]. Para esto suponemos que tendremos una funcin abrirArchivoDeDisco que abrir el archivo especicado por nuestra variable de clase archivo y mostrar su contenido en nuestro componente de texto. Siempre es bueno imaginar que tenemos funciones que hacen ciertas cosas, porque as vamos dividiendo el trabajo en tareas cada vez ms pequeas. Si queremos probar que el programa compila, slo hay que implementar las funciones vacas. Si el nombre de archivo que nos pasaron no es null [79], mandamos abrir el archivo en disco [80].Suponemos otro mtodo llamado abrirArchivoDeDisco que (como se puede sospechar) abrir el archivo del disco. Por ltimo, hacemos visible la ventana principal de nuestro programa [82].

Creacin de la ventana principal


Veamos ahora cmo crearemos la ventana principal con el mtodo creaVentanaPrincipal:
90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 marco = new JFrame ( "Editor -- <ArchivoNuevo ) ; >" t e x t o = creaAreaDeTexto ( ) ; JMenuBar b a r r a = creaBarraDeMenu ( ) ; estado = new JLabel ( " " ) ; JPanel panel = new JPanel ( ) ; J S c r o l l P a n e s c r o l l P a n e = new J S c r o l l P a n e ( t e x t o ) ; s c r o l l P a n e . s e t P r e f e r r e d S i z e (new Dimension (ANCHO, ALTO ) ) ; panel . s e t L a y o u t (new BoxLayout ( panel , BoxLayout . Y_AXIS ) ) ; panel . add ( s c r o l l P a n e ) ; panel . add ( estado ) ; C o n t a i n e r c = marco . getContentPane ( ) ; c . add ( panel ) ; marco . setJMenuBar ( b a r r a ) ; marco . pack ( ) ; t e x t o . setRequestFocusEnabled ( t r u e ) ; t e x t o . requestFocus ( ) ; Contina en la siguiente pgina

164
Contina de la pgina anterior 114 115 116 117 118 119 120 121 122 123 marco . addWindowListener (new WindowAdapter ( ) { public void windowClosing ( WindowEvent e ) { menuQuitarPrograma ( ) ; } }); Rectangle medidas = ob ten Medi dasP anta lla ( ) ; marco . s e t L o c a t i o n ( ( medidas . width ANCHO) / 2 , ( medidas . h e i g h t ALTO ) / 2 ) ; }

Primero, construimos nuestra ventana principal y le ponemos un ttulo apropiado [90]. Despus creamos el rea de texto [92], la barra del men [93] y la etiqueta con el estado del archivo [94]. Aqu volvemos a suponer dos mtodos, creaAreaDeTexto y creaBarraDeMenu. Creamos un panel [96] y una ventana corrediza [97]. A sta le pasamos nuestra rea de texto para que est dentro de ella, y luego le denimos el tamao con nuestras variables de clase estticas [98]. Despus le ponemos un administrador de trazado de caja [100], el cual slo acomoda uno tras otro a los componentes, ya sea vertical u horizontalmente. En particular, al nuestro le decimos que los acomode verticalmente (por eso el constructor recibe un BoxLayout.Y_AXIS). Aadimos nuestra ventana corrediza y nuestra etiqueta en el panel [101,102]. Sacamos la ventana contenedora de nuestra ventana principal [104] y le metemos el panel [105]. Tambin le ponemos la barra de men [107] y empacamos a la ventana [108]. Con esto se ajusta el tamao de los componentes. Queremos que cuando el programa empiece, el usuario pueda escribir inmediatamente, as que le pasamos el foco a nuestro componente de texto [110,111]. Por ltimo le aadimos un escucha a la ventana para que cuando el evento de cerrar ocurra (windowClosing), entonces el programa termine [114118]. Suponemos una nueva funcin: menuQuitarPrograma. El prejo menu es porque queremos que el evento de cerrar la ventana funcione igual que cuando seleccionemos la opcin de quitar el programa del men; para ambas usaremos este mtodo. Las dos ltimas lneas del mtodo [120,121] obtienen las dimensiones de la pantalla donde est corriendo el programa, para poder colocar la ventana de forma centrada. Esto es un adorno y es absolutamente prescindible, pero es el tipo de adornos que hacen agradables a los programas. Ah nos creamos otro mtodo: obtenMedidasPantalla. El mtodo es el que sigue:

Interfaces grcas

165

128 129 130 131 132 133 134 135 136 137 138 139 140 141

public Rectangle o bten Medi das Pant alla ( ) { Rectangle v i r t u a l B o u n d s = new Rectangle ( ) ; GraphicsEnvironment ge ; ge = GraphicsEnvironment . getLo calGr aphic sEnvi ronme nt ( ) ; GraphicsDevice [ ] gs = ge . getScreenDevices ( ) ; f o r ( i n t j = 0 ; j < gs . l e n g t h ; j ++) { GraphicsDevice gd = gs [ j ] ; G r a p h i c s C o n f i g u r a t i o n [ ] gc = gd . g e t C o n f i g u r a t i o n s ( ) ; f o r ( i n t i = 0 ; i < gc . l e n g t h ; i ++) { v i r t u a l B o u n d s = v i r t u a l B o u n d s . union ( gc [ i ] . getBounds ( ) ) ; } } return virtualBounds ; }

Est copiado tal cual de la clase GraphicsConguration, pero ah va una explicacin rpida: se crea un rectngulo [129], obtenemos el ambiente grco [131], del cual sacamos los dispositivos grcos disponibles [132], a cada uno de los cuales les sacamos la conguracin [135], y de todas las conguraciones hacemos una unin [137], para al nal regresar el rectngulo [140]. Y as obtenemos las medidas de la pantalla.

Creacin del rea de texto


Crear el rea de texto es relativamente sencillo:
148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 public JTextArea creaAreaDeTexto ( ) { JTextArea t e x t o = new JTextArea ( ) ; t e x t o . s e t E d i t a b l e ( true ) ; t e x t o . s e t M a r g i n (new I n s e t s ( 5 , 5 , 5 , 5 ) ) ; t e x t o . s e t F o n t (new Font ( "Monospaced" , Font . PLAIN , 1 4 ) ) ; t e x t o . getDocument ( ) . addDocumentListener ( new DocumentListener ( ) { public void changedUpdate ( DocumentEvent e ) { modificado = true ; estado . s e t T e x t ( "Modificado" ) ; } public void i n s e r t U p d a t e ( DocumentEvent e ) { modificado = true ; estado . s e t T e x t ( "Modificado" ) ; } Contina en la siguiente pgina

166
Contina de la pgina anterior 163 164 165 166 167 168 169 public void removeUpdate ( DocumentEvent e ) { modificado = true ; estado . s e t T e x t ( "Modificado" ) ; } }); return t e x t o ; }

Creamos el componente de texto [149], lo hacemos editable [150], le ponemos margen de 5 pixeles alrededor [151], le ponemos una fuente monoespaciada [152] (eso signica que todos los caracteres tienen el mismo ancho), y le ponemos un escucha para que en cada uno de sus eventos, se ponga el estado del editor como modicado [153167] (cambiamos la variable modicado a true, y ponemos la cadena "Modificado" en la etiqueta estado). Esto tiene sentido, ya que los tres eventos implican modicar el texto de alguna manera. Por ltimo regresamos el componente [168].

Creacin de la barra de men


Crear la barra de men tampoco es difcil:
174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 public JMenuBar creaBarraDeMenu ( ) { JMenuBar b a r r a = new JMenuBar ( ) ; JMenu menu ; JMenuItem e l ; menu = new JMenu ( "Archivo" ) ; menu . setMnemonic ( KeyEvent . VK_A ) ; b a r r a . add ( menu ) ; e l = new JMenuItem ( "Nuevo archivo" ) ; e l . s e t A c c e l e r a t o r ( KeyStroke . getKeyStroke ( KeyEvent . VK_N, A c t i o n E v e n t .CTRL_MASK ) ) ; e l . a d d A c t i o n L i s t e n e r (new A c t i o n L i s t e n e r ( ) { public void a c t i o n P e r f o r m e d ( A c t i o n E v e n t e ) { menuNuevoArchivo ( ) ; } }); Contina en la siguiente pgina

Interfaces grcas

167
Contina de la pgina anterior

191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231

menu . add ( e l ) ; e l = new JMenuItem ( "Abrir archivo" ) ; e l . s e t A c c e l e r a t o r ( KeyStroke . getKeyStroke ( KeyEvent . VK_O, A c t i o n E v e n t .CTRL_MASK ) ) ; e l . a d d A c t i o n L i s t e n e r (new A c t i o n L i s t e n e r ( ) { public void a c t i o n P e r f o r m e d ( A c t i o n E v e n t e ) { menuAbrirArchivo ( ) ; } }); menu . add ( e l ) ; e l = new JMenuItem ( "Salvar archivo" ) ; e l . s e t A c c e l e r a t o r ( KeyStroke . getKeyStroke ( KeyEvent . VK_S , A c t i o n E v e n t .CTRL_MASK ) ) ; e l . a d d A c t i o n L i s t e n e r (new A c t i o n L i s t e n e r ( ) { public void a c t i o n P e r f o r m e d ( A c t i o n E v e n t e ) { menuSalvarArchivo ( ) ; } }); menu . add ( e l ) ; e l = new JMenuItem ( "Salvar archivo como..." ) ; e l . a d d A c t i o n L i s t e n e r (new A c t i o n L i s t e n e r ( ) { public void a c t i o n P e r f o r m e d ( A c t i o n E v e n t e ) { menuSalvarArchivoComo ( ) ; } }); menu . add ( e l ) ; menu . addSeparator ( ) ; e l = new JMenuItem ( "Quitar programa" ) ; e l . s e t A c c e l e r a t o r ( KeyStroke . getKeyStroke ( KeyEvent . VK_Q, A c t i o n E v e n t .CTRL_MASK ) ) ; e l . a d d A c t i o n L i s t e n e r (new A c t i o n L i s t e n e r ( ) { public void a c t i o n P e r f o r m e d ( A c t i o n E v e n t e ) { menuQuitarPrograma ( ) ; } }); menu . add ( e l ) ; menu = new JMenu ( "Ayuda" ) ; menu . setMnemonic ( KeyEvent . VK_Y ) ; b a r r a . add ( menu ) ; Contina en la siguiente pgina

168
Contina de la pgina anterior 232 233 234 235 236 237 238 239 240 241 242 e l = new JMenuItem ( "Acerca de..." ) ; e l . a d d A c t i o n L i s t e n e r (new A c t i o n L i s t e n e r ( ) { public void a c t i o n P e r f o r m e d ( A c t i o n E v e n t e ) { menuAcercaDe ( ) ; } }); menu . add ( e l ) ; return barra ; }

El mtodo es muy largo; pero hace casi lo mismo seis veces. Primero creamos la barra de men [175] y despus declaramos un men [176] y un elemento de men [177]. Vamos a usar dos mens (Archivo y Ayuda) y seis elementos de men (Nuevo archivo, Abrir archivo, Guardar archivo, Guardar archivo como. . . , Quitar programa y Acerca de. . . ); as que para no declarar ocho variables, declaramos slo dos y las usamos varias veces.2 Primero creamos el men Archivo [179] y le declaramos una tecla mnemnica [180]. La tecla que elegimos es la tecla a (por eso el KeyEvent.VK_A), y signica que cuando hagamos M-a (o Alt-a en notacin no-XEmacs), el men se activar. Y aadimos el men a la barra [181]. Despus creamos el elemento de men Nuevo archivo [183] y le ponemos un acelerador [184,185]. El acelerador es parecido a la tecla mnemnica; cuando lo presionemos, se activar la opcin de men. El acelerador que escogimos es C-n (la tecla es KeyEvent.VK_N y el modicador es ActionEvent.CTRL_MASK, o sea Control ). De una vez le ponemos un escucha al elemento del men [186190]. El escucha slo manda llamar al mtodo (que supusimos) menuNuevoArchivo. Por ltimo, aadimos el elemento de men al men [191]. Las lneas [192226] son una repeticin de esto ltimo; creamos los dems elementos de men, les ponemos aceleradores (no a todos) y les ponemos escuchas que solamente mandan llamar a algn mtodo, ninguno de los cuales hemos hecho. La nica lnea diferente es la [217], en la que aadimos un separador al men (un separador es una lnea nada ms). Y por ltimo, en las lneas [229238] creamos el men Ayuda de forma casi idntica al men Archivo. Lo ltimo que hace el mtodo es regresar la barra de men [241].
Usar una misma variable para distintos objetos es considerado una mala prctica de programacin; pero no es un pecado capital, y puede ser utilizado en casos semi triviales, como ste.
2

Interfaces grcas

169

Los eventos del men


Los eventos del men son los ms importantes, porque sern los que determinen cmo se comporta el programa. Cuando el usuario selecciona un elemento de men (MenuItem), o presiona un acelerador asociado a l, se dispara un evento de accin (action event), que se maneja con un escucha de accin o action listener. La interfaz ActionListener slo tiene un mtodo, actionPerformed, as que este escucha no tiene adaptador. Para dividir el trabajo, supusimos que tendramos un mtodo para cada una de las opciones del men. Los mtodos correspondientes a ste son: menuNuevoArchivo, menuAbrirArchivo, menuSalvarArchivo, menuQuitarPrograma, menuSalvarArchivoComo y menuAcercaDe, que va a ser lo siguiente que veamos. menuNuevoArchivo
248 249 250 251 252 253 254 255 256 257 public void menuNuevoArchivo ( ) { i f ( modificado ) { confirmarDejarArchivo ( ) ; } t e x t o . s e t T e x t ( "" ) ; modificado = false ; estado . s e t T e x t ( " " ) ; archivoOriginal = archivo = null ; marco . s e t T i t l e ( "Editor -- <ArchivoNuevo ) ; >" }

Lo primero que hace el mtodo es vericar que el archivo en la ventana no est modicado [249]. Si est modicado, entonces hay que conrmar si se deja o no el archivo [250]; para eso suponemos un mtodo conrmarDejarArchivo. Despus limpiamos el texto del componente de texto [252], y dejamos el estado del editor como no modicado [253,254]. Ponemos el nombre del archivo en null [255] (porque es nuevo) y cambiamos el ttulo de la pantalla al nuevo estado. Noten que la variable archivoOriginal la dejamos igual que a archivo. menuAbrirArchivo
264 265 266 267 public void menuAbrirArchivo ( ) { i f ( modificado ) { confirmarDejarArchivo ( ) ; } Contina en la siguiente pgina

170
Contina de la pgina anterior 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 J Fi l e C ho o s er f c = n u l l ; try { S t r i n g d i r = System . g e t P r o p e r t y ( "user.dir" ) ; f c = new J F il e C ho o s er ( d i r ) ; } catch ( S e c u r i t y E x c e p t i o n se ) { f c = new J F il e C ho o s er ( ) ; } f c . s e t D i a l o g T i t l e ( "Abrir archivo" ) ; i n t r = f c . showOpenDialog ( marco ) ; i f ( r == J F il e C ho o s er . APPROVE_OPTION) { File f = fc . getSelectedFile ( ) ; i f ( ! f . exists ( ) ) { JOptionPane . showMessageDialog ( marco ,

"El archivo \""+ f + "\" no existe." , "El archivo no existe" ,


JOptionPane .ERROR_MESSAGE ) ; return ; } archivo = f . toString ( ) ; abrirArchivoDeDisco ( ) ; } }

Igual que menuAbrirArchivo, lo primero que hace el mtodo es comprobar si el archivo ha sido modicado [265] y si es as pide conrmacin respecto a si se quiere dejar el archivo [266]. Despus declaramos un JFileChooser [269]. Un JFileChooser abre una ventana de dilogo donde podemos seleccionar archivos. En un bloque try [270] tratamos de obtener el directorio de trabajo actual (current working directory) [271,272] para crear el JFileChooser. Si no podemos (o sea, si se lanza una excepcin) [273], usamos el directorio $HOME del usuario [274] (es lo que hace por default el constructor de JFileChooser). Le ponemos ttulo al JFileChooser [277], y obtenemos la opcin que haya presionado el usuario del dilogo [278]. Si el usuario presion la opcin Aceptar [279], obtenemos el archivo del dilogo [280] y comprobamos que exista [281]. Si el archivo no existe, mostramos un mensaje dicindolo [282287] y salimos de la funcin [288]. Si no entramos al cuerpo del if (el segundo if , anidado dentro del primero), entonces el archivo existe, as que obtenemos el nombre [291] y lo abrimos con abrirArchivoDeDisco

Interfaces grcas

171 [292], que an no tenemos. Si el usuario no presion Aceptar, entonces presion Cancelar, y ya no hacemos nada.
Actividad 10.8 Consulta la documentacin de la clase JOptionPane, y presta atencin al mtodo esttico showMessageDialog(). Haz lo mismo con la clase JFileChooser.

menuSalvarArchivo
302 303 304 305 306 307 308 309 310 311 public void menuSalvarArchivo ( ) { i f ( ! modificado ) { return ; } i f ( a r c h i v o == n u l l ) { menuSalvarArchivoComo ( ) ; } else { salvarArchivoEnDisco ( ) ; } }

Con este mtodo ocurre lo contrario a los primeros dos; si el archivo no ha sido modicado [303] entonces se sale del mtodo [304] (para qu lo salvamos si no ha habido cambios?). Despus comprueba si el nombre del archivo es null [306]. Si lo es signica que el archivo es nuevo, y entonces hay que hacer lo que el mtodo menuSalvarArchivoComo y, por lo tanto, lo manda llamar [307]. Si no, manda salvar el archivo en disco [309], con el mtodo salvarArchivoEnDisco. menuSalvarArchivoComo
319 320 321 322 323 324 325 326 public void menuSalvarArchivoComo ( ) { J F il e C ho o s er f c = n u l l ; try { S t r i n g d i r = System . g e t P r o p e r t y ( "user.dir" ) ; f c = new J Fi l e Ch o o s er ( d i r ) ; } catch ( S e c u r i t y E x c e p t i o n se ) { f c = new J Fi l e Ch o o s er ( ) ; } Contina en la siguiente pgina

172
Contina de la pgina anterior 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347

f c . s e t D i a l o g T i t l e ( "Salvar archivo como..." ) ; i n t r = f c . showSaveDialog ( marco ) ; File f = fc . getSelectedFile ( ) ; i f ( r == J F i le C h oo s e r . APPROVE_OPTION) { i f ( f . exists ( ) ) { i n t r2 ; r 2 = JOptionPane . showConfirmDialog ( marco , "El archivo \""+ f + "\" existe.\n"+ "Desea sobreescribirlo?" , "El archivo ya existe" , JOptionPane . YES_NO_OPTION ) ; i f ( r 2 ! = JOptionPane . YES_OPTION) return ; } archivo = f . toString ( ) ; salvarArchivoEnDisco ( ) ; } }

En las lneas [320326] hacemos lo mismo que en el mtodo menuAbrirArchivo: crear un JFileChooser, si es posible con el directorio de trabajo actual. Le ponemos un ttulo adecuado [328], obtenemos la opcin que presion el usuario en el dilogo [329], y obtenemos el archivo que se seleccion [330]. Si el usuario eligi la opcin de Aceptar en el dilogo [331], se comprueba que el archivo no exista [332]. Si existe, le preguntamos al usuario si quiere sobreescribirlo [334340]. Si la respuesta es distinta de S, entonces salimos del mtodo [341,342]. Si no, obtenemos el nombre del archivo [344], y mandamos salvar el archivo [345]. Si el usuario no eligi la opcin de Aceptar, ya no hacemos nada. menuQuitarPrograma
354 355 356 357 358 359 public void menuQuitarPrograma ( ) { i f ( modificado ) { ; confirmarDejarArchivo ( ) ; } System . e x i t ( 0 ) ; }

Interfaces grcas

173

Si el archivo est modicado [355], pedimos conrmacin para dejarlo [356] y despus salimos del programa [358]. acercaDe
365 366 367 368 369 370 public void menuAcercaDe ( ) { JOptionPane . showMessageDialog ( marco , "Editor de texto sencillo" , "Acerca de..." , JOptionPane . INFORMATION_MESSAGE ) ; }

ste es el mtodo ms idiota de todos los tiempos; slo muestra un dilogo con informacin del programa (que por cierto no tiene ninguna informacin til).

El resto de los mtodos


Ya hemos casi terminado los mtodos que habamos supuesto tener. Slo nos faltan tres, que son realmente los que realizan el trabajo pesado: leer y escribir el archivo en disco. abrirArchivoDeDisco
380 381 382 383 384 385 386 387 388 389 390 391 392 public void a b r i r A r c h i v o D e D i s c o ( ) { char [ ] c a r a c t e r e s = new char [BLOQUE ] ; int leidos ; S t r i n g B u f f e r sb = new S t r i n g B u f f e r (BLOQUE ) ; try { F i l e R ea d e r f r = new F i l e R ea d e r ( a r c h i v o ) ; do { l e i d o s = f r . read ( c a r a c t e r e s ) ; sb . append ( c a r a c t e r e s , 0 , l e i d o s ) ; } while ( l e i d o s ! = 1 && l e i d o s == BLOQUE ) ; f r . close ( ) ; t e x t o . s e t T e x t ( sb . t o S t r i n g ( ) ) ; texto . setCaretPosition ( 0 ) ; Contina en la siguiente pgina

174
Contina de la pgina anterior 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 } catch ( IOException i o e ) { JOptionPane . showMessageDialog ( marco , "El archivo \""+ a r c h i v o + "\" no se pudo leer." , "Error al leer" , JOptionPane .ERROR_MESSAGE ) ; archivo = archivoOriginal ; return ; } archivoOriginal = archivo ; modificado = false ; estado . s e t T e x t ( "" ) ; marco . s e t T i t l e ( "Editor -- "+ a r c h i v o ) ; }

El mtodo primero crea un arreglo de carcteres [381], del tamao que denimos en la variable BLOQUES, y declara un entero para saber cuntos carcteres leemos en cada pasada [382]. Tambin declara una cadena variable (StringBuffer) [383]. Las cadenas variables son como las cadenas, con la ventaja de que pueden aumentar y disminuir de tamao, editarse los carcteres que tienen dentro, etc. Despus, dentro de un bloque try [384] abrimos el archivo de texto para lectura [385], y en un ciclo [386-389] leemos todos los carcteres del archivo y los metemos en la cadena variable. Cerramos el archivo [390], ponemos todos esos carcteres como el texto de nuestro componente de texto [391] y hacemos que lo que se vea del texto sean los primeros carcteres [392]. Si algo sale mal en el try [393], suponemos que no pudimos leer el archivo, se lo informamos al usuario [394399], regresamos el nombre del archivo al original [400] (si no hacemos esto podemos perderlo) y nos salimos de la funcin [401]. Si salimos airosos del try, actualizamos la variable archivoOriginal [403] y ponemos el estado del editor en no modicado [404406], lo que tiene sentido pues acabamos de abrir el archivo.
Actividad 10.9 Consulta la documentacin de la clase FileReader y fjate en los constructores y en los mtodos read y close.

Interfaces grcas

175

salvarArchivoEnDisco
416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 public void s a l v a r A r c h i v o E n D i s c o ( ) { try { F i l e W r i t e r fw = new F i l e W r i t e r ( a r c h i v o ) ; fw . w r i t e ( t e x t o . g e t T e x t ( ) ) ; fw . c l o s e ( ) ; } catch ( IOException i o e ) { JOptionPane . showMessageDialog ( marco , "El archivo \""+ a r c h i v o + "\" no se pudo escribir." , "Error al escribir" , JOptionPane .ERROR_MESSAGE ) ; archivo = archivoOriginal ; return ; } archivoOriginal = archivo ; modificado = false ; estado . s e t T e x t ( "" ) ; marco . s e t T i t l e ( "Editor -- "+ a r c h i v o ) ; }

Dentro de un try [417], abrimos el archivo para escritura [418], le escribimos todo el texto de nuestro componente de texto [419] y cerramos el archivo [420]. Si algo sale mal [421], le decimos al usuario que no pudimos salvar su archivo [422427], regresamos el nombre del archivo al original [428] y salimos del programa [429]. Si salimos bien del try, actualizamos la variable archivoOriginal [431] y ponemos el estado del editor en no modicado [432434], lo que tiene sentido pues acabamos de salvar el archivo.
Actividad 10.10 Consulta la documentacin de la clase FileWriter y fjate en los constructores y en los mtodos write y close.

conrmarDejarArchivo
442 443 public void c o n f i r m a r D e j a r A r c h i v o ( ) { int r ; Contina en la siguiente pgina

176
Contina de la pgina anterior 444 445 446 447 448 449 450 451 452 r = JOptionPane . showConfirmDialog ( marco ,

"El archivo no se ha salvado.\n"+ "Desea salvarlo?" , "El archivo ha sido modificado" ,


JOptionPane . YES_NO_OPTION ) ; i f ( r == JOptionPane . YES_OPTION) menuSalvarArchivo ( ) ; }

Le avisamos al usuario que el archivo est modicado y preguntamos si quiere salvarlo [444449]. Si quiere, llamamos al mtodo menuSalvarArchivo [451], porque es equivalente.

El mtodo main
Por ltimo, hay que ver el mtodo main:
459 460 461 462 463 464 465 466 467 468 469 470 471 public s t a t i c void main ( S t r i n g [ ] args ) { i f ( args . l e n g t h > 1 ) { System . e r r . p r i n t l n ( "Uso: Editor [archivo]" ) ; System . e x i t ( 1 ) ; } Locale . s e t D e f a u l t (new Locale ( "es" , "MX" ) ) ; E d i t o r ed = n u l l ; i f ( args . l e n g t h == 0 ) { ; ed = new E d i t o r ( ) ; } else { ed = new E d i t o r ( args [ 0 ] ) ; } }

Lo que hace main es comprobar que a lo ms se llam al programa con un parmetro [460463]. Despus, dene el local del programa para que hable espaol de Mxico [464], declara un editor [465], y llama al constructor apropiado [466470].

Actividad 10.11 Consulta la documentacin de la clase Locale.

Interfaces grcas

177

Actividad 10.12 Compila el archivo Editor.java y ejectalo. Est incluido en el archivo ejemplos.tar.gz.

Observaciones nales
Hay que reexionar un poco acerca del diseo de nuestro editor. Si se dieron cuenta, dentro de los manejadores de eventos (todos en clases internas annimas) se intent utilizar el menor cdigo posible. Poner mucho cdigo en los manejadores de eventos slo nos complica la vida y hace el cdigo ms feo. En general, se encapsula lo que hay que hacer en un manejador de eventos en un solo mtodo y slo se invoca. El diseo se hizo de forma descendiente (top-down); comenzamos haciendo los mtodos ms generales para despus hacer los ms particulares. Java se presta mucho para trabajar de esta forma, y en el caso de interfaces grcas nos facilita la vida ya que es muy sencillo pensar en una ventana principal y qu va a ocurrir cuando elijamos un men o presionemos un botn. Fjense cmo todos los mtodos son compactos. Cada mtodo hace una nica cosa y se asegura de hacerla bien, manejando las excepciones de acuerdo. Ningn mtodo es realmente largo; casi todos caben en una pantalla de texto normal, y los que no es porque tienen mucho cdigo de interfaces grcas (como la creacin de la barra de men). Ningn mtodo por s mismo debe ser difcil de entender. Noten tambin que casi no hay repeticin de cdigo excepto para cosas triviales. Es necesario explicar por qu todo el programa consiste de una clase. En el caso de este problema (hacer un editor de texto), todas las clases necesarias las provee Java: toda la parte de interfaces grcas (Swing) y toda la parte de entrada/salida (las clases del paquete java.io). Por lo tanto, slo tenamos que hacer la clase de uso, que es nuestro programa. Un ltimo aspecto respecto a la clase Locale. Es importante que se acostumbren a tratar de usar el idioma nativo del usuario para un programa. Todas las cadenas que aparecen en clases como JFileChooser o JMessageDialog estn por omisin en ingls. Java por suerte proporciona la habilidad de cambiar dinmicamente esas cadenas, de acuerdo a la lengua que queramos usar. Java maneja el concepto de locales, que es la manera en que determina cmo presentar cierta informacin al usuario. Esto no slo se aplica a en qu idioma imprimir "S" o "Archivo", sino a muchas diferencias que hay entre idiomas, e incluso entre idiomas iguales en distintos pases. Por ejemplo, en Mxico escribimos 1,000.00 para representar un millar con dos cifras decimales; pero en Espaa escriben 1.000,00 para representar lo mismo. Y en ambos pases se habla espaol (o eso dicen en Espaa). Los locales de Java manejan todo este tipo de asuntos.

178 La lnea
464 Locale . s e t D e f a u l t (new Locale ( "es" , "MX" ) ) ;

dene como local por omisin al que habla espaol ("es"), en Mxico ("MX"). Pueden comentar esa lnea y volver a compilar y ejecutar el programa para que vean la diferencia. Fjense en particular cuando usamos las clases JFileChooser y JOptionPane. Los cdigos de lenguaje y pas usados para determinar los locales estn denidos en el estndar ISO-639 e ISO-3166 respectivamente. El primero lo pueden consultar en
<http://www.ics.uci.edu/pub/ietf/http/related/iso639.txt>

y el segundo en
<http://www.chemie.fu-berlin.de/diverse/doc/ISO_3166.html>

Ejercicios
1. Haz una interfaz grca para la Base de Datos. La ventana principal de la aplicacin mostrar tres campos vacos, que representarn el nombre, la direccin y el telfono; usa tres etiquetas para sealizar cul es cual. El usuario debe poder introducir cadenas en estos campos. Adems, la ventana tendr tres botones; uno que dir Agregar, otro que dir Borrar y otro que dir Buscar. En caso de que el usuario haga click en el botn Agregar, se deber comprobar que el usuario haya introducido datos en los campos y que sean correctos (o sea, que el telfono sea un entero vlido); de ser as, crear un RegistroAgenda y agregarlo a la base de datos. Despus de hacerlo, deber volver a dejar vacos los campos. Si el registro se repite, debe utilizar un dilogo para avisarle al usuario que no pudo agregar el registro porque ya exista uno igual. Si alguno de los campos est vaco cuando el usuario haga click en Agregar, un dilogo deber informarle que no puede agregar un registro incompleto. Si el usuario hace click en Buscar, se deber comprobar que slo el campo del nombre o el del telfono tenga informacin. De ser as, se realizar una bsqueda por nombre o por telfono (dependiendo de cul de los dos tenga informacin). Debe comprobarse que si es el campo del telfono el que tiene informacin, que sta sea un entero vlido. Si se encuentra el registro, los campos deben actualizarse con los datos del registro. Si no se encuentra, un dilogo deber decirle al usuario que el nombre (o telfono) que busc no existen.

Interfaces grcas

179 Si se hace click en Buscar sin que haya informacin en el campo del nombre o el del telfono, un dilogo deber decirle al usuario que necesita uno de ambos para buscar un registro. Al momento en que el usuario haga click en Borrar, debe haber informacin en los tres campos. De no ser as, un dilogo debe informar al usuario que no se puede borrar un registro del que no se tiene toda la informacin. Aqu se le deja al programador una de varias opciones. La primera es que el usuario pueda borrar cualquier registro si de memoria teclea los datos que le corresponden y despus hace click en Borrar. En este caso el programador tiene que hacer primero una bsqueda, despus una comprobacin con el mtodo equals, y en caso de que exista el registro, borrarlo y si no existe avisarle al usuario que est tratando de borrar un registro inexistente. La segunda es que el usuario slo pueda borrar un registro que haya buscado antes. Esta estrategia tiene la ventaja de que se garantiza que el registro exista y se evita la necesidad de buscarlo. Pero hay que tener siempre una referencia al ltimo registro buscado. Tambin es posible implementar ambas; aunque es particularmente engorroso. Se deja a criterio del programador cul debe de implementar. Adems de todo esto, el programa debe tener una barra de men con dos entradas: Base de Datos y Ayuda. En la entrada Base de Datos deben existir (al menos) las opciones Nueva Base de Datos, Guardar Base de Datos, Recuperar Base de Datos y Salir del programa. Cada una de las opciones hace lo que su nombre indica. Pon lo que quieras en la entrada de Ayuda, pero no la dejes vaca. Varios aspectos que el programa debe cumplir: Nunca debe abortar. El programa debe ser indestructible. No importa lo que pase, el programa debe poder continuar corriendo a menos que el usuario cierre la ventana o seleccione la opcin Salir del programa del men Base de Datos. O que caiga un meteoro en la computadora. Esto quiere decir, en primer lugar, que todas las excepciones deben manejarse de manera inteligente. Nunca debe terminar el programa, aunque no pueda leer o escribir la base de datos. Tampoco debe de terminar por alguna excepcin del tipo de NullPointerException. Asegrate de que todos tus mtodos funcionen con el caso de que la lista de registros sea null. Si algo sale mal, siempre debe ser avisado el usuario, con un mensaje claro y compacto. Se probarn todos los mtodos que se han visto en el curso. Se probar el poder buscar y agregar registros, as como el poder guardar y el poder recuperar la base de datos. Se probarn todos estos mtodos tambin cuando

180 la lista de registros sea null. Debe funcionar el programa en todos estos casos. El programa no debe permitir el perder informacin sin avisar al usuario. Si algn registro ha sido aadido o borrado de la base de datos, y el usuario quiere salir del programa, debe preguntrsele primero si quiere guardar la base de datos en disco duro. Para esto, el programa debe estar siempre consciente de si la base de datos ha sufrido alguna modicacin. 2. (Opcional) Hay muchas restricciones sobre cundo puede o no hacer click el usuario sobre los botones Agregar, Borrar y Bsqueda. Utiliza los mtodos setEnabled e isEnabled de la clase JButton (heredado el primero de AbstractButton y el segundo de Component), para que el usuario realmente slo pueda hacer click en uno u otro botn cuando las condiciones lo permitan.

Preguntas
1. Hay alguna parte del editor que no comprendas? 2. Qu se te antoja hacer con interfaces grcas? Explcate.

Anda mungkin juga menyukai