Mòdul 6

Pràctica 3: esdeveniments i listeners
Tornar presentació tema
Pràctica 2 Pràctica 2 Pràctica 1 Pràctica 4 Pràctica 5 Pràctica 6 Pràctica 6  
     
 

 

 
  Controls, esdeveniments i listeners  
     
  A les pràctiques anteriors, per aprendre a fer la distribució de controls en una finestra, només has fet servir el control JButton, que representa un botó i, després has après a fer etiquetes (objectes javax.swing.JLabel). Però aquests botons no tenien cap funcionalitat, en prémer-los no passava res!  
     
  En aquesta pràctica aprendràs a donar-los funcionalitat, a que passin coses! De moment només jugaràs amb dos controls: JLabels (etiquetes de text i imatges) i JButtons (botons).  
     
  Generació d'esdeveniments: objectes JButton    
     
  Tots els controls actius (botons, textboxes, radiobuttons, etc.) d'una GUI funcionen amb el mateix esquema: un control és un element visual (un element de la GUI) mitjançant el qual l'usuari d'una aplicació dóna o rep alguna informació a la aplicació. Si el control és per donar informació a la aplicació i no nomès per rebre'n (com és el cas dels objectes JLabel de la pràctica anterior) la acció de l'usuari sobre el control fa que aquest emeti un esdeveniment i que, els qui estaven escoltant-lo (els listeners del control) s'assabentin de l'acció i obrin en conseqüència. El procés té aquests passos:  
     
 
  1. L'objecte que representa el control, posem un botó, quan l'usuari el prem, immediatament, emet un esdeveniment (que és una certa mena d'objecte: a Java tot són objectes). En el cas d'un JButton, l'esdeveniment és una instància de java.awt.event.ActionEvent.

  2. Els objectes declarats com a oïdors (listeners) del control reben l'esdeveniment i, immeditament executen algun dels mètodes associats a la seva condició de listeners.
 
     
Ara faràs els primers assajos. Crea un nou projecte que es digui botons i crea una classe Botons que representi una finestra (JFrame) amb una etiqueta de text a la posició centre (BorderLayout.CENTER) i dos JButtons, un al nord (BorderLayout.CENTER) i l'altre al sud (BorderLayout.CENTER)  
     

import javax.swing.JFrame;
import java.awt.Container;
import java.awt.BorderLayout;

import javax.swing.JLabel;
import javax.swing.JButton;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;

/**
* Escriviu aquí una descripcìó de la classe Botons
*
* @author (el vostre nom)
* @version (un número de versió o la data)
*/

public class Botons extends JFrame {

    JLabel etiquetaText;
    JButton botoNord;
    JButton botoSud;


    /**
     * Mètode constructor per objectes de la classe Botons.
     */

    public Botons () {
        setTitle("Botons");
        Container cnt=getContentPane();
        etiquetaText=new JLabel("Una etiqueta");
        cnt.add(etiquetaText,BorderLayout.CENTER);
        botoNord=new JButton("Botó nord");
        cnt.add(botoNord,BorderLayout.NORTH);
        botoSud=new JButton("Botó sud");
        cnt.add(botoSud,BorderLayout.SOUTH);
        pack();
        show();
    }

}

 
     
  Observa totes les classes que cal importar!  
     
  Si compiles i crees un objecte Botons, has d'obtenir això:  
     
 
 
     
  i els dos botons són encara inactius... Per què? Cadascun dels botons emet un objecte java.awt.event.ActionEvent cada vegada que el prems, però no hi ha ningú que s'ho escolti aixó... i, en conseqüència, no passa res.  
     
  Ara es tracta de seguir fil per randa el procés de donar vida als controls (botons, per ara) que sempre és el mateix:  
     
 
  1. Donar al objecte que convingui la condició d'oïdor (listener) dels esdeveniments que emetrà el control. En el nostre cas, cal que li donis al JFrame Botons la condició de listener dels esdeveniments java.awt.ActionEvent que emeten els botons. Això ho aconseguiras implementant la interfície (interface) java.awt.ActionListener a aquesta classe. Això es fa tot modificant la primera línia de la definició de la classe. En lloc de

    public class Botons extends JFrame {

    ha de ser

    public class Botons extends JFrame implements ActionListener {

  2. Incloure en el codi de la classe tots els mètodes de la interfície que acabes d'implementar. La interfície ActionListener només té un mètode:

    public void actionPerformed (ActionEvent e)

    que caldrà, doncs, incloure a la classe Botons. Aquest mètode s'executa cada vegada que el control emet un esdeveniment ActionEvent.

  3. Incloure aquest listener a la llista de listeners del control. Tot objecte capaç d'emetre esdeveniments té un mètode per afegir-li objectes a la seva llista de listeners. En el cas d'un emissor d'esdeveniments ActionEvent el mètode és

    public void addActionListener (ActionListener lst)
 
  La classe Botons, així modificada, queda així:  
     

import javax.swing.JFrame;
import java.awt.Container;
import java.awt.BorderLayout;
import javax.swing.JLabel;
import javax.swing.JButton;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;

/**
* Escriviu aquí una descripcìó de la classe Botons
*
* @author (el vostre nom)
* @version (un número de versió o la data)
*/

public class Botons extends JFrame implements ActionListener {

    JLabel etiquetaText;
    JButton botoNord;
    JButton botoSud;

    /**
     * Mètode constructor per objectes de la classe Botons.
     */
    public Botons () {
        setTitle("Botons");
        Container cnt=getContentPane();
        etiquetaText=new JLabel("Una etiqueta");
        cnt.add(etiquetaText,BorderLayout.CENTER);
        botoNord=new JButton("Botó nord");

        botoNord.addActionListener(this);
        cnt.add(botoNord,BorderLayout.NORTH);
        botoSud=new JButton("Botó sud");
        botoSud.addActionListener(this);
        cnt.add(botoSud,BorderLayout.SOUTH);
        pack();
        show();
    }

    public void actionPerformed (ActionEvent e) {
    }

}

 
     
  L'esquema general, que val pels botons i per tots els altres controls, és aquest:  
     
 
 
     
  Ara ja només cal definir què ha de fer el listener (el JFrame Botons, en aquest cas) quant es produeixi l'esdeveniment que ha estat escoltant. Això s'aconsegueix tot posant el codi adequat al mètode actionPerformed(). Pots començar per fer que el text de l'etiqueta canviï:  
     

    public void actionPerformed (ActionEvent e) {
        etiquetaText.setText("Algú ha apretat un botó");
        pack();
// Cal redimensionar la finestra!
    }

 
     
  I sí, funciona!  
     
 
 
     
  Distingint la font d'un esdeveniment:    
     
  Hi ha dos botons i una sola acció: modificar el text del JLabel. Això és ben pobre! Cadascun dels dos botons hauria de fer una cosa diferent, oi? Aleshores és clar que, en el mètode actionPerformed hi ha d'haver alguna manera de distingir quin dels dos botons és l'emissor de l'esdeveniment. Això es fa de la manera següent:  
     
 
  1. Demanar a l'esdeveniment rebut quin és l'objecte que l'ha emès. El mètode per fer això és public Object getSource()de la classe ActionEvent. Cal, doncs, la línia de codi

    Object obj=e.getSource()

  2. Assegurar-se que l'emissor és, efectivament un JButton mitjançant la clàusula instanceof:

        if (obj instanceof JButton) {
            
    // codi
        }

  3. Aleshores, cal que obj es manifesti com a JButton:

        if (obj instanceof JButton) {
            JButton boto=(JButton)obj;
    // Càsting imprescindible!
            
    // codi
        }

  4. Ara ja podràs identificar el botó responsable de l'esdeveniment. Això se sol fer amb el mètode String getActionCommand() de la classe JButton. Aquest mètode retorna la cadena que hagis definit per a cada botó amb el mètode void setActionCommand(String cadena) i, si no ho has fet, retorna el text de l'etiqueta del botó:

        if (obj instanceof JButton) {
            JButton boto=(JButton)obj;
    // Càsting imprescindible!
            String cadena=boto.getActionCommand();
            
    // codi
        }

  5. Finalment, segons sigui la cadena, el programa farà una cosa o una altra:

        if (obj instanceof JButton) {
            JButton boto=(JButton)obj;
    // Càsting imprescindible!
            String cadena=boto.getActionCommand();
                if (cadena.equals("Botó nord") {
            
            // el que vulguis que passi en aquest cas
             
       } else if (cadena.equals("Botó sud") {
                    // el que vulguis que passi en aquest cas
                }
        }
 
  Fes que s'imprimeixi "El botó del nord" o "El botó del sud" segons el cas. El codi de la classe serà aquest:  
     

import javax.swing.JFrame;
import java.awt.Container;
import java.awt.BorderLayout;
import javax.swing.JLabel;
import javax.swing.JButton;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;

/**
* Escriviu aquí una descripcìó de la classe Botons
*
* @author (el vostre nom)
* @version (un número de versió o la data)
*/
public class Botons extends JFrame implements ActionListener {

    JLabel etiquetaText;
    JButton botoNord;
    JButton botoSud;

    /**
     * Mètode constructor per objectes de la classe Botons.
     */
    public Botons () {
        setTitle("Botons");
        Container cnt=getContentPane();
        etiquetaText=new JLabel("Una etiqueta");
        cnt.add(etiquetaText,BorderLayout.CENTER);
        botoNord=new JButton("Botó nord");
        botoNord.addActionListener(this);
        cnt.add(botoNord,BorderLayout.NORTH);
        botoSud=new JButton("Botó sud");
        botoSud.addActionListener(this);
        cnt.add(botoSud,BorderLayout.SOUTH);
        pack();
        show();
    }

    public void actionPerformed (ActionEvent e) {
        Object obj=e.getSource();
            if (obj instanceof JButton) {
                JButton boto=(JButton)obj;
// Càsting
                String cadena=boto.getActionCommand();
                    if (cadena.equals("Botó nord")) {
                        etiquetaText.setText("El botó del nord");
                    } else if (cadena.equals("Botó sud")) {
                        etiquetaText.setText("El botó del sud");
                    }
                pack();
// Cal redimensionar la finestra!
            }
    }

}

 
     
  Prova'n ara el comportament:  
     
 
 
     
  Botons amb imatges: un àlbum de fotos.    
     
  Els objectes JButton (botons) s'assemblen als objectes JLabel (etiquetes) en un aspecte: ambdós menes d'objectes poden visualitzar text i/o imatges. Per construir un JButton que mostri una imatge el mètode constructor és:  
     
 
public JButton (Icon imatge)
 
     
  exactament igual que en el cas de JLabels. De la mateixa manera, si hi vols text i imatge, aleshores el mètode constructor és:  
     
 
public JButton (String text,Icon imatge)
 
     
  i, novament, com als JLabels, pots definir les posicions relatives de text i imatge mitjançant els mètodes:  
     
 
  • public void setHorizontalAlignment (int alineament_horitzontal)

  • public void setHorizontalTextPosition(
                                        int posicioHoritzontalText)

  • public void setVerticalAlignment(int alineament_vertical)

  • public void setVerticalTextPosition(int posicioVerticalText);
 
  Els valors admisibles de alineament_horitzontal, posicioHoritzontalText, alineament_vertical i posicioVerticalText són els mateixos que els dels JLabels ja descrits a la pràctica anterior.  
     
Ara et proposem fer una cosa una mica més interessant... un àlbum de fotos! amb els botons de comanament amb imatges. Crea un nou projecte que es digui visorsFotos. Les imatges necessàries són aqui, (aquestes imatges han estat obtingudes de Wikimedia, on els seus autors les publicaren sota llicència GNU) i cal que les posis a la carpeta visorsFotos\imatges. Crea la classe VisorFotosPlus amb aquest codi (els comentaris expliquen el codi):  
     

import javax.swing.JFrame;
import java.awt.Container;
import java.awt.BorderLayout;
import javax.swing.JLabel;
import javax.swing.JButton;
import javax.swing.ImageIcon;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;

/**
 * Escriviu aquí una descripcìó de la classe VisorFotosPlus
 *
 * @author (el vostre nom)
 * @version (un número de versió o la data)
 */

public class VisorFotosPlus extends JFrame
                            implements ActionListener {

    /**
     * El JLabel on es mostraran les fotos.
     */

    JLabel etiquetaImatges;
    /**
     * El JLabel on es veurà una descripció de cada imatge.
     */

    JLabel etiquetaTextos;
    /**
     * Una matriu per emmagatzemar les imatges de l'àlbum.
     */

    ImageIcon[] imatges;
    /**
     * L'identificador de la imatge que es mostra en aquest
     * moment.
     */

    int fotoActual=0;
    /**
     * El nombre d'imatges que conté l'àlbum.
     */

    int quantes;

    /**
     * Mètode constructor per objectes de la classe VisorFotos.
     */

    public VisorFotosPlus () {
        setTitle("Visor de fotos");
        Container cnt=getContentPane();

        // Una matriu doble que conté el nom del fitxer de cada
        // imatge i un text amb la seva descripció:
        String[][] nomsImatges={{"Barcelona.gif",
                                 "Barcelona des de l'aire "+
                                 "(Foto: SergiL)"},
                                {"Bosc.gif",
                                 "Bosc a Ordesa (Foto: "+
                                  "Quique251)"},
                                {"Canigo.gif",
                                 "Canigó (Foto: S. Perrinel)"},
                                {"Estartit.gif",
                                 "Estartit (Foto: R. Martín S.)"},
                                {"MontPerdut.gif",
                                 "MontPerdut (Foto: N. Azcarate "+
                                  "i C. Bescos)"},
                                {"Pedraforca.gif",
                                 "Pedraforca (Foto: David Gaya)"},
                                {"Riu.gif",
                                 "Riu Foix a Castellet (Foto: "+
                                  "David Gaya)"},
                                {"Vignemale.gif",
                                 "Vignemale, cara nord (Foto: "+
                                  "Rokad)"},
                               };
        // Recompte del nombre total d'imatges:
        quantes=nomsImatges.length;
        // Construcció de la matriu que conté les imatges:
        imatges=new ImageIcon[quantes];
            for (int i=0;i<quantes;i++) {
                // Se suposa que els fitxers *.gif són a la
                // carpeta imatges
                imatges[i]=new ImageIcon(
                   "imatges/"+nomsImatges[i][0],nomsImatges[i][1]);
            }
        // Construcció i situació del JLabel que mostrarà les
        // imatges

        etiquetaImatges=new JLabel(imatges[fotoActual]);
        cnt.add(etiquetaImatges,BorderLayout.CENTER);
        // Construcció i situació del JLabel que mostrarà els
        // comentaris a les imatges

        etiquetaTextos=new JLabel(
                         nomsImatges[fotoActual][1],JLabel.CENTER);
        cnt.add(etiquetaTextos,BorderLayout.SOUTH);
        // Construcció i situació dels JButton de control de
        // l'àlbum. Aquests botons no mostren text sinó una
        // imatge:

        JButton botoMes=new JButton(
                          new ImageIcon("imatges/ArrowRight.gif"));
        // Com que el botó no té text, cal definir-li l'Action-
        // Command que el distingeix:
        botoMes.setActionCommand("mes");
        botoMes.addActionListener(this);
        cnt.add(botoMes,BorderLayout.EAST);

        JButton botoMenys=new JButton(
                          new ImageIcon("imatges/ArrowLeft.gif"));
        // Com que el botó no té text, cal definir-li l'Action-
        // Command que el distingeix:
        botoMenys.setActionCommand("menys");
        botoMenys.addActionListener(this);
        cnt.add(botoMenys,BorderLayout.WEST);
        pack();
        show();
    }

    /**
     * Mètode que s'executa quan algun dels JButton emet un
     * esdeveniment ActionEvent.
     */

    public void actionPerformed (ActionEvent e) {
        // Quin objecte ha emès l'esdeveniment ActionEvent?
        Object font=e.getSource();
            // Ha estat un botó?
            if (font instanceof JButton) {
                // Li demanes que s'identifiqui!
                String accio=e.getActionCommand();
                    // Ara pots distingir quin dels botons ha
                    // estat l'emissor de l'ActionEvent e.
                    if (accio.equals("mes")) {
                        // Passa a la foto següent i, si has
                        // arribat al final, torna a la foto 0:
                        fotoActual=(fotoActual+1)%quantes;
                    }
                    if (accio.equals("menys")) {
                        // Passa a la foto anterior i, si has
                        // arribat a la foto 0, passa a l'última:
                        fotoActual=(fotoActual+quantes-1)%quantes;
                    }
                // Posa la imatge al JLabel corresponent:
                etiquetaImatges.setIcon(imatges[fotoActual]);
                // Posa el comentari de la imatge al seu JLabel:
                etiquetaTextos.setText(
                            imatges[fotoActual].getDescription());
                // Reendreça i refresca el JFrame:
                pack();
            }
    }

}

 
     
  En crear un objecte VisorFotosPlus, obtindràs això:  
     
 
 
     
  Una manera alternativa de distingir botons    
     
  En lloc de fer que la classe principal VisorFotosPlus sigui la que escolti (listener) els esdeveniments dels botons (ActionEvents), es pot construir un objecte ActionListener per cadascun dels botons. Aleshores ja no caldrà que la classe principal implementi la interfície ActionListener ni el mètode actionPerformed(). Cadascun dels objectes ActionListener tindrà el seu propi mètode actionPerformed() i, com que només escolta el seu botó, no hi ha possibilitat de confusió.  
     
Vegem com fer-ho: crea una nova classe VisorFotosPlusAlt, que és la mateixa classe VisorFotosPlus, amb aquests canvis:  
     

import javax.swing.JFrame;
import java.awt.Container;
import java.awt.BorderLayout;
import javax.swing.JLabel;
import javax.swing.JButton;
import javax.swing.ImageIcon;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;

/**
 * Escriviu aquí una descripcìó de la classe VisorFotosPlusAlt
 *
 * @author (el vostre nom)
 * @version (un número de versió o la data)
 */

public class VisorFotosPlusAlt extends JFrame {

    /**
     * El JLabel on es mostraran les fotos.
     */
    JLabel etiquetaImatges;
    /**
     * El JLabel on es veurà una descripció de cada imatge.
     */
    JLabel etiquetaTextos;
    /**
     * Una matriu per emmagatzemar les imatges de l'àlbum.
     */
    ImageIcon[] imatges;
    /**
     * L'identificador de la imatge que es mostra en aquest
     * moment.
     */
    int fotoActual=0;
    /**
     * El nombre d'imatges que conté l'àlbum.
     */
    int quantes;

    /**
     * Mètode constructor per objectes de la classe
     * VisorFotosPlusAlt.
     */

    public VisorFotosPlusAlt () {
        setTitle("Visor de fotos");
        Container cnt=getContentPane();

        // Una matriu doble que conté el nom del fitxer de cada
        // imatge i un text amb la seva descripció:
        String[][] nomsImatges={{"Barcelona.gif",
                                 "Barcelona des de l'aire "+
                                 "(Foto: SergiL)"},
                                {"Bosc.gif",
                                 "Bosc a Ordesa (Foto: "+
                                  "Quique251)"},
                                {"Canigo.gif",
                                 "Canigó (Foto: S. Perrinel)"},
                                {"Estartit.gif",
                                 "Estartit (Foto: R. Martín S.)"},
                                {"MontPerdut.gif",
                                 "MontPerdut (Foto: N. Azcarate "+
                                  "i C. Bescos)"},
                                {"Pedraforca.gif",
                                 "Pedraforca (Foto: David Gaya)"},
                                {"Riu.gif",
                                 "Riu Foix a Castellet (Foto: "+
                                  "David Gaya)"},
                                {"Vignemale.gif",
                                 "Vignemale, cara nord (Foto: "+
                                  "Rokad)"},
                               };
        // Recompte del nombre total d'imatges:
        quantes=nomsImatges.length;
        // Construcció de la matriu que conté les imatges:
        imatges=new ImageIcon[quantes];
            for (int i=0;i<quantes;i++) {
                // Se suposa que els fitxers *.gif són a la
                // carpeta imatges
                imatges[i]=new ImageIcon(
                   "imatges/"+nomsImatges[i][0],nomsImatges[i][1]);
            }
        // Construcció i situació del JLabel que mostrarà les
        // imatges
        etiquetaImatges=new JLabel(imatges[fotoActual]);
        cnt.add(etiquetaImatges,BorderLayout.CENTER);
        // Construcció i situació del JLabel que mostrarà els
        // comentaris a les imatges
        etiquetaTextos=new JLabel(
                         nomsImatges[fotoActual][1],JLabel.CENTER);
        cnt.add(etiquetaTextos,BorderLayout.SOUTH);
        // Construcció i situació dels JButton de control de
        // l'àlbum. Aquests botons no mostren text sinó una
        // imatge:
        JButton botoMes=new JButton(
                          new ImageIcon("imatges/ArrowRight.gif"));
        // Aquí es crea l'objecte ActionListener per a aquest botó
        botoMes.addActionListener(new ActionListener() {
                                   // Mètode que s'executa quan
                                   // aquest botó emet un esdeve-
                                   // niment

                    public void actionPerformed (ActionEvent e) {
                        // Passa a la foto següent i, si has
                        // arribat al final, torna a la foto 0:

                        fotoActual=(fotoActual+1)%quantes;
                        // Actualització dels canvis
                        actualitza();
                    }
                                  }
                                 );
        cnt.add(botoMes,BorderLayout.EAST);

        JButton botoMenys=new JButton(
                          new ImageIcon("imatges/ArrowLeft.gif"));
        // Aquí es crea l'objecte ActionListener per a aquest botó
        botoMenys.addActionListener(new ActionListener() {
                                   // Mètode que s'executa quan
                                   // aquest botó emet un esdeve-
                                   // niment

                    public void actionPerformed (ActionEvent e) {
                        // Passa a la foto anterior i, si has
                        // arribat a la foto 0, passa a l'última:

                        fotoActual=(fotoActual+quantes-1)%quantes;
                        // Actualització dels canvis
                        actualitza();
                    }
                                  }
                                 );
        cnt.add(botoMenys,BorderLayout.WEST);
        pack();
        show();
    }

    /**
     * Mètode que s'executa quan algun dels JButton emet un
     * esdeveniment ActionEvent.
     */

    public void actualitza () {
        // Posa la imatge al JLabel corresponent:
        etiquetaImatges.setIcon(imatges[fotoActual]);
        // Posa el comentari de la imatge al seu JLabel:
        etiquetaTextos.setText(
                            imatges[fotoActual].getDescription());
        // Reendreça i refresca el JFrame:
        pack();
    }

}

 
     
  Compila i crea un objecte VisorFotosPlusAlt i veuràs que tot funciona bé i com abans.  
     
  Ja està clar com va això? Doncs ara:  
     
  Un exercici: el Visor de Fotos Xtra!    
     
Et proposem que, amb les imatges que ja has fet servir pel Visor de Fotos Plus (400x233 píxels ) i les seves versions reduïdes (80x46 píxels, prefix "ico" al nom del fitxer *.gif) construeixis el Visor de Fotos Xtra, el funcionament del qual ha de ser com el que veuràs quan facis clic aquí.  
     
  El codi solució és aquí (però no el miris encara!)  
     
    Tornar al principi