viernes, 13 de diciembre de 2013

Crear un proyecto JSF2 con Eclipse y Tomcat

2 comentarios

JSF2, Instalación y Configuración

Vamos a explicar como crear un proyecto JSF2 desde 0.

Requisitos:

  • Java --> Necesitaremos Java 6 o superior para utilizar Tomcat 7
  • Servidor --> Necesitamos un servidor que soporte servlets 2.5 o superior y además que contenga las librerías de JSF2, cosa que añadiremos manualmente ya que Tomcat no incorpora estas librerías
  • Librerías --> jsf-api.jar y jsf-impl.jar son extricatamente necesarias para utilizar JSF2 en Tomcat
  • Un IDE --> Eclipse 3.6 o superior, ya que desde esta versión tiene incorporado soporte para JSF2

Paso a paso

Una vez instalado Eclipse e integrado Tomcat, pasaremos a la creación del proyecto en sí.

  1. Creamos un nuevo proyecto de tipo Java
    Nuevo proyecto
  2.  Ahora lo configuramos como un proyecto web, para esto hacemos click sobre el y en el menú contextual seleccionamos Configure -> Convert to Faceted Form
  3. Elegimos ahora las facetas del proyecto que se muestran en la imagen siguiente y clickamos en el mensaje señalado para añadir las librerías necesarias:
  4. Si estamos utilizando Tomcat, deberemos añadir la implementación de JSF, para ello clickamos para crear una nueva User Library y añadimos los JARs necesarios, en este caso serían jsf-api.jar y jsf-impl.jar, aunque por comodidad añadiremos también la librería de etiquetas JSTL.
  5. Damos Ok a todo y cerramos las ventanas. Al final debería de quedarnos un proyecto con la siguiente estructura:

Como vemos durante este proceso se habrán generado los ficheros necesarios para configurar cualquier aplicación web JSF, faces-config.xml y web.xml.

El contenido de los mismos se detalla a continuación:
Fichero web.xml con el contenido mínimo para hacer funcionar nuestra aplicación JSF. El PROJECT_STAGE no es necesrio definirlo, pero de esta manera tendremos más información sobre fallos y otros asusntos durante el desarrollo.

  JSF2-inicio
  
    index.jsp
  
  
    Faces Servlet
    javax.faces.webapp.FacesServlet
    1
  
  
    Faces Servlet
    *.jsf
  
  
    javax.faces.PROJECT_STAGEDevelopment


Este fichero faces-config.xml debe estar siempre presente aunque no tenga ningún contenido, como es nuestro caso.




Para probar nuestra aplicación solo nos queda añadir un par de páginas estáticas con algo de contenido y ver que todo funciona correctamente.
Bajo la carpeta WebContent de nuestro proyecto añadimos los siguientes ficheros:

index.jsp
<% response.sendRedirect("hello.jsf"); %>

hello.xhtml
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html">



	Hola Mundo JSF!



	

		

Hola Mundo JSF!

</html>

Ahora solo nos queda desplegar la aplicación en nuestro Tomcat y usar cualquier navegador para acceder a http://localhost:3080/JSF2-inicio/ para lanzar nuestra aplicación JSF. El nombre de host y puerto dependerá de la configuración que cada uno tenga en su máquina.

A continuación os dejo unos enlaces para descargar este proyecto inicial en blanco ya montado, que nos servirá como base en el resto de capítulos del tutorial. JSF-inicio

Y las librerías JSF y JSTL que incluimos en el punto 4. Librerías

jueves, 5 de diciembre de 2013

JSF vs MVC usando servlet + RequestDispatcher. Ventajas y desventajas

0 comentarios
En esta entrada, y antes de entrar propiamente en materia, vamos a repasar algunas de las características de JSF que lo hacen una buena alternativa a usar en nuestras aplicaciones web para implementar el modelo MVC

Ventajas de JSF vs MVC usando RequestDispatcher

  • Controles de GUI personalizados --> JSF proporciona una serie de APIs y etiquetas personalizadas para crear formularios HTML con interfaces complejas
  • Manejo de eventos --> JSF hace fácil diseñar código Java que se invoca cuando se envían los formularios. El código puede responder a botones especiales, cambios de un valor particular, selecciones del usuario y más
  • Beans manejados --> En JSF podemos usar property="*" con jsp:setProperty para generar un bean basado en los parámetros de la request. JSF extiende esta capacidad y añade otras utilidades para simplificar el tratamiento de los parámetros de la request
  • Soporte AJAX integrado --> Podemos usar jQuery y otras librerías con servlets y JSPs, pero JSF utiliza AJAX sin necesidad de programación explícita Javascript y usando etiquetas muy simples.
  • Conversión y validación de campos de formularios --> JSF tiene incorporadas capacidades para comprobar que los valores de los formularios están en el formato requerido y para convertir desde cadenas de texto a otro mucho tipos de datos. Si faltan los valores o tienen un formato no apropiado, el formulario se puede repintar autómaticamente con mensajes de error y con los valores introducidos anteriormente.
  • Plantillas de página --> Aunque JSP tiene jsp:include para reutilizar contenido, JSF tiene un sistema completo de plantillas que permite construir páginas que comparten disposición o contenido.
  • Configuración centralizada basada en archivos --> Muchos de los valores de JSF se representan en ficheros XML o de propiedades. Esto implica que muchos cambios se pueden hacer sin necesidad de recompilar el código Java y que se pueden hacer cambios generales en la aplicación modificando un único fichero.
  • Enfoque coherente --> JSF fomenta el uso de MVC en toda la aplicación

DesVentajas de JSF vs MVC usando RequestDispatcher

  • Mayor curva de aprendizaje --> Para usar MVC con el RequestDispatcher estándar solo necesitamos conocer JSP estándar y el API servlet. Para usar MVC con JSF necesitas conocer el API servlet y un framework completo bastante complejo. Otro ejemplo, para añadir funcionalidad AJAX sobre una aplicación existente es mucho más fácil y rápido usar jQuery que convertir la aplicación a JSF2.
  • Peor documentación --> Comparada con los servlet estándar y la API JSP, JSF no tiene apenas documentación online disponible y clara.
  • Menos transparente --> En las aplicaciones JSF pasan muchas más cosas que se escapan de nuestro alcance que en una aplicación web java normal. Por esto las aplicaciones JSF son más difíciles de entender y más difícil de testear y optimizar.
  • El hecho de fomentar el uso de MVC hace muy difícil usar otro enfoque

Esto nos da algo de visión de conjunto de las bondades y debilidades de JSF frente a la implementación de un modelo MVC usando el core de Java, aunque por supuesto habría que entrar a valorar otra muchas alternativas y frameworks como:

  • Spring MVC
  • Apache Wicket
  • Apache Tapestry
  • jMaki
  • y otros tantos...

martes, 26 de noviembre de 2013

¿Que es JSF (Java Server Faces)?

0 comentarios
En esta nueva etapa vamos a adentrarnos en el uso de otra tecnología Java, se trata de JSF, cuya última versión, JSF 2.2 introduce soporte a HTML5, Faces Flow, Stateless views y Resource library contracts.

JavaServer Faces (JSF) es un framework MVC que facilita la construcción de la interfaz de usuario en aplicaciones web mediante el uso de componentes reutilizables.
JSF define una serie de componentes estándar y proporciona una API para el desarrollo de componentes personalizados, además de permitir extender los componentes estándar ya existentes.

Beneficios

JSF minimiza el esfuerzo de crear y mantener aplicaciones que corren en un servidor de aplicaciones Java por los siguientes motivos:

  • Proporciona componentes gráficos reusables
  • Hace fácil la transferencia de información entre componentes
  • Mantiene el estado de la interfaz entre peticiones al servidor
  • Permite implementar componentes personalizados
  • Enlaza eventos en la parte cliente con la parte servidora de la aplicación
  • Beans administrados
  • APIs para validar entradas, definir la navegación entre páginas, internacionalización y accesibilidad, etc...

A lo largo de una serie de entradas iremos introduciéndonos y profundizando en el manejo de esta tecnología en proyectos web.

jueves, 3 de mayo de 2012

JTable con imágenes y texto

9 comentarios
Hola a todos y especialmente a Marmoleria Cañones que es la persona que me pidió esta nueva entrada sobre JTable.

En la sección CONSULTAS ¿NECESITAS AYUDA CON TU CÓDIGO? me pedía si podía hacer un nuevo ejemplo con un TableCellRenderer para poder mostrar una imagen y una descripción en un JTable...

Nada más fácil, el TableCellRenderer por defecto que tiene instalado la clase JTable nos renderiza las imágenes sin necesidad de introducir código nuevo. Por lo tanto solo tenemos que pasar un objeto de la clase ImageIcon como contenido de una fila y se pintará automáticamente, siempre que la clase TableCellRenderer sepa que tipo de objeto debe pintar.
Por lo tanto la clave de esto pasa por decir que tipo de objeto se debe pintar, el modelo de datos por defecto de un JTable es el DefaultTableModel, este modelo hace creer al renderer que todos los objetos que debe pintar son Object, por lo que lo único necesario para poder mostrar una imagen es crear una clase que extienda DefaultTableModel y sobreescribir el método que indica que tipo de objeto contiene cada columna.
En el modelo por defecto tenemos:
public Class<?> getColumnClass(int columnIndex) {
 return Object.class;
    }
Y la modificación que debemos hacer:
 private class MyTableModel extends DefaultTableModel {

     public MyTableModel(Object[][] data, Object[] columnNames) {
         super(data, columnNames);
     }

  @Override
  public Class<?> getColumnClass(int columnIndex) {
                Class<?> clazz = Object.class;
  Object aux = getValueAt(0, columnIndex);
   if (aux != null) {
    clazz = aux.getClass();
   }
   
   return clazz;
  }
  
 }


Es decir, nuestra tabla queda así:
package tableIconoDescripcion;

import java.net.URL;

import javax.swing.ImageIcon;
import javax.swing.JTable;
import javax.swing.table.DefaultTableModel;

public class Table extends JTable {

 public Table() {
  super();
  String [] columnNames = new String[]{ "Nombre", "Icono", "Descripción"};
        Object [][] data      = new Object[][]{
          {"Angel", createImage("icono1.jpg"), "angelarcosheredia@gmail.com"},
          {"Juan", createImage("icono2.jpg"), "juan@gmail.com", false},
          {"Ana", createImage("icono3.jpg"), "ana@hotmail.com", false}
          };
        MyTableModel model = new MyTableModel(data, columnNames);
        this.setRowHeight(100);
        this.setModel(model);
 }

 /**
  * Para recuperar una imagen de un archivo...
  * @param path Ruta de la imagen relativa al proyecto
  * @return una imagen
  */
 public ImageIcon createImage(String path) {
  URL imgURL = getClass().getResource(path);
     if (imgURL != null) {
         return new ImageIcon(imgURL);
     } else {
         System.err.println("Couldn't find file: " + path);
         return null;
     }
 }

 private class MyTableModel extends DefaultTableModel {

     public MyTableModel(Object[][] data, Object[] columnNames) {
         super(data, columnNames);
     }

  @Override
  public Class<?> getColumnClass(int columnIndex) {
   Class<?> clazz = Object.class;
   Object aux = getValueAt(0, columnIndex);
   if (aux != null) {
    clazz = aux.getClass();
   }

   return clazz;
  }

 }

}


Este sería el resultado obtenido:



Y para lanzar la aplicación simplemente ejecutamos este código.
package tableIconoDescripcion;

import javax.swing.JFrame;
import javax.swing.JScrollPane;


public class Main {

 private static void createAndShowGUI() {
  JFrame frame = new JFrame("Componentes Swing en JTable");

  final Table table = new Table();

  // La añadimos a un scroll para mostrar la cabecera
  JScrollPane scroll = new JScrollPane(table);

  frame.add(scroll);
  frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
     frame.setSize(800, 200);
     frame.setVisible(true);
 }

 public static void main(String[] args) {

  javax.swing.SwingUtilities.invokeLater(new Runnable() {
   public void run() {
    createAndShowGUI();
   }
  });
 }

}

Eso es todo, para cualquier otra aclaración, no dudéis en contactar conmigo.

Un saludo

jueves, 19 de abril de 2012

Validable JTextField, espresiones regulares en Java

1 comentarios
Hola compañeros, en la mayoría de los formularios de nuestras aplicaciones nos vemos en la necesidad de validar las entradas del usuario.

Esto en primer lugar puede parecer una tarea tediosa, sin embargo, vamos a ver como realizarla de una manera fácil.

Expresiones regulares en Java

Para ello nos valdremos del uso de expresiones regulares. Las expresiones regulares son una manera de describir un conjunto de cadenas de texto que comparten características comunes. Se pueden usar para buscar, editar o manipular textos.
El soporte para las expresiones regulares en java lo proporciona la API java.util.regex.
Las clases principales de este paquete que vamos a utilizar son la clase Pattern y la clase Matcher.

La clase Pattern proporciona una versión compilada de una expresión regular, no tiene constructor público por lo que para usarlo deberemos invocar a uno de sus métodos estáticos compile.
Un objeto Matcher se encarga de interpretar el patrón y realiza operaciones buscando coincidencias en una cadena introducida. Al igual que la clase Pattern, no dispone de constructores públicos por lo que para obtener una instancia invocaremos al método matcher en un objeto Pattern.

Un ejemplo del uso básico de estas clases lo presentamos a continuación, las utilizaremos para buscar cadenas que sean exactamente “a”, “b” o “c”.
String regEx = "[abc]";
  Pattern pattern = Pattern.compile(regEx);
  Matcher matcher = pattern.matcher("a");
  if (matcher.matches()) {
   System.out.println("Se encontró la cadena buscada");
  }

Siguiendo con el tema de los formularios en Swing, podemos utilizar expresiones de este tipo para crear un componente de texto que valide la entrada.
Para ello crearemos una clase que hereda de JTextField y que nos permite pasar una expresión regular al constructor para validar la entrada cada vez que se pulse una tecla. Nuestro componente quedaría así:

package validableJTextfield;

import java.awt.Color;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.swing.BorderFactory;
import javax.swing.JTextField;
import javax.swing.border.Border;

public class ValidableTextField extends JTextField {

 private Pattern pattern;

 private Border wrongBorder = BorderFactory.createLineBorder(Color.RED);
 private Border defaultBorder;

 /**
  * Constructor.
  * @param regEx Expresión regular para evaluar
  */
 public ValidableTextField(String regEx) {
  super();
  this.defaultBorder = this.getBorder();
  this.setColumns(15);
  this.pattern = Pattern.compile(regEx);
  this.addKeyListener(new KeyListener() {

   @Override
   public void keyTyped(KeyEvent e) { }

   @Override
   public void keyReleased(KeyEvent e) {
    validateText();
   }

   @Override
   public void keyPressed(KeyEvent e) { }
  });
 }

 private void validateText() {
  Matcher matcher = pattern.matcher(this.getText());
  if (!matcher.matches()) {
   this.setBorder(wrongBorder);
  } else {
   this.setBorder(defaultBorder);
  }
 }

}

Os dejo un ejemplo de la construcción de las expresiones regulares más comunes:

[abc]                     “a”, “b”, o “c” (simple)
[^abc]                   Cualquier carácter excepto “a”, “b”, o “c” (negación)
[a-zA-Z]               De la “a” a la “z”, o de la “A” a la “Z”, ambas inclusive(rango)
[a-d[m-p]]            De la “a” a la “d”, o de la “m” a la “p”: [a-dm-p] (union)
[a-z&&[def]]        “d”, “e”, o “f” (intersección)
[a-z&&[^bc]]       “a” a la “z”, excepto “b” y “c”: [ad-z] (resta)
[a-z&&[^m-p]]     “a” a la “z”, pero no de la “m” a la “p”: [a-lq-z] (resta)

Y algunos atajos.
.                            Cualquier carácter.
\d                          Un dígito: [0-9]
\D                         Dígitos no: [^0-9]
\s                          Un espacio en blanco: [ \t\n\x0B\f\r]
\S                         Cualquiera excepto un espacio en blanco: [^\s]
\w                         Un carácter alfanumérico: [a-zA-Z_0-9]
\W                        Un carácter no de palabra: [^\w]

Veamos algunos ejemplos del uso de estar expresiones en nuestro componente, crearemos 4 ValidableTextField y le asignaremos a cada uno una expresión regular distinta para obtener algo como esto:


package validableJTextfield;

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;


public class Main {

 private static void createAndShowGUI() {
  JFrame frame = new JFrame("Validable JTextField");

  JPanel container = new JPanel();

  JLabel labelLetters = new JLabel("Solo letras");
  ValidableTextField textFieldLetters = new ValidableTextField("[a-zA-Z]*");

  JLabel labelNumbers = new JLabel("Solo números");
  ValidableTextField textFieldNumbers = new ValidableTextField("[0-9]*");

  JLabel labelLetters3 = new JLabel("Tres caracteres alfanuméricos");
  ValidableTextField textFieldLetters3 = new ValidableTextField("[a-zA-Z0-9]{3}");

  JLabel labelLetters35 = new JLabel("Entre 3 y 5 caracteres alfanuméricos");
  ValidableTextField textFieldLetters35 = new ValidableTextField("[a-zA-Z0-9]{3,5}");

  container.add(labelLetters);
  container.add(textFieldLetters);

  container.add(labelNumbers);
  container.add(textFieldNumbers);

  container.add(labelLetters3);
  container.add(textFieldLetters3);

  container.add(labelLetters35);
  container.add(textFieldLetters35);

     frame.getContentPane().add(container);
     frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
     frame.setSize(225, 250);
     frame.setVisible(true);
 }

 public static void main(String[] args) {

  javax.swing.SwingUtilities.invokeLater(new Runnable() {
   public void run() {
    createAndShowGUI();
   }
  });
 }
}

Y eso es todo por hoy si os han quedado cuestiones sin resolver sobre este tema, no dudéis en utilizar la página de Consultas ¿Necesitas ayuda con tu código? Para poneros en contacto conmigo.

viernes, 23 de marzo de 2012

Escalar imagen con Swing, Zoom a una imagen

3 comentarios
Vamos a ver como agrandar o reducir el zoom de una imagen con Swing. Esta entrada es una sugerencia de un compañero, por lo que aprovecho para deciros que si tenéis alguna sugerencia sobre lo que os gustaría que escribiésemos no tenéis más que decirlo.

Antes de explicar como lo hemos hecho, informamos de que desde el JDK1.1 la clase Image tiene un método de conveniencia llamado getScaledInstace() que devolverá una versión a escala de la imagen de acuerdo a las dimensiones proporcionadas.

Esta método además acepta un "hint" de los 5 que exponemos a continuación:

  • SCALE_REPLICATE: Mucho rendimiento, poca calidad, podemos obtener imágenes pixeladas
  • SCALE_FAST: Es sinónimo de SCALE_REPLICATE.
  • SCALE_AREA_AVERAGING: Es más lento que los anteriores pero proporciona más calidad.
  • SCALE_SMOOTH: Es sinónimo de SCALE_AREA_AVERAGING.
  • SCALE_DEFAULT: Tomaremos el valor por defecto, que será SCALE_FAST.


Más adelante con la introducción del JDK1.2, se nos ofrecieron clases como la BufferredImage y Graphics2D y unos “hint” más flexibles gracias a RenderingHints. A diferencia de los antiguos "hints" estos nos proporcionan un mayor control sobre el tratamiento de las imágenes cuando llamamos a Graphics2D.drawImage(). Estas claves ya las explicamos con anterioridad por lo que si queréis saber más sobre esto es conveniente que reviséis la entrada sobre RenderingHints que escribimos con anterioridad.

RenderingHints.KEY_INTERPOLATION:

  • VALUE_INTERPOLATION_NEAREST_NEIGHBOR: Mucho rendimiento, pero baja calidad.
  • VALUE_INTERPOLATION_BILINEAR: Es algo más lento pero proporciona mayor calidad.
  • VALUE_INTERPOLATION_BICUBIC: Es similar a BILINEAR pero utiliza más muestras a la hora de filtrar las imágenes y suele ofrecer más calidad.

RenderingHints.KEY_RENDER_QUALITY: 

  • VALUE_RENDER_SPEED: Significa “Prefiero velocidad antes que calidad, pero lo dejo en tus manos”, es sinónimo de VALUE_INTERPOLATION_NEAREST_NEIGHBOR.
  • VALUE_RENDER_QUALITY: “Prefiero calidad antes que velocidad, lo dejo en tus manos”, sería como usar VALUE_INTERPOLATION_BILINEAR.
  • VALUE_RENDER_DEFAULT: Significa “no me importa, hazlo sin más”, sinónimo de VALUE_RENDER_SPEED.


Usando RenderintHints, BufferedImage y Graphics2D obtendremos mejores resultados, más velocidad y calidad, por lo que será el método que utilizaremos en nuestro ejemplo.

Pasamos a mostrar el código y dado que es algo complejo explicaremos que hace cada método. En principio simplemente crearemos un panel con un par de botones para aumentar y reducir el nivel de zoom aplicado, y un panel donde pintaremos nuestra imagen con el zoom deseado.
Además de esto necesitamos algún método que nos transforme nuestro objeto Image a BufferredImage, para ello nos serviremos de toBufferredImage(Image imagen) y hasAlpha(Image imagen). Este último nos dirá si la imagen contiene píxeles transparentes ya que la manera de crear la imagen en este caso será distinta.

Después de esto solo nos queda sobreescribir el método paintComponent() del panel donde mostraremos la imagen para utilizar los parámetros de nivel de zoom y claves de renderizado que elegimos y ya lo tendremos todo armado.

package imageZoom;

import java.awt.BorderLayout;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.HeadlessException;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.Transparency;
import java.awt.event.ActionEvent;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.PixelGrabber;
import java.net.URL;

import javax.swing.AbstractAction;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JPanel;

public class ZoomPanel extends JPanel {

 private BufferedImage originalImage;

 public ZoomPanel(String pathToImage) {
  originalImage = toBufferedImage(createImage(pathToImage).getImage());
  initPanel();
 }

 private void initPanel(){
  final Zoom zoom = new Zoom();

  JButton plus = new JButton(new AbstractAction("+") {

   @Override
   public void actionPerformed(ActionEvent e) {
    zoom.increaseZoom();
   }
  });
  JButton minus = new JButton(new AbstractAction("-") {

   @Override
   public void actionPerformed(ActionEvent e) {
    zoom.decreaseZoom();
   }
  });


  this.setLayout(new BorderLayout());
  this.add(minus, BorderLayout.WEST);
  this.add(zoom, BorderLayout.CENTER);
  this.add(plus, BorderLayout.EAST);

 }

 private class Zoom extends JPanel {

  private float xScaleFactor = 1;
  private float yScaleFactor = 1;

  public void increaseZoom() {
   xScaleFactor+= 0.1;
   yScaleFactor+= 0.1;
   repaint();
  }

  public void decreaseZoom() {
   xScaleFactor-= 0.1;
   yScaleFactor-= 0.1;
   repaint();
  }


  @Override
  public void paintComponent(Graphics g) {
   Graphics2D g2 = (Graphics2D) g;
   int newW = (int) (originalImage.getWidth() * xScaleFactor);
   int newH = (int) (originalImage.getHeight() * yScaleFactor);
   g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
   g2.clearRect(0, 0, getWidth(), getHeight());
   g2.drawImage(originalImage, 0, 0, newW, newH, null);
  }

 }

 private ImageIcon createImage(String path) {
  URL imgURL = getClass().getResource(path);
     if (imgURL != null) {
         return new ImageIcon(imgURL);
     } else {
         System.err.println("Couldn't find file: " + path);
         return null;
     }
 }


 /**
  * Obtiene una BufferedImage a partir de una Image.
  * return BufferredImage bi
  */
 public static BufferedImage toBufferedImage(Image image) {
  if (image instanceof BufferedImage) {
   return (BufferedImage) image;
  }

  image = new ImageIcon(image).getImage();

  boolean hasAlpha = hasAlpha(image);

  BufferedImage bimage = null;
  GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
  try {

   int transparency = Transparency.OPAQUE;
   if (hasAlpha) {
    transparency = Transparency.BITMASK;
   }

   GraphicsDevice gs = ge.getDefaultScreenDevice();
   GraphicsConfiguration gc = gs.getDefaultConfiguration();
   bimage = gc.createCompatibleImage(image.getWidth(null), image.getHeight(null), transparency);
  } catch (HeadlessException e) {
   // The system does not have a screen
  }

  if (bimage == null) {
   int type = BufferedImage.TYPE_INT_RGB;
   if (hasAlpha) {
    type = BufferedImage.TYPE_INT_ARGB;
   }
   bimage = new BufferedImage(image.getWidth(null), image.getHeight(null), type);
  }

  Graphics g = bimage.createGraphics();

  g.drawImage(image, 0, 0, null);
  g.dispose();

  return bimage;
 }

 /**
  * Devuelve true si una imagen tiene pixeles transparentes.
  *
  * @param image a testear
  * @return true si tiene pixeles transparentes
  */
 public static boolean hasAlpha(Image image) {

  if (image instanceof BufferedImage) {
   BufferedImage bimage = (BufferedImage) image;
   return bimage.getColorModel().hasAlpha();
  }

  PixelGrabber pg = new PixelGrabber(image, 0, 0, 1, 1, false);
  try {
   pg.grabPixels();
  } catch (InterruptedException e) {
  }

  ColorModel cm = pg.getColorModel();
  return cm.hasAlpha();
 }

}

Lo que obtendríamos sería algo similar a esto que os muestro.




Para lanzar la aplicación de ejemplo, lanzar el código siguiente sustituyendo "images/ER.jpg" por una imagen de vuestro equipo y lo podréis ver en acción.

package imageZoom;

import java.awt.Dimension;

import javax.swing.JFrame;

public class Launch {

 private static void createAndShowGUI() {
  JFrame frame = new JFrame("Zoom a imagen");
  frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

  ZoomPanel zoom = new ZoomPanel("images/ER.jpg");

  frame.add(zoom);
  frame.setPreferredSize(new Dimension(485,300));

  frame.pack();
  frame.setVisible(true);
 }

 public static void main(String[] args) {
  // Schedule a job for the event dispatch thread:
  // creating and showing this application's GUI.
  javax.swing.SwingUtilities.invokeLater(new Runnable() {
   public void run() {
    createAndShowGUI();
   }
  });
 }

}


Saludos y hasta pronto, ya sabéis que se agradecen los comentarios.

miércoles, 21 de marzo de 2012

GradientPaint demo, ejemplos del uso de gradientes en java

4 comentarios
Hoy hablaremos de los gradientes o degradados de color tan usados como relleno del fondo en multitud de situaciones.
Para ponernos en situación explicaremos primero que es un gradiente.
Básicamente un gradiente nos permite rellenar una forma con una degradación lineal del color. Es decir, si tenemos un punto A con un color X y un punto B con otro color Y, el espacio entre ambos puntos va cambiando progresivamente de color desde el color X al color Y.

Una vez aclarado esto, paso a enseñaros unos ejemplos del uso de gradientes. Para crearlos haremos uso de la clase GradientPaint que nos permite aplicar esta degradación de color entre 2 puntos que explicábamos arriba.
Los constructores de esta clase nos permiten especificar los 2 puntos y los 2 colores que utilizaremos para crear el gradiente.

GradientPaint gp = new GradientPaint(Point2D pt1, Color color1, Point2D pt2, Color color2, boolean cyclic);

Estos puntos podemos especificarlos mediante un objeto Point o bien mediante coordenadas:

GradientPaint gp = new GradientPaint(float x1, float y1, Color color1, float x2, float y2, Color color2, boolean cyclic);

El último parámetro indica si el gradiente debe repetirse en todo el espacio de manera cíclica o si fuera del espacio contenido entre ambos puntos el color debe ser sólido.

Pasamos ahora a ver algunos ejemplos del uso de gradientes.

Gradiente horizontal

package gradientPaintDemo;

import java.awt.Color;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;

import javax.swing.JPanel;

public class HorizontalGradientPanel extends JPanel {

 @Override
 public void paint(Graphics g) {
  Graphics2D g2d = (Graphics2D) g;

  /*
   * Para crear un gradiente horizontal,
   * las coordenadas a usar seran del {0,0} al {anchura del componente, 0}
   */
  GradientPaint horizontalGradient = new GradientPaint(0, 0, Color.RED, getWidth(), 0, Color.BLUE);
  g2d.setPaint(horizontalGradient);

  g2d.fillRect(0, 0, getWidth(), getHeight());

 }

}

Gradiente vertical

package gradientPaintDemo;

import java.awt.Color;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;

import javax.swing.JPanel;

public class VerticalGradientPanel extends JPanel {

 @Override
 public void paint(Graphics g) {
  Graphics2D g2d = (Graphics2D) g;

  /*
   * Para crear un gradiente vertical,
   * las coordenadas a usar seran del {0,0} al {0, altura del componente}
   */
  GradientPaint verticalGradient = new GradientPaint(0, 0, Color.GREEN, 0, getHeight(), Color.YELLOW);
  g2d.setPaint(verticalGradient);

  g2d.fillRect(0, 0, getWidth(), getHeight());

 }

}

Gradiente diagonal


package gradientPaintDemo;

import java.awt.Color;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;

import javax.swing.JPanel;

public class DiagonalGradientPanel extends JPanel {

 @Override
 public void paint(Graphics g) {
  Graphics2D g2d = (Graphics2D) g;

  /*
   * Para crear un gradiente diagonal,
   * las coordenadas a usar seran del {0,0} al {anchura del componente / ,
   * altura del componente / 2}
   */
  GradientPaint diagonalGradient = new GradientPaint(0, 0, Color.GRAY, getWidth() / 2, getHeight() / 2, Color.ORANGE);
  g2d.setPaint(diagonalGradient);

  g2d.fillRect(0, 0, getWidth(), getHeight());

 }

}

Gradiente redondo
Este degradado es un tanto especial, no usaremos la clase GradientPaint para crearlo, en lugar de eso implementaremos nuestro propio Paint. La interfaz Paint define como generar los patrones de color usados para operar con Graphics2D. Este ejemplo no es tan fácil de entender como los demás que exponemos en esta entrada, pero podéis reutilizar el código a placer.
package gradientPaintDemo;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.PaintContext;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.ColorModel;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;

import javax.swing.JPanel;

public class RoundGradientPanel extends JPanel {

 @Override
 public void paint(Graphics g) {
  Graphics2D g2d = (Graphics2D) g;

  /* Para crear un gradiente circular. */
  RoundGradient roundGradient = new RoundGradient(getWidth() / 2, getHeight() / 2, Color.PINK, new Point(0, 100), Color.CYAN);
  g2d.setPaint(roundGradient);

  g2d.fillRect(0, 0, getWidth(), getHeight());
 }

 private class RoundGradient implements Paint {

  protected Point2D point;

  protected Point2D mRadius;

  protected Color color1, color2;

  public RoundGradient(double x, double y, Color color1, Point2D radius, Color color2) {
   if (radius.distance(0, 0) <= 0) {
    throw new IllegalArgumentException("Radius must be greater than 0.");
   }
   point = new Point2D.Double(x, y);
   this.color1 = color1;
   mRadius = radius;
   this.color2 = color2;
  }

  @Override
  public int getTransparency() {
   int a1 = color1.getAlpha();
   int a2 = color2.getAlpha();
   return (((a1 & a2) == 0xff) ? OPAQUE
          : TRANSLUCENT);
  }

  @Override
  public PaintContext createContext(ColorModel cm, Rectangle deviceBounds, Rectangle2D userBounds,
    AffineTransform xform, RenderingHints hints) {
   Point2D transformedPoint = xform.transform(point, null);
   Point2D transformedRadius = xform.deltaTransform(mRadius, null);
   return new RoundGradientContext(transformedPoint, color1, transformedRadius, color2);
  }

 }

 private class RoundGradientContext implements PaintContext {
  protected Point2D mPoint;

  protected Point2D mRadius;

  protected Color color1, color2;

  public RoundGradientContext(Point2D p, Color c1, Point2D r, Color c2) {
   mPoint = p;
   color1 = c1;
   mRadius = r;
   color2 = c2;
  }

  public void dispose() {
  }

  public ColorModel getColorModel() {
   return ColorModel.getRGBdefault();
  }

  public Raster getRaster(int x, int y, int w, int h) {
   WritableRaster raster = getColorModel().createCompatibleWritableRaster(w, h);

   int[] data = new int[w * h * 4];
   for (int j = 0; j < h; j++) {
    for (int i = 0; i < w; i++) {
     double distance = mPoint.distance(x + i, y + j);
     double radius = mRadius.distance(0, 0);
     double ratio = distance / radius;
     if (ratio > 1.0)
      ratio = 1.0;

     int base = (j * w + i) * 4;
     data[base + 0] = (int) (color1.getRed() + ratio * (color2.getRed() - color1.getRed()));
     data[base + 1] = (int) (color1.getGreen() + ratio * (color2.getGreen() - color1.getGreen()));
     data[base + 2] = (int) (color1.getBlue() + ratio * (color2.getBlue() - color1.getBlue()));
     data[base + 3] = (int) (color1.getAlpha() + ratio * (color2.getAlpha() - color1.getAlpha()));
    }
   }
   raster.setPixels(0, 0, w, h, data);

   return raster;
  }
 }

}


Gradiente horizontal cíclico

package gradientPaintDemo;

import java.awt.Color;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;

import javax.swing.JPanel;

public class HorizontalCyclicGradientPanel extends JPanel {

 @Override
 public void paint(Graphics g) {
  Graphics2D g2d = (Graphics2D) g;

  /*
   * Para crear un gradiente horizontal cíclico, simplemente especificar
   * la anchura del color
   * y crear el gradiente con la propiedad cíclica a "true"
   */
  GradientPaint diagonalGradient = new GradientPaint(0, 0, Color.RED, 20, 0, Color.BLUE, true);
  g2d.setPaint(diagonalGradient);

  g2d.fillRect(0, 0, getWidth(), getHeight());

 }

}

Gradiente vertical cíclico

package gradientPaintDemo;

import java.awt.Color;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;

import javax.swing.JPanel;

public class VerticalCyclicGradientPanel extends JPanel {

 @Override
 public void paint(Graphics g) {
  Graphics2D g2d = (Graphics2D) g;

  /*
   * Para crear un gradiente horizontal cíclico, simplemente especificar
   * la altura del color
   * y crear el gradiente con la propiedad cíclica a "true"
   */
  GradientPaint diagonalGradient = new GradientPaint(0, 0, Color.GREEN, 0, 20, Color.YELLOW, true);
  g2d.setPaint(diagonalGradient);

  g2d.fillRect(0, 0, getWidth(), getHeight());

 }

}

Este sería el resultado de juntar todos estos ejemplos en un panel.


Para ver todo esto en funcionamiento, aquí os dejo la clase que carga la aplicación.

package gradientPaintDemo;

import java.awt.GridLayout;

import javax.swing.JFrame;
import javax.swing.JPanel;

public class GradientsDemo extends JPanel {

 private static void createAndShowGUI() {
  // Crea y prepara la ventana.
  JFrame frame = new JFrame("Ejemplos de gradientes");
  frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

  frame.getContentPane().setLayout(new GridLayout(3, 3, 10, 10));

  frame.getContentPane().add(new HorizontalGradientPanel());
  frame.getContentPane().add(new VerticalGradientPanel());
  frame.getContentPane().add(new DiagonalGradientPanel());
  frame.getContentPane().add(new RoundGradientPanel());
  frame.getContentPane().add(new HorizontalCyclicGradientPanel());
  frame.getContentPane().add(new VerticalCyclicGradientPanel());

  frame.setSize(400, 400);
  frame.setVisible(true);
 }

 public static void main(String[] args) {
  javax.swing.SwingUtilities.invokeLater(new Runnable() {
   public void run() {
    createAndShowGUI();
   }
  });

 }
}

Eso es todo por hoy, espero que lo aquí expuesto os haya sido útil de alguna manera.

Un saludo

miércoles, 7 de marzo de 2012

Java GUI, Swing o AWT

0 comentarios
Muchas veces ante la necesidad de crear una interfaz gráfica nos vemos en la necesidad de decidir si utilizar Swing o AWT. Bien, veamos similitudes y diferencias entre uno y otro.

Un repaso a AWT

AWT son las siglas de Abstract Window ToolKit, las herramientas originales de Java para crear interfaces gráficas.
Es una librería GUI portable para aplicaciones autónomas y/o applets, proporciona la conexión entre nuestra aplicación y el GUI nativo.

Las prestaciones de AWT incluyen:

  • Un amplio grupo de componentes de usuario
  • Un modelo de manejo de eventos robusto
  • Herramientas gráficas y de imágenes (clases de Formas, colores y fuentes)
  • Manejadores de diseño que no dependen del tamaño de pantalla o resolución
  • Clases de transferencia de datos, para copiar-pegar a través del portapapeles de la plataforma

Los componentes de AWT dependen de componentes de código nativo, por lo que a los componentes se les suele llamar “heavyweight components” (componentes pesados).
AWT está pensado para aplicaciones que corran en navegadores antiguos y definido con los mínimos de cualquier aplicación, es por esto que no incluye componentes complejos como pueden ser tablas, vistas de árbol, barras de progreso y otros.

Un vistazo a Swing


Swing implementa un juego de componentes construidos sobre AWT y además proporciona un “look and feel” conectable/intercambiable. Está escrito 100% en código Java y basado en el framework Lightweight UI de la JDK 1.1.

Sus características incluyen:

  • Todas las prestaciones de AWT
  • Componentes 100% Java de las versiones de los componentes de AWT
  • Un rico conjunto de componentes de alto nivel (listas en árbol, paneles de pestañas, etc...)
  • Un diseño Java puro, no depende de terceros.
  • Look and feel intercambiable

Al no depender de componentes de la plataforma, a los componentes de Swing se les llama “lightweight components”.
Swing es parte de las JFC (Java Foundation Classes), se creó para solucionar muchas de las limitaciones de AWT. Swing se creó como un bien diseñado, flexible y poderoso juego de herramientas GUI.

Si nos paramos a comparar sus componentes, vemos que son bastante similares. Os dejo una tabla con la correspondencia de algunos componentes AWT con su análogo Swing.

AWTSwing
AppletJApplet
FrameJFrame
WindowJWindow
DialogJDialog
ComponentJComponent
PanelJPanel
ButtonJButton
CanvasPanel
CheckboxJCheckBox o JRadioButton
ChoiceJComboBox
LabelJLabel
ListJList
TextAreaJTextArea
TextFieldJTextField
MenuJMenu
MenuItemJMenuItem

Como conclusión, podríamos decir que si queremos que nuestra aplicación corra en cualquier entorno, independientemente de la antigüedad del mismo, deberíamos usar AWT; ahora bien, si lo que queremos es una herramienta potente, flexible, usar tablas y otros componentes complejos; y completamente adaptable a nuestras necesidades, desde luego nuestra decisión está clara, usaremos la tecnología Swing.

Como punto final vamos a ver como crear un programa sencillo, un panel al que añadimos 2 botones, usando ambas tecnologías para poder ver las diferencias.

Ejemplo con AWT
package AWTvsSwing;

import java.applet.Applet;
import java.awt.Button;
import java.awt.FlowLayout;

public class MainAWT extends Applet {

 @Override
 public void init() {

        Button button1 = new Button();

        button1.setLabel("My Button 1");

        Button button2 = new Button("My Button 2");

        setLayout(new FlowLayout());

        add(button1);
        add(button2);
 }

}


Ejemplo con Swing
package AWTvsSwing;

import java.awt.FlowLayout;

import javax.swing.JApplet;
import javax.swing.JButton;

public class MainSwing extends JApplet {

 @Override
 public void init() {
  JButton button1 = new JButton();

        button1.setText("My Button 1");

        JButton button2 = new JButton("My Button 2");

        setLayout(new FlowLayout());

        add(button1);
        add(button2);
 }

}

lunes, 20 de febrero de 2012

Bordes Java Swing, BorderFactory y ejemplos

1 comentarios
A cualquier JComponent se le puede poner uno o mas bordes. Un objeto Border sabe como dibujar los límites de un componente Swing y además de para dibujar lineas o cualquier otro aspecto visual, se pueden utilizar por ejemplo para dar mayor tamaño a los componentes.

Para establecer un borde a un componente debemos usar el método setBorder(Border border).
En otro tutorial ya enseñamos con anterioridad como definir nuestros propios bordes, hoy os enseñaré el uso de la clase BorderFactory que nos permite crear la mayoría de los tipos de bordes que Swing puede crear.

Veamos unos ejemplos de como crear los bordes mas comunes y las opciones que tenemos. En el código podéis leer comentarios sobre que tipos de bordes podemos crear con BorderFactory.

package borderFactory;

import java.awt.Color;
import java.awt.Container;
import java.net.URL;

import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.border.BevelBorder;
import javax.swing.border.Border;
import javax.swing.border.EtchedBorder;
import javax.swing.border.TitledBorder;

public class BorderSamples extends JPanel {

 /**
  * Constructor.
  */
 public BorderSamples() {
  super();
  addComponents();
 }

 /**
  * Añade los componentes.
  */
 private void addComponents() {

  // Borde vacio con 10px por cada lado
  Border empty = BorderFactory.createEmptyBorder(10, 10, 10, 10);

  /** Bordes simples */
  // Borde de color rojo con grosor de linea de 2px
  Border line = BorderFactory.createLineBorder(Color.RED, 2);
  // Bordes biselado de varios colores
  Border bevelRaised = BorderFactory.createBevelBorder(BevelBorder.RAISED, Color.BLUE, Color.GREEN, Color.BLACK, Color.RED);
  Border bevelLowered = BorderFactory.createBevelBorder(BevelBorder.LOWERED, Color.BLUE, Color.GREEN, Color.BLACK, Color.RED);
  // Bordes grabados de varios colores
  Border etchedRaised = BorderFactory.createEtchedBorder(EtchedBorder.RAISED, Color.RED, Color.GRAY);
  Border etchedLowered = BorderFactory.createEtchedBorder(EtchedBorder.LOWERED, Color.RED, Color.GRAY);

  /** Bordes mate */
  /* En este tipo de bordes podemos elegir el grosor de cada uno de los lados y el color con el que pintarlo*/
  Border matte  = BorderFactory.createMatteBorder(3, 3, 5, 5, Color.ORANGE);
  URL imgURL = getClass().getResource("images/star.png");
  ImageIcon ico = new ImageIcon(imgURL);
  /* Ademas de elegir el grosor podemos establecer una icono como imagen de fondo del borde. */
  Border matteIco = BorderFactory.createMatteBorder(10, 10, 0, 20, ico);

  /** Bordes compuestos */
  /* El TitledBorder añade una etiqueta al borde que utilizamos para crearlo,
   * podemos elegir la posición del texto */
  Border titled = BorderFactory.createTitledBorder(line, "titulo", TitledBorder.CENTER, TitledBorder.TOP, null, Color.BLUE);
  /* Un CompoundBorder lo creamos por la combinación de 2 bordes simples. */
  Border compound = BorderFactory.createCompoundBorder(titled, etchedRaised);

  // Paneles
  JPanel simplePanel = new JPanel();
  JPanel mattePanel = new JPanel();
  JPanel compoundPanel = new JPanel();
  simplePanel.setLayout(new BoxLayout(simplePanel, BoxLayout.Y_AXIS));
  mattePanel.setLayout(new BoxLayout(mattePanel, BoxLayout.Y_AXIS));
  compoundPanel.setLayout(new BoxLayout(compoundPanel, BoxLayout.Y_AXIS));

  createComponent("LineBorder", line, simplePanel);
  createComponent("EmptyBorder", empty, simplePanel);
  createComponent("BevelBorder elevado", bevelRaised, simplePanel);
  createComponent("BevelBorder rebajado", bevelLowered, simplePanel);
  createComponent("EtchedBorder elevado", etchedRaised, simplePanel);
  createComponent("EtchedBorder rebajado", etchedLowered, simplePanel);
  createComponent("MatteBorder", matte, mattePanel);
  createComponent("MatteBorder con icono", matteIco, mattePanel);
  createComponent("TitledBorder", titled, compoundPanel);
  createComponent("CompoundBorder", compound, compoundPanel);

  simplePanel.setBorder(BorderFactory.createTitledBorder("Bordes simples"));
  mattePanel.setBorder(BorderFactory.createTitledBorder("Bordes mate"));
  compoundPanel.setBorder(BorderFactory.createTitledBorder("Bordes compuestos"));
  this.add(simplePanel);
  this.add(mattePanel);
  this.add(compoundPanel);
 }

 /**
  * Crea un panel al que le inserta un label con el texto especificado,
  * le establece el borde deseado y lo inserta
  * en el contenedor que le pasamos como parametro
  * @param text Texto de la etiqueta
  * @param border Borde a establecer
  * @param container Contenedor a usar
  */
 private void createComponent(String text, Border border, Container container) {
  JPanel panel = new JPanel();
  JLabel label = new JLabel(text);
  label.setBorder(border);

  panel.add(label);
  container.add(panel);
 }

}

Este sería el resultado de aplicar a las etiquetas creadas cada uno de los distintos bordes.


Y para lanzar la aplicación de ejemplo.

package borderFactory;

import javax.swing.JFrame;


public class Main {

 private static void createAndShowGUI() {
  JFrame frame = new JFrame("Ejemplos de uso de BorderFactory");

  BorderSamples panel = new BorderSamples();

     frame.getContentPane().add(panel);
     frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
     frame.setSize(350, 450);
     frame.setVisible(true);
 }

 public static void main(String[] args) {

  javax.swing.SwingUtilities.invokeLater(new Runnable() {
   public void run() {
    createAndShowGUI();
   }
  });
 }
}


Por supuesto hay muchas más opciones que explotar, pero esa tarea de investigación la dejo para vosotros :)
Esto es todo por hoy, gracias por leerme y espero haberos sido de ayuda.

jueves, 9 de febrero de 2012

BorderLayout y GridLayout, ejemplos de layouts en java

4 comentarios
Hola a todos, hoy vamos a hablar sobre la manera en la que disponemos los componentes Swing en los contenedores de nuestras aplicaciones. Es aquí donde los LayoutManager entran en juego. Por ejemplo cuando hacemos:

JPanel panel = new JPanel();
for(int i = 0; i < 10; i++) {
 panel.add(new JLabel("Etiqueta " + i));
}
Las etiquetas que estamos añadiendo se disponen en fila de izquierda a derecha, esto es porque al crear un panel, por defecto tiene establecido un FlowLayout. Este dispondrá de esta manera los componentes, con una separación de 10 pixeles entre componentes hasta que no quepan mas componentes en la misma línea, momento en el cual saltará a la siguiente. Pero no debemos limitarnos a la manera de disponer los componentes por defecto, vamos a pasar a explicar dos de los LayoutManager que mas se usan. Estos son: BorderLayout y GridLayout.

BorderLayout 

Una disposición Border, dispone un contenedor ordenando y redimensionando sus componentes para adaptarse a cinco regiones: NORTE, SUR, ESTE, OESTE y CENTRO.
Cada una de estas regiones está definida por constantes propias de la clase BordeLayout:

  • BorderLayout.NORTH 
  • BorderLayout.SOUTH 
  • BorderLayout.EAST 
  • BorderLayout.WEST 
  • BorderLayout.CENTER 
Para utilizarlo, bastará con especificar una de estas constantes como limitación al añadir el componente.

JPanel panel = new JPanel();
//Establecemos el BorderLayout al panel
panel.setLayout(new BorderLayout(10, 10));
panel.add(new JButton(“boton”), BorderLayout.CENTER);
Por conveniencia si no especificamos ninguna de estas constantes el componente se insertará en el centro del contenedor. El constructor BorderLayout(int hgap, int vgap) nos permite especificar el espacio en pixeles que existirá entre los componentes en el contenedor, así por ejemplo para hacer que entre los componentes exista un espacio horizontal de 10 pixeles y otro vertical de 20 pixeles bastaría con:
BorderLayout layout = new BorderLayout(10, 20);
Si no especificamos ningún espacio, por defecto no habrá separación entre los componentes. Veamos un ejemplo real de como usarlo:
package layout.borderLayout;

import java.awt.BorderLayout;

import javax.swing.JButton;
import javax.swing.JPanel;

public class BorderLayoutPanel extends JPanel {

 public BorderLayoutPanel() {
  // establecemos el layout
  this.setLayout(new BorderLayout(5, 10));
  init();
 }

 /**
  * Añadimos componentes
  */
 private void init() {
  JButton north = new JButton("NORTH");
  JButton south = new JButton("SOUTH");
  JButton east = new JButton("EAST");
  JButton west = new JButton("WEST");
  JButton center = new JButton("CENTER");

  this.add(north, BorderLayout.NORTH);
  this.add(south, BorderLayout.SOUTH);
  this.add(east, BorderLayout.EAST);
  this.add(west, BorderLayout.WEST);
  this.add(center, BorderLayout.CENTER);


 }

}
Este código nos creará un panel al que se le establece un BorderLayout con 5 pixeles de separación horizontal entre los componentes y 10 pixeles de separación vertical.
Para verlo en funcionamiento:
package layout.borderLayout;

import javax.swing.JFrame;


public class Main {

 private static void createAndShowGUI() {
  JFrame frame = new JFrame("Ejemplos de uso de layouts");

  BorderLayoutPanel panel = new BorderLayoutPanel();

     frame.getContentPane().add(panel);
     frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
     frame.setSize(400, 400);
     frame.setVisible(true);
 }

 public static void main(String[] args) {

  javax.swing.SwingUtilities.invokeLater(new Runnable() {
   public void run() {
    createAndShowGUI();
   }
  });
 }
}

Y este sería el resultado.



GridLayout

El GridLayout dispone los componentes de un contenedor en una rejilla rectangular. El contenedor se dividirá en rectangulos iguales y en cada uno se colocará uno de los componentes. A la hora de construir este layout debemos especificar el número de filas y columnas que necesitamos para nuestros componentes, para ello usaremos el siguiente constructor GridLayout(int rows, int cols) , además si queremos controlar el espacio que existirá entre los componentes en el contenedor de manera similar a como hicimos con BorderLayout podemos usar este otro: GridLayout(int rows, int cols, int hgap, int vgap). Pasemos a un ejemplo que nos aclaré más las cosas, por ejemplo digamos que queremos disponer 10 JButton en un panel de manera que se distribuyan en 2 filas y 5 columnas, con una separación horizontal de 15 pixeles y una separación vertical de 5 pixeles. Aquí os dejo el código que configuraría el panel:
package layout.gridLayout;

import java.awt.GridLayout;

import javax.swing.JButton;
import javax.swing.JPanel;

public class GridLayoutPanel extends JPanel {

 public GridLayoutPanel() {
  // establecemos el layout
  this.setLayout(new GridLayout(2, 5, 15, 5));
  init();
 }

 /**
  * Añadimos componentes
  */
 private void init() {

  for(int i = 0; i < 10; i++) {
   this.add(new JButton("Boton " +  i));
  }

 }

}
Para verlo en funcionamiento:
package layout.gridLayout;

import javax.swing.JFrame;


public class Main {

 private static void createAndShowGUI() {
  JFrame frame = new JFrame("Ejemplos de uso de layouts");

  GridLayoutPanel panel = new GridLayoutPanel();

     frame.getContentPane().add(panel);
     frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
     frame.setSize(500, 400);
     frame.setVisible(true);
 }

 public static void main(String[] args) {

  javax.swing.SwingUtilities.invokeLater(new Runnable() {
   public void run() {
    createAndShowGUI();
   }
  });
 }
}
Y este sería el aspecto que tendría este contenedor.




Podríamos hablar largo y tendido sobre LayoutManager en Java, pero esto será tema de siguiente artículos.

Un saludo y gracias por leerme.

miércoles, 18 de enero de 2012

JButton, JCheckBox, JComboBox en JTable

11 comentarios

El componente JTable es uno de los mas complejos que nos ofrece el paquete Swing en cuanto a diseño de interfaces se refiere, precisamente por su complejidad también permite una personalización casi absoluta del componente.

La clase JTable sirve para mostrar tablas de datos, permitiendo opcionalmente al usuario editar los datos. Los JTable no contienen ni almacenan datos, simplemente es una manera de mostrarlos.
Los JScrollPane son los componentes que habitualmente sirven como contenedor para los JTable, este automáticamente coloca la cabecera de la tabla en la parte superior de la vista de manera que los nombres de las columnas permanecen visibles cuando hacemos “scroll”.

El encargado de decirnos como se muestra un valor en cada celda es el TableCellRenderer, esta interfaz define el método requerido por un objeto que querría ser dibujado en las celdas de un JTable.
Así mismo, el encargado de decirnos como se comporta cada celda es el TableCellEditor, una interfaz que define el método que cualquier objeto que querría ser editor de valores de un JTable necesita implementar.

Otro de los componentes mas importantes es el TableModel, esta interfaz especifica los métodos que el JTable usará para interrogar a un modelo de datos tabulado. Un JTable puede configurarse para mostrar cuanlquier modelo de datos que implemente esta interfaz con solo un par de líneas de código:

MyTableModel model = new MyTableModel(columnNames, data);
// Establecemos el modelo
JTable table = new JTable(model);

En nuestro ejemplo de hoy, explicamos como mostrar componentes complejos como JCheckBox, JComboBox y JButton en el interior de nuestras tablas.

En primer lugar definiremos nuestro TableModel, en este estableceremos como queremos que nuestra tabla se comporte.

package tablaCompleja.componentes;

import javax.swing.table.AbstractTableModel;

public class MyTableModel extends AbstractTableModel {

 /** Nombre de las columnas. */
 private String[] columnNames;
 /** Datos. */
 private Object[][] data;

 /**
  * Constructor.
  * @param columnNames Nombres de las columnas
  * @param data Datos de la tabla
  */
 public MyTableModel(String[] columnNames, Object[][] data) {
  this.columnNames = columnNames;
  this.data = data;
 }

 @Override
 public String getColumnName(int column) {
  // Nombre de las columnas para la cabecera
  return columnNames[column];
 }

 @Override
 public int getRowCount() {
  // Devuelve el número de filas
  return data != null ? data.length : 0;
 }

 @Override
 public int getColumnCount() {
  // Devuelve el número de columnas
  return columnNames.length;
 }

 /**
  * Nos devolverá la clase que contiene cada columna,
  * es necesario para trabajar correctamente con los componentes
  * que mostraremos en la tabla.
  */
 @Override
 public Class getColumnClass(int columnIndex) {
  Class clazz = Object.class;

  Object aux = getValueAt(0, columnIndex);
  if (aux != null) {
   clazz = aux.getClass();
  }

  return clazz;
 }

 @Override
 public Object getValueAt(int rowIndex, int columnIndex) {
  // Devuelve el valor que se debe mostrar en la tabla en cada celda
  return data[rowIndex][columnIndex];
 }

    @Override
 public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
     // Si queremos que la tabla sea editable deberemos establecer estos valores
     data[rowIndex][columnIndex] = aValue;
     fireTableCellUpdated(rowIndex, columnIndex);
    }

 @Override
 public boolean isCellEditable(int rowIndex, int columnIndex) {
  // Permitimos editar todas las celdas de la tabla
  return true;
 }

 /**
  * Nos servira para limpiar la información de una fila
  * @param row
  */
 public void reset(int row) {

  for (int i = 0; i < data[row].length - 1; i++) {
   // Para las columnas con String
   if (getColumnClass(i) == String.class) {
    setValueAt("", row, i);
   } else if(getColumnClass(i) == Boolean.class) {
    setValueAt(false, row, i);
   }
  }

 }

}

Vamos a crear un JTable personalizado que heredará del JTable original, en esta clase vamos a ver como especificar que renderers y editors usaremos para cada columna dependiendo del tipo de objeto que contenga.
package tablaCompleja;

import javax.swing.DefaultCellEditor;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JTable;

import tablaCompleja.componentes.ButtonCellEditor;
import tablaCompleja.componentes.ButtonCellRenderer;
import tablaCompleja.componentes.MyTableModel;

public class MyTable extends JTable {

 /**
  * Constructor.
  */
 public MyTable() {

  this.setRowHeight(30);

  JComboBox states = new JComboBox(new String[]
                                     {"España",
                                     "Argentina",
                                     "EEUU"}
                                     );

  String [] columnNames = new String[]{ "Nombre", "email", "Fumador", "Nacionalidad", "" };
        Object [][] data      = new Object[][]{
          {"Angel", "angelarcosheredia@gmail.com", false, "Click para elegir", new JButton("Reset")},
          {"Juan", "juan@gmail.com", false, "Click para elegir", new JButton("Reset")},
          {"Ana", "ana@hotmail.com", false, "Click para elegir", new JButton("Reset")}
          };

        MyTableModel model = new MyTableModel(columnNames, data);

        // Establecemos el modelo
        this.setModel(model);

        // Establecemos el renderer y editor que usaremos para el boton
        this.setDefaultRenderer(JButton.class, new ButtonCellRenderer());
        this.setDefaultEditor(JButton.class, new ButtonCellEditor());

        // Editores para cada tipo de objeto, estos nos permitirán darles el comportamiento adecuado
        this.getColumn("Nacionalidad").setCellEditor(new DefaultCellEditor(states));
        this.setDefaultEditor(JCheckBox.class, new DefaultCellEditor(new JCheckBox()));

 }

}
Como vemos en el código anterior los valores boolean se muestran automáticamente como un JCheckBox, y el DefaultCellEditor que es el editor por defecto que usa un JTable también se encargará de utilizar un JComboBox apropiadamente si se lo especificamos. Para el renderizar y darle comportamiento a un JButton si tendremos que implementar nuestro propio renderer y editor. Aquí os dejo el código que, como podéis ver, no es nada complicado.
package tablaCompleja.componentes;

import java.awt.Component;

import javax.swing.JButton;
import javax.swing.JTable;
import javax.swing.table.TableCellRenderer;

public class ButtonCellRenderer implements TableCellRenderer {

 @Override
 public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus,
   int row, int column) {

  // Devolvemos el botón tal cual
  if (value instanceof JButton) {
   return (JButton) value;
  }

  return null;

 }

}
package tablaCompleja.componentes;

import java.awt.Component;
import java.awt.event.ActionEvent;

import javax.swing.AbstractAction;
import javax.swing.AbstractCellEditor;
import javax.swing.JButton;
import javax.swing.JTable;
import javax.swing.table.TableCellEditor;

public class ButtonCellEditor extends AbstractCellEditor implements TableCellEditor {

 /** Componente que estamos editando. */
 private Component currentValue;

 @Override
 public Component getTableCellEditorComponent(final JTable table, Object value, boolean isSelected, final int row, int column) {

  JButton button = null;

  if (value instanceof JButton) {
   button = (JButton) value;
   // Action que permite "limpiar" los valores de una fila
   button.setAction(new AbstractAction("Reset") {

    @Override
    public void actionPerformed(ActionEvent e) {
     ((MyTableModel) table.getModel()).reset(row);

    }
   });
  }

  currentValue = button;

  return button;
 }

 @Override
 public Object getCellEditorValue() {
  return currentValue;
 }

}
Y ahora ya solo nos queda el fragmento de código para echar a andar nuestro ejemplo:
package tablaCompleja.componentes;

import java.awt.Component;
import java.awt.event.ActionEvent;

import javax.swing.AbstractAction;
import javax.swing.AbstractCellEditor;
import javax.swing.JButton;
import javax.swing.JTable;
import javax.swing.table.TableCellEditor;

public class ButtonCellEditor extends AbstractCellEditor implements TableCellEditor {

 /** Componente que estamos editando. */
 private Component currentValue;

 @Override
 public Component getTableCellEditorComponent(final JTable table, Object value, boolean isSelected, final int row, int column) {

  JButton button = null;

  if (value instanceof JButton) {
   button = (JButton) value;
   // Action que permite "limpiar" los valores de una fila
   button.setAction(new AbstractAction("Reset") {

    @Override
    public void actionPerformed(ActionEvent e) {
     ((MyTableModel) table.getModel()).reset(row);

    }
   });
  }

  currentValue = button;

  return button;
 }

 @Override
 public Object getCellEditorValue() {
  return currentValue;
 }

}

Espero haberos aclarado un poco más como funciona un JTable y que todo esto os haya sido útil de alguna manera.

Un saludo y hasta pronto.

lunes, 19 de diciembre de 2011

RenderingHints, renderizados y operaciones de manipulación de imágenes

0 comentarios
Hola otra vez, hoy vamos a profundizar un poco en las claves de la manipulación de imágenes y textos con Graphics2D y de como mejorar la resolución de los objetos u imágenes mostrados.
Para ellos nos serviremos de la clase RenderingHints y le estableceremos los valores apropiados para los resultados que pretendemos obtener.

La clase RenderingHints define y maneja una colección de claves y valores asociados que permite a una aplicación proporcionar entradas a las opciones de algoritmos usados por otras clases que realizan renderizados y manipulación de imágenes. La clase Graphics2D, y clases que implementan BufferedImageOp y RasterOp proporcionan métodos para obtener y establecer individualmente o grupos de claves RenderingHints y sus valores asociados. Cuando estas implementaciones realizan cualquier renderizado u operaciones de manipulación de imágenes deberían examinar estos valores que son requeridos por el llamante y adaptar los algoritmos usados en consecuencia y en la medida de su capacidad.

A partir de aquí paso a explicar cada uno de los indicadores y como modifican el renderizado de los componentes.

KEY_ALPHA_INTERPOLATION
La clave ALPHA_INTERPOLATION es una clave general que proporciona una recomendación de alto nivel para que haciendo uso de un algoritmo de mezcla de alpha elegir entre mayor velocidad o calidad al evaluar ventajas y desventajas en el renderizado.
Esta clave controlaría la elección del factor alpha en los cálculos que sacrificarían algo de precisión para usar tablas de consulta rápida o reducir la precisión de la instrucciones SIMD (Single Instruction, Multiple Data). Esta clave también controlaría si un color y su valor alpha se convierten a un color linear durante los cálculos para un efecto visual mas lineal con el coste de hacer cálculos por pixel adicionales.
Los posibles valores son:

  • VALUE_ALPHA_INTERPOLATION_SPEED
  • VALUE_ALPHA_INTERPOLATION_QUALITY
  • VALUE_ALPHA_INTERPOLATION_DEFAULT


KEY_ANTIALIASING
Clave de suavizado. La clave ANTIALIASING controla si la representación geométrica en los métodos de un objeto Graphics2D intentará reducir el aliasing en los bordes de las formas.
Un algoritmo de suavizado típico funciona fundiendo los colores existentes en la frontera de una forma con el color de relleno de acuerdo a la cobertura de pixeles parcial estimada de la forma.
Los posibles valores son:

  • VALUE_ANTIALIAS_ON
  • VALUE_ANTIALIAS_OFF
  • VALUE_ANTIALIAS_DEFAULT


KEY_COLOR_RENDERING
La clave COLOR_RENDERING controla la precisión de aproximaciones y conversiones al almacenamos colores en una imagen o superficie destino.
Cuando al renderizar o realizar una operación de manipulación de imágenes se produce un valor de color que debe ser guardado en un destino, primero de debe convertir ese color en un tipo capaz de ser almacenado en la imagen o superficie destino. Como mínimo, las componentes del color deben ser convertidas a una representación de bits y ordenarse correctamente o un índice en una tabla de decisión de colores debe elegirse antes de que los datos sean guardados en la memoria destino. Si esta conversión mínima, los datos en el destino podrían perfectamente parecer aleatorios, incorrectos o incluso valores no soportados. Los algoritmos que se usan para convertir los resultados del renderizado rápidamente en el formato de color adecuado son muy conocidos y están bastante optimizados.
Simplemente realizando las conversiones de formato de color más básicas para guardarlos en un destino puede ignorar una diferencia en el calibrado del ColorSpace del origen y destino u otros factores tales como la linealidad de la corrección gamma. A menos que el ColorSpace origen y destino sean idénticos, para realizar correctamente una operación de renderizado con teniendo el máximo cuidado en la precisión de los colores representados, los colores origen deben transformarse a un ColorSpace independiente del dispositivo donde se valla a mostrar y posteriormente convertirlos al ColorSpace destino. Aún más, si cálculos tales como la mezcla de múltiples colores origen se van a realizar durante la operación de renderizado, se puede obtener más claridad si el ColorSpace intermedio elegido tiene una relación lineal entre los valores que se están calculando y la percepción del ojo humano a las curvas de respuesta del dispositivo de salida.
Los posibles valores son:

  • VALUE_COLOR_RENDER_SPEED
  • VALUE_COLOR_RENDER_QUALITY
  • VALUE_COLOR_RENDER_DEFAULT


KEY_DITHERING
Clave de Dithering. La clave DITHERING controla como de cerca aproximar un color cuando lo almacenamos en un destino con una resolución de color limitada.
Algunos destinos de renderizado pueden soportar un número limitado de colores lo cual puede que no permita representar el espectro completo de colores que podrían resultar durante las operaciones de renderizado. Para estos destinos, la clave DITHERING controla si el renderizado se realiza con un color de relleno sólido en un pixel específico que es de los colores permitido el mas cercano al que se solicita, o si las formas se rellenaran con un patrón de colores que se combinan para aproximarse lo máximo posible a este color deseado.
Los posibles valores son:

  • VALUE_DITHER_DISABLE
  • VALUE_DITHER_ENABLE
  • VALUE_DITHER_DEFAULT


KEY_FRACTIONALMETRICS
Clave de métricas fraccionadas en las fuentes. La clave FRACTIONALMETRICS controla si al posicionar los glifos de un carácter en particular se tiene en cuenta la precisión sub-pixel de los avances de carácter escalado de la fuente o si estos vectores de avance se redondean a un entero. Esta clave solo recomienda cuanta precisión debe usarse para posicionar los glifos y no especifica ni recomienda si la rasterización o las fronteras de pixel de un glifo deben modificarse para coincidir.
Renderizar texto a un dispositivo de baja resolución como una pantalla necesariamente implicará una serie de operaciones de redondeo ya que la definición de alta calidad y muy precisa de la forma y métricas de los glifos de un carácter deben ajustarse a un número discreto de pixeles en el dispositivo. Idealmente el posicionado de los glifos durante la disposición del texto debería calcularse escalando las métricas diseñadas en la fuente de acuerdo al tamaño del punto, pero entonces el ancho del avance escalado no será necesariamente un número entero de pixeles. Si los glifos se posicionan con una precisión sub-pixel de acuerdo a estas métricas de diseño escaladas, entonces la rasterización idealmente necesitará ser ajustada para cada posible origen sub-pixel.
Desafortunadamente, escalar cada glifo de manera personalizada a su origen sub-pixel exacto durante la disposición del texto sería prohibitivamente caro por lo que un sistema simplificado basado en las posiciones enteras de un dispositivo se usa para disponer el texto. La rasterización de un glifo y el ancho de avance escalado son ambos ajustados a la vez para conseguir que el texto se vea bien en la resolución del dispositivo y tenga distancias enteras de pixeles entre los glifos que ayude a que los glifos parezcan uniformes y consistentemente espaciados y legibles.
Este proceso de redondeo del ancho del avance para los glifos rasterizados significa que la densidad de un carácter y la longitud promedia de una cadena de texto será diferente entre las medidas teóricas del diseño debido a la acumulación de una serie de pequeñas diferencias en los anchos ajustados de cada glifo. Cada diferencia específica será diferente en cada glifo, algunos siendo mas anchos y otros más estrechos que sus teóricos anchos de diseño. Así la diferencia global en la densidad de carácter y la longitud variará por un número de factores incluyendo la fuente, la resolución específica del dispositivo destino, y los glifos elegidos para representar la cadena que se está renderizando. Como resultado, renderizar la misma cadena en múltiples resoluciones puede arrojar métricas muy varadas sobre cadenas enteras.
Cuando FRACTIONALMETRICS están habilitadas, las métricas de diseño originales se escalan a tamaño de punto y se usan para disponer con precisión sub-pixel. La densidad media de los glifos y la longitud total de una cadena larga de caracteres será por ello más aproximada al diseño teórico de la fuente, pero la legibilidad puede verse afectada ya que cada par individual de caracteres puede que no tengan distancias consistentes además dependiendo de como la acumulación sub-pixel de los orígenes de los glifos se engrane con la malla de pixeles del dispositivo. Habilitar esta indicación puede ser deseado cuando la disposición del texto que se realiza deba ser consistente a lo largo de una variedad de resoluciones de salida. Específicamente, esta indicación puede ser deseable en situaciones cuando la disposición del texto se previsualiza en dispositivos con baja resolución como una pantalla para salidas que serán eventualmente renderizadas en una impresora de alta resolución o dispositivo de composición.
Cuando está deshabilitado, las métricas escaladas se redondean o ajustan a distancias enteras para disponerse. Las distancias entre cada par específico de glifos será mas uniforme en el dispositivo, pero la densidad y longitud total de cadenas largas puede que no coincida ya con las intenciones teóricas del diseñador de la fuente. Deshabilitar este indicador producirá normalmente resultados más legibles en dispositivos de baja resolución como monitores de ordenador.
Los posibles valores son:

  • VALUE_FRACTIONALMETRICS_OFF
  • VALUE_FRACTIONALMETRICS_ON
  • VALUE_FRACTIONALMETRICS_DEFAULT


KEY_INTERPOLATION
Clave de Interpolación. El indicador INTERPOLATION controla como los pixeles de las imágenes se filtran o remuestrean durante una operación de renderizado de imágenes.
Implícitamente las imágenes se definen para proporcionar muestras de color en unas coordenadas enteras. Cuando las imágenes se renderizan tal cual sin escalado al destino, la elección de que pixeles de la imagen se mapean a que pixeles del dispositivo es obvio y las muestras en las coordenadas de la imagen se transfieren a los pixeles correspondientes en la rejilla de pixeles del destino uno a uno. Cuando las imágenes se renderizan en un sistema de coordenadas escalado, rotado o transformado de cualquier manera, entonces el mapeado de los pixeles del dispositivo con la imagen puede originar la pregunta de que muestra de color usar para las continuas coordenadas que existen entre las localizaciones enteras y las proporcionadas en las muestras de la imagen. Los algoritmos de interpolación definen funciones que proporcionan una muestra de color para una coordenada continua en una imagen basada en las muestras de color de las coordenadas colindantes.
Los posibles valores son:

  • VALUE_INTERPOLATION_NEAREST_NEIGHBOR → la muestra de color de la coordenada vecinas mas cercana en la imagen es la usada.
  • VALUE_INTERPOLATION_BILINEAR → la muestra de color se obtiene mediante interpolación lineal de las 4 coordenadas más cercanas.
  • VALUE_INTERPOLATION_BICUBIC → las muestras de color de 9 coordenadas cercanas en la imagen se interpolan usando una función cúbica en X e Y para producir la muestra de color.
KEY_RENDERING

Clave de renderizado. El indicador RENDERING es un indicador general que proporciona recomendaciones de alto nivel sobre como sesgar las opciones de un algoritmo hacia velocidad o calidad cuando evaluá pros y contras. El indicador puede ser consultado por cualquier renderizado u operación de manipulación de imágenes, pero las decisiones normalmente harán honor a otros indicadores mas específicos en relación a este indicador.
Los posibles valores son:

  • VALUE_RENDER_SPEED
  • VALUE_RENDER_QUALITY
  • VALUE_RENDER_DEFAULT


KEY_STROKE_CONTROL
Clave para normalización de trazo. El indicador STROKE_CONTROL controla si una implementación de renderizado debería o tiene permitido modificar la geometría de las formas renderizadas para varios fines.
Algunas implementaciones podrían ser capaces de usar una librería optimizada de la plataforma que podría ser mas rápida que los algoritmos de renderizado software tradicionales en una plataforma dada, pero que podría no soportar coordenadas de punto flotante. Algunas implementaciones podrían también tener sofisticados algoritmos que perturban las coordenadas de un trazado de tal manera que las lineas anchas aparezcan mas uniformes en anchura y espaciado.
Si una implementación realiza cualquier tipo de modificación o "normalización" de un trazado, debería mover las coordenadas mas de medio pixel en cada dirección.
Los posibles valores son:

  • VALUE_STROKE_NORMALIZE
  • VALUE_STROKE_PURE
  • VALUE_STROKE_DEFAULT


KEY_TEXT_ANTIALIASING
Clave de antialiasing de texto. El indicador TEXT_ANTIALIASING puede controlar el uso de algoritmos antialiasing para el texto independientemente de la elección para renderizado de formas. Muchas veces una aplicación puede querer usar antialiasing solo para texto y no para otras formas, Además, los algoritmos para reducir el solapamiento para texto suelen ser mas sofisticados que aquellos que se han desarrollado para renderizado general por lo que este indicador proporciona valores adicionales que pueden controlar las opciones de estos algoritmos específicos para texto. Si se deja en el valor DEFAULT, este indicador diferirá al valor del indicador general KEY_ANTIALIASING.
Los posibles valores son:

  • VALUE_TEXT_ANTIALIAS_ON → Antialiasing habilitado
  • VALUE_TEXT_ANTIALIAS_OFF → Antialiasing deshabilitado
  • VALUE_TEXT_ANTIALIAS_DEFAULT → El renderizado se realiza de acuerdo al valor del indicador KEY_ANTIALIASING
  • VALUE_TEXT_ANTIALIAS_GASP → El renderizado requiere usar la información en el recurso de la fuente que especifica para cada tamaño de punto si usar o no los valores VALUE_TEXT_ANTIALIAS_ON o VALUE_TEXT_ANTIALIAS_OFF.
  • VALUE_TEXT_ANTIALIAS_LCD_HRGB → El texto mostrado será optimizado para un display LCD con subpixeles para mostrar de izquierda a derecha RGB, de tal manera que la resolución horizontal subpixel es tres veces la de la resolución de un pixel horizontal completo (HRGB).
  • VALUE_TEXT_ANTIALIAS_LCD_HBGR → El texto mostrado será optimizado para un display LCD con subpixeles para mostrar de izquierda a derecha BGR, de tal manera que la resolución vertical subpixel es tres veces la de la resolución de un pixel vertical completo (HBGR).
  • VALUE_TEXT_ANTIALIAS_LCD_VRGB → El texto mostrado será optimizado para un display LCD con subpixeles para mostrar de arriba a abajo RGB, de tal manera que la resolución horizontal subpixel es tres veces la de la resolución de un pixel horizontal completo (VRGB).
  • VALUE_TEXT_ANTIALIAS_LCD_VBGR → El texto mostrado será optimizado para un display LCD con subpixeles para mostrar de arriba a abajo BGR, de tal manera que la resolución vertical subpixel es tres veces la de la resolución de un pixel vertical completo (VBGR).


KEY_TEXT_LCD_CONTRAST
Clave de contraste de texto LCD. El valor es un valor Integer que se usa como un ajuste en el contraste del texto cuando se usa en conjunción con un indicador de antialiasing de texto LCD tal como VALUE_TEXT_ANTIALIAS_LCD_HRGB.
Los valores deben ser enteros positivos en el rango 100 a 250.

  • Un valor bajo (100) corresponde a texto con mayor contraste cuando se muestra texto oscuro en un fondo claro.
  • Un valor alto (200) corresponde a texto de bajo contraste cuando se muestra texto oscuro en un fondo claro.
  • Un valor típico esta en el estrecho rango 140-180.
  • Si no se especifica ningún valor, una implementación por defecto o del sistema será aplicada.
El valor por defecto debería ser apropiado para la mayoría de los fines, por lo que los clientes raramente necesitan especificar un valor para este indicador a menos que tengan información concreta sobre un valor apropiado. Un valor más alto no significa mayor contraste, en realidad es al contrario. La corrección se aplica de una manera similar a un ajuste gamma para una respuesta de luminosidad de percepción no lineal de los sistemas de visualización, pero no indica una corrección total para esto.

Y como siempre os dejo algunos ejemplos simples de la aplicación de estos indicadores.

Y por aquí el código del ejemplo:

package RenderingHintsSamples;

import java.awt.BasicStroke;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.RenderingHints;

import javax.swing.JLabel;
import javax.swing.JPanel;

public class RenderingHintsSamples extends JPanel {

 public RenderingHintsSamples() {
  this.setLayout(new GridLayout(4, 2));

  this.add(new JLabel("KEY_ANTIALIASING --> OFF"));
  this.add(new AntialiasingPanel(false));
  this.add(new JLabel("KEY_ANTIALIASING --> ON"));
  this.add(new AntialiasingPanel(true));

  this.add(new JLabel("KEY_TEXT_ANTIALIASING --> OFF"));
  this.add(new TextAntialiasingPanel(false));
  this.add(new JLabel("KEY_TEXT_ANTIALIASING --> ON"));
  this.add(new TextAntialiasingPanel(true));
 }

 private class AntialiasingPanel extends JPanel {

  boolean claveActiva = false;

  public AntialiasingPanel(boolean claveActiva) {
   this.claveActiva = claveActiva;
  }

  @Override
  public void paint(Graphics g) {

   Graphics2D g2d = (Graphics2D) g;
   if (claveActiva) {
    g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
   } else {
    g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
   }

   g2d.setStroke(new BasicStroke(5f));
   g2d.drawOval(20, 20, 60, 60);
   g2d.drawLine(80, 20, 150, 75);

  }
 }

 private class TextAntialiasingPanel extends JPanel {

  boolean claveActiva = false;

  public TextAntialiasingPanel(boolean claveActiva) {
   this.claveActiva = claveActiva;
  }

  @Override
  public void paint(Graphics g) {

   Graphics2D g2d = (Graphics2D) g;
   if (claveActiva) {
    g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
   } else {
    g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
   }

   g2d.setFont(new Font("Serif", Font.ITALIC, 30));
   g2d.drawString("TEXT_ANTIALIASING", 0, 40);

  }
 }

}

package RenderingHintsSamples;

import javax.swing.JFrame;


public class Main {

 private static void createAndShowGUI() {
  JFrame frame = new JFrame("RenderingHints: ejemplos");

  RenderingHintsSamples ejemplos = new RenderingHintsSamples();

     frame.getContentPane().add(ejemplos);
     frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
     frame.setSize(565, 370);
     frame.setVisible(true);
 }

 public static void main(String[] args) {

  javax.swing.SwingUtilities.invokeLater(new Runnable() {
   public void run() {
    createAndShowGUI();
   }
  });
 }
}


Un saludo y hasta la próxima entrada.

martes, 22 de noviembre de 2011

Diálogos personalizados, bordes redondeados, quitar botones de los diálogos.

2 comentarios
Hola a todos.

Hace un tiempo tuve que pelearme mucho para encontrar la solución a 2 problemas bastante comunes. Los diseñadores que se encargan de la interfaz gráfica de la aplicación que estábamos desarrollando insistían en eliminar los botones clásicos que aparecen en el marco de los diálogos, si, si, los de minimizar, maximizar y cerrar, y querían utilizar unos personalizados.
Por si esto fuera poco, además pedían que los diálogos no tuviesen la forma cuadrada normal, si no que tuviesen bordes redondeados o formas específicas.

Pues bien, después de mucho indagar dí con la solución a ambos problemas de un plumazo. Paso a detallaros como hacerlo.

Un JDialog no es más que un Frame más de nuestra aplicación, como tal tiene los botones mencionados anteriormente, se le puede añadir un JMenu y demás parafernalia que muchas veces nos sobra en nuestros diseños.
Para conseguir quitar el marco exterior de los diálogos y mostrar solo el contenido, es decir, quitarle la decoración, la única manera de conseguirlo es llamar al método setUndecorated(true). Esto elimina estos botones y nos deja solo el contenido a la vista.

Esta condición anterior es necesaria para conseguir nuestro segundo objetivo, darles una forma personalizada a nuestras ventanas. Para ello nos valdremos de la clase de utilidades AWTUtilities, esta clase además de permitir cambiar la forma de las ventanas permite aplicar ciertas clases de transparencia a las mismas aunque los resultados no son siempre los esperados dependiendo de la plataforma de desarrollo (aunque esto es un tema que no vamos a tratar de momento). Esta clase se distribuye a partir de la JRE|JDK 6u10, por lo que los usuarios de versiones anteriores no podréis hacer uso de ella :(

Este sería el resultado que podríamos obtener haciendo uso de lo que os he comentado:


Bien, así pues aquí os dejo el código para conseguir estos resultados.

Primero la clase de nuestro diálogo personalizado, aquí simplemente heredamos de un JDialog, le quitaremos el marco y la botonera y en su método paint(Graphics g) haremos que se dibuje con la forma deseada.
package dialogos;

import java.awt.Graphics;
import java.awt.Shape;

import javax.swing.JDialog;

import com.sun.awt.AWTUtilities;

public class ShapedDialog extends JDialog {

 // La forma de nuestro dialogo
 Shape dialogShape;

 public ShapedDialog() {
  super();
  /* Con esta instruccion eliminamos la barra superior y los botones de
   * minimizar, maximizar y cerrar.
   */
  this.setUndecorated(true);
  // Esta orden centra el dialogo en la pantalla
  this.setLocationRelativeTo(null);
 }

 /**
  * Establece la forma de la ventana.
  * @param shape
  */
 public void setShape(Shape shape) {
  this.dialogShape = shape;
                /* Imprescindible para mostrar el diálogo, hasta que no hagamos esto,
   * la ventana permanece invisible
   */
  this.setVisible(true);
 }

 @Override
 public void paint(Graphics g) {

  // Pintamos todo el contenido
  super.paint(g);

  // Establecemos la forma de la ventana
  AWTUtilities.setWindowShape(this, dialogShape);

 }

}


Como veis ha sido bastante simple, a pesar de ello, a más de uno os habrá dolido la cabeza antes de llegar a este post, jeje.

Y ahora la clase desde donde lanzaremos nuestra aplicación y crearemos los diálogos.

package dialogos;

import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.geom.Ellipse2D;
import java.awt.geom.RoundRectangle2D;

import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;


public class Main {

 private static void createAndShowGUI() {
  JFrame frame = new JFrame("Ejemplos de dialogos");

  final ShapedDialog dialog = new ShapedDialog();
  // Este sera el contenido de nuestro dialogo
  JPanel dialogContent = new JPanel(new BorderLayout());
  dialogContent.add(new JLabel("Interior del dialogo", JLabel.CENTER), BorderLayout.CENTER);

  // Dado que eliminamos el boton de cerrar el dialogo, tendremos que añadirle un boton para ello
  JButton close = new JButton(new AbstractAction("Cerrar") {

   @Override
   public void actionPerformed(ActionEvent e) {
    dialog.dispose();
   }
  });
  dialogContent.add(close, BorderLayout.SOUTH);


  dialog.setContentPane(dialogContent);
  dialog.setSize(400, 200);

  JButton openDialog = new JButton();
  openDialog.setAction(new AbstractAction("Dialogo redondo") {

   @Override
   public void actionPerformed(ActionEvent e) {
    Shape oval = new Ellipse2D.Float(0, 0, 400, 200);
    dialog.setShape(oval);
   }
  });
  JButton openDialog2 = new JButton();
  openDialog2.setAction(new AbstractAction("Dialogo bordes redondeado") {

   @Override
   public void actionPerformed(ActionEvent e) {
    Shape roundRectangle = new RoundRectangle2D.Float(0, 0, 400, 200, 20, 20);
    dialog.setShape(roundRectangle);
   }
  });

  frame.getContentPane().setLayout(new FlowLayout());
     frame.getContentPane().add(openDialog);
     frame.getContentPane().add(openDialog2);
     frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
     frame.setSize(200, 150);
     frame.setVisible(true);
 }

 public static void main(String[] args) {

  javax.swing.SwingUtilities.invokeLater(new Runnable() {
   public void run() {
    createAndShowGUI();
   }
  });
 }
}

Bien, pues esto es todo por hoy, espero que os estén siendo de ayuda estos mini-tutoriales.

Un saludo.

PD: ahora también podéis seguirme en Facebook y hacerme saber si os gusta lo que hago.