Dpto. Electrónica y Telecomunicaciones CAPÍTULO 12: EVENTOS ÍNDICE 1 2 3 4 5 6 7 8 Introducción ........................................................................................................................................2 Swing: elementos básicos ...................................................................................................................3 2.1 Ejemplo 1: mi primera interfaz Swing ...........................................................................................6 Trabajando con botones ......................................................................................................................7 Gestión de eventos en Swing ..............................................................................................................8 4.1 Listeners. Definición y creación...................................................................................................10 4.2 Tipos de eventos y oyentes ..........................................................................................................13 Gestores de disposición.....................................................................................................................14 5.1 BorderLayout ...............................................................................................................................15 5.2 BoxLayout....................................................................................................................................15 5.3 FlowLayout ..................................................................................................................................15 5.4 GridLayout ...................................................................................................................................15 5.5 GridBagLayout.............................................................................................................................15 Ejemplos con otros elementos Swing ...............................................................................................16 6.1 Componentes de texto ..................................................................................................................16 6.2 Casillas de verificación ................................................................................................................17 6.3 Listas ............................................................................................................................................18 6.4 Ejemplo 2: mi segunda interfaz Swing.........................................................................................20 Ejercicios propuestos ........................................................................................................................27 Referencias .......................................................................................................................................28 8.1 Bibliografía ..................................................................................................................................28 8.2 Internet .........................................................................................................................................28 Java – Introducción a la programación 1 Capítulo 12: Eventos 1 INTRODUCCIÓN Los eventos están muy relacionados con la programación de interfaces gráficas de usuario (GUI). Un evento, en general, es una acción que se ejecuta sobre la aplicación (por ejemplo, escribir un texto, hacer clic sobre un botón, seleccionar una opción de un menú…) y que produce una respuesta (un sonido, un texto, el cierre de una ventana…), o debería producirlo, siempre que el evento lleve asociado en el programa un código que genere dicha respuesta. Tradicionalmente, un programa que quisiera atender a las peticiones del usuario, debía esperar, en un gran bucle, comprobando en cada momento si el usuario había hecho algo o no. Esta técnica recibe el nombre de polling. El polling, por una parte, consume recursos ejecutando un código simple de espera (bucle) y, además, requiere que todo el código asociado a las acciones del usuario se encuentre en ese bucle, por grande que pueda ser el número de éstas. Java resolvió estas desventajas del polling mediante la orientación a eventos, que se introdujo con las clases del AWT (Abstract Window Toolkit) de Java 1.1. El diseño de interfaces gráficas con AWT, sin embargo, fue superado en Java 2 por las Java Foundation Classes (JFC), cuya parte de diseño GUI se denomina “Swing”. A pesar de su sencillez, pensamos que el diseño con AWT, así como su modelo de eventos, ha quedado bastante anticuado, por lo que nos centraremos en este capítulo únicamente en la moderna biblioteca Swing. El lector podrá encontrar fácilmente información sobre el viejo modelo de eventos de AWT en las referencias de este libro. En resumen, en este capítulo veremos cómo escribir código para que nuestros programas interactúen con los usuarios en lo que se conoce como ‘programación conducida por eventos’. El número de eventos que pueden producirse en un programa es elevado, como lo es el número de elementos (gráficos o no) de que puede constar una interfaz de usuario. Por ello, nos limitaremos a realizar una introducción a los elementos GUI más comunes de Swing y su gestión de eventos asociada, de forma que el lector pueda profundizar posteriormente en otros elementos más avanzados y su modelo de eventos, bien a través de las referencias bibliográficas o de la documentación de la API de Java. La forma de desarrollar una inferfaz gráfica que se presenta en este capítulo es totalmente manual y su objetivo es el introducir al lector en el tema, pero dependiendo de la complejidad, los programadores expertos hacen uso de herramientas avanzadas o constructores de GUI, que permiten crear y situar (drag & drop) los componentes gráficamente, generando de forma automática el código fuente apropiado. Java – Introducción a la programación 2 Capítulo 12: Eventos 2 SWING: ELEMENTOS BÁSICOS En AWT, los elementos básicos se dividían en componentes y contenedores. Todos ellos provenían de la superclase Component. Los componentes permitían al usuario interactuar con la aplicación: botones, menús, barras de desplazamiento, etiquetas de texto, listas de selección, etc. Los contenedores permitían agrupar componentes: ventanas, frames, cuadros de diálogo, paneles, etc. Muchos de estos componentes eran dependientes de la plataforma; esto era un inconveniente, ya que la interfaz gráfica podía ofrecer un aspecto diferente en distintas plataformas. En Swing, los componentes heredan de la clase JComponent, mientras que los contenedores lo hacen de la clase Container. Algunos de los componentes Swing más utilizados son los siguientes: • • • Botones: aunque existen varios tipos de botones, la clase básica para generar un botón es JButton. Botones de opción: grupos de botones de los cuales sólo uno puede estar pulsado en cada momento. JRadioButton y ButtonGroup. En la siguiente figura se pueden ver algunos ejemplos: Casillas de verificación: JCheckBox. Figura 1. Botones y casillas de verificación • • Etiquetas: texto no seleccionable que se puede situar en cualquier lugar de la aplicación (JLabel). Campos de texto: áreas de inserción y edición de texto de una sola fila (JTextField). Figura 2. Una etiqueta con un campo de texto • • Áreas de texto: zonas de más de una fila y columna para introducir texto (JTextArea). Listas desplegables de selección única: JComboBox. Figura 3. Lista desplegable de selección única Java – Introducción a la programación 3 Capítulo 12: Eventos • Listas de selección múltiple: JList. Figura 4. Lista de selección También encontramos elementos contenedores muy utilizados, como los siguientes: • Paneles o contenedores de propósito general: permiten agrupar diversos componentes en su interior. Existen varios tipos, como se muestra en las figuras, aunque el más simple es JPanel. Paneles (JPanel) Paneles con barras de desplazamiento (JScrollPane) Paneles divididos (JSplitPane) Paneles de selección (JTabbedPane) Barras de herramientas (JToolBar) Figura 5. Contenedores de propósito general Finalmente, podemos citar elementos más complejos o de propósito especial: • Barras de progreso: JProgressBar. Java – Introducción a la programación 4 Capítulo 12: Eventos • Cuadros de selección de ficheros: JFileChooser. • Tablas: JTable. • Árboles: JTree. Y otros muchos elementos y clases para diseñar prácticamente cualquier elemento de una interfaz gráfica que podamos necesitar y que el lector podrá encontrar al completo en el Tutorial de Java de Sun o bien en la documentación del JDK que tenga instalado. Java – Introducción a la programación 5 Capítulo 12: Eventos 2.1 Ejemplo 1: mi primera interfaz Swing Las clases Swing se encuentran en el paquete javax.swing, por lo que será necesario importarlo (como hemos aprendido en el capítulo 10) al principio de nuestro programa. Asimismo, muchos programas Swing precisan de otros paquetes del AWT, que también es conveniente importar. Así, lo más conveniente será empezar nuestros programas con: import javax.swing.*; import java.awt.*; import java.awt.event.*; Un programa con una interfaz gráfica Swing precisa, al menos, de un contenedor de alto nivel, que le proporciona a los componentes el soporte que precisan para su creación (o pintado) y manejo de eventos. Hay tres clases que representan contenedores de alto nivel: JFrame, que implementa una simple ventana; JDialog, que implementa una ventana secundaria o cuadro de diálogo y JApplet, que representa el área de visualización de un applet en un navegador. Ventana (JFrame) Cuadro de diálogo (JDialog) Applet (JApplet) Figura 6. Contenedores de alto nivel. Para nuestros ejemplos, utilizaremos el contenedor JFrame, de forma que la ejecución de nuestros programas aparecerá en una ventana similar a ésta: El código fuente (Prueba.java) de este primer ejemplo es el siguiente: public class Prueba { public static void main(String[] args) { //Creación de objetos. JFrame ven = new JFrame ("Prueba"); final JLabel etiq = new JLabel ("¡Hola mundo!"); //Añadir la etiqueta al contenedor de alto nivel //y visualizar la ventana. ven.getContentPane( ).add(etiq); ven.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE); ven.pack( ); ven.setVisible (true); } } Java – Introducción a la programación 6 Capítulo 12: Eventos Como se puede ver, los elementos de que consta esta interfaz son básicamente dos: la ventana (JFrame) y una etiqueta de texto (JLabel) con el texto típico de los ejemplos de Java: “¡Hola mundo!” Además, nuestro ejemplo contempla un evento básico de la ventana, como es el hecho de cerrarla, mediante el método setDefaultCloseOperation ( ), contenido en la clase JFrame desde la versión 1.3 de Java. Se trata de una forma sencilla y cómoda de contemplar el evento, pero si no dispusiéramos de él, habría que realizar la gestión del evento como veremos posteriormente para el resto de elementos Swing. 3 TRABAJANDO CON BOTONES Generar botones es bastante sencillo. Primero, se definen y crean los objetos botón llamando al constructor de la clase JButton con la etiqueta que deseemos para el botón, como se muestra a continuación: JButton b1 = new JButton (“Botón 1”), b2 = new JButton (“Botón 2”); Una vez hecho esto, habrá que ubicar el botón en un contenedor (un panel, una ventana…), llamando al método add( ) de éste. Así, si el contenedor donde vamos a colocar nuestros botones es un objeto Container llamado con, haríamos: con.add (b1); con.add (b2); Aún no hemos hablado acerca de la disposición de los componentes en un contenedor, pero veremos que es posible definir la forma en que se sitúan mediante los “gestores de disposición” (Layout Managers). Tampoco hemos visto aún cómo gestionar los eventos generados al actuar sobre los botones, por lo que, aunque hagamos clic sobre ellos, veremos que no ocurre nada. Veamos cómo solucionarlo. Java – Introducción a la programación 7 Capítulo 12: Eventos 4 GESTIÓN DE EVENTOS EN SWING A modo de breve ‘reseña histórica’, resumiremos a continuación el modelo de eventos de AWT, a fin de que el lector pueda tener un ligero contacto con él y ver sus diferencias y similitudes con el modelo de eventos Swing, antes de pasar al tema que nos ocupa. Los eventos en AWT se indican mediante objetos de la clase Event que contienen toda la información acerca de la acción: objeto sobre el que interactuó el usuario, posición del ratón, teclas pulsadas, etc. La forma de recibir los eventos en la aplicación es mediante métodos action( ) que hay que redefinir en la clase correspondiente. Estos métodos reciben cualquier evento que se produzca sobre el elemento, si bien puede que sean capaces de gestionarlo o no, en cuyo caso devolverán un false al contenedor de nivel superior. class MyButton extends Button { Button(String nombre) { super(nombre); } public boolean action(Event evt, Object obj) { // Código de gestión del evento return true; } } Esto supone que el método action( ) debe comprobar en cada momento qué tipo de evento se ha producido y si es necesario gestionarlo. Otra posibilidad es redefinir el método action( ) en la clase principal, lo que nos obliga no sólo a comprobar el tipo de evento ocurrido, sino también el objeto que lo ha generado, con todos los problemas e incomodidades que ello conlleva. Algo similar a esto último es lo que hace el método handleEvent( ), que recoge todos los eventos que ocurran en la aplicación, aunque no deban ser gestionados en ese punto. class interfaz extends Frame { public boolean action(Event evt, Object obj) { if (art==boton) ... else if (art==check) ... else if (art==texto) ... else ... } return true; } La forma de manejar los eventos en Swing es separando la interfaz (los componentes) de la lógica (el código que se ejecutará cuando se produce un evento en un componente). Cada componente Swing puede informar acerca de todos los eventos que le pueden ocurrir o bien podemos gestionarlos individualmente. En el caso del componente más simple, un botón, un evento podría ser el hecho de que se presione dicho botón (parece ser el más interesante), pero también podría serlo el hecho de que el ratón pase por encima de él. Vamos a centrarnos en el primero de ellos: Java – Introducción a la programación 8 Capítulo 12: Eventos Para activar la gestión de los eventos de pulsación del botón, llamamos al método addActionListener ( ) de JButton. Este método recibe como parámetro un objeto (que llamaremos oyente o receptor) que implemente la interfaz ActionListener, cuyo único método es actionPerformed ( ). Por tanto, debemos adjuntar un código ActionListener en una clase y asociarlo al botón vía addActionListener( ). Veámoslo en un ejemplo: JButton b1 = new JButton (“Alex”), b2 = new JButton (“Amaia”); JTextField mensajes = new JTextField (10); class BotonListener implements ActionListener { public void actionPerformed (ActionEvent e) { String etiqueta = ((JButton)e.getSource ( )).getText ( ); mensajes.setText (etiqueta); } } BotonListener prueba = new BotonListener ( ); b1.addActionListener (prueba); b2.addActionListener (prueba); El objeto ActionEvent que recibe el método actionPerformed( ) contiene toda la información acerca del evento y su procedencia. Además, dispone del método getSource( ), que permite obtener directamente el objeto que ha producido el evento. Esta información se utiliza en el ejemplo para escribir en un campo de texto (JTextField) la etiqueta del botón que ha sido pulsado. Aunque en el ejemplo anterior se ha utilizado una clase BotonListener para ambos componentes, lo más común suele ser codificar el ActionListener como una clase interna anónima. Así, el código en negrita quedaría escrito de la siguiente forma: ActionListener prueba = new ActionListener ( ) { public void actionPerformed (ActionEvent e) { String etiqueta = ((JButton)e.getSource ( )).getText ( ); mensajes.setText (etiqueta); } }; El receptor de eventos de botón únicamente gestiona los eventos de tipo Action, pero un receptor puede gestionar varios eventos, uno por cada método de la clase receptora. Java – Introducción a la programación 9 Capítulo 12: Eventos 4.1 Listeners. Definición y creación Ya hemos visto que los listeners (también llamados oyentes) son objetos de una clase que implementa un tipo particular de interfaz oyente. En el caso de los botones, la interfaz era ActionListener. El programador únicamente debe preocuparse de crear un objeto oyente y registrarlo con el componente que dispara el evento, mediante un método del tipo addXYZListener( ), donde XYZ representa el evento que se escucha. Si se intenta escuchar eventos incorrectos, el compilador nos indicará un error al compilar nuestro programa. Generalmente, el primer paso para gestionar eventos de un componente concreto, es consultar la página de documentación de dicho componente (o bien el tutorial completo de Sun, donde también hay referencias), ya que contiene descripciones y ejemplos sobre los eventos más comunes: por ejemplo, en la página que explica cómo diseñar ventanas podremos encontrar ejemplos sobre cómo escribir un oyente que cierre la aplicación cuando se cierre la ventana, tal y como hemos visto en los primeros ejemplos de este capítulo. Por otra parte, ya hemos visto que la gestión de un evento precisa un código concreto para: • Indicar en la declaración de la clase del oyente que ésta implementa una interfaz listener adecuada o bien extiende una clase que ya la implementa. Por ejemplo: public class miClaseOyente implements ActionListener {... • Registrar una instancia de dicha clase como oyente de uno o más componentes: miComponente.addActionListener(instanciaDemiClaseOyente); • Escribir el/los método/s de la clase oyente que gestione/n el evento: public void actionPerformed(ActionEvent e) { // Código que genera la respuesta al evento. } Vamos a ver estos conceptos en un ejemplo algo más complejo (AplicacionSwing.java), que comentaremos paso a paso a continuación: public class AplicacionSwing { private static String frase = "Número de clics: "; private int numClics = 0; public Component crearComponentes() { final JLabel etiq = new JLabel(frase + "0 "); JButton boton = new JButton("Esto es un botón"); boton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { numClics++; etiq.setText(frase + numClics); } }); JPanel pane = new JPanel(); Java – Introducción a la programación 10 Capítulo 12: Eventos pane.setBorder(BorderFactory.createEmptyBorder (30, 30, 10, 30)); pane.setLayout(new GridLayout(0, 1)); pane.add(boton); pane.add(etiq); } return pane; //Método main. Se llama al ejecutar la clase. public static void main(String[] args) { //Crear el contenedor de alto nivel. JFrame ven = new JFrame("AplicacionSwing"); //Crear el objeto de la clase AplicacionSwing y //llamar al método que crea los componentes. AplicacionSwing app = new AplicacionSwing (); Component contenido = app.crearComponentes(); //Añadir los componentes y dibujar la ventana. ven.getContentPane().add(contenido, BorderLayout.CENTER); ven.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE); ven.pack(); ven.setVisible(true); } } La ejecución de este programa genera la siguiente ventana en la cual, al pulsar el botón, se actualiza la etiqueta de texto con el número de veces que se ha hecho clic sobre él: Vamos a ver las partes más importantes del programa: • Creación de componentes: la aplicación dispone de un botón (JButton) y una etiqueta de texto (JLabel). La creación de componentes tiene lugar en el método crearComponentes( ), que devuelve un componente de tipo panel (JPanel) como veremos enseguida. Por ello, se crea un objeto de la clase AplicacionSwing y se llama a dicho método: AplicacionSwing app = new AplicacionSwing (); Component contenido = app.crearComponentes(); En este método, por un lado, se crea el botón: Java – Introducción a la programación 11 Capítulo 12: Eventos JButton boton = new JButton("Esto es un botón"); Y, por otro lado, se crea la etiqueta con el texto almacenado en una variable de tipo String y el número inicial de clics (cero, 0): final JLabel etiq = new JLabel(frase + "0 • "); Definición y creación del oyente: en el siguiente código: boton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { numClics++; etiq.setText(frase + numClics); } }); Se define un gestor de los eventos del botón (oyente, new ActionListener( )) y se asocia al objeto boton (addActionListener( )). En este caso, el parámetro de entrada de este método es una clase oyente anónima (en color azul), cuyo único método es actionPerformed( ), en el que se genera la respuesta al evento: por cada pulsación, se actualiza el número de clics de ratón y se reescribe la etiqueta de texto. El objeto ActionEvent ‘e’ permite que el método pueda obtener información acerca del origen del evento, pero en este caso no se emplea. • Creación de contenedores y adición de los componentes a los contenedores: el botón y la etiqueta se agrupan en un panel (JPanel), que es devuelto por el método anterior. Se trata de una especie de jerarquía de componentes gráficos que luego se irán añadiendo a la ventana. Como siempre, primero se crea el objeto panel: JPanel pane = new JPanel(); Se establecen unos bordes (márgenes) para separarlo del borde de la ventana: pane.setBorder(BorderFactory.createEmptyBorder (30, 30, 10, 30)); Se indica la forma de organizar los elementos en su interior (v. gestores de disposición): pane.setLayout(new GridLayout(0, 1)); Y, finalmente, se añaden (agrupan) los dos componentes en el panel creado: pane.add(boton); pane.add(etiq); • Selección del contenedor de alto nivel: ya lo hemos introducido anteriormente. De nuevo, el contenedor utilizado es una ventana JFrame. JFrame ven = new JFrame("AplicacionSwing"); • Adición de elementos al contenedor de alto nivel: en el método principal (main), antes de dibujar el contenedor de alto nivel, hay que añadir los componentes (el panel en el que los hemos agrupado): Java – Introducción a la programación 12 Capítulo 12: Eventos ven.getContentPane( ).add(contenido,BorderLayout.CENTER); El resto de métodos de la clase JFrame ya los hemos visto en ejemplos anteriores. 4.2 Tipos de eventos y oyentes Todos los elementos Swing disponen de métodos addXYZListener y removeXYZListener, que permiten añadir y eliminar los tipos de oyentes adecuados a cada componente. Algunos eventos básicos, así como los oyentes y métodos apropiados, junto con los componentes que los soportan, se presentan en la siguiente relación: Evento ActionEvent Component Event ItemEvent Interfaz, adaptador* y métodos add-remove ActionListener addActionListener( ) removeActionListener( ) ComponentListener ComponentAdapter addComponentListener( ) ComponentListener( ) ItemListener addItemListener( ) removeItemListener( ) KeyListener KeyAdapter addKeyListener( ) removeKeyListener( ) Métodos de la interfaz actionPerformed(ActionEvent) componentHidden(ComponentEvent) ,componentShown (ComponentEvent), componentMoved (ComponentEvent), componentResized (ComponentEvent) itemStateChanged(ItemEvent) Componentes JButton, JList, JMenuItem, JTextField Component y sus derivados, incluyendo JButton, JCheckBox, JPanel, Container, JApplet, JFrame, JLabel, JList, JTextArea JCheckbox, JCheckboxMenuItem, JComboBox, JList keyPressed(KeyEvent), keyReleased(KeyEvent), keyTyped(KeyEvent) Component MouseEvent (clics) MouseListener MouseAdapter addMouseListener( ) removeMouseListener( ) mousePressed(MouseEvent), mouseReleased(MouseEvent), mouseClicked(MouseEvent), mouseEntered(MouseEvent), mouseExited(MouseEvent) Component MouseEvent (movimiento) MouseMotionListener MouseAdapter addMouseMotionListener( ) removeMouseMotionListener( ) mouseDragged(MouseEvent), mouseMoved(MouseEvent) Component KeyEvent WindowListener WindowAdapter WindowEvent addWindowListener( ) removeWindowListener( ) windowClosing(WindowEvent), windowOpened(WindowEvent), windowClosed(WindowEvent), windowIconified(WindowEvent), windowDeiconified(WindowEvent), windowActivated(WindowEvent), windowDeactivated(WindowEvent) Window y sus derivados, incluyendo JDialog, JFileDialog y JFrame. *Si procede. Como se puede observar en la tabla, existen interfaces oyentes con más de un método, que coinciden con aquéllas que disponen de adaptador. Un adaptador proporciona por defecto métodos vacíos para Java – Introducción a la programación 13 Capítulo 12: Eventos cada uno de los métodos de la interfaz. Por tanto, para definir el oyente, simplemente habrá que extender el adaptador y sobrescribir los métodos que se necesite cambiar, es decir, aquellos eventos cuyas respuestas deseemos generar. Por ejemplo, si recordamos la ventana del ejemplo inicial de este capítulo, la forma de cerrarla cuando se haga clic sobre el botón de cierre, definiendo una clase oyente será: class miGestorVentana extends WindowAdapter { public void windowClosing(WindowEvent e) { System.exit(0); } } 5 GESTORES DE DISPOSICIÓN La siguiente figura muestra cinco interfaces gráficas. Todas ellas tienen cinco botones y el código para generarlos es prácticamente el mismo en todos los casos. Sin embargo, su aspecto es muy diferente, ya que utilizan diferentes gestores de disposición (Layout Managers) para controlar el tamaño y posición de los elementos. Figura 7. Distintas disposiciones de elementos Java proporciona cinco gestores de disposición muy sencillos de utilizar: BorderLayout, BoxLayout, FlowLayout, GridLayout y GridBagLayout. Vamos a ver cómo funciona cada uno de ellos. Todos los contenedores utilizan una disposición por defecto: en el caso de objetos JPanel, éstos utilizan FlowLayout, mientras que los contenedores de alto nivel utilizan BorderLayout. Si no queremos utilizar la disposición por defecto, simplemente tenemos que llamar al método setLayout( ) del contenedor antes de añadirle los componentes. Así, para el caso de querer utilizar BorderLayout: Java – Introducción a la programación 14 Capítulo 12: Eventos JPanel pan = new JPanel(); pan.setLayout(new BorderLayout()); Los parámetros del constructor de la clase dependerán del tipo de gestor elegido, si bien en el caso anterior no precisa de ningún parámetro de entrada. Posteriormente, cuando añadamos componentes a este contenedor (en este caso, un panel), los argumentos (además del objeto a añadir) que recibirá el método add( ) dependerán del tipo de disposición elegida. Como siempre, en caso de precisar ayuda, remitimos al lector a la documentación de la API para más detalles. 5.1 BorderLayout Esta disposición permite situar los componentes en cinco zonas conocidas como NORTH, SOUTH, EAST, WEST, y CENTER. Por tanto, al añadir cada elemento a un contenedor con esta disposición, habrá que añadir como segundo parámetro, el nombre de la zona en la que lo queremos colocar. No se respeta el tamaño de los componentes y el espacio no utilizado se coloca en la zona central. Por ejemplo, para añadir un botón b1 en la parte izquierda (WEST) del panel definido anteriormente: pan.add(b1, BorderLayout.WEST); 5.2 BoxLayout BoxLayout dispone los componentes en una fila o columna simple, respetando el tamaño máximo de los mismos y permitiendo alinearlos. 5.3 FlowLayout Esta disposición simplemente añade los componentes de izquierda a derecha, uno detrás de otro, comenzando nuevas filas si fuera necesario. 5.4 GridLayout GridLayout divide el área del contenedor en una matriz (o rejilla) con tantas filas y columnas como indiquemos, disponiendo los componentes en las casillas correspondientes, todas de igual tamaño. 5.5 GridBagLayout Se trata del gestor más sofisticado y flexible. Coloca los componentes en una matriz, permitiendo que ocupen más de una celda. Las filas de la matriz pueden ser de distinta altura, así como las columnas pueden tener distinta anchura, como se puede comprobar en la figura 7. Java – Introducción a la programación 15 Capítulo 12: Eventos 6 EJEMPLOS CON OTROS ELEMENTOS SWING 6.1 Componentes de texto Estos elementos permiten visualizar texto y opcionalmente, permitir al usuario editarlo. Swing dispone de cinco componentes de texto, que heredan de la superclase JTextComponent. Son los siguientes: • • • • JTextField: que permite crear campos de texto de una sola línea. JPasswordField: genera campos de texto oculto, para introducir datos sensibles, como contraseñas. JTextArea: para trabajar con áreas de texto plano, sin estilo. JEditorPane y JTextPane: son clases más complejas para crear áreas de texto con formato y definir diversos atributos para el texto (HTML, RTF…). En el siguiente código se presentan los métodos de creación de campos de texto, campos de texto oculto y áreas de texto. Se han destacado en negrita los nombres de los constructores de las clases y aquellos métodos más utilizados para el manejo del texto en los objetos. //Creación de un campo de texto de 20 caracteres. JTextField campo= new JTextField(20); campo.addActionListener(this); ... panelPrincipal.add(campo); //Este método se ejecuta al producirse un evento Action //en el campo de texto, por ejemplo, pulsar ENTER. public void actionPerformed(ActionEvent evt) { //Se almacena el texto contenido en el campo en el //objeto text. String texto = campo.getText(); //Se añade el texto del objeto al área de texto. area.append(texto + “\n”); } //Creación de un campo de texto oculto de 10 caracteres. //Se define como carácter oculto el comodín (#) JPasswordField contrasenia = new JPasswordField(10); contrasenia.setEchoChar('#'); //Creación de un área de texto, definición de un texto //inicial y selección del tipo de letra. JTextArea area = new JTextArea(15, 20); area.setText( "Esto es un area de texto editable " + "inicializada con el método setText " + "que permite mostrar texto plano " + "con tipo de letra Serif." ); textArea.setFont(new Font("Serif", Font.ITALIC, 16)); Java – Introducción a la programación 16 Capítulo 12: Eventos 6.2 Casillas de verificación La clase JCheckBox proporciona el soporte para la creación y manejo de casillas o botones de verificación. También es posible colocar estos elementos dentro de un menú, utilizando la clase especial JCheckBoxMenuItem. Estas clases heredan de la clase AbstractButton por lo que las casillas de verificación comparten funcionalidades con los botones, por ejemplo, es posible especificar imágenes para utilizarlas en ellas. Vamos a ver algunos de los métodos más comunes de la clase JCheckBox en un ejemplo de código para crear casillas y gestionar sus eventos: //Código de inicialización de la clase. Podría ser del //constructor o del método init( ) de un applet. opcionA = new JCheckBox("Opción A"); opcionA.setMnemonic(KeyEvent.VK_A); opcionA.setSelected(false); opcionB = new JCheckBox("Opción B"); opcionB.setMnemonic(KeyEvent.VK_B); opcionB.setSelected(flase); opcionC = new JCheckBox("Opción C"); opcionC.setMnemonic(KeyEvent.VK_C); opcionC.setSelected(false); //Registramos una clase oyente para las tres casillas, //que en este caso coincide con la clase principal. opcionA.addItemListener(this); opcionB.addItemListener(this); opcionC.addItemListener(this); //Definimos los métodos que atenderán a los eventos de //tipo ItemEvent que se produzcan. public void itemStateChanged(ItemEvent e) { ... Object origen = e.getItemSelectable( ); if (origen == opcionA) { //Generar respuesta al seleccionar la opción A. } else if (origen == opcionB) { //Generar respuesta al seleccionar la opción B. } else if (origen == opcionC) { //Generar respuesta al seleccionar la opción C. } } if (e.getStateChange() == ItemEvent.DESELECTED) //Generar respuesta al deseleccionar cualquier //casilla. El código en color rojo crea los objetos JCheckBox con una etiqueta que aparecerá junto a las casillas. Además, las dos sentencias siguientes hacen que la casilla también cambie de estado al pulsar la tecla correspondiente a su opción y establece que el estado inicial de todas ellas sea “false”, es decir, no seleccionadas. Java – Introducción a la programación 17 Capítulo 12: Eventos El código en color azul describe la forma de gestionar los eventos que ocurran sobre las casillas. Como siempre, primero se asocia una clase oyente (en este caso, coincide con la clase actual, this) y después se definen los métodos que generarán la respuesta. Lo que queremos es que nuestra aplicación genere respuestas ante cambios de estado en las casillas, de ahí que el código se disponga en el método itemStateChanged( ), donde se comprueba cuál de las casillas ha sido el origen del evento y se realiza una determinada acción como respuesta al mismo. Si trabajamos con varias casillas de verificación, podrán estar seleccionadas una o varias simultáneamente. Para obligar a que sólo una de las casillas de un grupo pueda seleccionarse, se puede emplear la clase JRadioButton y ButtonGroup, e incluso colocarlas en un menú con JRadioButtonMenuItem. Las posibilidades son infinitas. 6.3 Listas Un objeto JList presenta al usuario una serie de opciones (items) a elegir, en una o varias columnas. Las listas suelen tener múltiples opciones, por lo que se las coloca en paneles con scroll. Existen otros componentes que también permiten presentar diversas opciones al usuario, como las combo boxes o listas desplegables (JComboBox), los menús (JMenuBar, JMenu, JMenuItem) y las tablas (JTable), así como los grupos de casillas de verificación, grupos de botones, etc. //Se crea una lista en la que únicamente se puedan //seleccionar elementos consecutivamente, a partir de un //modelo o plantilla (DefaultListModel). DefaultListModel modelo = new DefaultListModel( ); modelo.addElement("Alex Muñoz"); modelo.addElement("Amaia Gómez"); modelo.addElement("Cristina Perfecto"); modelo.addElement("Jesús Romo"); modelo.addElement("Juanjo Igarza"); modelo.addElement("Eva Ibarrola"); modelo.addElement("Fidel Liberal"); JList lista = new JList(modelo); lista.setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_S ELECTION); lista.setSelectedIndex(2); lista.setLayoutOrientation(JList.HORIZONTAL_WRAP); lista.setVisibleRowCount(-1); Existen distintos modos de controlar la selección de los elementos de la lista, con el método setSelectionMode( ): selección de un único elemento (ListSelectionModel.SINGLE_SELECTION), selección de varios elementos consecutivos (ListSelectionModel.SINGLE_INTERVAL_SELECTION) y selección de varios elementos cualesquiera (ListSelectionModel.MULTIPLE_INTERVAL_SELECTION). El método setSelectedIndex ( ) indica qué elemento aparecerá seleccionado al crear la lista y el método setLayoutOrientation( ) indica el tipo de distribución que tendrán los elementos de la lista: así, podemos encontrar distribución por filas de izquierda a derecha (JList.HORIZONTAL_WRAP), por columnas de arriba abajo (JList.VERTICAL_WRAP) o en una única columna de arriba abajo (JList.VERTICAL). Finalmente, el método setVisibleRowCount( ) con el argumento -1 hace que la lista muestre el máximo número de Java – Introducción a la programación 18 Capítulo 12: Eventos elementos posible en el espacio de que disponga. De esta forma, la lista creada en este ejemplo, tendría más o menos este aspecto: Figura 8. Lista de ejemplo Java – Introducción a la programación 19 Capítulo 12: Eventos 6.4 Ejemplo 2: mi segunda interfaz Swing Vamos a ver, a través de un último ejemplo, cómo trabajar con algunos de estos (y otros) elementos y cómo gestionar los eventos que generan; en concreto, veremos el programa que genera la siguiente interfaz de usuario (elementosgui.java): Figura 9. Interfaz de usuario elementosgui.java En ella, como veremos sobre el código, además de dibujar los componentes, se gestionan dos tipos de eventos, además del cierre de la propia ventana: por un lado, la inserción de texto en el campo de texto normal y en el campo de texto oculto, que se presenta en la etiqueta que hay debajo de ellos al pulsar ENTER; y por otro lado, la selección/deselección de las casillas de verificación A, B, C y D, bien al hacer clic sobre ellas o al pulsar la combinación de teclas ALT + LETRA_CASILLA, que hace que aparezca el texto “Has seleccionado/deseleccionado la casilla X” en el área de texto etiquetada como “Texto Plano”. Además, al introducir el texto en el campo oculto, se borra el contenido del área de texto. Veamos el código fuente del programa: import javax.swing.*; import javax.swing.text.*; import java.awt.*; import java.awt.event.*; import java.net.URL; import java.io.IOException; //La propia clase implementa las interfaces para atender los tipos de //eventos que nos interesan: ActionEvent e ItemEvent. public class elementosgui extends JFrame Java – Introducción a la programación 20 Capítulo 12: Eventos implements ActionListener, ItemListener { protected static final String textoCampoTexto = "JTextField"; protected static final String textoCampoPassword = "JPasswordField"; protected JLabel etiqueta; protected JTextArea areaTexto; protected JCheckBox casillaA, casillaB, casillaC, casillaD; //Constructor de la clase. Inicializa variables y componentes. public elementosgui() { super("Ejemplo con varios componentes"); //Creamos un campo de texto normal y asociamos la clase oyente. JTextField campoTexto = new JTextField(10); campoTexto.setActionCommand(textoCampoTexto); campoTexto.addActionListener(this); //Creamos un campo de texto oculto y asociamos la clase oyente. JPasswordField campoPassword = new JPasswordField(10); campoPassword.setActionCommand(textoCampoPassword); campoPassword.addActionListener(this); //Creamos las etiquetas para añadirlas junto a los //campos de texto. JLabel etiquetaCampoTexto = new JLabel(textoCampoTexto + ": "); etiquetaCampoTexto.setLabelFor(campoTexto); JLabel etiquetaCampoPassword = new JLabel(textoCampoPassword + ": "); etiquetaCampoPassword.setLabelFor(campoPassword); //Creamos la etiqueta que mostrará los mensaje del usuario. etiqueta = new JLabel("Escribe en uno de los campos y pulsa Enter."); etiqueta.setBorder(BorderFactory.createEmptyBorder(10,0,0,0)); //Se define la disposición de los campos de texto y etiquetas. JPanel panelTexto = new JPanel(); GridBagLayout gridbag = new GridBagLayout(); GridBagConstraints c = new GridBagConstraints(); panelTexto.setLayout(gridbag); JLabel[] arrayEtiquetas = {etiquetaCampoTexto, etiquetaCampoPassword}; JTextField[] campoTextos = {campoTexto, campoPassword}; addLabelTextRows(arrayEtiquetas, campoTextos, gridbag, panelTexto); c.gridwidth = GridBagConstraints.REMAINDER; c.anchor = GridBagConstraints.WEST; c.weightx = 1.0; gridbag.setConstraints(etiqueta, c); panelTexto.add(etiqueta); panelTexto.setBorder( BorderFactory.createCompoundBorder( BorderFactory.createTitledBorder("Campos de texto"), BorderFactory.createEmptyBorder(5,5,5,5))); //Creamos el área de texto. areaTexto = new JTextArea( "Esto es un objeto JTextArea editable. " + "Es un componente de texto \"plano\", " + "lo que significa que, aunque puede contener texto " + "de cualquier tipo de letra, todo el texto debe " + "ser del mismo tipo." ); Java – Introducción a la programación 21 Capítulo 12: Eventos areaTexto.setFont(new Font("Serif", Font.ITALIC, 16)); areaTexto.setLineWrap(true); areaTexto.setWrapStyleWord(true); JScrollPane panelAreaTexto = new JScrollPane(areaTexto); panelAreaTexto.setVerticalScrollBarPolicy( JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); panelAreaTexto.setPreferredSize(new Dimension(250, 250)); panelAreaTexto.setBorder( BorderFactory.createCompoundBorder( BorderFactory.createCompoundBorder( BorderFactory.createTitledBorder("Texto Plano"), BorderFactory.createEmptyBorder(5,5,5,5)), panelAreaTexto.getBorder())); //Creamos las casillas de verificación. casillaA = new JCheckBox("Casilla A"); casillaA.setMnemonic(KeyEvent.VK_A); casillaA.setSelected(true); casillaB = new JCheckBox("Casilla B"); casillaB.setMnemonic(KeyEvent.VK_B); casillaB.setSelected(false); casillaC = new JCheckBox("Casilla C"); casillaC.setMnemonic(KeyEvent.VK_C); casillaC.setSelected(false); casillaD = new JCheckBox("Casilla D"); casillaD.setMnemonic(KeyEvent.VK_D); casillaD.setSelected(false); //Registramos las clases oyente para las //casillas de verificación. casillaA.addItemListener(this); casillaB.addItemListener(this); casillaC.addItemListener(this); casillaD.addItemListener(this); //Colocamos las casillas en un panel de una sola columna. JPanel panelCasillas = new JPanel(new GridLayout(0, 1)); panelCasillas.add(casillaA); panelCasillas.add(casillaB); panelCasillas.add(casillaC); panelCasillas.add(casillaD); panelCasillas.setBorder(BorderFactory.createCompoundBorder( BorderFactory.createTitledBorder("JCheckBox"), BorderFactory.createEmptyBorder(5,5,5,5))); //Creamos los elementos de la lista. DefaultListModel modeloLista = new DefaultListModel(); modeloLista.addElement("Alex Muñoz"); modeloLista.addElement("Cristina Perfecto"); modeloLista.addElement("Jesús Romo"); modeloLista.addElement("Juanjo Igarza"); modeloLista.addElement("Eva Ibarrola"); modeloLista.addElement("Fidel Liberal"); modeloLista.addElement("Juanjo Unzilla"); modeloLista.addElement("Mariví Higuero"); modeloLista.addElement("Armando Ferro"); modeloLista.addElement("Eduardo Jacob"); modeloLista.addElement("Koldo Espinosa"); //Creamos la lista. JList lista = new JList(modeloLista); lista.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); lista.setSelectedIndex(0); lista.setLayoutOrientation(JList.HORIZONTAL_WRAP); lista.setVisibleRowCount(-1); Java – Introducción a la programación 22 Capítulo 12: Eventos //Colocamos la lista en un panel. JScrollPane panelLista = new JScrollPane(lista); panelLista.setVerticalScrollBarPolicy( JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); panelLista.setPreferredSize(new Dimension(250, 250)); panelLista.setBorder( BorderFactory.createCompoundBorder( BorderFactory.createCompoundBorder( BorderFactory.createTitledBorder("JList"), BorderFactory.createEmptyBorder(5,5,5,5)), panelLista.getBorder())); //Agrupamos los paneles antes de colocarlos en la ventana. //El panel derecho contendrá el panel de casillas y el panel con //la lista. JPanel panelDerecho = new JPanel(); BoxLayout cajaDerecha = new BoxLayout(panelDerecho, BoxLayout.Y_AXIS); panelDerecho.setLayout(cajaDerecha); panelDerecho.add(panelCasillas); panelDerecho.add(panelLista); //El panel izquierdo contendrá el panel con los campos de //texto y el panel con el área de texto. JPanel panelIzquierdo = new JPanel(); BoxLayout cajaIzquierda = new BoxLayout(panelIzquierdo, BoxLayout.Y_AXIS); panelIzquierdo.setLayout(cajaIzquierda); panelIzquierdo.add(panelTexto); panelIzquierdo.add(panelAreaTexto); //Agrupamos el panel izquierdo y derecho en un panel contenedor. JPanel panelContenedor = new JPanel(); BoxLayout caja = new BoxLayout(panelContenedor, BoxLayout.X_AXIS); panelContenedor.setLayout(caja); panelContenedor.add(panelIzquierdo); panelContenedor.add(panelDerecho); //Por fin, definimos el panel contenedor como el contenido de la //ventana. setContentPane(panelContenedor); } //Este método coloca las etiquetas junto a los campos de texto. private void addLabelTextRows(JLabel[] arrayEtiquetas, JTextField[] campoTextos, GridBagLayout gridbag, Container contenedor) { GridBagConstraints c = new GridBagConstraints(); c.anchor = GridBagConstraints.EAST; int numLabels = arrayEtiquetas.length; for (int i = 0; i < numLabels; i++) { c.gridwidth = GridBagConstraints.RELATIVE; c.fill = GridBagConstraints.NONE; c.weightx = 0.0; gridbag.setConstraints(arrayEtiquetas[i], c); contenedor.add(arrayEtiquetas[i]); } c.gridwidth = GridBagConstraints.REMAINDER; c.fill = GridBagConstraints.HORIZONTAL; c.weightx = 1.0; gridbag.setConstraints(campoTextos[i], c); contenedor.add(campoTextos[i]); Java – Introducción a la programación 23 Capítulo 12: Eventos } //Este método genera la respuesta al evento ActionEvent, escribiendo //en la etiqueta el texto introducido por el usuario en el campo. //Dependiendo del campo origen, el método para seleccionar el texto //es diferente. public void actionPerformed(ActionEvent e) { String frase = "Has escrito \""; if (e.getActionCommand().equals(textoCampoTexto)) { JTextField origen = (JTextField)e.getSource(); etiqueta.setText(frase + origen.getText() + "\""); } else { JPasswordField origen = (JPasswordField)e.getSource(); etiqueta.setText(frase + new String(origen.getPassword()) + "\""); areaTexto.setText(""); } } //Este método genera la respuesta al evento ItemEvent, escribiendo //en el área de texto el nombre de la casilla seleccionada o //deseleccionada por el usuario. public void itemStateChanged(ItemEvent e) { JCheckBox origen = (JCheckBox) e.getItemSelectable(); //Comprobamos si se ha seleccionado o deseleccionado e //introducimos el texto correspondiente. if (e.getStateChange() == ItemEvent.DESELECTED) { areaTexto.setText("Has deseleccionado la " + origen.getText() + ".\n"); } else { areaTexto.setText("Has seleccionado la " + origen.getText() + ".\n"); } } //Método principal. Crea el objeto de la clase elementosgui y //espera a que se cierre la ventana. public static void main(String[] args) { JFrame frame = new elementosgui(); //Si se cierra la ventana, salimos de la aplicación: frame.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); //El código anterior es equivalente a la siguiente línea: //frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.pack(); frame.setVisible(true); } } El método addLabelTextRows( ) es un método simple para colocar etiquetas, por lo que carece de importancia para el tema que estamos estudiando. Asimismo, omitiremos la explicación de los métodos para crear componentes y contenedores, agrupar componentes, etc. por que su funcionamiento es similar a los ya descritos para otros elementos. Sin embargo, hemos destacado en el código, además de los comentarios (en negrita), los métodos que pueden resultar más interesantes para comprender el tratamiento de los eventos: Java – Introducción a la programación 24 Capítulo 12: Eventos • Asociar a los componentes su clase oyente: una vez creados los componentes cuyos eventos queremos atender, debemos asociar una clase con métodos para generar respuestas ante esos eventos. En estas líneas de código del programa se realiza esa asociación: campoTexto.addActionListener(this); ... campoPassword.addActionListener(this); ... casillaA.addItemListener(this); casillaB.addItemListener(this); casillaC.addItemListener(this); casillaD.addItemListener(this); ... frame.addWindowListener(new WindowAdapter() { ... } ); Destacar que la clase oyente es, para los elementos de texto y las casillas de verificación, la propia clase elementosgui, de ahí que el argumento de los métodos addXYZListener sea “this”. Ya sabemos que la clase oyente debe implementar las interfaces adecuadas a los eventos a atender y es por esto que en la definición de nuestra clase lo indiquemos explícitamente: public class elementosgui extends JFrame implements ActionListener, ItemListener {...} Por el contrario, en el caso de la ventana, se utiliza una clase adaptadora (new WindowAdapter( )) en la que se sobrescribe un único método: el que se ejecuta al cerrarla. • Escribir el código de los métodos que responden al evento: en este caso, son dos métodos de la propia clase los que atienden a los eventos de los campos de texto y las casillas, respectivamente: public void actionPerformed(ActionEvent e) {...} public void itemStateChanged(ItemEvent e) {...} El código de ambos métodos realiza las acciones que hemos comentado en el funcionamiento del programa, valiéndose de la información contenida en el objeto XYZEvent para ofrecer al usuario la información que espera. El método de que dispone la clase WindowAdapter es: public void windowClosing(WindowEvent e) { System.exit(0); } Para concluir, decir que existen muchos más recursos gráficos para utilizar en una interfaz de usuario en Java: colores de fondo y de letra, tipos de letra, tamaños, bordes, imágenes, etc. Éstos no se han incluido en este capítulo debido a su extensión y por resultar menos interesantes para una introducción al lenguaje Java, pero el lector interesado puede encontrar extensa información sobre ellos y emplearlos para mejorar las interfaces de usuario que diseñe, así como las que se proponen en los ejercicios. Java – Introducción a la programación 25 Capítulo 12: Eventos Asimismo, en este capítulo sólo hemos incluido algunos de los elementos Swing más importantes y utilizados de cara a explicar el funcionamiento de la gestión de eventos. Sin embargo, y como recomendaciones finales, cabe recordar que: • • • La documentación HTML de Sun permite consultar la API de todas las clases y métodos Swing disponibles. Asimismo, el tutorial de Sun contiene información con ejemplos gráficos y de código acerca de todos los elementos Swing. Gracias a la nomenclatura de los eventos Swing, es sencillo descubrir, escribir y asociar un gestor para un tipo particular de evento. Si la interfaz a diseñar es muy compleja, habría que plantearse utilizar un compilador que nos permita construir GUI gráficamente y de manera automática. Java – Introducción a la programación 26 Capítulo 12: Eventos 7 EJERCICIOS PROPUESTOS 1.- Desarrollar una aplicación que gestione la agenda personal de un usuario. La aplicación constará de una interfaz gráfica con los siguientes elementos: • • • • Tres listas de selección desplegables donde aparecerán los días del mes, los meses del año, y una serie de años (por ejemplo, de 2000 a 2010). Un área de texto, donde se mostrarán al usuario los datos del día pedido. Un campo de texto, que servirá de entrada de datos del usuario. Tres botones: uno con la etiqueta “Mostrar”, otro con la etiqueta “Nueva” y otro con la etiqueta “Eliminar”. La forma de colocar estos elementos en el contenedor será empleando el gestor de disposición GridBagLayout. El funcionamiento de la aplicación será el siguiente: el usuario podrá consultar las entradas de su agenda seleccionando en las listas desplegables el día, mes y año que desee; al pulsar el botón “Mostrar”, la agenda le presentará al usuario en el área de texto el contenido correspondiente a ese día. Si el usuario introduce un texto en el campo de texto y pulsa ENTER o bien hace clic sobre el botón “Nueva”, la agenda añadirá el texto introducido al contenido del día seleccionado. Si, por el contrario, hace clic sobre el botón “Eliminar” la agenda borrará toda la información almacenada en ese día. Opcionalmente, se pueden utilizar aquellos recursos gráficos que se estimen interesantes: colores, letras, imágenes, ventanas nuevas, etc. Asimismo, se puede mejorar la agenda sustituyendo las listas desplegables por un calendario que permita seleccionar directamente el día, mes y año deseado. Java – Introducción a la programación 27 Capítulo 12: Eventos 8 REFERENCIAS 8.1 Bibliografía • • • Bruce Eckel. Piensa en Java. 2ª edición. Pearson Educación - Prentice Hall. Madrid. 2002. Tema 13. Crear ventanas y applets. El modelo de eventos de Swing. Mary Campione, Kathy Walrath. The Java Tutorial. Object-oriented Programming for the Internet. Addison-Wesley. California (USA). 1996. Creating a user interface. Lessons 17-20. Laura Lemay, Rogers Cadenhead. Aprendiendo Java 2 en 21 días. Prentice Hall. México. 1999. Tema 13. Respuesta a la entrada del usuario en un applet. Manejo de eventos. 8.2 Internet • • Tutorial de Java: control de eventos. Agustín Froufe. URL: http://orion.qro.itesm.mx/~java/tutorial/JavaTutorial/Cap4/eventos.html The Java Tutorial. Sun Microsystems. URL: http://java.sun.com/docs/books/tutorial/ Event Handling (Swing). URL: http://java.sun.com/docs/books/tutorial/uiswing/overview/event.html Writing event listeners. URL: http://java.sun.com/docs/books/tutorial/uiswing/events/index.html Java – Introducción a la programación 28