ActionListener

De ChuWiki
Saltar a: navegación, buscar

Los ActionListener

Los componentes java (JButton, JTextField, etc) y algunas clases que no lo son (como TableModel, ListModel, etc) permiten que nos "suscribamos" a eventos que pasan en ellos, de forma que cuando ocurre este evento, el componente nos avisa. Por ejemplo, podemos estar interesados en cuándo se pulsa un botón, cuando un componente gana el foco, cuando pasa el ratón por encima, cuándo se cierra una ventana, etc, etc.

Para enterarnos de todos estos eventos, los componentes tienen métodos del estilo add...Listener() donde los puntos suspensivos, de alguna forma, representan el nombre del tipo de evento. Así, por ejemplo, los componentes pueden tener métodos addActionListener(), addMouseListener(), addWindowListener(), etc.

Lo que se comenta aquí valdrá para todos ellos, pero nos centraremos en el ActionListener.

Cuando usamos el addActionListener() de un componente, nos estamos suscribiendo a la "acción típica" o que java considera más importante para ese componente. Por ejemplo, la acción más típica de un JButton es pulsarlo. Para un JTextField, java considera que es pulsar <INTRO> indicando que hemos terminado de escribir el texto, para un JComboBox es seleccionar una opción, etc.

Cuando llamamos al addActionListener() de cualquiera de estos componentes, nos estamos suscribiendo a la acción más "típica" de ese componente.

Como parámetro, debemos pasar una clase que implemente ActionListner. Por tanto, debemos hacer una clase que implemente la interface ActionListener. Hay muchas formas de hacer esto y vamos a verlas.

Una clase normalita

Una forma es hacer una clase en un fichero java que implemente ActionListener.

public class MiClase implements ActionListener
{
   public void actionPerformed (ActionEvent e)
   {
      // Aqui el código que queremos que se ejecute cuando tiene lugar la acción.
      // la pulsación del botón, el <INTRO> en el JTextField, elección en el JComboBox, etc.
   }
}

Luego, simplemente tenemos que añadir esta clase al componente java usando su método addActionListener().

JButton boton = new JButton ("Pulsame");
MiClase elListener = new MiClase();
boton.addActionListener (elListener);

Ya está, cuando se pulse el botón, java llamará a nuestro método actionPerformed() de nuestra clase MiClase.

Este método no se suele usar casi nunca, puesto que es bastante engorroso hacerse una clase para cada uno de los componentes java que tengamos en la ventana que hagan algo.

Otra pega es que la clase es totalmente independiente, por lo que no tenemos acceso a los métodos de ninguna ortra clase, salvo que hagamos algo para pasárselas y hacerselas accesibles.

Una clase interna

Otra forma es hacer una clase interna dentro de otra clase. Por ejemplo, si nuestra clase es una clase que hereda de JFrame y tiene botones, podemos hacerlo así

public class MiVentana extends JFrame
{
   public MiVentana()
   {
       JButton boton = new JButton ("Pulsame");
       MiClase elListener = new MiClase();
       boton.addActionListener (elListener);
   }
   public void pulsado()
   {
      System.out.println("Pulsado el botón");
   }
   ...
   // Esta es la clase interna. Está definida DENTRO de MiVentana
   public class MiClase implements ActionListener
   {
      public void actionPerformed (ActionEvent e)
      {
         pulsado();
      }
   }
}

Este método sigue presentando el inconveniente de que hay que hacer una clase completa para cada componente java que tengamos en la ventana que queramos que haga algo.

La ventaja es que desde esta clase interna sí podemos acceder a métodos de la clase externa a la que pertenece. Como vemos en el ejemplo, desde MiClase podemos llamar al método pulsado() de la clase MiVentana.

Hacer que la clase principal implemente ActionListener

Otra forma que sí se usa a veces es hacer que la clase principal implemente ActionListener así

public class MiVentana extends JFrame implements ActionListener
{
   public MiVentana()
   {
       JButton boton = new JButton ("Pulsame");
       MiClase elListener = new MiClase();
       boton.addActionListener (this);
   }
   public void actionPerformed (ActionEvent e)
   {
         System.out.println("Pulsado");
   }
}

Aquí ahorramos hacer una clase nueva y tenemos accesibles todos los métodos de la clase principal. Esta opción es muy cómoda si tenemos pocos componentes a los que añadir el listener.

Clase anónima

La opción que más se usa es la de hacer la clase "sobre la marcha". El código es este

public class MiVentana extends JFrame
{
   public MiVentana()
   {
       JButton boton = new JButton ("Pulsame");
       MiClase elListener = new MiClase();
       final int unaVariable=3;
       boton.addActionListener (new ActionListener()
       {
         public void actionPerformed (ActionEvent e)
         {
            pulsado();
            // Aquí está accesible unaVariable
            System.out.println(unaVariable);
         }
       });
   }
   public void pulsado()
   {
      System.out.println("Pulsado el botón");
   }
}

De esta forma, cuando compilemos se creará algo parecido a un fichero

MiVentana$1.class

Ese es el fichero de la clase anónima. Esa clase existe, pero no tenemos el fuente separado.

Aquí también tenemos acceso a todo lo de la clase MiVentana. Incluso tenemos acceso a las variables locales y parámetros del método de la clase principal donde hayamos añadido el listener. En este caso, a las variables locales y parámetros del constructor.

La pega de acceder a estas variables locales es que cuando se pulse en botón puede que ya no existan, así que si las usamos directamente, java no nos dejará compilar. Para que esto no pase, debemos declarar los parámetros y variables locales que queramos usar como final. En el código se ve el caso con la variable entera unaVariable.

Un equívoco habitual es que esta clase interna NO es la clase MiVentana. Por ello, this no hace referencia a MiVentana, sino a la clase interna. Si queremos acceder a MiVentana con this (aunque realmente no hace falta), debemos hacer esto

         public void actionPerformed (ActionEvent e)
         {
            MiVentana.this.pulsado();
         }

Distinguir qué botón causa la acción

Cuando nos decidimos a hacer una clase, suele ser habitual querer aprovechar la clase para varios botones. En el método actionPerformed() de esa clase se nos pasa un ActionEvent. Con este ActionEvent podemos obtener información sobre quién es el que ha provocado el evento.

Object fuente = event.getSource();

Este método nos devuelve el componente java (JButton, JTextField, etc) que ha provocado el evento. Si la clase está añadida, por ejemplo a boton1, boton2 y boton3, en el método actionPerformed() podemos hacer algo como esto

public void actionPerformed (ActionEvent e)
{
   Object fuente = e.getSource();
   if (fuente==boton1)
      metodoParaBoton1();
   else if (fuente==boton2)
      metodoParaBoton2();
   else if (fuente==boton3)
      metodoParaBoton3();
}

Otra opción interesante, es decirle al botón cual es su "comando". Se hace así

boton.setActionCommand("Borra");  // Borra es un texto cualquiera, que NO se ve en el botón

Luego, en el ActionEvent, podemos obtener el "comando" del componente que ha provocado el evento.

public void actionPerformed (ActionEvent e)
{
   String comando = e.getActionCommand();
   if (comando.equals("Borra"))
      metodoParaBorra();
   else if (comando.equals("Crea"))
      metodoParaCrea();
   else if (comando.equals("Modifica"))
      metodoParaModifica();
}

Este método tiene la ventaja, además de ser más claro, que permite que añadamos el mismo comando a varios botones o componentes y todos ellos harán lo mismo. Por ejemplo, imagina que tenemos un menú en el que una de las opciones es "Borra" y una barra de herramientas rápida en la que tenemos un botón que también "Borra". Añadiendo el mismo ActionListener a ambos, menú y botón, la acción que se ejecutará es la misma.

Si hicieramos esto con el e.getSource(), deberíamos poner un if con un OR.

 if (( e.getSource()==boton ) || (e.getSource()==opcionMenu ))
 ...