Realizar una acción al pulsar un botón en Java

Las aplicaciones han de responder a las acciones que realiza el usuario, por ejemplo: cerrar la ventana al pulsar un botón. Es necesario poder detectar las acciones de usuario y convertirlas en acciones de programa. Java dispone de varias formas de implementar este funcionamiento. En este ejercicio se mostrarán algunas de ellas. Todas se basan en este mecanismo básico:

Las Interfaces son clases “prefabricadas” con propósitos definidos. Cada Interface contiene declaraciones de métodos (nombre del método, lista de argumentos y tipo de retorno) pero estos métodos no están implementados (carecen de cuerpo); todos los métodos de las Interfaces son public. Las Interfaces también pueden declarar constantes (public final static).

En Java no existe la herencia múltiple como tal: una clase no puede heredar (extends) de más de una clase. Pero una clase puede implementar más de una Interface, ésta es una manera de tener herencia múltiple. En la clase que implementa una Interface han de ser sobrescritos (override) todos los métodos declarados en la Interface. La clase que implementa una Interface es la Interface.

ActionListener es una Interface del grupo de los Listeners (escuchadores). ActionListener tiene un sólo método: void actionPerformed(ActionEvent e). ActionListener se usa para detectar y manejar eventos de acción (ActionEvent): los que tienen lugar cuando se produce una acción sobre un elemento del programa.

Un evento ActionEvent se produce:

  • al pulsar un botón (Button)
  • al hacer doble clic en un elemento de lista (List)
  • al pulsar INTRO en una caja de texto (TextFiel)
  • al elegir un menú (MenuItem)

Los distintos elementos del programa (botones, etc…) están  vigilados por Listeners que detectan las acciones que tienen lugar sobre el elemento vigilado. Cuando ActionListener detecta una acción (por ejemplo: pulsar sobre un botón) se genera un evento de acción (ActionEvent) en el elemento (botón). Los ActionEvent invocan el método actionPerformed(ActionEvent e) que realiza las acciones programadas ante ese evento. La clase que implementa ActionListener ha de importar el paquete java.awt.event.*.

De manera que cuando el usuario pulsa un botón:

  • el botón sobre el que se produce una acción y genera un evento (event source, este dato se puede obtener con el método getSource) ha de estar previamente registrado con algún objeto que gestione el evento (ActionListener en este caso) mediante el método:
    boton.addActionListener(ActionListener nombre_de_actionListener)
  • el botón genera un ActionEvent (clase del paquete java.awt.event)
  • ActionEvent invoca al método actionPerformed que realiza las acciones programadas
  • si la clase implementa ActionListener, el método public void actionPerformed(ActionEvent e) ha de ser sobreescrito (override)

A continuación se muestran los aspectos más destacados del código en 4 versiones diferentes.

1. La clase pricipal implementa ActionListener; el registro del botón con ActionListener es:

boton.addActionListener(this);
//
public void actionPerformed(ActionEvent e) {
... código ... ;
}

2. La clase principal no implementa ActionListener pero sí lo hace otra clase; el botón es registrado con ActionListener asociándolo a una nueva instancia de esta clase:

boton.addActionListener(new ClaseClicEnBoton());
//
class ClaseClicEnBoton implements ActionListener {
public void actionPerformed(ActionEvent e) {
... código ... ;
}
}

3. La clase principal no implementa ActionListener; el botón es registrado con ActionListener asociándolo a una nueva instancia de la clase ActionListener:

boton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
clicEnBoton(e); }
});
 
private void clicEnBoton(ActionEvent e) {
... código ... ;
}

4. En caso de tener que detectar el botón que origina un evento (event source) entre varios botones registrados con un ActionListener, puede ser útil recurrir al método setActionCommand, que establece un nombre, independiente de su etiqueta, para el botón:

boton1.setActionCommand ("nombre_ActionCommand1");
boton2.setActionCommand ("nombre_ActionCommand2");
posteriormente se emplea una estructura condicional para elegir la acción dependiendo del botón origen del evento:
public void actionPerformed(ActionEvent e) {
//si se ha pulsado en el botón 1 (si ActionCommand es nombre_ActionCommand1)
if ("nombre_ActionCommand1".equals(e.getActionCommand())) {
... código ... ; }
//si se ha pulsado en el botón 2 (si ActionCommand es nombre_ActionCommand2)
else if ("nombre_ActionCommand2".equals(e.getActionCommand())) {
... código ... ; }
}

A continuación se comentan con más detalle estos 4 códigos diferentes.

Código 1: la clase principal implementa ActionListener

Declarar la clase principal del programa (esta clase sí implementa la interfaz ActionListener):

class action_Listener1 implements ActionListener { ... }

Registrar el botón con la propia clase a que pertenece (ya que implementa ActionListener):

boton1.addActionListener(this);
El método actionPerformed contiene las instrucciones que ejecutan la acción deseada:
// LO QUE SUCEDE AL PULSAR EL BOTÓN
public void actionPerformed(ActionEvent e) {
... código ... ;
}


Código 2
: la clase principal no implementa ActionListener

Declarar la clase principal del programa (esta clase no implementa la interfaz ActionListener):

class action_Listener1 { ... }

Registrar el botón con una instancia de la Interface ActionListener:

boton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
clicEnBoton(e); }
});

El método clicEnBoton contiene las instrucciones que ejecutan la acción deseada:

private void clicEnBoton(ActionEvent evt) {
... código ... ;
}


Código 3
: la clase principal no implementa ActionListener

Declarar la clase principal del programa (esta clase no implementa la interfaz ActionListener):

class action_Listener1 { ... }

Crear una nueva clase implementando ActionListener:

class ClaseClicEnBoton implements ActionListener {
public void actionPerformed(ActionEvent evt) {
... código ...; }
}

Registrar el botón con una instancia de la clase ClaseClicEnBoton:

boton.addActionListener(new ClaseClicEnBoton());


Código 4
: detectar qué botón ha generado el evento

Declarar la clase principal del programa (esta clase sí implementa la interfaz ActionListener):

class action_Listener1 implements ActionListener { ... }

Configurar la propiedad ActionCommand de los botones:

boton1.setActionCommand ("nombre_ActionCommand1");
boton2.setActionCommand ("nombre_ActionCommand2");
Registrar los botones con la propia clase a que pertenecen (ya que implementa ActionListener):
boton1.addActionListener(this);
boton2.addActionListener(this);

Recuperar el valor de la propiedad ActionCommand para saber cuál es el botón que ha generado el evento:

public void actionPerformed(ActionEvent e) {
//si se ha pulsado en el botón 1 (si ActionCommand es nombre_ActionCommand1)
if ("nombre_ActionCommand1".equals(e.getActionCommand())) {
... código ... ; }
//si se ha pulsado en el botón 2 (si ActionCommand es nombre_ActionCommand2)
else if ("nombre_ActionCommand2".equals(e.getActionCommand())) {
... código ... ; }
}


Código común

Es necesario importar los paquetes requeridos para el manejo de eventos:

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

Al crear y mostrar la ventana es posible configurar la posición centrada en la pantalla. setLocationRelativeTo(null) ha de ir colocado cuando ya se ha configurado adecuadamente el tamaño de la ventana:

ventana.setSize(220,100);
ventana.setLocationRelativeTo(null); //centrar el formulario en la pantalla
ventana.setResizable(false);
ventana.setVisible(true);

Todos los métodos de las Interfaces son públicos, por lo que al sobrescribir el método actionPerformed hay que declararlo como public:

public void actionPerformed(ActionEvent e) {
... código ... ; }
}

Se trata de una ventana con una etiqueta para mostrar un número y 4 botones de comando; cada botón, al ser pulsado, implementa la acción de forma diferente, según lo referido más arriba; todos ellos suman una cifra al número mostrado.

Puedes ver el código completo de la clase action_listener:

/* CAPTURA DE EVENTOS DE ACCION USANDO 
* CLASES DERIVADAS DE LA INTERFAZ ActionListener Y EVENTOS DE TIPO ActionEvent.
* ActionListener GENERA EVENTOS ActionEvent CUANDO OCURRE UNA ACCION SOBRE EL ELEMENTO
* Y MEDIANTE SU METODO ActionPerformed(ActionEvent e) REALIZA LAS ACCIONES PREVISTAS */

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

// CLASE PRINCIPAL DEL PROGRAMA (implementa ActionListener)
public class Action_listener extends JFrame implements ActionListener {

// variables a nivel de clase
private JLabel contador;
private JButton bt1, bt2, bt3, bt4, bt5, bt6;
private int cuenta = 0; 
private JPanel p1, p2;

//  METODO DE ENTRADA AL PROGRAMA
public static void main(String[] args) {
	// instancia de la clase principal
	new Action_listener();
}

// METODO CONSTRUCTOR QUE CREA Y MUESTRA LA INTERFAZ
	public Action_listener() {
	// decoracion de Java
	JFrame.setDefaultLookAndFeelDecorated(true);

	/* la variable especial this se usa en metodos de instancia
	* para hacer referencia al objeto que contiene al metodo,
	* aqui equivale a JFrame */

	// accion por defecto al cerrar la ventana: salir del programa
	this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		
	/* paneles de disposicion: por defecto, los paneles se crean con diseño FlowLayout
	* por ello es preferible definir el Layout en el constructor del panel, para crearlo
	* desde el principio con ese diseño y evitar la creacion de un FlowLayout temporal;
	* es preferible hacer:
	* p1 = new JPanel(new BorderLayout());
	* mejor que hacer esto otro:
	* p1 = new JPanel();
	* p1.setLayout(new BorderLayout()); */
	p1 = new JPanel(new BorderLayout());
	//  borde del panel: sin relleno, 10px por cada lado
	p1.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
	// panel para ocupar BorderLayout.SOUTH
	p2 = new JPanel();
	p2.setLayout(new GridLayout(1,4,10,10));

	//  configurar fuente y convertir numero a String para JLabel
	contador = new JLabel(Integer.toString(cuenta), JLabel.CENTER);
	Font letra1 = new Font("Tahoma", Font.BOLD, 12);
	contador.setFont(letra1);
	//  configurar fuente para los botones
	Font letra2 = new Font("Tahoma", Font.PLAIN, 12);
	bt1 = new JButton("De 1 en 1");
	bt1.setFont(letra2);
	bt2 = new JButton("De 2 en 2");
	bt2.setFont(letra2);
	bt3 = new JButton("De 3 en 3");
	bt3.setFont(letra2);
	bt4 = new JButton("De 4 en 4");
	bt4.setFont(letra2);

	// tooltips para los botones
	bt1.setToolTipText("Contar de 1 en 1");
	bt2.setToolTipText("Contar de 2 en 2");
	bt3.setToolTipText("Contar de 3 en 3");
	bt4.setToolTipText("Contar de 4 en 4");

	/* accion por defecto ante los eventos de bt1 y bt3,
	* para poder diferenciar mas tarde cual de los 2 botones
	* ha generado el evento */
	bt1.setActionCommand ("Contar1");
	bt3.setActionCommand ("Contar3");

	/*colocar los componentes en los paneles;
	* para añadir un componente a un JFrame es preferible
	* frame.getContentPane().add(p1);
	* en lugar de
	* frame.add(p1);*/
	this.getContentPane().add(p1);
	p1.add(contador, BorderLayout.NORTH);
	p1.add(p2, BorderLayout.SOUTH);
	p2.add(bt1);
	p2.add(bt2);
	p2.add(bt3);
	p2.add(bt4);

	// crear y mostrar la ventana
	this.setTitle("ActionListener");
	this.setSize(500,100);
	this.setLocationRelativeTo(null); // centrar el formulario en la pantalla
	this.setResizable(false);
	this.setVisible(true);

	/*la clase principal implementa la interfaz ActionListener
	* por ello se pueden emplear sus metodos directamente.
	* aqui se detectan las acciones sobre los botones bt1 y bt3: */
	bt1.addActionListener(this);
	bt3.addActionListener(this);

	/*Boton bt2: se asocia con una instancia de la clase Contar,
	* la clase Contar también implementa ActionListener por lo que 
	* este metodo podria usarse aunque la clase principal no fuese ActionListener */
	bt2.addActionListener(new Contar());

	/* si la clase principal no implementa la interfaz ActionListener
	* se puede asociar el boton con una instancia de ActionListener;
	* su metodo actionPerformed() enlaza con el metodo clicEnBoton2() que 
	* se ejecuta cuando se detectan acciones sobre el boton */
	bt4.addActionListener(new ActionListener() {
	public void actionPerformed(ActionEvent e) {
		clicEnBoton2(e); }
	});

}

  //  LO QUE SUCEDE AL PULSAR LOS BOTONES 1 y 3 
  public void actionPerformed(ActionEvent e) {
	  // si se ha pulsado en el boton bt1 (si ActionCommand es Contar1)
	if ("Contar1".equals(e.getActionCommand())) {
	  cuenta ++; // contar 1
	  contador.setText(Integer.toString(cuenta)); }
	  // si se ha pulsado en el boton bt3 (si ActionCommand es Contar3)
	else if ("Contar3".equals(e.getActionCommand())) {
	  cuenta +=3; // contar 3
	  contador.setText(Integer.toString(cuenta)); }
	}

 //  LO QUE SUCEDE AL PULSAR EL BOTON 2
 // si se ha pulsado en el boton bt2
 class Contar implements ActionListener {
    public void actionPerformed(ActionEvent evt) {
	  cuenta +=2; // contar 3
	  contador.setText(Integer.toString(cuenta)); }
	}

 //  LO QUE SUCEDE AL PULSAR EL BOTON 4 
private void clicEnBoton2(ActionEvent evt) {
	  // si se ha pulsado en el boton bt4
	  cuenta +=4; // contar 4
	  contador.setText(Integer.toString(cuenta));
	}

}