Un botón rudimentario como bean

prev.gif (997 bytes)chapter.gif (1105 bytes)home.gif (1232 bytes)next.gif (1211 bytes)

JavaBeans

Crear un proyecto

Añadir un bean al proyecto

Propiedades simples

Persistencia

Aspecto del botón

El tamaño del canvas

Relación entre el aspecto y las propiedades del bean

Respondiendo a las acciones del usuario

El botón emite un suceso del tipo ActionEvent.

El bean terminado

La clase BeanInfo

Deployment

El código fuente


En esta página vamos a estudiar como se crea un JavaBean y cómo se pone en la barra de herramientas. El bean va a consistir en un control muy simple que simula un botón. Comprobaremos cómo funciona el bean arrastrándolo con el ratón desde la barra de herramientas y depositándolo en  el applet. Veremos que al pulsar con el ratón sobre el botón se realiza cierta acción consistente, en el cambio del texto de un control etiqueta (label).

 

Crear un proyecto

Ya hemos estudiado en parte los pasos para crear un bean con el asistente JavaBean Wizard.

Creamos un nuevo proyecto seleccionando File/New Project

 

Añadir un bean al proyecto

Añadimos al proyecto un JavaBean seleccionado File/New y a continuación en el diálogo New seleccionamos el icono JavaBean

En el diálogo que aparece titulado JavaBean Wizard ponemos Boton en el campo Name of new JavaBean, el nombre del bean, y en el campo Base class to inherit from seleccionamos java.awt.Panel, aunque nuestra intención es que derive de java.awt.Canvas, pero esta opción no está habilitada.

El IDE genera el siguiente código

package bean1;

import java.awt.*;

public class Boton extends Panel {
  BorderLayout borderLayout1 = new BorderLayout();


  public Boton() {
    try  {
      jbInit();
    }
    catch (Exception ex) {
      ex.printStackTrace();
    }
  }

  private void jbInit() throws Exception {
    this.setLayout(borderLayout1);
  }
}

Modificamos el código para que la clase Boton derive de Canvas, quedando del siguiente modo.

import java.awt.*;

public class Boton extends Canvas {

  public Boton() {
    try  {
      jbInit();
    }
    catch (Exception ex) {
      ex.printStackTrace();
    }
  }

  private void jbInit() throws Exception {
  }
}

 

Propiedades simples

El botón va a tener dos propiedades simples el color y el texto o título del botón.

Para definir dichas propiedades seleccionamos la pestaña Bean y a continuación la pestaña Properties, y pulsamos en el botón addProperty, apareciendo el diálogo titulado New Property

Para definir la propiedad titulo ponemos

Property name titulo
Type String
Binding none

En la tercera fila, none indica una propiedad simple

A continuación, pulsamos el botón Apply.

Para definir la propiedad color ponemos

Property name color
Type Color
Binding none

A continuación, pulsamos el botón OK para cerrar el diálogo

En el panel Bean/Properties queda reflejado las dos nuevas propiedades que ocupan las dos primeras filas.

En el código fuente, se definen dos nuevos miembros datos, y dos funciones miembro que empiezan por set y get para cada uno de dichos miembros. (Se ha retocado ligeramente el código para simplificarlo, evitando redundancias)

import java.awt.*;

public class Boton extends Canvas{
  private String titulo;
  private Color color;

//...
  public void setTitulo(String newTitulo) {
    titulo = newTitulo;
  }
  public String getTitulo() {
    return titulo;
  }

  public void setColor(Color newColor) {
    color = newColor;
  }
  public Color getColor() {
    return color;
  }
}

En init o jbInit establecemos el tamaño inicial del canvas, los valores iniciales de las propiedades color y titulo, así como de la fuente de texto empleada para mostrar el título centrado en el canvas.

  private void jbInit() throws Exception {
    setSize(60,40);
    this.titulo="Bean";
    color=Color.yellow;
    setFont(new Font("Dialog", Font.BOLD, 12));
  }

Persistencia

Vamos al panel Bean/General, y activamos la casilla, Support serialization. En la introdución a los JavaBeans hemos visto que esta es una de sus características.

 serializacion.gif (16009 bytes)

La persistencia significa que podemos guardar el estado de un bean una vez que ha sido personalizado por el programador. Para que el bean tenga esta característica ha de implementar el interface Serializable. Dicho estado se recupera cuando volvemos a correr el IDE con el programa que usa el bean.

import java.awt.*;

public class Boton extends Canvas implements java.io.Serializable {
//...
}

El IDE añade dos funciones miembro readObject y writeObject que en principio, no son necesarias por lo que las eliminamos del código.

 

Aspecto del botón

Para dibujar el botón redefinimos la función paint de la clase base Canvas. El botón va a consistir en una región rectangular (el canvas) pintado del color que guarda la propiedad color, con un borde de forma rectangular y con el título centrado.

 public synchronized void paint(Graphics g) {
    int ancho=getSize().width;
    int alto=getSize().height;

    g.setColor(color);
    g.fillRect(1, 1, ancho-2, alto-2);
    g.draw3DRect(0, 0, ancho-1, alto-1, false);

    g.setColor(getForeground());
    g.setFont(getFont());

    g.drawRect(2, 2, ancho-4, alto-4);
    FontMetrics fm = g.getFontMetrics();
    g.drawString(titulo, (ancho-fm.stringWidth(titulo))/2, 
	(alto+fm.getMaxAscent()-fm.getMaxDescent())/2);
 }

El tamaño del canvas

Por defecto, el control Canvas no tiene tamaño, lo puede representar un problema cuando disponemos varios componentes en el applet. Por ejemplo, pueden ocurrir situaciones en las que el canvas no se vea. En este caso, es necesario redefinir las funciones miembro getPreferredSize y getMinumunSize, ambas funciones devuelven un objeto de la clase Dimension.

Determinamos el tamaño del canvas como la suma del tamaño del texto más un cierto margen, definido por las constantes MARGEN_X y MARGEN_Y.

public class Boton extends Canvas implements java.io.Serializable {
  private String titulo;
  private static final int MARGEN_X=12;
  private static final int MARGEN_Y=8;
//...
   public Dimension getPreferredSize() {
    FontMetrics fm=getFontMetrics(getFont());
    return new Dimension(fm.stringWidth(titulo)+MARGEN_X,
	fm.getMaxAscent()+fm.getMaxDescent()+MARGEN_Y);
  }

  public Dimension getMinimumSize() {
    return getPreferredSize();
  }
}

Relación entre el aspecto y las propiedades del bean

Cuando cambiamos la propiedad color, mediante la función  miembro setColor, se ha de reflejar en el aspecto del botón. La forma de hacerlo es volver a dibujar el botón, llamando a la función paint.

  public void setColor(Color newColor) {
    color = newColor;
    repaint();
  }

Cuando se cambia la propiedad titulo mediante setTitulo, el tamaño del botón ha de cambiar para acomodar al nuevo texto. El código es algo más complicado. Cuando se cambia el titulo se calcula la nueva dimensión del canvas mediante getPrefferedSize y se cambia la dimensión del canvas mediante setSize. .

  public void setTitulo(String newTitulo) {
    titulo = newTitulo;
    Dimension d = getPreferredSize();
    setSize(d.width, d.height);
    invalidate();
  }

Respondiendo a las acciones del usuario

Un control botón se activa pulsando el botón izquierdo del ratón (mousePressed), el control parece hundirse y a la vez aparece un rectángulo dibujado a puntos alrededor del título. Cuando se deja de pulsar el botón izquierdo (mouseReleased) se realiza la acción: cerrar un diálogo, compilar un programa, guardar un archivo, etc.. Por tanto, hemos de definir las funciones respuesta a estas dos acciones del usuario sobre el botón simulado.

Pulsamos con el ratón sobre la pestaña Design,  y vamos a la hoja de propiedades y sucesos del canvas, pulsamos sobre la pestaña Events y hacemos doble-clic en el editor situado a la derecha de mousePressed, y luego hacemos lo mismo con mouseReleased..

Se genera el código correspondiente a la respuesta a las acciones del usuario sobre dicho componente Se relaciona mediante addMouseListener la fuente de los sucesos, el canvas, this, con un objeto de una clase anónima que implementa el interface MouseListener y define las funciones mousePressed y mouseReleased.

  private void jbInit() throws Exception {
//...
   this.addMouseListener(new java.awt.event.MouseAdapter() {
      public void mousePressed(MouseEvent e) {
        this_mousePressed(e);
      }
      public void mouseReleased(MouseEvent e) {
        this_mouseReleased(e);
      }
    });

  void this_mousePressed(MouseEvent e) {
  //poner aquí el código de la función respuesta
  }
  void this_mouseReleased(MouseEvent e) {
  //poner aquí el código de la función respuesta
  }

El botón emite un suceso del tipo ActionEvent.

Cada vez que se pulsa sobre un botón se realiza una acción. El botón emite un suceso del tipo ActionEvent. Por tanto, hemos de programar nuestro bean para que emita sucesos de este tipo.

Seleccionamos la pestaña Bean y a continuación Events, en el panel aparecen dos listas. Nos detenemos en la de la izquierda Fire these types of events, y activamos la casilla titulada Action debajo de AWT events. Al pulsar sobre la pestaña Source veremos que se ha generado nuevo código.

public class Boton extends Canvas implements java.io.Serializable{
  private transient Vector actionListeners;
//...
  public synchronized void removeActionListener(ActionListener l) {
    if (actionListeners != null && actionListeners.contains(l)) {
      Vector v = (Vector) actionListeners.clone();
      v.removeElement(l);
      actionListeners = v;
    }
  }

  public synchronized void addActionListener(ActionListener l) {
    Vector v = actionListeners == null ? new Vector(2) : (Vector) actionListeners.clone();
    if (!v.contains(l)) {
      v.addElement(l);
      actionListeners = v;
    }
  }

  protected void fireActionPerformed(ActionEvent e) {
    if (actionListeners != null) {
      Vector listeners = actionListeners;
      int count = listeners.size();
      for (int i = 0; i < count; i++)
        ((ActionListener) listeners.elementAt(i)).actionPerformed(e);
    }
  }
}

Se añade a la clase que describe el bean un miembro dato actionListeners de la clase Vector que tiene delante el modificador transient. Una clase que implementa el interface Serializable guardará en disco los valores de sus miembros dato, excepto aquellos que están marcados por la palabra clave transient.

Al explicar el significado de una propiedad ligada (bound), vimos cómo se notifica a los objetos (listeners) interesados el cambio en dicha propiedad. El código como podemos observar es similar.

Cada vez que se pulsa el botón izquierdo del ratón sobre el bean y a continuación se libera, se ha de notificar dicha acción a los objetos interesados que se guardan en el vector actionListeners y también, se ha de reflejar esta circustancia en el aspecto del botón. En este ejemplo, estudiaremos solamente cómo se notifica dicha acción.

En la definición de la función respuesta mousePressed o bien this_mousePressed, que se llama cuando se pulsa sobre el botón izquierdo del ratón, la variable bPulsado del tipo boolean toma el valor true.

En la definición de la función respuesta mouseReleased o bien this_mouseReleased, que se llama cuando se libera el botón izquierdo del ratón, la variable bPulsado del tipo boolean toma el valor false. Según sea el valor de esta variable el aspecto del botón cambia de normal a pulsado (aparece como hundido y con un rectángulo punteado alrededor del título).

Cuando se deja de pulsar el botón izquierdo del ratón se emite un suceso del tipo ActionEvent. En el cuerpo de la función respuesta  this_mouseReleased se llama a la función fireActionPerformed. En dicha función, todos los objetos (listeners) interesados y que se guardan en el vector listeners, llaman a la función miembro actionPerformed, siempre que la clase que describe a dichos objetos implemente el interface ActionListener.

  void this_mousePressed(MouseEvent e) {
    bPulsado=true;
  }

  void this_mouseReleased(MouseEvent e) {
    if(bPulsado){
        bPulsado=false;
        ActionEvent ev=new ActionEvent(e.getSource(), e.getID(), titulo);
        fireActionPerformed(ev);
    }
  }

El único problema que se presenta en la definición de la función respuesta this_mouseReleased es la conversión de un suceso (event) de la clase MouseEvent que proporciona la función respuesta a un suceso de la clase ActionEvent, que requiere la función fireActionPerformed.

Esta transformación se lleva a cabo en el cuerpo de la función respuesta this_mouseReleased. Extraemos del objeto e de la clase MouseEvent, la fuente de los succesos mediante getSource, el identificador mediante getID, y se los pasamos en los dos primeros parámetros del constructor de ActionEvent. El tercer parámetro se denomina command y es el título del botón.

 

El bean terminado

El código completo del bean denominado Boton es el siguiente

package bean1;

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

public class Boton extends Canvas implements java.io.Serializable{
  private String titulo;
  private Color color;
  private static final int MARGEN_X=12;
  private static final int MARGEN_Y=8;
  private transient Vector actionListeners;
  boolean bPulsado=false;
  public Boton() {
    try  {
      jbInit();
    }
    catch (Exception ex) {
      ex.printStackTrace();
    }
  }

  private void jbInit() throws Exception {
    setSize(60,40);
    this.titulo="Bean";
    color=Color.yellow;
    setFont(new Font("Dialog", Font.BOLD, 12));
    this.addMouseListener(new java.awt.event.MouseAdapter() {
      public void mousePressed(MouseEvent e) {
        this_mousePressed(e);
      }
      public void mouseReleased(MouseEvent e) {
        this_mouseReleased(e);
      }
    });
  }

  public void setTitulo(String newTitulo) {
    titulo = newTitulo;
    Dimension d = getPreferredSize();
    setSize(d.width, d.height);
    invalidate();
  }
  public String getTitulo() {
    return titulo;
  }

  public void setColor(Color newColor) {
    color = newColor;
    repaint();
  }
  public Color getColor() {
    return color;
  }
  public synchronized void paint(Graphics g) {
    int ancho=getSize().width;
    int alto=getSize().height;

    g.setColor(color);
    g.fillRect(1, 1, ancho-2, alto-2);
    g.draw3DRect(0, 0, ancho-1, alto-1, false);

    g.setColor(getForeground());
    g.setFont(getFont());

    g.drawRect(2, 2, ancho-4, alto-4);
    FontMetrics fm = g.getFontMetrics();
    g.drawString(titulo, (ancho-fm.stringWidth(titulo))/2, (alto+fm.getMaxAscent()-fm.getMaxDescent())/2);
 }

   public Dimension getPreferredSize() {
    FontMetrics fm=getFontMetrics(getFont());
    return new Dimension(fm.stringWidth(titulo)+MARGEN_X, fm.getMaxAscent()+fm.getMaxDescent()+MARGEN_Y);
  }
  public Dimension getMinimumSize() {
    return getPreferredSize();
  }

  void this_mousePressed(MouseEvent e) {
    bPulsado=true;
  }
  void this_mouseReleased(MouseEvent e) {
    if(bPulsado){
        bPulsado=false;
        ActionEvent ev=new ActionEvent(e.getSource(), e.getID(), titulo);
        fireActionPerformed(ev);
    }
  }

  public synchronized void removeActionListener(ActionListener l) {
    if (actionListeners != null && actionListeners.contains(l)) {
      Vector v = (Vector) actionListeners.clone();
      v.removeElement(l);
      actionListeners = v;
    }
  }
  public synchronized void addActionListener(ActionListener l) {
    Vector v = actionListeners == null ? new Vector(2) : (Vector) actionListeners.clone();
    if (!v.contains(l)) {
      v.addElement(l);
      actionListeners = v;
    }
  }

  protected void fireActionPerformed(ActionEvent e) {
    if (actionListeners != null) {
      Vector listeners = actionListeners;
      int count = listeners.size();
      for (int i = 0; i < count; i++)
        ((ActionListener) listeners.elementAt(i)).actionPerformed(e);
    }
  }
}

 

La clase BeanInfo

Como se ha explicado en la introducción a los JavaBeans, el IDE descubre las propiedades y los sucesos (events) de un bean a través del mecanismo denominado introspection. Otra forma de hacerlo, es a través de una clase que implemente el interface BeanInfo. Habitualmente las clases derivan de SimpleBeanInfo en vez de implementar todos los métodos del interface BeanInfo. Recuérdese la diferencia entre Listeners y Adapters. Para que el IDE encuentre la correspondiente clase BeanInfo el nombre de la clase debe ser el mismo que el nombre del bean seguido por el string BeanInfo, por ejemplo BotonBeanInfo.

Una clase BeanInfo especifica la siguiente información:

La clase que implementa el interface BeanInfo se usa antes que el mecanismo de la introspección. Añadiendo una clase BeanInfo a nuestro bean podemos incluso ocultar propiedades que no queremos que se muestren en la hoja de propiedades del componente en el IDE, que de otro modo si serían mostradas.

Una vez que el bean se ha compilado con éxito, creamos un icono representativo del mismo, para que se pueda identificar en la barra de herramientas. Dicho icono, lo ponemos en el subdirectorio del proyecto y lo añadimos al mismo pulsando en el botón + del panel de navegación, véase la figura inferior.

Seleccionado la pestaña Bean y a continuación BeanInfo aparece un panel, con información relativa al bean, y cuatro campos en la parte inferior en el que podemos poner el nombre de los archivos que guardan los iconos representativos del bean. Se pueden poner cuatro iconos en blanco y negro, y en color, en una tamaño de 16x16 o de 32x32. En nuestro caso hemos creado un único icono en color y con un tamaño de 32x32 pixels.

beanInfo.gif (15901 bytes)

A continuación, pulsamos en el botón titulado Generate Bean Info, situado en la parte superior izquierda. Se crea una clase que se guarda en el archivo BotonBeanInfo.java que se añade automáticamente al proyecto.

De este modo, se concluye el proyecto que está formado, por el código fuente del bean, un archivo que contiene una clase con información relativa al bean, y el icono o los iconos representativos del bean.

 

Deployment

Si queremos distribuir el bean a otras personas, para que puedan usarlo, la mejor manera es empaquetarlo en un archivo JAR, similar a los archivos ZIP. De hecho se descomprimen con la misma herramienta, el programa WinZip. Un archivo JAR contiene habitualmente varias clases y archivos auxiliares.

Para crear un archivo JAR, JBuilder dispone de un asistente denominado Deployment Wizard al que se accede seleccionando en el menú Wizard/Deployment Wizard.

Seleccionamos los archivos que nos interesa comprimir, en este caso, excluímos el archivo .HTML En el campo Archive Output Path ponemos un nombre significativo al archivo JAR, y también podemos indicar el subdirectorio en el cual guardarlo. Finalmente, pulsamos en el botón Finish. El resto de las opciones las dejamos sin modificar.

deployment.gif (7823 bytes)

 

El código fuente

disco.gif (1035 bytes)boton.gif, Boton.java, BotonBeanInfo.java, boton.jar