Java para programadores (6.3):Eventos de Component

CUANDO EL USUARIO interactua con los componentes de la interface gráfica, el programa debe responder de forma apropiada. El programa recibe una notificación de que se ha producido una acción por parte del usuario por medio de un evento. Los eventos se envían al programa por medio de llamadas a determinados métodos. Como programador, deberá indicar como responderá su programa a estos eventos definiendo los métodos apropiados.

Una interface GUI utiliza muchos componentes gráficos como botones, checkBoxes, cajas de entrada de texto, …. Las clases Java que implementan estos componentes se han explicado en el articulo anterior. Como se comentó otro articulo, los componentes pueden estar dentro de contenedores, que a su vez son componentes y como tales, pueden estar dentro de otros contenedores. Con una estructura tan compleja como puede llegar a ser una interface gráfica, es necesario comprender perfectamente como funcionan los eventos, y como se pueden manejar.

Cada componente–esto es, cada objeto perteneciente a alguna subclase de la clase Component— tiene un método llamado handleEvent. Los eventos se envían a los componentes llamando al método handleEvent(). Este método puede reenviar algunos eventos para que sean tratados por métodos adecuados como puedan ser mouseMove() y action(). Estos métodos se comentaron cuando hablabamos de graphic y paint. Para un programa que utilice múltiples niveles de anidamiento de componentes, aparecen dos preguntas: Cuando ocurre un evento ¿Que componente lo enviará? y si el evento lo envía un componente para el que no se ha programado manejador, ¿Qué pasa con ese evento?.


La segunda pregunta, es fácil de contestar: Si un componente genera un evento, y dicho componente no tiene manejador para ese evento, el evento se envía al contenedor en el que se encuentra el componente o, si el componente no se encuentra en ningún contenedor, entonces, el evento es ignorado. Entonces, si quiere definir una respuesta para un evento, puede programar la respuesta  en el componente que recibe el evento, o en cualquiera de los componentes que lo contengan en los sucesivos niveles de anidamiento.

Vamos a ver esto con mas detalle en un ejemplo. Considere un Button,bttn, que esta contenido en un Panel,pnl él que a su vez esta contenido en un Applet,aplt. Cuando se pulsa el botón bttn se genera un evento de acción que normalmente será manejado por el método action() del propio componente. El evento es enviado inicialmente al mismo bttn. Si bttn no tiene un manejador para el evento, caso normal, será enviado al panel pnl. Y si este tampoco tiene un manejador de evento, será enviado al applet aplt. Finalmente, si aplt no tiene tampoco un manejador para el evento, el sistema abandona y el evento es, simplemente, ignorado.

Ahora, por defecto, Buttons, Panels y Applets ignoran todos los eventos de acción. Si quiere que el botón tenga algún servicio, deberá programar en algún sitio, bttn, pnl, o aplt, la acción que desee realizar ante el evento generado por el botón. Para ello, deberá definir en la subclase Button, Panel o Applet un método action() para sobregrabar el existente en la subclase que deba manejar el evento generado por el botón. Si quiere que el botón maneje el evento , deberá sobregrabar la clase Button y definir bttn como un objeto de esa subclase. De forma similar,  puede definir pnl como miembro de una subclase de Panel, y puede programar que esa subclase maneje los eventos de acción. Finalmente, puede hacer que sea el propio applet el que maneje los eventos de acción generados por el botón.

Naturalmente, para aplt todo es mas fácil ya que él ya es miembro de una subclase de Applet mas que un Applet. Por eso es muy tentador el dejar que sea el applet el que maneje los eventos de acción generados por sus componentes. Para applets sencillos, la solución no es mala, pero cuando se empiezan a complicar, se convierten en poco manejables porque hay demasiados componentes y demasiados posibles eventos. Deberá pensar en termino de cajas negras: Si un panel tiene ciertas funciones, y si el botón es una parte interna de la implementacion de estas funciones, entonces los eventos de acción del botón deberán ser resueltos dentro del panel. El applet no debería saber nada de todo ello. Esta es la situación mas normal. (Por otra parte, es extraño el querer un botón que tenga un comportamiento independiente del componente que contiene el botón. Por eso es raro el sobregrabar la clase Button).


La pregunta de a donde se envían pos eventos en primer lugar es un poco mas difícil de contestar de lo que debería ser. (Esto es parte del problema por el que la documentación de Java 1.0 no es tan clara como hubiera debido ser). El problema en Java 1.0 es que ciertos componentes no reciben ciertos eventos.Por ejemplo, Labels no recibe ningún evento. No es un problema demasiado grave, pero hace imposible , por ejemplo, escribir una subclase de Label que responda al clic del ratón. Aun peor es que TextComponents en Java 1.0  no recibe ningún evento de teclado. Esto hace que sea imposible programar componentes de texto que reaccionen de forma especial ante determinados caracteres.

Aquí tiene como funcionan las cosas:

  • Eventos de acción, que se generan por componentes como botones y checkboxes, se envían en primer lugar al componente que genera el evento. (Normalmente, sin embargo, el componente lo ignora y es manejado por el contenedor como un panel, una ventana o un applet tal y como hemos comentado anteriormente.)
  • MouseEnter, mouseMove, y mouseExit, estos eventos se envían a cualquiera de los componentes sobre los que el ratón se esta moviendo, siempre que no se pulse ningún botón del ratón. ( En Java 1.0 esto es cierto para la mayoría de los componentes pero no para todos.) Un evento de ratón normalmente deberá ser manejado por el componente en el que se generó.
  • En evento mouseDown se envía al componente sobre el que el usuario hace el clic. Si el usuario mueve el ratón con el botón pulsado, una serie de eventos mouseDrag se envían al mismo componente, incluso si el ratón se mueve fuera del componente. Cuando el usuario libera el botón,  se envía al mismo componente un evento mouseUp aunque el ratón haya abandonado el área del componente. (Existen problemas con algunas implementaciones de de Java para seguir estas normas. Por ejemplo, en la versión de Java facilitada con Netscape 3.0, el evento mouseUp no se envía necesariamente al mismo componente que recibió el evento mouseDown. Esto hace que un componente se quede esperando indefinidamente un evento que nunca le llegará.)
  • Los eventos Keyboard se envían al componente que en ese momento tenga el foco de entrada. Un componente puede recibir foco llamando al método requestFocus(). A menudo, esto se hace cuando el usuario hace clic sobre él. (En Java 1.0, no todos los componentes pueden recibir el foco, y los componentes de texto no reciben eventos de teclado incluso cuando tienen foco.)

Aunque desgraciadamente en Java 1.0 los manejadores de eventos no están bien definidos, normalmente no debería tener problemas a menos que se le ocurra crear una subclase de Label o TextField con algún manejador de eventos especial.

He notado que en la versión 1.1 de Java se ha realizado un cambio bastante importante en el modelo del manejador de eventos. Sin embargo, el modelo antiguo, que describo en estas notas, esta todavía soportado (al menos por algún tiempo). Para los programas y applets sencillos, el modelo de eventos original, probablemente  es superior. El nuevo modelo de eventos es mas útil en proyectos grandes y complejos.

He explicado los eventos de ratón y teclado en «El ciclo de vida de un applet». En el resto de esta sección, explicare los eventos generados por algunos otros componentes de la interface del usuario.


Eventos de acción

Los eventos de acción se generan por varios tipos distintos de componentes: Button, Checkbox, Choice, List, y TextField. Estos eventos son manejados por el método action() que tiene el siguiente formato:

public boolean action (Event evt, Object arg) {
    // manejo del evento
}

Este método debe retornar un valor boleano true si procesa el evento. Si no lo procesa, normalmente debe volver a super.action(evt,arg). Esto da a la superclase la posibilidad de procesar el evento.

Para manejar los eventos de acción, debe definir el método action() en la subclase Applet, Panel o Window. Cuando escribe el método action(), generalmente debe conocer exactamente los componentes que pueden generar este evento para poder escribir las instrucciones concretas para ellos.

El segundo parámetro del método action(), arg, contiene información relevante del evento. El tipo de información que contiene arg depende del tipo de objeto que haya generado el evento. El primer parámetro, evt es una instancia de la variable evt.target, que nos dice que componente ha sido el primero en recibir el evento. Para cualquier evento de acción, este será también el componente que ha generado el evento en primer lugar. Por todo esto, el método action() a menudo tienen un aspecto semejante a este:

public boolean action(Event evt, Object arg) {
    if (evt.target == button1) {
        //manejar click de button1
        return true;
    }
    else if (evt.target == button2) {
        //manejar click en button2
        return true;
    }
    else if (evt.target == OpcionColor) {
        // manejar seleccion desde Choice
        return true;
    }
    .
    . // seguir manejando otros eventos
    .
    else
        return super.action(evt,arg);
}

(En algunos casos,  puede considerar interesante el utilizar el operador instanceof de evt.target para asegurarse de que tipo de objeto ha generado el evento. En los casos sencillos, cuando hay solo unos pocos de componentes para preocuparse de ellos, puede incluso que no tenga que comprobar evt.target; el segundo parámetro, arg puede que contenga toda la información que necesita.)

Aquí tiene los detalles acerca los eventos de acción que pueden ser generados por varios tipos de componentes:

  • El Button genera un evento de acción cuando el usuario pulsa el botón. El parámetro arg es una string que es igual a la etiqueta del botón. (Si quiere utilizar este parámetro, debe cambiar el tipo a String).
  • El Choice genera un evento de acción cuando el usuario selecciona uno de los elementos en el menú pop-up. El parámetro arg es el texto del elemento que el usuario a seleccionado.
  • El List genera un evento de acción cuando el usuario hace un doble clic en un elemento de la lista. El parámetro arg es una string que contiene el texto del elemento.
  • El TextField genera un un evento de acción cuando el usuario pulsa la tecla «Return» mientras esta tecleando en la caja de entrada. El parámetro arg es una string que contiene el texto existente en la caja de entrada.
  • El Checkbox genera un evento de acción cuando el usuario hace clic sobre el. El arg es un objeto de tipo Boolean que indica cual es el nuevo estado de la caja. (Un objeto tipo Boolean contiene un valor perteneciente al tipo primitivo boolean . Un valor booleano no es un objeto. La envoltura que define el objeto Boolean hace posible en tratarlo como un objeto. Para tomar el valor booleano contenido en arg debe decir "boolean newState = ((Boolean)arg).booleanValue();". Admito que es bastante complicado. Es mucho mas fácil el usar el método de Checkbox getState() para averiguar cual es el estado.)

Eventos de Scrollbar

Una barra de scroll genera un evento siempre que el valor de la barra cambia. Sin embargo, estos eventos no son de acción. De hecho para manejar los eventos de una barra de scroll, debe sobregrabar el método handleEvent(). El método tiene la siguiente forma:

 

public boolean handleEvent(Event evt) {....}

 

La instancia de la variable evt.target nos dice que componente ha sido el primero en recibir el evento. Para los eventos de barra de scroll, es la misma barra. Otra variable instanciada, event.id indica que tipo de evento es. La barra de scroll produce cinco tipos diferentes de eventos, dependiendo de en que parte de la barra haya pulsado el usuario:

  • SCROLL_LINE_UP,
  • SCROLL_LINE_DOWN,
  • SCROLL_PAGE_UP,
  • SCROLL_PAGE_DOWN, y
  • SCROLL_ABSOLUTE.

El ultimo de estos, se genera cuando el usuario arrastra el tab de la barra) Y evt.arg contiene otras informaciones relativas al evento. Para los eventos de barra de scroll, event.arg es un objeto del tipo integer que contiene el nuevo valor de la barra. (Un objeto Integer es un envoltorio para el valor del tipo primitivo int. Para tomar el valor actual int deberá usar ((Integer)(evt.arg)).intValue() Pero también puede tomar el valor de una forma mas sencilla, llamando al método getValue() de la barra.)

Si quiere sobregrabar handleEvent() para manejar los eventos de una barra de scroll, debería hacer algo semejante a esto (Asumimos que ha escrito el método doScroll(), para manejar los eventos de la barra)

public boolean handleEvent(Event evt) {
    if (evt.id == Event.SCROLL_LINE_UP ||
        evt.id == Event.SCROLL_LINE_DOWN ||
        evt.id == Event.SCROLL_PAGE_UP ||
        evt.id == Event.SCROLL_PAGE_DOWN ||
        evt.id == Event.SCROLL_ABSOLUTE ) {
        doScroll(evt.target);
        return true;  // el evento ha sido procesado
    }
    else if .... //puede tratar otros eventos
    else
         return super.handleEvent(evt);
}

En el caso en que handleEvent() no procesa el evento, es esencial el pasar el control a super.handleEvent(evt). que es una llamada a esta rutina en la superclase de donde proviene esta subclase que ha creado. El método de la superclase debe ser llamado para procesar cualquier evento que no se procese en la subclase.


Otros eventos

Hay algunos otros tipos de eventos que pueden aparecer en el método handleEvent(). No los voy a explicar aquí en detalle, pero los dejare enunciados:

  • El objeto List genera un evento con evt.id = LIST_SELECT cuando el usuario marca un elemento haciendo un clic sobre el. Cuando el elemento se desmarca, se genera otro evento LIST_DESELECT
  • El objeto tipo Frame puede tener barra de menú. Los elementos de estos menús pueden generar eventos de acción (aunque los elementos de menú no sean componentes).
  • Los objetos de tipo Window, incluyendo Trames y Dialog pueden recibir eventos con id igual a
    • WINDOW_DESTROY,
    • WINDOW_ICONIFY,
    • WINDOW_DEICONIFY y
    • WINDOW_MOVED


Un ejemplo de manejador de eventos

Para completar esta sección, voy a escribir un applet completo que usa componentes anidados y maneja algunos eventos. Primero, aquí esta el applet:

Su navegador no soporta Java;
aqui es donde podria ver el applet:


Este applet puede mostrar como manejar los diferentes colores para el texto y el dibujo. La barra horizontal controla el color del fondo (background), mientras que la vertical controla el color de los dibujos y de los textos (foreground). Varios controles en la parte inferior determinan que dibujo y texto se muestra, así como si se utilizan colores brillantes o no. (Para cambiar el texto en la pantalla, debe teclear el nuevo texto en la caja, y pulsar «Intro».)

Primero, vamos a definir la clase que representa el área de pantalla del applet. Dado que lo usaremos para dibujar, deberá ser una subclase de canvas. Esta definición asume que las clases del paquete java.awt se han importado.

class ColorCanvas extends Canvas {

      // presenta el dibujo y el texto.
      // Los metodos setForeground() and setBackground()
      // deben llamarse para indicar los colores que
      // se usaran para dibujar

   public String text; // text a presentar
   public int shp;   // codigo del dibujo a presentar;

   public final static int RECT = 0;  // codigo para rectangulo
   public final static int OVAL = 1;  // codigo para elipse
   public final static int ROUNDED = 2; // codigo paraa esquinas redondas

   public ColorCanvas() {
       text = "Hola Mundo";  // texto por defecto
       shp = RECT;  // dibujo por defecto
   }

   public void paint(Graphics g) {
       int width = size().width;   // tomar tamaño del area
       int height = size().height;
       int shp_left = width / 9;  // calcula posicion y tamaño del dibujo
       int shp_top = height / 3;
       int shp_width = (7*width / 9);
       int shp_height = (5*height / 9);
       switch (shp) {   // realiza el dibujo
          case RECT:
             g.fillRect(shp_left,shp_top,shp_width,shp_height);
             break;
          case OVAL:
             g.fillOval(shp_left,shp_top,shp_width,shp_height);
             break;
          case ROUNDED:
             g.fillRoundRect(shp_left,shp_top,shp_width,shp_height,16,16);
             break;
       }
       g.drawString(text,width/9,2*height/9);  // dibuja el texto
   }

 }  // fin de la clase ColorCanvas

Ahora podeos definir el applet. La estructura del applet se establecce en el método INIT(). Los eventos se manejan en el método Action() y en el método handleEvent()

import java.awt.*;
import java.applet.*;

public class EventDemo extends Applet {

    /*
    El applet presenta un dibujo y algo de texto. El color 
    del texto y del dibujo, se controla con la barra vertical
    mientras que el color del fondo es controlado por la barra
    horizontal. El texto que se quiera presentar, puede entrarse
    en la caja de texto(TextField) que hay al pie. El dibujo a 
    presentar, se puede escoger del componente Choice.
    Los colores brillantes o no, se seleccionan utilizando
    el checkbox
    El area de dibujo se implementa en ColorCanvas; esta clase esta
    esta definida despues, en este mismo fichero
    */

   ColorCanvas display;  // area de display
   Choice shpChoice;   // para seleccionar el dibujo a presentar
   Checkbox brightColors;// para seleccionar colores brillantes
   TextField text;       // El texto a presentar
   Scrollbar hScroll;    // horizontal scroll bar
   Scrollbar vScroll;    // vertical scroll bar

   public void init() {  // establecer el contenido del applet

       Panel topPanel = new Panel(); // para guardar 
                         //el area de display y las scroll bars
       topPanel.setLayout(new BorderLayout());
       display = new ColorCanvas();
       topPanel.add("Center", display);
       hScroll = new Scrollbar(Scrollbar.HORIZONTAL,0,1,0,100);
       topPanel.add("South", hScroll);
       vScroll = new Scrollbar(Scrollbar.VERTICAL,50,1,0,100);
       topPanel.add("East", vScroll);

       Panel bottomPanel = new Panel();  // para los controles
       bottomPanel.setLayout(new GridLayout(1,3,5,5));
       shpChoice = new Choice();
       shpChoice.addItem("Rectangulo");
       shpChoice.addItem("Elipse");
       shpChoice.addItem("Rectangulo redondeado");
       bottomPanel.add(shpChoice);
       brightColors = new Checkbox("Color brillante");
       bottomPanel.add(brightColors);
       text = new TextField("Hola Mundo");
       bottomPanel.add(text);

       setLayout(new BorderLayout(5,5));  // lo aplica al mismo applet
       add("Center", topPanel);
       add("South", bottomPanel);

       setBackground(Color.darkGray);   // fondo para el applet
       setDisplayColors();  // definido abajo

   } // final de init()

   public boolean action(Event evt, Object arg) {
      if (evt.target == shpChoice) {
         // Si el usuario seleciona un dibujo, deja la
         //variable en pantalla, y avisa al sistema para
         // que redibuje la pantalla 
         switch (shpChoice.getSelectedIndex()) {
            case 0:
               display.shp = ColorCanvas.RECT;
               break;
            case 1:
               display.shp = ColorCanvas.OVAL;
               break;
            case 2:
               display.shp = ColorCanvas.ROUNDED;
               break;
         }
         display.repaint();
      }
      else if (evt.target == brightColors) {
         // si el usuario ha cambiado el estado del checkbox;
         // ajusta el color de la pantalla y,
         // avisa al sistema para que redibuje
         setDisplayColors();
         display.repaint();
      }
      else if (evt.target == text) {
         // el usuario ha entrado un nuevo texto en el area
         // y ha pulsado return; establece la variable correspondiente
         //en la pantalla y avisa al sistema para que redibuje.
         display.text = text.getText();
         display.repaint();
      }
      return true;
   } // final de action()

   public boolean handleEvent(Event evt) {
      if ( evt.id == Event.SCROLL_LINE_UP ||
           evt.id == Event.SCROLL_LINE_DOWN ||
           evt.id == Event.SCROLL_PAGE_UP ||
           evt.id == Event.SCROLL_PAGE_DOWN ||
           evt.id == Event.SCROLL_ABSOLUTE ) {
         // el usuario ha cambiado el valor de una de las barras
         // se debe ajustar los colores de pantalla y redibujarla
         // (No tengo que comprobar que barra es porque
         //  setDisplayColors() siempre comprueba el valor de ambas)

         setDisplayColors();
         Graphics g = display.getGraphics();
         display.update(g); // llama update() para redibujar
         g.dispose(); //inmediatamente, mientras 
              //el usuario mueve la barra
         return true;
      }               
      else
         return super.handleEvent(evt);
   } // fin de  handleEvent()

   void setDisplayColors() {
        // establece los colores de primer plano y de fondo para ,
        // el area de dibujo, dependiendo de los valores de las barras
        // y del estado del checkbox.  (Los colores se hacen
        // utilizando Color.getHSBColor(float,float,float),
        // que crea el color definiendo el matiz, la saturacion
        // y el brillo  Los parametros deben estar entre 
        // 0.0 and 1.0.)
      float backgroundHue = hScroll.getValue() / 100.0F;
      float foregroundHue = vScroll.getValue() / 100.0F;
      float saturation = 1.0F;
      float brightness;
      if (brightColors.getState())
         brightness = 1.0F;
      else 
         brightness = 0.6F;
      Color backgroundColor = 
             Color.getHSBColor(backgroundHue,saturation,brightness);
      Color foregroundColor = 
             Color.getHSBColor(foregroundHue,saturation,brightness);
      display.setBackground(backgroundColor);
      display.setForeground(foregroundColor);
   } // fin de setDisplayColors()

} // fin de la clase Event Demo

Acerca de Miguel Garcia

Programador, Desarrollador web, Formador en distintas areas de informatica y director de equipos multidisciplinares.
Esta entrada fue publicada en Formacion, Java y etiquetada , , , . Guarda el enlace permanente.

Deja un comentario

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.