Archive | enero 2010

Uso avanzado de SwingWorker

Puede ver una versión activa de este post en mi nuevo blog. He decidido dejar de publicar en este blog por éstas razones. Su filosofía es la misma que éste, pero espero no tener los problemas que me he encontrado aquí.

En una entrada anterior vimos cómo hacer un uso básico de SwingWorker así como cuándo usarlo. Vimos que había dos métodos de recuperar el resultado de nuestros cálculos o tarea de larga duración.

Aquí veremos un uso avanzado de SwingWorker. Para ello realizaremos una sencilla aplicación que calcule los N primeros números de la serie de fibonacci, los vaya mostrando en un área de texto según los vaya calculando y mostraremos el progreso de la tarea un una barra de progreso.

La clase SwingWorker tiene la siguiente definición: SwingWorker<T, V>. En la entrada anterior vimos que la forma básica de recuperar el resultado de nuestros cálculos era a través del método get(), el cuál retorna un valor de tipo genérico T. El genérico V es el tipo de datos que admiten los métodos publish y process y es el tipo de datos en el que generaremos los resultados intermedios.

Para realizar nuestra tarea del cálculo de los números de fibonacci crearemos una clase FibonacciWorker que herede de SwingWorker y que haga uso de los métodos adecuados para notificar el progreso hecho en los cálculos. Esta notificación la haremos a través de eventos de cambios en una propiedad. Es posible utilizar PropertyChangelListeners con propiedades definidas por nosotros y utilizar el método firePropertyChange para lanzar estos eventos. Para este ejemplo utilizaremos la propiedad progress que es una propiedad que ya está enlazada por defecto. La propiedad state (indica el estado de la ejecución de la tarea: INCIADA, CANCELADA, COMPLETADA, …) también lo está.

class FibonacciWorker extends SwingWorker<List<Integer>, Integer> {
  PrimeNumbersTask(JTextArea textArea, int numbersToCalculate) {
    //initialize
  }

  @Override
  public List<Integer> doInBackground() {
    while (numbers.size <= numberstoCalculate && !isCancelled()) {
      number = nextFibonacciNumber();
      numbers.add(number);
      publish(number);
      setProgress(100 * numbers.size() / numbersToCalculate);
    }
    return numbers;
  }

  @Override
  protected void process(List<Integer> chunks) {
    for (Integer number : chunks) {
      textArea.append(number + "\n");
    }
  }

  private Integer nextFibonacciNumber(){
    //  calculate next fibonacci number and return it
  }
}

En el código anterior se han omitido detalles de implementación para no desviar la atención. Del código anterior cabe destacar la llamada a publish en la línea 11, la llamada a setProgress (línea 12) y la implementación del método process.

Mediante la llamada al método publish indicamos a SwingWorker que vaya almacenando resultados intermedios con los cuales llamará al método process, el cual hemos sobrescrito y lo usaremos para mostrar esos resultados intermedios. Es muy importante recalcar que mientras el método publish se ejecuta en el hilo (thread) del cálculo exhaustivo, el método process se invoca en el Event Dispatching Thread (EDT), y es por ello por lo que es seguro actualizar el interfaz de usuario.

Con la llamada al método setProgress() estamos indicando que se lance un evento de cambio sobre la propiedad progress (propiedad creada por defecto junto con state para la notificación de cambios en propiedades). El evento propagado incluirá entre sus datos el parámetro que recibe la función. Para mostrar ese progreso utilizaremos una barra de progreso, la cual la registraremos como interesada en recibir eventos de cambios en las propiedades de FibonacciWorker.

final JProgressBar progressBar = new JProgressBar(0, 100);
FibonacciWorker task = new FibonacciWorker(textArea, numbersToCalculate);
task.addPropertyChangeListener(
  new PropertyChangeListener() {
    public  void propertyChange(PropertyChangeEvent evt) {
      if ("progress".equals(evt.getPropertyName())) {
        progressBar.setValue((Integer)evt.getNewValue());
      }
    }
  });

En el siguiente enlace es posible ver una aplicación demostrando lo aprendido. Si se desea descargar el código fuente en forma de proyecto de NetBeans: FibonacciWorker NetBeans project.

Uso de GridBagLayout (y II)

Puede ver una versión activa de este post en mi nuevo blog. He decidido dejar de publicar en este blog por éstas razones. Su filosofía es la misma que éste, pero espero no tener los problemas que me he encontrado aquí.

Antes de leer esta entrada deberías leer Uso de GridBagLayout I, si no lo has hecho ya.

Veamos algunos parámetros que nos permitirán configurar cómo se verán nuestros componentes con más flexibilidad que la conseguida con los parámetros vistos en el post anterior:

fill
Define la forma en la que el componente crecerá o no cuando se cambie el tamaño del panel al que pertenece. Los valores posibles están definidas como constantes en GridBagConstraints y son:

  • NONE: valor por defecto. Indica que el componente no cambiará su tamaño.
  • HORIZONTAL: hará crecer el componente hasta completar el tamaño horizontal de la celda o celdas donde se encuentra el componente.
  • VERTICAL: igual que HORIZONTAL pero en sentido vertical.
anchor
Este parámetro se utiliza cuando el componente es más pequeño que su área de dibujo – la celda o celdas donde yace el componente – e indica el lugar al que el componente quedará fijado cuando se modifique el tamaño del panel. Los valores están definidos como constantes en GridBadConstraints. Podemos ver los valores y el lugar donde el componente quedará fijado en la siguiente tabla:
FIRST_LINE_START PAGE_START FIRST_LINE_END
LINE_START CENTER LINE_END
LAST_LINE_START PAGE_END LAST_LINE_END
weightx y weighty
Estos parámetros especifican cómo distribuir el espacio entre las columnas – weightx –  y las filas – weighty -. Los valores que pueden tomar varían entre 0.0 y 1.0, siendo 1.0 el de más peso, lo que indicaría que ese componente ocuparía el mayor espacio posible. Si no se indica lo contrario, ambos parámetros toman el valor 0.0, lo que indica a GridBagLayout que el espacio extra al cambiar el tamaño al panel se dibujará entre el borde de las filas y columnas y el borde del contenedor, dejando un espacio libre entre ellos.

Ahora veamos el código que genera el panel con el gestor GridBagLayout:

package es.rchavarria.gridbaglayouthowto;

public class GridBagLayoutDemo extends javax.swing.JPanel {
  public GridBagLayoutDemo() {
    initComponents();
  }

 private void initComponents() {
   java.awt.GridBagConstraints gridBagConstraints;

   btn5 = new javax.swing.JButton();
   btn1 = new javax.swing.JButton();
   btn2 = new javax.swing.JButton();
   btn4 = new javax.swing.JButton();
   btn6 = new javax.swing.JButton();
   btn3 = new javax.swing.JButton();

   setLayout(new java.awt.GridBagLayout());

   btn1.setText("btn 01");
   add(btn1, new java.awt.GridBagConstraints());

   btn2.setText("btn 02");
   gridBagConstraints = new java.awt.GridBagConstraints();
   gridBagConstraints.gridwidth = 2;
   add(btn2, gridBagConstraints);

   btn3.setText("btn 03");
   gridBagConstraints = new java.awt.GridBagConstraints();
   gridBagConstraints.gridx = 0;
   gridBagConstraints.gridy = 1;
   add(btn3, gridBagConstraints);

   btn4.setText("btn 04");
   gridBagConstraints = new java.awt.GridBagConstraints();
   gridBagConstraints.gridx = 0;
   gridBagConstraints.gridy = 2;
   gridBagConstraints.fill = java.awt.GridBagConstraints.VERTICAL;
   gridBagConstraints.anchor = java.awt.GridBagConstraints.SOUTH;
   add(btn4, gridBagConstraints);

   btn5.setText("btn 05");
   gridBagConstraints = new java.awt.GridBagConstraints();
   gridBagConstraints.gridx = 2;
   gridBagConstraints.gridy = 3;
   gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
   add(btn5, gridBagConstraints);

   btn6.setText("large btn");
   gridBagConstraints = new java.awt.GridBagConstraints();
   gridBagConstraints.gridx = 1;
   gridBagConstraints.gridy = 1;
   gridBagConstraints.gridwidth = 2;
   gridBagConstraints.gridheight = 2;
   gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
   gridBagConstraints.weightx = 1.0;
   gridBagConstraints.weighty = 1.0;
   add(btn6, gridBagConstraints);
 }

 private javax.swing.JButton btn1;
 private javax.swing.JButton btn2;
 private javax.swing.JButton btn3;
 private javax.swing.JButton btn4;
 private javax.swing.JButton btn5;
 private javax.swing.JButton btn6;
}

Mediante las líneas del código del tipo:

add(componente, gridBagConstraint);

se especifican las restricciones para cada componente. Es posible utilizar el mismo objeto para especificar las restricciones pero es recomendable utilizar distintas instancias.

Para ver el resultado de este código, simplemente podemos probarlo mediante la siguiente clase:


package es.rchavarria.gridbaglayouthowto;

import javax.swing.JFrame;
import javax.swing.SwingUtilities;

public class RunDemo {
public static void main(String[] args) {
  SwingUtilities.invokeLater(new Runnable() {
    public void run() {
      JFrame frm = new JFrame("GridBagLayout HowTo");
      frm.getContentPane().add(new GridBagLayoutDemo());
      frm.pack();
      frm.setVisible(true);
    }
  });
}
}

Buenos programadores merecen mejores salarios

Recientemente he leído esta entrada en el blog de Felipe Gaucho, pertenciente a un importante grupo de usuarios de Java (Brazilian JUG).

En su artículo discute sobre por qué los buenos desarrolladores de Java están desapareciendo, o al menos, las buenas ofertas para trabajar como desarrollador Java.

Ni corto ni perezoso, enumera una serie de recomendaciones para ofertar un puesto de trabajo interesante. Entre ellas podría destacar:

  1. Subir el salario (sin excusas) -Me encanta ésta-
  2. Permite a los desarrolladores participar en las decisiones del proyecto
  3. Proporciona a los desarrolladores más que agua y café, que tal fruta u otras bebidas? …. -¿En algún trabajo en España se da esto?, en los que yo conozco no-

Cito las razones que nos da Felipe:

  1. Raise the salary (no excuses).
  2. Offer learning as part of the job benefits (conferences, books, courses, etc).
  3. Allow your developers to take project decisions.
  4. Use modern Java technologies (Still using Java 1.4?)
  5. Give the developers some stability and carrier perspective, and don’t try that in a bureaucratic way.
  6. Flexible working time and remote office should be available.
  7. Give the developers more than water and coffee.. how about fruits? cokes and other beverages? How much it costs for you to buy 1 coke per developer a day? If you think it is too much, please leave the market 🙂
  8. Don’t try poor copies of Google and IBM ideas, these companies are just richer than yours. Be creative and honest with your developers.

¿Eres programador Java? ¿Crees que merecerías mejores condiciones de trabajo? ¿Cuál valorarías más?

SwingWorker en Java 5

Puede ver una versión activa de este post en mi nuevo blog. He decidido dejar de publicar en este blog por éstas razones. Su filosofía es la misma que éste, pero espero no tener los problemas que me he encontrado aquí.

SwingWorker es una clase de utilidad incluida en Java SE 6 que soluciona el problema de utilizar un hilo (thread) separado al de Swing para realizar tareas de larga duración. Anteriormente ya existían implementaciones que realizaban esta tarea, pero en la versión 6 de Java SE se ha decidido incluirla en la propia distribución de Java. Existe un backport de SwingWorker para utilizar los mismos conceptos en Java SE 5. Esta entrada es aplicable tanto para SwingWorker incluido en Java SE 6 como para el backport.

El hilo manejador de eventos (Event Dispatching Thread)

Durante el desarrollo de interfaces gráficas suelen aparecer tareas cuya realización conllevan mucho tiempo, por ejemplo, leer un archivo XML cuando el usuario pulse un botón. La forma más rápida y sencilla de realizar esta tarea podría ser los siguiente:

JButton button = new JButton("Cargar fichero XML");
button.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent e) {
        xmlDocument = loadXML();
    }
});

De esta forma, la carga del archivo se realiza en el hilo manejador de eventos (EDT – Event Dispatching Thread) de Swing, y como la tarea necesita mucho tiempo, no es posible manejar el resto de eventos que la interfaz gráfica genera y se quedara congelado, fijo, sin respuesta aparente, hasta que la tarea finalice. Esto produce un efecto indeseable de cara al usuario de la aplicación.

De hecho, a la hora de desarrollar interfaces gráficas se deberían respetar estas dos reglas básicas:

  • Las tareas de larga duración no deben ejecutarse nunca en el hilo manejador de eventos (EDT), de otra forma la aplicación no responderá al usuario.
  • Los componentes gráficos se deben acceder únicamente desde el hilo manejador de eventos.

Cualquier otra debería realizarse en un hilo separado (difícil pero es lo más recomendable).

Solución

Efectivamente, la solución pasa por utilizar un hilo distinto al EDT. SwingWorker nos proporciona la base para utilizar un hilo distinto para realizar tareas costosas en cuanto al tiempo de ejecución. SwingWorker permite ejecutar tareas de larga duración mientras la interfaz gráfica sigue respondiendo a la interacción con el usuario.

La forma más simple de utilizar SwingWorker es implementando el método doInBackground extendiendo la clase SwingWorker o creando una clase anónima:

SwingWorker worker = new SwingWorker<MyXMLFile, Void>() {
public MyXMLFile doInBackground() {
MyXMLFile myXMLDoc = loadXML();
return myXMLDoc;
}
};

Para que la tarea se ejecute debemos invocar al método execute de SwingWorker. Dicho método se encarga de llamar a doInBackground asegurándose de que se ejecuta en un hilo diferente al EDT.

Pero, ¿cómo recupero el resultado de la larga tarea?. SwingWorker proporciona el método get para recuperar el resultado, pero sólo debe ser llamado una vez la tarea ha finalizado. Existen dos métodos para asegurarnos que la tarea ha finalizado:

  1. Sobrescribiendo el método done, el cual es ejecutado en el hilo manejador de eventos después de que la tarea haya finalizado.
    private MyXMLFile doc;
    //...
    SwingWorker<Document, Void> worker = new SwingWorker<MyXMLFile, Void>() {
    public MyXMLFile doInBackground() {
    MyXMLFile myXMLDoc = loadXML();
    return myXMLDoc;
    }
    public void done() {
    doc = get();
    }
    };
    
  2. Registrar un listener mediante el método addPropertyChangeListener(PropertyChangeListener listener) el cual será notificado de cambios en el estado de SwingWorker.
    Actualización (26-01-2010): Uso avanzado de SwingWorker