• No se han encontrado resultados

La gestión de los eventos

In document JAVA7 (página 86-106)

Diseño de una interfaz gráfica

2. La gestión de los eventos

Todos los sistemas operativos que emplean una interfaz gráfica deben vigilar permanentemente los diferentes periféricos de introducción de datos para detectar las acciones del usuario y transmitirlas a las diferentes aplicaciones. Para cada acción del usuario, se crea un evento. Luego estos eventos son propuestos a cada aplicación que determina si el evento la concierne, y en este caso, lo que debe hacer para responder a ello. La manera de gestionar estos eventos difiere según los lenguajes. En algunos casos, cada componente dispone de una porción de código predefinida asociada automáticamente a cada tipo de evento. En este caso, el papel del desarrollador consiste en personalizar las diferentes porciones de código asociadas a los eventos. En otros lenguajes, el sistema ubica los eventos en una fila y, le corresponde al desarrollador vigilar esta fila para determinar qué componente es concernido por el evento y provocar la ejecución de la porción de código que tendrá previsto. El planteamiento empleado por Java es una técnica intermedia. Java se encarga de determinar qué evento acaba de ocurrir y sobre qué elemento. El desarrollador es responsable de la elección de la porción de código que va a tratar el evento. Desde un punto de vista más técnico, el elemento al origen del evento se llama fuente de evento, y el elemento que contiene la porción de código encargada de gestionar el evento se llama escuchador de evento. Las fuentes de eventos gestionan, para cada evento que pueden activar, una lista que les permite saber qué escuchadores deben ser avisados si el evento se produce. Por supuesto, las fuentes de eventos y los escuchadores de eventos son objetos. Es necesario prever qué escuchadores van a gestionar los eventos que les va a transmitir la fuente de evento.

Para garantizar eso, a cada tipo de evento corresponde una interfaz que debe implementar un objeto si quiere ser candidato para la gestión de este evento. Para evitar la duplicación de las interfaces (ya muy numerosas), se agrupan los eventos en categorías. El nombre de estas interfaces siempre respeta la convención siguiente:

public interface MouseMotionListener extends EventListener

{

void mouseDragged(MouseEvent e); void mouseMoved(MouseEvent e); }

La primera parte del nombre representa la categoría de eventos que los objetos, que implementan esta interfaz, pueden gestionar. El nombre siempre se termina por Listener.

Por ejemplo, tenemos la interfaz MouseMotionListener que corresponde a los eventos activados por los movimientos del ratón, o la interfaz ActionListener que corresponde a un clic en un botón. En cada una de estas interfaces encontramos las firmas de los diferentes métodos asociados a cada evento.

Cada uno de estos métodos espera como argumento un objeto que representa el propio evento. Este objeto es creado automáticamente en el momento de la activación del evento y luego es pasado como argumento al método encargado de gestionar el evento en el escuchador de evento. En general, contiene información adicional relativa al evento y es específico para cada tipo de evento.

Necesitamos crear clases que implementen estas interfaces. Desde este punto de vista, tenemos una multitud de posibilidades:

Crear una clase "normal" que implemente la interfaz. Implementar la interfaz en una clase ya existente. Crear una clase interna que implemente la interfaz.

Crear una clase interna anónima que implemente la interfaz.

En algunos casos, quizá sea necesario no gestionar todos los eventos presentes en la interfaz. Sin embargo, es obligatorio escribir todos los métodos exigidos por la interfaz incluso si varios de ellos no contienen ningún código. Esto puede perjudicar la legibilidad del código. Para paliar este problema, Java propone para casi cada interfaz XXXXXListener una clase abstracta correspondiente que implementa ya la interfaz, y que contiene los métodos exigidos por la interfaz. Estos métodos no contienen código alguno ya que el tratamiento de cada evento debe ser específico a cada aplicación. Estas clases emplean la misma convención de nombramiento que las interfaces, excepto que se sustituye Listener por Adapter. Tenemos por ejemplo la claseMouseMotionAdapter que implementa la interfaz MouseMotionListener. Se pueden utilizar estas clases de varias maneras:

Crear una clase "normal" que herede de una de estas clases. Crear una clase interna que herede de una de estas clases.

Crear una clase interna anónima que herede de una de estas clases.

El uso de una clase interna anónima es la solución que más se utiliza con el pequeño inconveniente de tener una sintaxis difícil de leer si uno no está acostumbrado.

Para aclarar todo esto, vamos a ilustrar cada una de estas posibilidades con un pequeño ejemplo. Este ejemplo nos va a permitir terminar correctamente la aplicación en el momento del cierre de la ventana principal al llamar el método System.exit(0).

Se debe llamar este método durante la detección del cierre de la ventana. Para esto, debemos gestionar los eventos relacionados con la ventana y en particular, el evento windowClosing que es activado en el momento en el cual el usuario pide el cierre de la ventana por el menú sistema. La interfaz WindowListener está perfectamente adaptada para este tipo de trabajo.

http://www.eni-training.com/client_net/mediabook.aspx?idR=65887 6/23

package es.eni;

import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JPanel; public class Pantalla extends JFrame

{

public Pantalla() {

setTitle("primera ventana en JAVA"); setBounds(0,0,300,100);

setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); // creación de los tres botones

JButton b1,b2,b3; b1=new JButton("Rojo"); b2=new JButton("Verde"); b3=new JButton("Azul");

// creación del contenedor intermediario JPanel pano;

pano=new JPanel();

// añadido de los botones en el contenedor intermediario pano.add(b1);

pano.add(b2); pano.add(b3);

// añadido del contenedor en el ContentPane getContentPane().add(pano);

} }

package es.eni; public class Main {

public static void main(String[] args) { // creación de la ventana Pantalla ventana; ventana=new Pantalla(); // visualización de la ventana ventana.setVisible(true); } } package es.eni; import java.awt.event.WindowEvent; import java.awt.event.WindowListener;

Si ejecutamos este código, la ventana aparece pero ya no es posible cerrarla y aún menos detener la aplicación. Veamos ahora cómo remediar este problema con las diferentes soluciones mencionadas más arriba.

public class EscuchadorVentana implements WindowListener { public void windowActivated(WindowEvent arg0)

{ }

public void windowClosed(WindowEvent arg0) {

}

public void windowClosing(WindowEvent arg0) {

System.exit(0); }

public void windowDeactivated(WindowEvent arg0) {

}

public void windowDeiconified(WindowEvent arg0) {

}

public void windowIconified(WindowEvent arg0) {

}

public void windowOpened(WindowEvent arg0) {

} }

package es.eni; public class Main {

public static void main(String[] args) {

// creación de la ventana Pantalla ventana;

ventana=new Pantalla();

// creación de una instancia de la clase encargada // de gestionar los eventos

EscuchadorVentana ev;

ev=new EscuchadorVentana();

// referenciación de esta instancia de clase // como escuchador de evento para la ventana ventana.addWindowListener(ev); // visualización de la ventana ventana.setVisible(true); } } package es.eni; import java.awt.event.WindowEvent; import java.awt.event.WindowListener;

Implementar la interfaz en una clase ya existente

En esta solución, vamos a confiar a la clase que representa la ventana la tarea de gestionar sus propios eventos al hacerle implementar la interfaz WindowListener.

http://www.eni-training.com/client_net/mediabook.aspx?idR=65887 8/23

import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JPanel;

public class Pantalla extends JFrame

implements WindowListener {

public Pantalla() {

setTitle("primera ventana en JAVA"); setBounds(0,0,300,100);

setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); // creación de los tres botones

JButton b1,b2,b3;

b1=new JButton("Rojo"); b2=new JButton("Verde"); b3=new JButton("Azul");

// creación del contenedor intermediario JPanel pano;

pano=new JPanel();

// añadido de los botones en el contenedor intermediario pano.add(b1);

pano.add(b2); pano.add(b3);

// añadido del contenedor intermediario en el ContentPane getContentPane().add(pano);

// referenciación de la propia ventana // como escuchador de sus propios eventos addWindowListener(this);

}

public void windowActivated(WindowEvent arg0) {

}

public void windowClosed(WindowEvent arg0) {

}

public void windowClosing(WindowEvent arg0) {

System.exit(0); }

public void windowDeactivated(WindowEvent arg0) {

}

public void windowDeiconified(WindowEvent arg0) {

}

public void windowIconified(WindowEvent arg0) {

}

public void windowOpened(WindowEvent arg0) {

} }

package es.eni; public class Main {

{ // creación de la ventana Pantalla ventana; ventana=new Pantalla(); // visualización de la ventana ventana.setVisible(true); } } package es.eni; import java.awt.event.WindowEvent; import java.awt.event.WindowListener; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JPanel;

public class Pantalla extends JFrame

{

public Pantalla() {

setTitle("primera ventana en JAVA"); setBounds(0,0,300,100);

setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); // creación de los tres botones

JButton b1,b2,b3; b1=new JButton("Rojo"); b2=new JButton("Verde"); b3=new JButton("Azul");

// creación del contenedor intermediario JPanel pano;

pano=new JPanel();

// añadido de los botones en el contenedor intermediario pano.add(b1);

pano.add(b2); pano.add(b3);

// añadido del contenedor intermediario // en el ContentPane

getContentPane().add(pano);

// creación de una instancia de la clase encargada // de gestionar los eventos

EscuchadorVentana ev;

ev=new EscuchadorVentana();

// referenciación de esta instancia de clase // como escuchador de evento para la ventana addWindowListener(ev);

}

Con esta solución, el código se centraliza en una única clase. Si hay que gestionar varios eventos, esta clase va a contener una multitud de métodos.

Crear una clase interna que implemente la interfaz

Esta solución es una mezcla de las dos anteriores ya que tenemos una clase específica para la gestión de los eventos pero ésta está definida en el interior de la clase que corresponde a la ventana.

http://www.eni-training.com/client_net/mediabook.aspx?idR=65887 10/23

public class EscuchadorVentana implements WindowListener {

public void windowActivated(WindowEvent arg0) {

}

public void windowClosed(WindowEvent arg0) {

}

public void windowClosing(WindowEvent arg0) {

System.exit(0); }

public void windowDeactivated(WindowEvent arg0) {

}

public void windowDeiconified(WindowEvent arg0) {

}

public void windowIconified(WindowEvent arg0) {

}

public void windowOpened(WindowEvent arg0) {

} } }

package es.eni; public class Main {

public static void main(String[] args) { // creación de la ventana Pantalla ventana; ventana=new Pantalla(); // visualización de la ventana ventana.setVisible(true); } } package es.eni; import java.awt.event.WindowEvent; import java.awt.event.WindowListener; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JPanel;

Con esta solución, se reparten las responsabilidades entre varias clases, pero a cambio, vamos a obtener una multiplicación del número de clases.

Crear una clase interna anónima que implemente la interfaz

Esta solución es una ligera variante de la anterior ya que seguimos teniendo una clase específica encargada de la gestión de los eventos, pero ésta es declarada en el momento de su instanciación.

public class Pantalla extends JFrame

{

public Pantalla() {

setTitle("primera ventana en JAVA"); setBounds(0,0,300,100);

setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); // creación de los tres botones

JButton b1,b2,b3; b1=new JButton("Rojo"); b2=new JButton("Verde"); b3=new JButton("Azul");

// creación del contenedor intermediario JPanel pano;

pano=new JPanel();

// añadido de los botones en el contenedor intermediario pano.add(b1);

pano.add(b2); pano.add(b3);

// añadido del contenedor intermediario // en el ContentPane

getContentPane().add(pano);

// creación de una instancia de una clase anónima // encargada de gestionar los eventos

addWindowListener(new WindowListener() // principio de la definición de la clase {

public void windowActivated(WindowEvent arg0) {

}

public void windowClosed(WindowEvent arg0) {

}

public void windowClosing(WindowEvent arg0) {

System.exit(0); }

public void windowDeactivated(WindowEvent arg0) {

}

public void windowDeiconified(WindowEvent arg0) {

}

public void windowIconified(WindowEvent arg0) {

}

public void windowOpened(WindowEvent arg0) {

}

} // fin de la definición de la clase

); // fin de la llamada del método addWindowListener }// fin del constructor

}// fin de la clase Pantalla package es.eni;

public class Main {

http://www.eni-training.com/client_net/mediabook.aspx?idR=65887 12/23 { // creación de la ventana Pantalla ventana; ventana=new Pantalla(); // visualización de la ventana ventana.setVisible(true); } } package es.eni; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent;

public class Escuchadorventana extends WindowAdapter {

public void windowClosing(WindowEvent arg0) {

System.exit(0); }

}

package es.eni; public class Main {

public static void main(String[] args)

{

// creación de la ventana Pantalla ventana;

ventana=new Pantalla();

// creación de una instancia de la clase encargada // de gestionar los eventos

EscuchadorVentana ev; ev=new EscuchadorVentana();

// referenciación de esta instancia de clase // como escuchador de evento para la ventana ventana.addWindowListener(ev);

// visualización de la ventana ventana.setVisible(true); }

}

El único reproche que se le pueda hacer a esta solución reside en la relativa complejidad de la sintaxis. Los comentarios entre las diferentes líneas ofrecen una ayuda valiosa para no perderse entre las llaves y paréntesis.

Por el contrario, hay un reproche global que se les puede hacer a todas estas soluciones: para un único método realmente útil, tenemos que escribir siete.

Para evitar este código inútil, podemos trabajar con una clase que implemente ya la interfaz correcta y volver a definir únicamente los métodos que nos interesan.

package es.eni; import java.awt.event.WindowEvent; import java.awt.event.WindowListener; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JPanel;

public class Pantalla extends JFrame

{

public Pantalla() {

setTitle("primera ventana en JAVA"); setBounds(0,0,300,100);

setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); // creación de los tres botones

JButton b1,b2,b3;

b1=new JButton("Rojo"); b2=new JButton("Verde"); b3=new JButton("Azul");

// creación del contenedor intermediario JPanel pano;

pano=new JPanel();

// añadido de los botones en el contenedor intermediario pano.add(b1);

pano.add(b2); pano.add(b3);

// añadido del contenedor intermediario // en el ContentPane

getContentPane().add(pano);

// creación de una instancia de la clase encargada // de gestionar los eventos

EscuchadorVentana ev; ev=new EscuchadorVentana();

// referenciación de esta instancia de clase // como escuchador de evento para la ventana addWindowListener(ev);

}

public class EscuchadorVentana extends WindowAdapter {

public void windowClosing(WindowEvent arg0) { System.exit(0); } } } package es.eni; public class Main {

public static void main(String[] args)

http://www.eni-training.com/client_net/mediabook.aspx?idR=65887 14/23 { // creación de la ventana Pantalla ventana; ventana=new Pantalla(); // visualización de la ventana ventana.setVisible(true); } } package es.eni; import java.awt.event.WindowEvent; import java.awt.event.WindowListener; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JPanel;

public class Pantalla extends JFrame

{

public Pantalla() {

setTitle("primera ventana en JAVA"); setBounds(0,0,300,100);

setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); // creación de los tres botones

JButton b1,b2,b3;

b1=new JButton("Rojo"); b2=new JButton("Verde"); b3=new JButton("Azul");

// creación del contenedor intermediario JPanel pano;

pano=new JPanel();

// añadido de los botones en el contenedor intermediario pano.add(b1);

pano.add(b2); pano.add(b3);

// añadido del contenedor intermediario // en el ContentPane

getContentPane().add(pano);

// creación de una instancia de una clase anónima // encargada de gestionar los eventos

addWindowListener(new WindowAdapter() // principio de la definición de la clase {

public void windowClosing(WindowEvent arg0) {

System.exit(0); }

} // fin de la definición de la clase

); // fin de la llamada al método addWindowListener }// fin del constructor

}// fin de la clase Pantalla package es.eni;

public class Main {

public static void main(String[] args) { // creación de la ventana Pantalla ventana; ventana=new Pantalla(); // visualización de la ventana ventana.setVisible(true); } } package es.eni; import java.awt.Color; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JPanel;

public class Pantalla extends JFrame

{

JPanel pano; public Pantalla () {

setTitle("primera ventana en JAVA"); setBounds(0,0,300,100);

setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); // creación de los tres botones

JButton btnRojo,btnVerde,btnAzul; btnRojo=new JButton("Rojo"); btnVerde=new JButton("Verde"); btnAzul=new JButton("Azul");

Por supuesto, esta solución es la más ahorradora en número de líneas y también la que utilizan numerosas herramientas de desarrollo que generan automáticamente código. La relativa complejidad del código puede inquietar cuando uno no está acostumbrado a ello.

Hasta ahora, tenemos una fuente de evento y un escuchador para esta fuente de evento. En algunos casos, podemos estar en la situación de tener varias fuentes de eventos y desear utilizar el mismo escuchador o tener una fuente de evento y avisar varios escuchadores. La situación clásica en la cual tenemos varias fuentes de eventos y un único escuchador ocurre cuando proporcionamos al usuario varias soluciones para lanzar la ejecución de una misma acción (menú y barra de herramientas o botones). Sea cuál sea el medio utilizado para lanzar la acción, el código a ejecutar sigue siendo el mismo. En este supuesto, podemos emplear el mismo escuchador para las dos fuentes de eventos. Para ilustrar esto, vamos a añadir un menú a la aplicación y hacer de tal manera que la utilización del menú o de uno de los botones ejecute la misma acción al modificar el color de fondo correspondiente al botón o al menú usado. Como debemos utilizar el mismo escuchador para dos fuentes de eventos, es preferible utilizar una clase interna para la creación del escuchador.

http://www.eni-training.com/client_net/mediabook.aspx?idR=65887 16/23

// creación de los tres escuchadores EscuchadorRojo escR; EscuchadorVerde escV; EscuchadorAzul escA; escR=new EscuchadorRojo(); escV=new EscuchadorVerde(); escA=new EscuchadorAzul();

// asociación del escuchador a cada botón btnRojo.addActionListener(escR);

btnVerde.addActionListener(escV); btnAzul.addActionListener(escA); // Creación del menú

JMenuBar barraMenu; barraMenu=new JMenuBar(); JMenu mnuColores; mnuColores=new JMenu("Colores"); barraMenu.add(mnuColores); JMenuItem mnuRojo,mnuVerde,mnuAzul; mnuRojo=new JMenuItem("Rojo"); mnuVerde=new JMenuItem("Verde"); mnuAzul=new JMenuItem("Azul"); mnuColores.add(mnuRojo); mnuColores.add(mnuVerde); mnuColores.add(mnuAzul);

// asociación del escuchador a cada menú // ( los mismos que para los botones ) mnuRojo.addActionListener(escR); mnuVerde.addActionListener(escV); mnuAzul.addActionListener(escA); // añadido del menú en la ventana setJMenuBar(barraMenu);

// creación del contenedor intermediario pano=new JPanel();

// añadido de los botones en el contenedor intermediario pano.add(btnRojo);

pano.add(btnVerde); pano.add(btnAzul);

// añadido del contenedor intermediario // en el ContentPane

getContentPane().add(pano);

// creación de una instancia de una clase anónima // encargada de gestionar los eventos de la ventana addWindowListener(new WindowAdapter()

{

public void windowClosing(WindowEvent arg0) { System.exit(0); } } ); }

public class EscuchadorRojo implements ActionListener {

public void actionPerformed(ActionEvent arg0) {

pano.setBackground(Color.RED); }

}

public class EscuchadorVerde implements ActionListener {

{

pano.setBackground(Color.GREEN); }

}

public class EscuchadorAzul implements ActionListener {

public void actionPerformed(ActionEvent arg0) { pano.setBackground(Color.BLUE); } } } package es.eni; import java.awt.Color; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JPanel;

public class Pantalla extends JFrame { JPanel pano; JButton btnRojo,btnVerde,btnAzul; JMenuItem mnuRojo,mnuVerde,mnuAzul; public Pantalla () {

setTitle("primera ventana en JAVA"); setBounds(0,0,300,100);

setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); // creación de los tres botones

btnRojo=new JButton("Rojo"); btnVerde=new JButton("Verde"); btnAzul=new JButton("Azul");

// creación de los tres escuchadores EscuchadorColor ec;

ec=new EscuchadorColor();

// asociación del escuchador a cada botón btnRojo.addActionListener(ec);

btnVerde.addActionListener(ec); btnAzul.addActionListener(ec); // Creación del menú

JMenuBar barraMenu;

En este código, tenemos nuestras tres clases escuchador que son muy similares. Con un pequeño truco, vamos a poder simplificar el código para obtener una sola clase escuchador para los tres botones. Se llamará al mismo método actionPerformed con un clic en cualquiera de los botones. La elección de la acción a ejecutar se hará en el interior de este método. Para esto, vamos a utilizar el parámetro ActionEvent facilitado a este método. Éste permite obtener una referencia sobre el objeto al origen del evento a través del método getSource. A continuación se presenta el código simplificado:

http://www.eni-training.com/client_net/mediabook.aspx?idR=65887 18/23 barraMenu=new JMenuBar(); JMenu mnuColores; mnuColores=new JMenu("Colores"); barraMenu.add(mnuColores); mnuRojo=new JMenuItem("Rojo"); mnuVerde=new JMenuItem("Verde"); mnuAzul=new JMenuItem("Azul"); mnuColores.add(mnuRojo); mnuColores.add(mnuVerde); mnuColores.add(mnuAzul);

// asociación del escuchador a cada menú // ( el mismo que para los botones ) mnuRojo.addActionListener(ec); mnuVerde.addActionListener(ec); mnuAzul.addActionListener(ec); // añadido del menú en la ventana setJMenuBar(barraMenu);

// creación del contenedor intermediario pano=new JPanel();

// añadido de los botones en el contenedor intermediario pano.add(btnRojo);

pano.add(btnVerde); pano.add(btnAzul);

// añadido del contenedor intermediario en el ContentPane getContentPane().add(pano);

// creación de una instancia de una clase anónima // encargada de gestionar los eventos

addWindowListener(new WindowAdapter() {

public void windowClosing(WindowEvent arg0)

In document JAVA7 (página 86-106)