09 noviembre, 2012

Seleccion de área de una imágen con Java

De vez en cuando se necesita que el usuario seleccione un área de una imágen para darle tratamiento. Por ejemplo supongamos que tenemos un programa que almacena fichas de empleados y queremos que en cada ficha se almacene la foto del empleado.

Puede darse el caso de que las imágenes vengan en distinto tamaño, por lo que es útil proporcionar una herramienta que permita la selección de una determinada área de la imágen para su almacenamiento.

El problema en si no es muy complejo. A grandes rasgos se resume en:

  • Determinar el punto de origen.
  • Determinar el área.
  • Seleccionar el área.


Para ello utilizaremos un JPanel que contendra la imágen, la cual estaŕa a su vez contenida en un BufferedImage, un MouseListener para detectar cuando el usuario presiona y suelta el boton del mouse y un MouseMotionListener para determinar cuando el usuario mueve el mouse para seleccionar el área.

Supongamos que el usuario hace clic en un punto de la imagen que llamaremos pi(xi, yi) y arrastra el mouse hasta un punto que llamaremos pf(xf, yf).

El primer paso consiste en determinar al ancho y alto del área abarcada por el arrastre del mouse.

      ancho = Math.abs(xi - xf);
    alto = Math.abs(yi - yf);


Luego obtenemos una imagen con el área seleccionada. Para ello utilizaremos el metodo getSubimage() de BufferedImage.

     BufferedImage seleccion = imagen.getSubimage(x, y, ancho, alto);

Para crear el efecto de opacidad de fondo y seleccion más clara, dibujaremos primero la imagen aplicando un filtro que reduce en un 50% el color. Para ello usaremos RescaleOp.

BufferedImageOp op = new RescaleOp(new float[]{0.5f, 0.5f, 0.5f}, new float[]{0f, 0f, 0f}, null);

Finalmente sobrecargamos el metodo paintComponent() del JPane para que se dibuje la imagen y el área seleccionada.

@Override
public void paintComponent(Graphics gr) {
   Graphics2D g = (Graphics2D) gr;
   super.paintComponent(g);
   if (imagen == null) {
       return;
   }
   if (puntoInicial != null && puntoFinal != null) {
       g.drawImage(imagen, op, 0, 0);
       g.drawImage(getImagenSeleccionada(), x, y, this);
       g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,         RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
       g.setColor(Color.WHITE);
       g.drawRect(x, y, ancho, alto);
       g.drawString("Área: " + ancho + "x" + alto, 10, 20);
   } else {
       g.drawImage(imagen, 0, 0, this);
   }
}


La verdad, es que el tema quiza sea un poco más complejo, porque debemos cuidarnos de que los puntos esten dentro de la imágen. También podríamos forzar a seleccionar un área siempre cuadrada o bien un rectángulo libre.

El código es largo y la verdad me da flojera escribirlo todo aca, por eso los ejemplos resumidos.

Por lo mismo, en este enlace se puede descargar el código fuente del Selector de Área de Imágen y un Jframe con el ejemplo de uso para que puedas incluirlo en tu programa. Cualquier comentario, observación y correción del código es bienvenida.



24 octubre, 2012

Modulador de Ancho de Pulso (PWM) en Java

javaPWM.png


JavaPWM es una sencilla aplicación que permite convertir cualquier PC con un puerto serial en un modulador de ancho de pulso (PWM).

Existen en internet varios circuitos simples que permiten controlar un relé con el puerto serial. JavaPWM aprovecha esta característica y envia pulsos al puerto serial que pueden servir para controlar un relé u otro dispositivo similar. He aqui un esquemático simple para controlar un relé por medio del puerto serial:

(originalmente publicada en http://www.windmeadow.com/node/4)

Permite configurar el puerto, modo de operacion, ciclo de trabajo y frecuencia. Se puede trabajar con un pulso simple de duracion determinada o bien como un oscilador a la frecuencia indicada.

El ciclo de trabajo (duty cycle) puede variar entre 1 y 99%. 

En modo oscilador (continuo) la frecuencia puede variar entre 1 y 50 Hz.

En modo pulso, el pulso puede durar entre 1 y 100 segundos.

El modo "off/on" invierte el ciclo.

Requiere Java 6 o superior y la biblioteca rxtx correctamente instalada o configurada.

En ciertos casos puede dejar el puerto bloqueado. En linux se desbloquea eliminando el archivo Lock del puerto en /var/lock. En Windows no tengo idea como desbloquear un puerto.

Para descargar la aplicación clic aqui. Es totalmente gratuita!
El codigo fuente se puede descargar desde aca.

Si te sirve, te gusta o tienes alguna sugerencia, tu comentario es bienvenido.



24 septiembre, 2012

Algunos tips con reflexion en Java.

La API de reflexión en Java presenta herramientas que son muy útiles a la hora de hacer que nuestros programas sean "dinámicos" o "expansibles".

Es muy útil en algunos casos, explorar un objeto en tiempo de ejecucion para conocer sus métodos y atributos.

He aqui algunos tips básicos para iniciarse con reflexión.

Clase: Class

La clase Class representa justamente eso: Una clase o una interfaz de Java. Mediante la instancia de Class es posible conocer los métodos que expone la clase, sus atributos, constructores, etc.

Clase: Constructor

La clase Constructor proporciona informacion de y acceso a un constructor de una clase.

Clase: Method

La clase Method proporciona información de y acceso a un método de una clase.

Clase: Field

La clase Field proporciona información de y acceso a un atributo de una clase.


Hay mucho más, pero estos simples conceptos son suficientes para comenzar.

Tomemos como ejemplo, la siguiente clase, la cual nos servirá para explorar un poco de la API java.lang.reflect:

public class Clase {

private String atributo;

public Clase() {
}

public Clase(String atributo) {
    this.atributo = atributo;
}

public String getAtributo() {
    return atributo;
}

public void setAtributo(String atributo) {
    this.atributo = atributo;
}

public void verAtributo() {
    System.out.println("El valor del atributo es: " + atributo);
}

public void metodo() {
    System.out.println("Este es el metodo.");
}

public void metodo(String parametro) {
    System.out.println("Este es el metodo con parametro. El parametro fue: " +       parametro);
}

public static void estatico() {
    System.out.println("Este es el metodo estatico.");
}

public static void estatico(String parametro) {
    System.out.println("Este es el metodo estatico con parametro. El parametro       fue: " + parametro);
}
}


Examinando esta clase, vemos que posee las siguientes características:


  • Un atributo de tipo String.
  • Un constructor sin parámetros.
  • Un constructor que recibe un parámetro de tipo String.
  • Un métod getter para el atributo.
  • Un método setter para el atributo.
  • Un método sin parámetros que imprime un texto en consola.
  • Un método con un parámetro String que imprime un texto en consola.
  • Un método estático sin parámetros que imprime un texto en consola.
  • Un método estático con parámetros que imprime un texto en consola.
Esto es fácil, porque estamos viendo el código fuente de la clase, pero podemos examinarla completamente usando un programa en java.

Lo primero es obtener una instancia de Class.

    Class c = Class.forName("paquete.Clase")
    Class c = Clase.class

Puede ser por el nombre completo de la clase (incluyendo el paquete), o bien invocando directamente el atributo class que toda clase Java tiene.

El siguiente código instancia una clase por su nombre e imprime el nombre de la clase en pantalla:


    Class c = Class.forName("paquete.Clase");
    System.out.println("Clase instanciada: " + c.getName());


La ejecución de este código dará como resultado lo siguente:

    run:
    Clase instanciada: paquete.Clase


Teniendo una instancia de la clase, podemos comenzar a explorarla:

El siguiente código permite obtener los constructores de la Clase:

System.out.println("\nConstructores de la clase:");
Constructor[] cons = c.getConstructors(); //c.getDeclaredConstructors();
for (Constructor co : cons) {
   System.out.println("Constructor : " + co.getName());
   Class[] param = co.getParameterTypes();
   if (param.length == 0) {
      System.out.println("\tConstructor sin parámetros.");
   } else {
      for (Class p : param) {
         System.out.println("\tParametro: " + p.getName());
      }
   }
}


La ejecución del código anterior dará como resultado:


Constructores de la clase:
Constructor : paquete.Clase
     Constructor sin parámetros.
Constructor : paquete.Clase
     Parametro: java.lang.String

Si se nota, ya esposible obtener información de la clase. En este caso el método c.getConstructors() nos permite obtener todos los constructores de la clase. Su hermano c.getDeclaredConstructors() nos permite obtener los constructores que fueron declarados específicamente en la clase que estamos explorando (útil cuando una clase hereda de otra).

Exploremos ahora los atributos que posee la clase:

System.out.println("\nAtributos de la clase:");
Field[] fields = c.getFields();
for (Field f : fields) {
   System.out.println("Atributo: " + f.getName());
   System.out.println("\tTipo del Atributo: " + f.getType().getName());
}



La ejecución del código anterior dará como resultado:


   Atributos de la clase:


En este caso, no hay nada. ¿Por qué? 

Si miramos el código de la clase, veremos que contiene un único atributo de tipo private por lo que no es visible fuera de la clase. Si agregamos el siguiente atributo:


   public String atributoPublico;


Y ejecutamos el código, veremos lo siguiente.


Atributos de la clase:
Atributo: atributoPublico
   Tipo del Atributo: java.lang.String



Ahora exploremos los métodos. El siguiente código muestra los métodos de la clase:


System.out.println("\nMetodos de la clase: ");
Method[] metodos = c.getMethods();
for (Method m : metodos) {
   System.out.println("Metodo: " + m.getName());
   Class[] param = m.getParameterTypes();
   if (param.length == 0) {
      System.out.println("\tEl método no acepta parametros.");
   } else {
      for (Class p : param) {
         System.out.println("\tParametro: " + p.getName());
      }
   }
}


El cual, al ser ejecutado mostrará:


Metodos de la clase:
Metodo: getAtributo
   El método no acepta parametros.
Metodo: setAtributo
   Parametro: java.lang.String
Metodo: verAtributo
   El método no acepta parametros.
Metodo: metodo
   El método no acepta parametros.
Metodo: metodo
   Parametro: java.lang.String
Metodo: estatico
   El método no acepta parametros.
Metodo: estatico
   Parametro: java.lang.String
Metodo: wait
   Parametro: long
Metodo: wait
   Parametro: long
   Parametro: int
Metodo: wait
   El método no acepta parametros.
Metodo: equals
   Parametro: java.lang.Object
Metodo: toString
   El método no acepta parametros.
Metodo: hashCode
   El método no acepta parametros.
Metodo: getClass
   El método no acepta parametros.
Metodo: notify
   El método no acepta parametros.
Metodo: notifyAll
   El método no acepta parametros.


En este caso, vemos métodos que no fueron declarados en la clase. ¿Por que? Porque para obtener los métodos usamos el método getMethods(), el cual retorna todos los métodos en la jerarquía de la clase. Es decir, los métodos de la clase y de su(s) superclase(s). 


Si en lugar de getMethods() usamos getDeclaredMethods(), obtendremos el siguiente resultado:

Metodos de la clase:
Metodo: getAtributo
   El método no acepta parametros.
Metodo: setAtributo
   Parametro: java.lang.String
Metodo: verAtributo
   El método no acepta parametros.
Metodo: metodo
   El método no acepta parametros.
Metodo: metodo
   Parametro: java.lang.String
Metodo: estatico
   El método no acepta parametros.
Metodo: estatico
   Parametro: java.lang.String



Pero ésto sólo nos proporciona información de una clase. Ahora pasaremos a la acción.


Vamos a utilizar 2 instancias de nuestro objeto 'Clase'. En una invocaremos el constructor sin parámetros, y en otra invocaremos el constructor que recibe el parámetro String.

Object o = c.newInstance();
Object o2 = c.getConstructor(String.class).newInstance("Instancia 2");

System.out.println("Instacia o de Clase: " + o);
System.out.println("Instancia o2 de Clase: " + o2);


La ejecución del código anterior dará como resultado:

Instacia o de Clase: paquete.Clase@62f72617
Instancia o2 de Clase: paquete.Clase@4fe5e2c3


En el primer caso, la llamada del método newInstance() llama directamente al contructor sin parámetros. 

Para invocar la llamada al constructor con parámetros, invocamos el método getConstructor() y le entregamos como parámetro String.class. Literalmente le estamos diciendo: "Dame el constructor que recibe como parámetro un String".


Al constructor retornado le invocamos el método newInstance() y le pasamos como parámetro un String.


Ejercicio: Intentar construir la instancia o2, pasándole un Integer (u otro). Tendremos una linda excepción.

Ahora vamos a invocar el método para ver el valor del atributo:

Method verAtributo = c.getDeclaredMethod("verAtributo", null);
verAtributo.invoke(o, null);
verAtributo.invoke(o2, null);



Puede observarse que el código en si es simple. A la clase le pedimos el método llamado verAtributo que no tiene parámetros.


Luego, invocamos el método mediante invoke(), y le pasamos como parámetro la instancia de la clase en la que invocaremos el método y los parámetros del método (si aplica). La ejecución del código anterior, dará como resultado:

El valor del atributo es: null
El valor del atributo es: Instancia 2


Lo cual es correcto, ya que al momento de instanciar, sólo declaramos el atributo en la instancia 2.

Invoquemos un método con parametros.

  Method setAtributo = c.getDeclaredMethod("setAtributo", String.class);
  setAtributo.invoke(o, "Un parámetro");


Como puede notarse, la metodología es similar a la invocación del constructor. Le decimos a la clase "Dame el método llamado setAtributo que recibe un String como parámetro". Luego invocamos el método en la instancia o y le pasamos como parámetro un String.

Si volvemos a invocar el método verAtributo en la instancia o, veremos el siguiente resultado:

El valor del atributo es: Un parámetro

Finalmente, invocaremos un método estático con parámetros.

Method estatico = c.getDeclaredMethod("estatico", String.class);
estatico.invoke(null, "¡Es un método estático!");


Como puede notarse, un método estático no necesita una instancia donde invocarse, por lo que el primer parámetro puede ser NULL. La ejecución del código dará como resultado:

Este es el metodo estatico con parametro. El parametro fue: ¡Es un método estático!

Como puede verse, la API de reflexión es muy potente y trabajando en conjunto con Interfaces y Generics se pueden hacer cosas muy interesantes como por ejemplo una aplicación modular mediante plug-ins o herramientas que analizen y exploren código java... por decir algo.

Hay mucho, mucho más por explorar. Animo al lector a darse un tiempo de explorar esta API ya que soluciona muchos dolores de cabez y permite que nuestras aplicaciones sean flexibles y extensibles con poco esfuerzo.

Archivos: Clase.java Reflexion.java

Salu2!!!

17 mayo, 2012

Tratamiento de ResultSet con multiples hilos

Aunque el título de la entrada no es el mas adecuado, la implementación de esta técnica permite procesar aparentemente un ResultSet con múltiples hilos.

Típicamente, en una aplicación que manipula una gran cantidad de datos con un ResultSet, tenemos el problema de que el tratamiento puede llegar a ser costoso en terminos de tiempo. Esto se debe a que el ResultSet no es thread-safe, es decir, no puede se accedido desde múltiples hilos a la vez.

Por lo tanto, un escenario como el que se ilustra abajo es técnicamente imposible:




Luego, se está obligado a utilizar un único hilo que acceda al ResultSet, lo que se traduce en un desperdicio de recursos y en un altísimo tiempo de espera en caso de contar con grandes volúmenes de información.

Una solución simple, es almacenar los datos en un objeto que actúe como buffer y que pued ser leído desde múltiples hilos. De este modo, se tiene por un lado a un hilo único accediendo al ResultSet y dejando los datos en nuestro buffer, y por otro lado a varios hilos que leen los datos de este buffer y los procesan en forma paralela.

Se llega a un escenario más cómodo, que permite el uso de múltiples hilos para el procesamiento de nuestros datos:



Para esto, se necesita un JavaBean que contenga los datos que son leídos desde el ResultSet, una cola para almacenar estos JavaBeans a medida que se van leyendo y una clase que extienda Thread (o implemente Runnable) que será la encargada del proceso. 

La secuencia sería más o menos así:
  • Obtener el ResultSet
  • En un hilo, leer el ResultSet, crear los Beans de Datos y llenar el buffer.
  • Lanzar uno o varios hilos que lean el buffer.
Adicionalmente se necesitan algunos mecanismos de control para:
  • Ganatizar que el Buffer no crezca descontroladamente.
  • Asegurar que los hilos de proceso se esten ejecutando hasta que se haya leido completamente el ResultSet.
  • No continuar hasta que todos los hilos de proceso hayan terminado (este paso puede ser opcional).

Existen muchas formas de implementar esta lógica. Aqui propongo una simple, que puede ser mejorada en gran medida.

Primero: Definimos una clase que actue como "Bean" de datos:

class Bean {
        
        private String dato1;
        private String dato2;
        private String dato3;

        public String getDato1() {
            return dato1;
        }

        public void setDato1(String dato1) {
            this.dato1 = dato1;
        }

        public String getDato2() {
            return dato2;
        }

        public void setDato2(String dato2) {
            this.dato2 = dato2;
        }

        public String getDato3() {
            return dato3;
        }

        public void setDato3(String dato3) {
            this.dato3 = dato3;
        }
        
    }

Luego, definimos una clase donde se hará el proceso:



class Proceso {
    
    private static final int MAXIMO_BUFFER = 10;
    private static final int MAXIMO_HILOS_PROCESO = 5;
    private ResultSet rs;
    private Thread[] hilosProceso;
    private volatile boolean leyendoRs;
    private LinkedBlockingQueue buffer;

    /**
     * Este metodo debe ser lanzado desde un hilo que no sea el 
     * event-dispatch-thread
     */
    public void procesar() throws SQLException {
        buffer = new LinkedBlockingQueue();
        hilosProceso = new Thread[MAXIMO_HILOS_PROCESO];
        rs = leerBaseDatos();
        leyendoRs = true;
        Runnable proceso = new Runnable() {

            @Override
            public void run() {
                while (true) {
                    Bean b = buffer.poll();
                    if (b == null) {
                        if (leyendoRs) {
                            try {
                                Thread.sleep(250);
                            } catch (InterruptedException ex) {
                                ex.printStackTrace();
                            }
                        } else {
                            break;
                        }
                    } else {
                        //Hacer algo con Bean b
                    }
                }
            }
        };
        for (int i = 0; i < MAXIMO_HILOS_PROCESO; i++) {
            hilosProceso[i] = new Thread(proceso, "Hilo Proceso " + i);
            hilosProceso[i].start();
        }
        
        while (rs.next()) {
            Bean b = new Bean();
            b.setDato1(rs.getString("dato1"));
            b.setDato2(rs.getString("dato2"));
            b.setDato3(rs.getString("dato3"));
            buffer.offer(b);
            if (buffer.size() >= MAXIMO_BUFFER) {
                try {
                    Thread.sleep(250);
                } catch (InterruptedException ex) {
                    ex.printStackTrace();
                }
            }
        }
        rs.close();
        leyendoRs = false;
        
        for (Thread hilo : hilosProceso) {
            if (hilo.isAlive()) {
                try {
                    Thread.sleep(500);
                } catch (InterruptedException ex) {
                    ex.printStackTrace();
                }
            }
        }
        
        
        /*
         * En este punto estamos completamente seguros de que 
         * todos los datos han sido procesados.
         * 
         */
    }

}

Como puede verse, la clase que procesa los datos es bastante simple. Utilizamos como buffer la clase LinkedBlockingQueue que permite un acceso concurrente a los metodos offer() y poll(). 

Obetenemos los datos con algún método a nuestro gusto, y luego levantamos un flag que indica que estamos leyendo los datos. 

Utilizamos la interfaz Runnable y definimos una pequeña clase anónima que procesa los datos. Esta clase está constantemente leyendo los datos desde el buffer. Si en algún momento no se han leído datos, pero el flag de lectura aún está a true, el hilo esperará 250 milisegundos y continuará su lectura. Esto permite que no se detengan los hilos aunque se produzca algun retraso en el llenado del buffer.

El bloque while que lee el RsultSet crea las instancias de nuestro bean de datos y las encola. Contiene un pequeño bloque de control, el cual verifica que si el tamaño del buffer supera un máximo establecido, se esperan 250 milisegundos antes de continuar. Esta tecnica me ha dado mejores resultados que definir el tamaño máximo de LinkedBlockingQueue (se me estaban perdiendo datos). Cuando termina el ciclo de lectura del ResultSet, el flag de lectura se lleva a false, lo que indica a los hilos de proceso que ya no habrá mas datos para procesar.

Finalmente, existe un pequeño bloque de control, el cual verifica que mientras existan hilos de proceso activos, se esperará. Por lo tanto en este punto la ejecución no continuará hasta que todos los hilos hayan finalizado.

El código es bastante rudimentario, pero es efectivo. Pueden hacerse varias mejoras en las secciones de control (sobre todo la del final), pero prefiero publicar este código ya que es simple de entender. Todas las mejoras y extensiones quedan a gusto del consumidor ;-)


bytes!




13 marzo, 2012

Actualización de la zona horaria en Java

Hasta que pasó... llegó el día del cambio de hora, pero se decidió aplazarlo hasta el 28 de abril.

Esto pasa con bastante frecuencia y en varios países. Se adelanta o se cambia el horario de verano y nuestros computadores son los primeros en sufrir las consecuencias.

Pero ¿que es lo que pasa?

En los sistemas operativos modernos, el reloj hardware del computador almacena la hora oficial universal (GMT/UTC) que en español significa que el reloj de nuestro computador está a la hora con el reloj de Greenwich, Inglaterra.

El Sistema Operativo deduce entonces la hora de una zona en base a la configuración de zona horaria y la hora UTC. Adicionalmente se utiliza la configuración de horario de verano (DST en ingles) para ajustar la hora a los estándares del momento.

Por ejemplo, aca en Chile continental, tradicionalmente el horario de verano comienza el segundo sábado de septiembre y termina el segundo sábado de marzo. Nuestra zona horaria es GMT -4, lo que significa que al horario de Greenwich se le restan 4 horas para obtener la hora de Chile continental.

Es decir: Hora de Chile continental  = Hora GMT/UTC - 4.

Hasta ahí, todo simple, pero además tenemos nuestras reglas de configuracion de horario de verano (DST). Estas reglas introducen un "delta" adicional a la zona horaria.

O sea: Hora de Chile continental  = Hora GMT/UTC - 4 +- DST.

El problema ocurre justamente con ese delta DST. Por regla general los sistemas operativos tratan de mantenerse actualizados y suelen hacerlo bastante bien, por lo que a menos que usemos un sistema sin soporte o con soporte escaso, esto no debiera presentar mayor inconveniente.

Java utiliza su propia información de zona horaria. Esto significa que aunque este configurado para uilizar la misma zona horaria del sistema operativo, la información DST que contiene no es la misma del sistema. Como resultado, aunque se actualice la información DST del sistema operativo, la maquina de Java no se verá afectada.

Y eso fue lo que pasó el sabado pasado. Mi computador se actualizó correctamente y se mantuvo a la hora oficial correcta, pero mis aplicaciones Java muestran la hora con una hora de atraso con respecto al computador:

Hora UTC: 16:00
Hora Chile Continental: 13:00
Hora PC: 13:00
Hora Java: 12:00

La máquina virtual Java "cree" que estamos en horario de invierno y por lo tanto muestra la hora que su información de zona horaria le indica que debe mostrar.

Se puede corregir de varias formas:

  • Mover la zona horaria del computador o cambiar la hora manualmente.
  • Actualizar la versión de Java.
  • Corregir la información de zona horaria de Java.
La opción 1 no es válida, a menos que solo usemos el computador para la aplicación Java conflictiva. El problema es que cuando sea el cambio de horario real, tendremos que mover a mano nuevamente la configuración horaria.

La opción 2 es adecuada, pero a veces no es posible. Hay aplicaciones que dependen de una versión particular de java y no es posible actualizar, en otros casos Oracle no ha liberado la actualización para la zona horaria.

La opción 3 puede ser útil en casos que no se pueda actualizar. La información de zona horaria es compatible entre diferentes versiones de Java.

La información de zona horaria se encuentra en:

JAVA_HOME/lib/zi

Por lo tanto es posible sobreescribir la informacion de zona horaria con la de una versión actualizada.

Por ejemplo, puedo sobreescribir el directorio 'zi' de una version de Java Sun 1.4 con el directorio 'zi' de una versión de OpenJDK actualizado. Con esto la información de zona horaria se actualizará y la máquina virtual mostrará la información correcta.

En sistemas UNIX puede usarse un enlace simbólico a una versión que sabemos se mantiene constantemente actualizada y asi incluso nos evitaremos hacer actualizaciones a mano.

Opciones y soluciones hay... la proxima vez no tendremos problemas con el cambio de horario.


bytes!

06 febrero, 2012

Agregar Texto en color a un JTextPane

De vez en cuando se me ha hecho necesario utilizar un JTextPane para entregar mensajes al usuario conforme se realiza una tarea. Por lo mismo es muy importante que el usuario pueda detectar rápidamente mensajes de error y que mejor para esto que utilizar colores en el texto. Los mensajes normales pueden ir en negro, los mensajes de error en rojo, etc.



Para ello se puede utilizar la clase StyledDocument de Java que permite definir algunas caracteristicas del texto que se insertará en el JTextPane


Sin más, he decidido crear una clase que extienda de JTextPane y que proporcione los métodos que necesito:



public class Consola extends JTextPane {



    public void append(Color color, String texto) {
        StyledDocument doc = getStyledDocument();
        Style syle = doc.addStyle("txt", null);
        StyleConstants.setForeground(syle, color);
        try {
             doc.insertString(doc.getLength(), texto, syle);
             setCaretPosition(doc.getLength());
         } catch (BadLocationException ex) {
             ex.printStackTrace();
         }
    }



    public void appendRed(String texto) {
        append(Color.RED, texto);
    }


    public void append(String texto) {
        append(Color.BLACK, texto);
    }


}



Una solución simple que puede ser de gran utilidad. Jugando un poco con las clases StyledDocument, Style y StyleConstants pueden obtenerse resultados muy interesantes.





bytes!

02 febrero, 2012

JTextArea transparente en Look & Feel Nimbus

Un problema que he observado al utilizar el look & feel 'Nimbus' de Java 6 es que las JTextArea mantenien el color de fondo blanco a pesar de que se ha invocado el método setOpaque(false).


Por lo que he leído, el problema se debe al diseño original de Swing. El metodo setOpaque() tiene el efecto de que el componente pueda no pintar alguno o todos sus pixeles, y en clases que extiendan a JComponent esto depende del look & feel.


En el caso de Nimbus, este look and feel requiere transparencia en los componentes para dibujar los bordes redondeados, por lo que los pixeles del componente deben ser pintados.


La solucion es utilizar un color de fondo transparente (alfa=0) y tambien un borde transparente a fin de que el efecto final sea un componente 100% transparente:




textArea.setBorder(BorderFactory.createEmptyBorder()); 

textArea.setBackground(new Color(0, 0, 0, 0));




Con esta simple operación, el componente será completamente transparente, no sólo omitiendo el dibujado de pixeles, sino haciendo que los pixeles que se dibujan sean realmente transparentes.