Mostrando entradas con la etiqueta Swing. Mostrar todas las entradas
Mostrando entradas con la etiqueta Swing. Mostrar todas las entradas

domingo, 10 de febrero de 2019

Errores Comunes con JPA

En los últimos meses he estado depurando una aplicación Swing que hace uso de JPA para la persistencia de datos. La integración presenta múltiples violaciones a las reglas de uso impactando en el rendimiento y confiabilidad de la aplicación.
En esta entrada voy a compartirles algunos errores muy comunes en el uso de JPA, conocerlos te ayudará a ser un mejor Ingeniero de Desarrollo de Software, creando aplicaciones de calidad, que siempre estén disponibles, haciendo más felices a los usuarios finales.

El ciclo de Vida de un EntityManager

Debes tener muy claro cual será el ciclo de vida de un EntityManager, recuerda que un EntityManager tiene asociado una cache (JPA First Cache). Un EntityManager es un objeto ligero, crear instancias no consumen mucho recurso, así que puedes crear tantas instancias de EntityManager requieras.
Una vez que termines de usar tu instancia EntityManager debes de cerrarlo explicita mente llamando al método close. No hacerlo puede dejar conexiones abiertas y no liberar la memoria ocupada por el cache. Algunos servidores tienen un máximo de conexiones permitido, al alcanzarlo el servidor se puede bloquear.
En cuanto a la cache, la aplicación se puede quedar sin memoria disponible causando una excepción del tipo Java Heap Space, cerrar el EntityManager libera el espacio ocupado por el cache.
No hay una regla especifica para decir el ciclo de vida de un EntityManager, pero ten presente la cache asociada y cerrar el EntityManager una vez que decidas terminar el ciclo de vida.
En una aplicación Web es un poco distinto porque es el contenedor Web quien gestiona el ciclo de vida de forma automática, el decide cuando crearlos y cuando cerrarlos.

Continuará...

miércoles, 7 de marzo de 2018

Como leer una imagen en Java

Leer recursos es una tarea muy usada por cualquier aplicación Java, cuando usas un IDE como Netbeans, le delegas mucho de esta tarea, siempre llega el momento en el que necesitas escribir las lineas de código tu mismo.

En esta entrada me enfocaré unicamente en mostrar como leer una imagen y mostrarla en un JLabel, el cual estará en un JFrame.

Si deseas profundizar más te invito a leer la entrada Localizar y Leer recursos en Java

IDE
Netbeans 8.2

Precondiciones

La imagen a leer tiene que estar en algún lugar dentro de los fuentes de tu proyecto, por ejemplo en algún package diferente al de tu clase. En las siguientes imágenes te muestro la estructura de mi proyecto en NetBeans.




Pasos para leer y mostrar la imagen

Vamos a leer y mostrar la imagen en 5 pasos, lo único que tienes que cambiar es el path, según donde tengas la imagen.

//1. Obtener el objeto Class NombreClase.class
Class clazz = JFrameImageDemo.class;

//2. Crear el path "/com/unadm/swing/images/book-icon.png"
//   Ruta absoluta tomando como contexto el classpath de nuestro proyecto
//   Nuestra imagen se encuentra dentro del directorio build/classes el cual se
//   agrega al classpath
String path = "/com/unadm/swing/images/";
path = path + "book-icon.png";

//3. Obtener la url de nuestra imagen
URL url = clazz.getResource(path);

//4. Leer la imagen con la clase ImageIO
try {
BufferedImage image = ImageIO.read(url);

//5. Setear la imagen al JLabel
emptyLabel.setIcon(new ImageIcon(image));
} catch (IOException ioexception) {
System.err.println(ioexception);
}


Codigo Fuente de la Clase

A continuación el código fuente de la clase, antes de ejecutar la clase es recomendable que realices un "clean and build" a tu projecto, muchas veces NetBeans necesita compilar todo el proyecto para incluir tu imagen junto a las clases compiladas.

domingo, 4 de marzo de 2018

Swing: Como crear ventanas principales (JFrame) en Java

Este tutorial como otros tantos de este blog, es una traducción del tutorial oficial, enriquecido con comentarios y experiencia propia.

Una venta JFrame es una ventana de nivel superior (en la jerarquía de componentes), el cual tiene un titulo y border. Un frame (traducido como marco o ventana) es una instancia de la clase JFrame, es una ventana que posee un border, un titulo y botones en la esquina superior derecha para cerrar, minimizar y restaurar la ventana. Una aplicación que usa interfaz grafica de usuario debe tener al menos un frame.

Para crear una ventana que es dependiente de otra ventana - se oculta cuando la otra ventana es minimizada, por ejemplo - debes usar una instancia de un JDialog en lugar de un JFrame. Para crear una ventana que se muestre dentro de otra ventana, debes usar un JInternalFrame.

Creando y Mostrando Ventanas


Para crear y mostrar una ventana lo haremos en 5 pasos


//1.- Crear la Ventana con el titulo "FrameDemo"
JFrame frame = new JFrame("FrameDemo");

//2.- Opcional: Que pasará al cerrar la ventana? Salir de la aplicación.
//Cuando se cierra un frame (botón 'X') la aplicación aun sigue corriendo
//con está opción también cerramos la aplicación
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

//3.- Crear componentes y agregarlos a la ventana
//crear un JLabel vacio...
JLabel emptyLabel = new JLabel("");
frame.getContentPane().add(emptyLabel, BorderLayout.CENTER);

//4.- Ajustar el tamaño de la ventana de acuerdo al tamaño de los componentes agregados
frame.pack();

//5.- Mostrar la ventana
frame.setVisible(true);

Aquí algunos detalles sobre el código

1. La primera linea de código crea un frame usando un constructor que te permite definir el titulo de la ventana. Otro constructor de JFrame usado con mucha frecuencia es el constructor sin argumento.

2. El siguiente codigo define que pasará cuando el usuario cierra la ventana. La operación EXIT_ON_CLOSE cierra el programa cuando el usuario cierra la ventana. Este comportamiento es apropiado para este programa porque el programa tiene una sola ventana principal, y cerrando la ventana principal deja al programa inutilizable.

3. Las siguientes lineas de código agrega un label vacío al contenedor (content pane) del frame. Si no estás familiarizado con contenedores y como agregar componentes a ellos, escribiré una entrada más adelante.

Para frame que tienen menús, tu puedes agregar la barra de menús al frame usando el metodo setJMenuBar. También escribiré una entrada más adelante sobre Como usar Menús.

Es muy importante agregar los componentes al frame por medio del ContentPane, el LayoutManager del ContentPane es un BorderLayoutManager.

Como información adicional, un JFrame tiene un RootPane, ContentPane y GlassPane. En el futuro escribiré una entrada cobre estos Paneles.

4. El metodo pack establece el tamaño del frame en relación con el tamaño (preferredSize) de todos los componentes agregados al contenedor del frame. Una alternativa al metodo pack es establecer el tamaño del frame explicitamente llamando al metodo setSize. En general, es preferible llamar a pack sobre setSize, ya que el metodo pack delega la tarea al LayoutManager.

5. Ahora si, llamando al metodo setVisible(true) haremos que el frame se muestre en la pantalla. Las versiones anteriores usaban el metodo show para mostrar el frame, pero es mejor usar setVisible(true)

Codigo fuente del Ejemplo

package com.macrob.swing;

import java.awt.BorderLayout;
import java.awt.Dimension;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.WindowConstants;

/**
 *
 * @author Roberto Lopez <marcos.roberto.lopez@gmail.com>
 */
public class JFrameDemo {

public static void main(String args[]){
//1. Creamos el frame con un titulo
JFrame frame = new JFrame("FrameDemo");

//2.- Opcional: Que pasara al cerrar la ventana? Salir de la aplicacion.
//Cuando se cierra un frame (boton 'X') la aplicacion aun sigue corriendo
//con está opcion tambien cerramos la aplicacion
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);

//3.- Crear componentes y agregarlos a la ventana
//crear un JLabel vacio...
JLabel emptyLabel = new JLabel("");
emptyLabel.setPreferredSize(new Dimension(250, 100));
frame.getContentPane().add(emptyLabel, BorderLayout.CENTER);

//4.- Ajustar el tamaño de la ventana de acuerdo al tamaño de los componentes agregados
frame.pack();

//5.- Mostrar la ventana
frame.setVisible(true);
}


}


El API del JFrame

El API para uso de frames puede caer dentro de las siguientes categorías
  • Creando y Configurando un Frame
  • Estableciendo el tamaño e ubicación del Frame
  • Metodos relacionados con el RootPane

Creando y Configurando un Frame
Método o constructorPropósito
JFrame()
JFrame(String)
Crea un frame que inicialmente no se muestra. El parámetro String proporciona un titulo para el frame. Para mostrar el frame debes invocar al método setVisible(true)
void setDefaultCloseOperation(int)
int getDefaultCloseOperation()
Establecer u obtener la operación que ocurre cuando el usuario presiona el botón cerrar del frame.
Posibles opciones son:
  • DO_NOTHING_ON_CLOSE (no hacer nada)
  • HIDE_ON_CLOSE (ocultar el frame)
  • DISPOSE_ON_CLOSE (ocultar y liberar recursos graficos usados, salir de la aplicación después de liberar el frame)
  • EXIT_ON_CLOSE (Salir de la aplicación inmediatamente)
Las primeras tres constantes están definidas en la interface WindowConstants, el cual JFrame implementa. La constante EXIT_ON_CLOSE está definida en la clase JFrame
void setIconImage(Image)
Image getIconImage()
(in Frame)
Establecer u obtener el icono que representa el frame. Observar que el parámetro es un objeto java.awt.Image y no un javax.swing.ImageIcon.
void setTitle(String)
String getTitle()
(in Frame)
Establecer u obtener el titulo del frame
void setUndecorated(boolean)
boolean isUndecorated()
(in Frame)
Set or get whether this frame should be decorated. Works only if the frame is not yet displayable (has not been packed or shown). Typically used with full-screen exclusive mode or to enable custom window decorations.
static void setDefaultLookAndFeelDecorated(boolean)
static boolean isDefaultLookAndFeelDecorated()
Determine whether subsequently created JFrames should have their Window decorations (such as borders, and widgets for closing the window) provided by the current look-and-feel. Note that this is only a hint, as some look and feels may not support this feature.

Establecer el tamaño e ubicación del Frame
MétodoPropósito
void pack()
(in Window)
Calcula y establece el tamaño según el preferred size de los componentes agregados.
void setSize(int, int)
void setSize(Dimension)
Dimension getSize()
(in Component)
Establecer y obtener el tamaño del frame. Los parámetros int para setSize especifican el ancho y altura respectivamente.
void setBounds(int, int, int, int)
void setBounds(Rectangle)
Rectangle getBounds()
(in Component)
Establecer y obtener el tamaño y ubicación del frame. Para el método con parámetros int, la esquina superior izquierda se especifica por x, y, el cual son los dos primeros parametros, los otros dos corresponden al ancho y alto.
void setLocation(int, int)
Point getLocation()
(in Component)
Establecer y obtener la ubicación de la esquina superior izquierda del frame. Los parámetros son x e y respectivamente.
void setLocationRelativeTo(Component)
(in Window)
Posiciona el frame en el centro del componente especificado. Si el argumento es null, el frame es centrado en la pantalla. Este método se debe invocar después de establecer el tamaño del frame.


Metodos relacionados con el RootPane

MethodPurpose
void setContentPane(Container)
Container getContentPane()
Set or get the frame content pane. The content pane contains the visible GUI components within the frame.
JRootPane createRootPane()
void setRootPane(JRootPane)
JRootPane getRootPane()
Create, set, or get the frame root pane. The root pane manages the interior of the frame including the content pane, the glass pane, and so on.
void setJMenuBar(JMenuBar)
JMenuBar getJMenuBar()
Set or get the frame menu bar to manage a set of menus for the frame.
void setGlassPane(Component)
Component getGlassPane()
Set or get the frame glass pane. You can use the glass pane to intercept mouse events or paint on top of your program GUI.
void setLayeredPane(JLayeredPane)
JLayeredPane getLayeredPane()
Set or get the frame layered pane. You can use the frame layered pane to put components on top of or behind other components.

Referencia

https://docs.oracle.com/javase/tutorial/uiswing/components/frame.html



domingo, 17 de diciembre de 2017

Sub Procesamiento Multiple, Threads, Hilos


Probablemente ya estes familiarizado con el termino subprocesamiento multiple o multi tarea: La capacidad de tener mas de un programa trabajando y que pareciera que se realizan al mismo tiempo. Por ejemplo tu puedes estar imprimiendo mientras estas editando o enviando un fax. Por supuesto, al menos que tengas una maquina con varios procesadores, lo que realemente esta pasando es que el sistema operativo esta repartiendo los recursos a cada programa, dando la impresion que trabajan en paralelo. Esta distribucion de recursos es posible porque mientras piensas mantienes el computador en estado desocupado, por ejemplo mientras tecleas, la mayoria de los CPU'S procesan la data en intervalos muy cortos (teclear datos puede tomar alrededor de 1/20 segundos por cada caracter, despues de todo queda mucho tiempo ocioso para el cpu.)

La multitarea se puede hacer de dos maneras, dependiendo de si el sistema operativo interrumpe el progama sin consultar con ellos primero o si los programas son interrumpidos unicamente cuando estan dispuestos a ceder el control. El primero es llamado multitarea preventiva; el ultimo es llamado multitarea cooperativa o simplemente multitarea no preventiva.

En Java, el subprocesamiento multiple se implementa con Hilos, también conocidos como Threads en ingles.

Aquí os dejo una lista de articulos relacionados con Hilos en Java
Swing y Hilos
Swing y SwingWorker

lunes, 10 de julio de 2017

Java Swing: SwingWorker y el hilo que despacha los Eventos

Concurrencia en Swing
Cuando programamos aplicaciones con componentes Java Swing, debemos tener mucho cuidado con la concurrencia(ejecutar más de una tarea al mismo tiempo). En Java la concurrencia se implementa mediante Threads o Hilos. Un programa Swing diseñado correctamente es aquel que usa la concurrencia para crear una interfaz de usuario que no se "congele" mientras que el programa ejecuta una tarea que toma mucho tiempo. El programa siempre está disponible para responder a las peticiones del usuario, no importa lo que esté haciendo. Para crear un programa responsive(que reacciona rapidamente y positivamente) el programador debe aprender como el framework Swing emplea los hilos.

Un programador Swing trata con los siguientes tipos de hilos:
  • Hilos iniciales, el hilo que inicia la ejecución del codigo de la aplicación, usualmente llamado main.
  • El hilo que despacha los eventos(EDT), donde se ejecuta todo el código que maneja eventos del usuario y codigo que manipule cualquier componente Swing.
  • Hilos Background(Worker Threads), hilos que ejecutan aquellas tareas que consumen un tiempo considerable(más de un segundo para mi criterio)
El programador no necesita explicitamente codificar la creación de estos hilos: El trabajo del programador es utilizar estos hilos para crear un programa Swing mantenible y responsive.
Como cualquier otro programa corriendo en la plataforma Java, un programa Swing puede adicionalmente crear otros hilos y pools de hilos, siempre y cuando se maneje correctamente la concurrencia.

El fragmento de código del siguiente programa no maneja correctamente la concurrencia

/**
 *
 * @author Roberto Lopez
 */
public class WrongSwingProgram {
 
 public static void main(String args[]){
  final JLabel label = new JLabel();
  
  Thread anyThread = new Thread(new Runnable() {
   @Override
   public void run() {
    label.setText("Hola mundo!!");
    label.setPreferredSize(new Dimension(150, 32));
   }
  });
  
  anyThread.start();
 }
}

El componente Swing label está siendo manipulado por el código que será ejecutado en el hilo anyThread, un hilo que no es el hilo EDT, esto puede generar inconsistencias ya que más de un hilo puede manipular el estado del objeto. Más adelante veremos cómo manejar correctamente la concurrencia en programas Swing.

Hilos Iniciales
Todo programa tiene asociado un conjunto de Hilos que se inician junto con el programa. En un programa estandar solo se tiene un hilo, el hilo que invoca al metodo main de la clase. En applets uno de los hilos inicial es aquel que construye el objeto apple e invoca a los metodos init y start; estas acciones pueden ocurrir en un solo hilo,  o en dos o tres diferentes hilos, dependiendo de la implementación de la plataforma Java. A estos hilos se les llama Hilos Iniciales.

En programas Swing, el hilo inicial no tiene mucho que hacer. La mayor parte del trabajo consiste en crear un objeto Runnable que inicializa la GUI y programa su ejecución en el hilo de eventos EDT, mas adelante explicaré en detalle el hilo de eventos.
Una vez que la GUI esta creada, el progama es principalmente manejado por eventos GUI, cada uno de los cuales ejecutan una tarea corta en el hilo de eventos. Encolar tareas en hilo de eventos sin que necesariamente sean eventos es posible, ya que no existe restricción, sin embargo deben ser tareas que consuman muy poco tiempo, menos de un segundo a mi criterio, así no interferimos con el procesamiento de eventos, para tareas que demanden más de un segundo de tiempo sera necesario usar un hilo worker.

Un hilo inicial encola la tarea que crea la GUI (en el hilo de eventos) invocando a javax.swing.SwingUtilities.invokeLater o javax.swing.SwingUtilities.invokeAndWait. Ambos metodos toman un solo argumento: el objeto Runnable que define la nueva tarea. La unica diferencia la podemos deducir por sus nombres, invoqueLater simplemente encola la tarea en el hilo EDT y regresa; invoqueAndWait espera hasta que la tarea termine y solo cuando esta haya terminado regresa.

Por qué no simplemente es el hilo inicial el encargado de crear la GUI? Esto se debe a que la mayoria del codigo interactua con componentes Swing debe ser ejecutado en el hilo que despacha eventos. Esta restricción será explicada más adelante.

El hilo que despacha eventos EDT
El código que maneja los eventos Swing se ejecuta en un hilo especial conocido como el hilo que despacha los eventos. La mayoría del código que invoca metodos de componentes Swing también se ejecutan en este hilo. Esto es necesario porque la mayoria de los componentes Swing no son "thread safe": acceder a estos metodos desde varios hilos es peligroso, los hilos pueden modificar el estado del objeto uno sobre otro o errores de consistencia de memoria (diferentes hilos tienen una vista inconsistente de lo que debería ser el mismo dato). Todo código que accesa o invocación de metodos de componentes Swing debe ser ejecutado desde el hilo que despacha los eventos. Programas que ignoren esta regla pueden funcionar correctamente la mayor parte del tiempo, pero están sujetos a comportamientos y errores impredecibles que son dificiles de reproducir.

Es útil pensar en los códigos que se ejecutan en el hilo que despacha los eventos como un conjunto de tareas pequeñas. La mayoria de estas tareas son invocaciones de metodos que manejan eventos tales como ActionListener.actionPerformed. Otras tareas pueden ser encoladas en el hilo mediante el código de la aplicación, usando invoqueLater o invoqueAndWait.
Las tareas a ejecutarse en el hilo que despacha eventos debe terminar muy rapidamente, de no ser así se corre el riesgo que el hilo no esté disponible para ejecutar los eventos de la interfaz de usuario.
Si necesitas determinar si tu codigo está siendo ejecutado por el hilo que despacha eventos, invocar a javax.swing.SwingUtilities.isEventDispatchThread.

Worker Threads y SwingWorker
Cuando un programa Swing necesita ejecutar una tarea que consume un tiempo considerable (más de un segundo a mi criterio), generalmente usaremos un WorkerThread, también conocido como un background thread. Cada tarea corriendo en un worker thread es representado por una instancia de javax.swing.SwingWorker. SwingWorker es una clase abstracta; debes crear una subclase para crear tu propio SwingWorker y poder crear instancias; las clases internas son muy usadas para crear objetos SwingWorker simples.

SwingWorker proporciona un conjunto de caracteristicas de control y comunicación:

  • La subclase SwingWorker puede sobreescribir al metodo done, que es automaticamente invocado y ejecutado en el hilo que despacha los eventos, se llama cuando la tarea a ejecutar ha finalizado.
  • SwingWorker implementa a java.util.concurrent.Future. Esta interfaz permite a la tarea que se ejecuta en modo background retornar un valor a otro hilo. Otros metodos en esta interface permite cancelar la tarea y consultar por el estado de la tarea, saber si ha finalizado o fue cancelado.
  • La tarea ejecutándose en background puede entregar resultados del avance de la tarea, invocando a SwingWorker.publish, haciendo que se invoque a SwingWorker.process sobre el hilo que despacha los eventos.
  • La tarea ejecutándose en background puede definir un conjunto propios de propiedades. Cambios sobre estás propiedades disparan eventos, los metodos del manejo de eventos son ejecutados en el hilo que despacha los eventos, el EDT.
Una tarea simple que se ejecuta en modo background (en segundo plano, fuera de la GUI)

Vamos a empezar con a una tarea que es muy simple pero que puede consumir un buen de tiempo en ejecutarse. La clase TumbleItem carga un conjunto de imagenes que se usaran en una animación. Si los archivos de las imagenes son cargados desde un hilo inicial (el hilo main, por ejemplo), pasará un buen tiempo antes que la GUI se muestre. Si los archivos de las imagenes son cargados desde el hilo que despecha los eventos EDT, la GUI quedará congelada temporalmente y no responderá a otros eventos.
Para evitar estos problemas, TumbleItem crea y ejecuta una instancia de SwingWorker desde el hilo inicial. El metodo doInBackground, ejecutandose en un WorkerThread, carga las imagenes dentro un arreglo de ImageIcon, y devuelve una referencia del arreglo. Entonces, el metodo done se ejecuta en el Hilo que despacha los eventos, para recuperar la referencia al arreglo invocamos el metodo get. Cargar las imagenes en modo background permitirá construir y visualizar inmediamente la GUI, sin tener que esperar a que la carga de imagenes termine.

NOTA: Algunas clases y porciones de clases son propiedad de Oracle

Aquí el código que define y ejecuta el objeto SwingWorker.


SwingWorker worker = new SwingWorker<ImageIcon[], Void>() {
    @Override
    public ImageIcon[] doInBackground() {
        final ImageIcon[] innerImgs = new ImageIcon[nimgs];
        for (int i = 0; i < nimgs; i++) {
            innerImgs[i] = loadImage(i+1);
        }
        return innerImgs;
    }

    @Override
    public void done() {
        //Remove the "Loading images" label.
        animator.removeAll();
        loopslot = -1;
        try {
            imgs = get();
        } catch (InterruptedException ignore) {}
        catch (java.util.concurrent.ExecutionException e) {
            String why = null;
            Throwable cause = e.getCause();
            if (cause != null) {
                why = cause.getMessage();
            } else {
                why = e.getMessage();
            }
            System.err.println("Error retrieving file: " + why);
        }
    }
};



El poyecto completo lo puedes descargar desde mi repositorio en GitHub

Toda clase concreta que extienda de SwingWorker debe implementar el metodo doInBackground; la implementación del metodo done es opcional.
Observa que SwingWorker es una clase generica, con dos tipos de parametros. El primer tipo de parametro especifica el tipo que retornará el metodo doInBackground, y también para el metodo get, que es invocado por otro hilo que recibe el objeto retornado por doInBackground. El segundo tipo de parametro de SwingWorker especifica un tipo para resultados preliminares que son retornados mientras la tarea en background todavía está activa.
Para este ejemplo no retornamos resultados preliminares así que especificamos el tipo Void, mas adelante sacaré una nueva versión con resultados preliminares.
Es posible que te preguntes si el codigo que obtiene el arreglo de iconos imgs es innecesariamente complicado. ¿Por qué hacemos que doInBackground retorne un objeto y usamos el metodo done para recuperarlo? ¿Por qué no solo obtenemos el arreglo imgs directamente del metodo doInBackground? El problema es que el objeto imgs  es creado en el hilo worker y usado en el hilo que despacha los eventos. Cuando los objetos son compartidos entre hilos siguiendo esta forma, nos aseguramos que los cambios hechos en un hilo son visibles en los otros. Usando get lo garantizamos, porque usando get creamos una relación happens before(relación entre el resultado de dos eventos, tal que uno de estos eventos debe ocurrir antes que otro evento) entre el codigo que crea el arreglo imgs y el codigo que lo usa.
Actualmente hay dos formas de recuperar el objeto retornado por doInBackground.


  • Invocando SwingWorker.get sin argumentos. Si la tarea en background no ha finalizado, get bloquea el hilo hasta que termine la tarea.
  • Invocando SwingWorker.get con argumentos indicando un time-out. Si la tarea en background no ha finalizado, el metodo get bloquea el hilo hasta que termine la tarea - al menos que el time-out expire primero, en ese caso get lanza una Excepción java.util.concurrent.TimeoutException.

Hay que tener cuidado cuando se invoque al metodo get desde el hilo que despacha los eventos. Hasta que get retorne, la GUI no podrá procesar más eventos, y la GUI quedará "congelada". No invoques el metodo get sin argumentos al menos que estes completamente seguro de que la tarea en background ha finalizado completamente.

Referencia
https://docs.oracle.com/javase/tutorial/uiswing/concurrency/index.html

domingo, 16 de octubre de 2011

Interfaces de Usuario Swing con Hilos

En esta entrada quiero compartir algunos problemas de subprocesamiento multiples  (ejecución de tareas en paralelo) relacionados con la programacion de interfaces graficas de usuarios.

Hilos en Swing
Una de las razones de usar hilos en tus aplicaciones es la de hacer que tu aplicacion sea mas interactivo. Cuando tu aplicacion realiza una tarea que requiere mucho tiempo, más de dos segundos a mi juicio, entonces debes ejecutar la tarea en otro hilo para evitar que la interfaz de usuario se quede "congelada".

Hay un hilo diseñado exclusivamente para pintar la interfaz grafica y para procesar los eventos del usuario, este hilo es conocido como el EDT (Event Dispatch Thread), todo código que requiera manipular componentes swing debe ser ejecutado en este hilo, de no hacerlo así te puedes llevar varias sorpresas. Swing es not thread safe. Es decir, la mayoria de los metodos de las clases Swing no son sincronizados. Si tu intentas manipular los elementos de la interfaz grafica del usuario desde varios hilos entonces la interfaz de usuario se dañara.

Evolución del Multiprocesamiento en Java
Me inicié en el mundo Java por los años 2008, han pasado ya casí 10 años y he visto como Java ha ido creciendo y evolucionando para facilitarle las tareas al programador. Antes de la versión 1.6, para ejecutar tareas en paralelo tenías que extender explicitamente a la clase Thread y programar toda la lógica del subprocesamiento multiple, sincronización, interacción con la GUI y demás. Con la llegada de la versión 1.6 aparece una clase llamada SwingWorker el cual se encarga de manejar toda la logica del subprocesamiento en paralelo, facilitandote en mucho la programación.

Resumiendo, hay dos formas de programar el subprocesamiento multiple con Swing.
  1. La forma tradicional en la que extiendes a Thread y programas toda la lógica de hilos.
  2. Extendiendo la clase SwingWorker

Yo recomiendo la segunda, usando la clase SwingWorker.
Para profundizar en el hilo EDT y SwingWorker puedes leer mi entrada SwingWorker y el Hilo que despacha los Eventos ahí explico detalladamente estás dos clases.

Problemas más comunes con sub procesamiento multiple

Por ejemplo corre el siguiente programa.

package com.maro.gui;

import java.util.Random;

import javax.swing.JComboBox;

public class BadWorkerThread extends Thread {

public BadWorkerThread(JComboBox aCombo){
combo = aCombo;
generator = new Random();
}
public void run(){
try {
while (!interrupted()){
int i = Math.abs(generator.nextInt());
if(i % 2 == 0){
combo.insertItemAt(new Integer(i), 0);
} else if(combo.getItemCount() > 0){
combo.removeItemAt(i % combo.getItemCount());
}
sleep(1);
}
} catch(InterruptedException iee){
}
}
private JComboBox combo;
private Random generator;
}


Un frame para visualizarlo, una vez que corras el programa presiona el boton "Bad" y juega con el combo box, notaras que el compilador lanza varias excepciones.

package com.maro.gui;

import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class TestBadWorkerThread extends JFrame{

private JComboBox combo;
private Thread longTask;
public TestBadWorkerThread (){
super("Prueba");
combo = new JComboBox();
JPanel contentPanel = new JPanel();
contentPanel.setLayout(new FlowLayout());
JButton badButton = new JButton("Bad");
badButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
longTask = new BadWorkerThread(combo);
longTask.start();
}
});
contentPanel.add(badButton);
contentPanel.add(combo);
this.getContentPane().add(contentPanel);
this.pack();
//this.setVisible(true);
}
public static void main(String args[]){
SwingUtilities.invokeLater(new Runnable() {
public void run() {
// TODO Auto-generated method stub
TestBadWorkerThread pr = new TestBadWorkerThread();
pr.setVisible(true);
}
});
}
}