Enrera
Mòdul 5
Iniciació a la programació en Java
  Pràctica
1
2
3
4
   
Exercicis
Exercicis
 
 
  Captura d'esdeveniments
   

Quan ja hem après una mica a disposar elements en un contenidor (pràctica 2) ,fent servir algun dels layout managers disponibles, ara és el moment de donar funcionalitat a aquests elements.Amb aquesta pràctica aprendrem a fer anar uns controls senzills: els botons i a més,amb això pretenem:

  • Continuar amb l'aprenentatge del paradigma esdeveniment-oient (event-listener), que és la base de la funcionalitat dels controls.

  • Aprendre a identificar quin objecte ha emès un cert esdeveniment.
  • Explorar alguns dels mètodes de la classe java.awt.Color.

  • Fer servir efectivament la noció de conversió (casting).
   
Controls que controlen ( capturant esdeveniments)
   
Atenció !

En aquest context, 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 labels 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.

Així, l'esquema general de funcionament d'un control és aquest:

  1. El control rep l'acció de l'usuari (per exemple: el control és un botó i l'usuari el prem) i, immediatament, emet un esdeveniment (que és una certa mena d'objecte: a Java tot són objectes. En el cas d'un botó, 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.
Què cal tenir en compte de manera ineludible?
   
Atenció !

Cal recordar dues coses:

  1. Donar als objectes que siguin del cas la condició de listeners d'una certa mena d'esdeveniments (events)

  2. Afegir a la llista de listeners del control que ens ocupa els objectes que l'han d'escoltar
Primer pas: donar a un objecte la condició de "listener"
   
Atenció ! Això s'aconsegueix mitjançant la interfície (interface) corresponent. Cada mena d'esdeveniment té la corresponent interfície per a construir listeners d'aquest esdeveniment. Aquí n'hi ha algunes de les més usuals; les classes que emeten els esdeveniments són del paquet java.awt, els esdeveniments i les interfícies són del paquet java.awt.event:
   
 
Control: Classe: Esdeveniment: Interface:
Finestres: Frame WindowEvent WindowListener
Botons: Button ActionEvent ActionListener
Ratolí:   MouseEvent MouseListener
Ratolí:   MouseMotionEvent MouseMotionListener
Check Box: Checkbox ItemEvent ItemListener
Choice: Choice ItemEvent ItemListener
List: List ActionEvent
ItemEvent
ActionListener
ItemListener
Text Field: TextField ActionEvent
TextEvent
ActionListener
TextListener
Text Area: TextArea TextEvent TextListener
Barres de scroll: Scrollbar
AdjustmentEvent AdjustmentListener
   
 

El mecanisme és el següent: quan definim la classe els fills de la qual han de ser listeners d'una determinada mena d'esdeveniment cal, a més de la clàusula extends (si és que aquesta classe la derivem d'alguna altra, cosa que és el cas més usual), fer servir la clàusula implements per a la interface. Així:

class ElMeuPanel extends Panel implements ActionListener {
    ...
    ...
    ...
    ...
}

El resultat d'això és que,

a) ara la classe definida així no només hereta els mètodes de la classe mare, sinó que, també hereta els mètodes de la interfície corresponent, i

b) aquesta classe ja serà identificada per Java com a listener d'aquesta mena d'esdeveniments. En certa forma, els objectes derivats d'aquesta classe tenen ara una doble identitat: son, a la vegada, instàncies de la classe mare i de l'interfície. Per aquells que conegueu C++, això és el màxim que Java permet pel que fa a herència múltiple.

Ja està? No, no encara... Els mètodes de les interfícies són buits: no contenen cap instrucció i, com que el compilador ho "sap" ens n'avisa. Cal sobreescriure (override) aquests mètodes dins de la classe i posar-hi les instruccions que ens convinguin. Si preveiem que algun mètode de la interfície no serà utilitzat, deixem-lo sense cos, sense instruccions, sense línies de codi al seu interior, però el mètode ha de ser-hi!

   
  Segon pas: establir la llista de "listeners" d'un cert control
   
  El control tindrà un mètode per afegir-li listeners. Per exemple,en el cas dels botons, el mètode és public void addActionListener(ActionListener l). Només cal fer-lo servir...
   
Un primer exemple:
   
Pràctica Farem una petita finestra com aquesta:
   
 
   
  amb un botó a la part inferior i un label a la superior. Es tracta que, quan es prem el botó, el label es torni vermell:
   
 
   
  Farem, doncs un frame de 200 x 100, amb GridLayout de dues files i una columna com a layout manager.
   
 

quan el premem,el botó, en prémer-lo, emetrà una instància de java.awt.eventActionEvent (això no cal programar-ho, és una de les funcionalitats que ja porta la classe java.awt.Button). Haurem de definir algú que escolti al botó: serà el propi frame, el qual ha de ser definit ara com

class Botons01 extends Frame implements ActionListener {
    ...
    ...
    ...
    ...
}

Per tant, caldrà que, inexcusablement, la classe Botons01 implementi l'unic mètode de la interface java.awt.event.Action Listener, que és public void actionPerformed(ActionEvent e) en el qual hi posarem les instruccions necessàries per tal que el label canviï de color.

D'altra banda, tot just després de definir el botó, haurem d'incorporar el frame a la llista dels qui l'escolten. Això ho farem amb la línia

boto.addActionListener(mainFrame);
   
  així creem el projecte Botons01 com a "Basic Java Application" i altre cop després de comentar la línia package myprojects.Botons01;, modifiqueu el codi que JCreator ha posat en el fitxer Botons01.java. Les parts a modificar estan en color groc clar:
   
 
/*
* @(#)Botons01.java 1.0 02/09/17
*
* You can modify the template of this file in the
* directory ..\JCreator\Templates\Template_1\Project_Name.java
*
* You can also create your own project template by making a new
* folder in the directory ..\JCreator\Template\. Use the other
* templates as examples.
*
*/
//package myprojects.Botons01;

import java.awt.*;
import java.awt.event.*;

class Botons01 extends Frame implements ActionListener {

    Label label;
    public Botons01() {
        label=new Label("Prem el botó...",Label.CENTER);
        addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {
                dispose();
                System.exit(0);
            }
        });
    }

    public static void main(String args[]) {
        System.out.println("Starting Botons01...");
        Botons01 mainFrame = new Botons01();

        mainFrame.setSize(200, 100);
        mainFrame.setTitle("Un botó i un label actius");
        mainFrame.setLayout(new GridLayout(2,1));
        mainFrame.add(mainFrame.label);
        Button boto=new Button("Prem...");
        boto.addActionListener(mainFrame);
        mainFrame.add(boto);

        mainFrame.setVisible(true);
    }

    public void actionPerformed(ActionEvent e) {
        label.setBackground(Color.red);
    }
}
   
Observació molt important:
   
Atenció !

Com que el label es crida des de dos mètodes diferents (un quan el construim, a main, i una altra quan el canviem de color, a actionPerformed) cal que sigui una variable de classe i, per tant, accessible a tots els seus mètodes. Això ho fem mitjançant la declaració del principi de la classe:

    Label label;
   
  A Java, com a C i C++ i la majoria de llenguatges estructurats, la vida d'una variable és la de la rutina o funció (aquí mètode) en la qual ha estat declarada. En canvi, com ja haureu observat, a diferència de C, no cal que la declaració de les variables es faci al principi del mètode.
   
Fem-ho una mica més interessant;
   
Atenció ! Podem fer que el nostre label canviï de color si fem alguns canvis al mètode actionPerformed:
   
  public void actionPerformed(ActionEvent e) {
  //  label.setBackground(Color.red); // Eliminem aquesta línia
    Color color=label.getBackground(); // De quin color és el
                                          label ara mateix?

    int r=color.getRed();        // Quin n'és el valor de vermell?
    int g=color.getGreen();      // Quin n'és el valor de verd?
    int b=color.getBlue();       // Quin n'és el valor de blau?
    color=new Color((r+100)%256, // Canvis en els valors de vermell,
                    (g+200)%256, // verd i blau, mirant de no passar
                    (b+300)%256);// de 255
    label.setBackground(color);  // Nou color pel label
  }
   
  i, de passada, explorem una mica més els mètodes de la classe java.awt.Color i fem ús de l'operador %...
   
Una mica més sofisticat...
   
Atenció !

Si hi ha més d'un control que envia esdeveniments al mateix listener, és clar que cal dotar al listener de la capacitat de distingir quin dels controls és l'emissor de l'esdeveniment per tal de decidir què ha de fer.

Encara que per a determinats tipus de controls hom pot acudir a d'altres mètodes, l'esquema general de resolució de la qüestió és acudir al mètode

public Object getSource()

de la classe java.util.EventObject, que és la classe mare de totes les classes d'esdeveniments, que l'hereden. Aquest mètode ens permet conèixer quin objecte concret ha estat l'emissor del esdeveniment i, a partir d'aquesta informació, decidir què fa l'objecte listener.

  Vegem-ho en el següent exemple:
   
Un segon exemple:
   
Pràctica Construirem un frame com aquest:
   
 
   
  Crearem un panel per a contenir-ho tot en el qual el layout manager serà el java.awt.GridLayout amb dues files i una columna. A la fila (casella) de dalt hi haurà un label de color Color.LIGHT_GRAY per al fons. A la fila (casella) de baix hi haurà un altre panel amb, també, java.awt.GridLayout com a layout manager, però d'una fila i tres columnes, el qual rebrà tres botons. El panel que ho conté tot l'afegirem a la posició "center" del layout (java.awt.BorderLayout) del frame. Com que a les altres quatre posicions, "north", "south", "east" i "west" no hi haurà res, allò que posem a "center" omplirà tot l'espai disponible al frame.
   
  La funcionalitat serà aquesta: quan premem qualsevol dels tres botons, el text del label canviarà i ens informarà de quin botó és el que hem apretat.
   
  Els objectes emissors d'esdeveniments seran els botons i, com és d'esperar, emetran instàncies de java.awt.event.ActionEvent (que caldrà distingir segons el botó que els origina!) i l'objecte listener serà el panel que ho conté tot.
   
  Creem, doncs, el projecte Botons02 com a "Basic Java Application" (comenteu la línia package myprojects.Botons02;!) i modifiqueu el codi creat per JCreator. Les parts a modificar estan en color groc clar:
   
 
/*
* @(#)Botons02.java 1.0 02/09/16
*
* You can modify the template of this file in the
* directory ..\JCreator\Templates\Template_1\Project_Name.java
*
* You can also create your own project template by making a new
* folder in the directory ..\JCreator\Template\. Use the other
* templates as examples.
*
*/
//package myprojects.Botons02;
import java.awt.*;
import java.awt.event.*;

class Botons02 extends Frame {

    public Botons02() {
        addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {
                dispose();
                System.exit(0);
            }
        });
    }

    public static void main(String args[]) {
        System.out.println("Starting Botons02...");
        Botons02 mainFrame = new Botons02();

        mainFrame.setSize(400, 100);
        mainFrame.setTitle("Tres botons i un label");
        mainFrame.add(new ElMeuPanel(),BorderLayout.CENTER);
        mainFrame.setVisible(true);
    }
}
class ElMeuPanel extends Panel implements ActionListener {

    Label label=new Label("Prem qualsevol dels tres                            botons...",Label.CENTER);

    public ElMeuPanel() {
// constructor
        super();
        setBackground(Color.LIGHT_GRAY);
        setLayout(new GridLayout(2,1));
        add(label);
        Panel panel=new Panel();
        panel.setLayout(new GridLayout(1,3));
        ElMeuBoto boto_A=new ElMeuBoto("Botó 'A'",0);
        boto_A.addActionListener(this);
        panel.add(boto_A);
        ElMeuBoto boto_B=new ElMeuBoto("Botó 'B'",1);
        boto_B.addActionListener(this);
        panel.add(boto_B);
        ElMeuBoto boto_C=new ElMeuBoto("Botó 'C'",2);
        boto_C.addActionListener(this);
        panel.add(boto_C);
        add(panel);
    }

    public void actionPerformed(ActionEvent e) {
        Object botoObj=e.getSource();
            if (botoObj instanceof ElMeuBoto) {
                ElMeuBoto botoMeu=(ElMeuBoto)botoObj;
                int idBoto=botoMeu.getIdentificador();
                    switch (idBoto) {
                        case 0:
                            label.setText("Has apretat el botó "+
                                          "
'A'");
                            break;
                        case 1:
                            label.setText("Has apretat el botó "+
                                          "'B'");
                            break;
                        case 2:
                            label.setText("Has apretat el botó "+
                                          "'C'");
                            break;
                    }
            }
    }
}

class ElMeuBoto extends Button {

    int identificador;

    public ElMeuBoto (String text,int id) { // constructor
        super(text);
        identificador=id;
    }

    public int getIdentificador () {
        return identificador;
    }
}
   
 

A més, en lloc de fer servir directament les classes java.awt.Panel i java.awt.Button directament, ara necessitem derivar-ne, respectivament, les classes ElMeuPanel (codi en fons taronja) i ElMeuBoto (codi en fons rosa). Per què?

  • Com que el panel que ho conté tot ha de ser el listener dels esdeveniments que envien els botons, cal donar-li aquesta condició, cosa que implica definir-lo amb la clàusula implements i, obligatòriament, implementar-li el mètode public void actionPerformed(ActionEvent e) que la classe mare java.awt.Panel no té.

  • Cal que l'objecte listener pugui distingir el boto origen de l'esdeveniment dels altres. Això pot fer-se de moltes maneres. Una ben senzilla és proveir al botó d'un identificador, un nombre enter, per exemple, com fem aquí; afegim la variable de classe int identificador i disposem el mètode constructor de manera que, en construir-se l'objecte, aquest adquireixi un identificador únic. Cal, a més, fer possible preguntar-li al botó quin és el seu identificador i això es pot fer de dues maneres:

    • Declarar la variable identificador com a variable pública (public int identificador;) però, com que cal reduir al mínim el nombre de variables públiques, aquesta és una pràctica no recomanable i, per tant, no apareix a l'exemple.

    • Escriure un mètode public que serveixi per obtenir el valor de la variable identificador: és el que hem fet a l'exemple al mètode getter public int getIdentificador ()

    És clar que, com que aquestes funcionalitats no són pas presents a la classe java.awt.Button: cal derivar-ne la classe ElMeuBoto i implementar aquestes funcionalitats en la forma que acabem d'explicar.
Conversió (casting) ...
   
Atenció ! Bé, al mètode public void actionPerformed(ActionEvent e) de la classe listener ElMeuPanel la línia
   
  Object botoObj=e.getSource();
   
 

posa a la variable botoObj l'objecte emissor de l'esdeveniment e. Per tant, ElMeuPanel ja "sap" quin és l'objecte emissor. Què cal fer ara?

Observeu que l'objecte emissor arriba aquí com a instància de java.lang.Object (la classe mare de totes les classes) i no pas com a instància de ElMeuBoto!. Això implica que si demanem botoObj.getIdentificador() el compil·lador ens respondrà que no hi ha pas tal mètode a la classe java.lang.Object.

  • Per seguretat,primer cal demanar si aquest objecte botoObj és una instància de ElMeuBoto:

    if (botoObj instanceof ElMeuBoto) { ... }

    (Observeu l'ús de l'operador relacional binari "instanceof")

  • Després, cal "extreure" de botoObj la qualitat de ser una instància de la classe ElMeuBoto (ara ja sabem que ho és). Això s'aconsegueix amb la conversió (casting, vegeu la pràctica 5 del mòdul 2 i la pràctica 2 del mòdul 3):

    ElMeuBoto botoMeu=(ElMeuBoto)botoObj;

  • Ara, l'objecte botoMeu ja és una instància de ElMeuBoto, precisament l'objecte emissor de l'esdeveniment, i ja li podem preguntar quin identificador té:

    int idBoto=botoMeu.getIdentificador();

  • I a partir del valor de idBoto, una estructura de control switch-case ens permet fer les accions adequades per canviar el text del label.
   
Ara una mica més senzill:
   
Atenció !

La classe java.awt.Button porta, però, una variable no pública, String actionCommand, que permet emmagatzemar una cadena (string) per a identificació. Per defecte, el valor n'és null.

Si volem fixar el valor d'aquesta variable hi ha el mètode setter public void setActionCommand(String command) i per recuperar-lo hi ha el mètode getter public String getActionCommand() el qual, si ActionCommand és null, retorna l'etiqueta (label) del botó.

Això ens pot estalviar de definir una classe derivada de java.awt.Button, tal com fèiem abans:

   
Tot identificant botons:
   
Pràctica Creem el projecte Botons03 com abans. El codi a modificar, però que coincideix amb el del exemple anterior, és en groc, mentre que el codi en fons taronja correspon a les noves idees introduïdes aquí.
   
  /*
* @(#)Botons03.java 1.0 02/09/21
*
* You can modify the template of this file in the
* directory ..\JCreator\Templates\Template_1\Project_Name.java
*
* You can also create your own project template by making a new
* folder in the directory ..\JCreator\Template\. Use the other
* templates as examples.
*
*/
  //package myprojects.Botons03;
  import java.awt.*;
import java.awt.event.*;

class Botons03 extends Frame {

    public Botons03() {
        addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {
                dispose();
                System.exit(0);
            }
        });
    }

    public static void main(String args[]) {
        System.out.println("Starting Botons03...");
        Botons03 mainFrame = new Botons03();

          mainFrame.setSize(400, 100);
        mainFrame.setTitle("Tres botons i un label");
        mainFrame.add(new ElMeuPanel(),BorderLayout.CENTER);
 

        mainFrame.setVisible(true);
    }
}

 

class ElMeuPanel extends Panel implements ActionListener {

    Label label=new Label("Prem qualsevol dels tres "+
                          "botons...",Label.CENTER);

    public ElMeuPanel() { // constructor
        super();
        setBackground(Color.LIGHT_GRAY);
        setLayout(new GridLayout(2,1));
        add(label);
        Panel panel=new Panel();
        panel.setLayout(new GridLayout(1,3));

          Button boto_A=new Button("Botó 'A'");
        boto_A.setActionCommand("A");
          boto_A.addActionListener(this);
        panel.add(boto_A);
          Button boto_B=new Button("Botó 'B'");
        boto_B.setActionCommand("B");
          boto_B.addActionListener(this);
        panel.add(boto_B);
          Button boto_C=new Button("Botó 'C'");
        boto_C.setActionCommand("C");
          boto_C.addActionListener(this);
        panel.add(boto_C);
        add(panel);
    }

    public void actionPerformed(ActionEvent e) {
        Object botoObj=e.getSource();

              if (botoObj instanceof Button) {
                Button botoMeu=(Button)botoObj;
                String idBoto=botoMeu.getActionCommand();
                label.setText("Has apretat el botó '"+idBoto+"'");
              }
    }
}
   
Atenció !
Després d'executar boto_X.setActionCommand("X");, la variable ActionCommand de cadascun dels botons conté, respectivament, les cadenes "A", "B" o "C" (de la mateixa manera que, en l'exemple anterior la variable identificador contenia els nombres enters 0, 1 o 2). La cadena que ha d'aparèixer al label s'obté per concatenació de les cadenes "Has apretat el botó '", idBoto i "'", mitjançant l'ús de l'operador binari "+".
   
   
 
Amunt