Aportación Java México - PCJ - Java Look & Feel

Java Look & Feel
En ocasiones al hacer desarrollo de aplicaciones con Java, descuidamos un aspecto que a mi parecer es uno de los importantes, y esto es el aspecto que tiene la interfaz de nuestras aplicaciones, para esto Java posee una API que se encarga de manipular las interfaces gráficas hechas con Swing, la cual permite crear GUI's verdaderamente atractivas.

Antes que cualquier cosa debemos tener claros unos conceptos como lo son el que es Swing, y que es el Java Look and Feel

Los componentes de Swing están escritos en Java, sin ningún código de especificación de ventanas. Esto ayuda a la creación de interfaces gráficas sin importar el sistema operativo donde se trabaje, y haciendo mas simple el desarrollo de aplicaciones. Swing además soporta una arquitectura de visualización (Look And Feel). Ésta característica les da a los usuarios la habilidad de cambiar la apariencia de una aplicación sin reiniciarla y sin que el desarrollador se tenga que cambiar por completo su conjunto de subclases, mas o menos como lo hacen las hojas de estilo en una pagina web.

Java Look And Feel (de ahora en adelante J&F) es un API multiplataforma proporcionada por JavaSoft.
Java L&F implementa todas las funcionalidades básicas de Swing, pero también se extiende en otras áreas incluyendo:
Temas
Sliders (barras de selección de rangos)
Toolbars (Barras de herramientas)
Trees (arboles de contenido)

Para Java existen muchos Look&Feel. Algunos son gratuitos y "open source", otros son propietarios, por lo que para usarlos se deberá pagar una cierta cantidad de dinero y adquirir una licencia de uso.
Comúnmente las ventanas en Java tienen un aspecto y estilos propio pero este se puede cambiar fácilmente si se tiene solamente dos cosas a la mano: una librería adecuada creada con el propósito de cambiar el Look&Feel de Java y una sola línea de código.

vamos a tomar como ejemplo un programa hecho por los maestros del L&F: Michael Albers, Tom Santos, Jeff Shapiro y Steve Wilson.

El programa lo puedes encontrar en los demos que se encuentran en los JSDK. Vas a donde tienes instalado el SDK de Java y buscas la carpeta demo/jfc/Metalworks, allí  encontrarás la aplicación que vamos a tomar como objeto de ejemplo.
Para ejecutar el programa que ya está compilado (Metalworks.jar), digitas desde una shell:

java -jar Metalworks.jar

Y lo que veras es un programa como este (ten en cuenta que según tu versión del SDK el programa puede cambiar o no estar)

Como puedes observar es una interfaz gráfica realmente bonita y llamativa. Aunque es la que viene por defecto con el L&F en su versión 5. Puedes jugar con este programita que te dará más de una sorpresa agradable. Por ejemplo vas al menú Theme y seleccionas el que quieras.
Como podemos ver la aplicación cambia de aspecto rápidamente sin necesidad de reiniciarla, Pero lo que nosotros queremos es saber como hacer esto, veámos:

Creando un JFrame con el Look And Feel activado
Esto es muy sencillo de hacer. Lo único que tenemos que hacer es activar el Look And Feel, pasando true como parámetro al método estático setDefaultLookAndFeelDecorated de JFrame y de JDialog. Por ej.:

//Ejemplo1Look .java
import javax.swing.*;
import java.awt.event.*;
import java.awt.*;
public class Ejemplo1Look extends Jframe
{
public Ejemplo1Look()
   {
      super("Look and Feel");
      JButton boton = new JButton("Ver dialogo");
      boton.addActionListener(new ActionListener(){
            public void actionPerformed(ActionEvent e){
               JoptionPane.showMessageDialog(null,
               "Cambio el mensaje de dialogo de error",
               "Mensaje",JOptionPane.ERROR_MESSAGE);
            }
         }
      );
      getContentPane().add(new Jlabel("Este es un Frame"),
         BorderLayout.NORTH);
      getContentPane().add(boton);
      setSize(200,80);
      setVisible(true);
   }
   public static void main(String args[])
   {
      Jframe.setDefaultLookAndFeelDecorated(true);
      Jdialog.setDefaultLookAndFeelDecorated(true);
      Ejemplo1Look ventana = new Ejemplo1Look();
      ventana.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
   }
}

asi se veria la salida del programa:

Creando estilos visuales a partir de archivos de texto plano

Este es uno de los tantos métodos para crear estilos o temas y consiste en crear un archivo de texto plano, que contendrá la configuración de visualización. Un ejemplo sencillo puede ser este:
Código:
#Aquí  puedes colocar comentarios
name=PruebaLF
primary1=200,0,66
primary2=0,0,255
primary3=0,255,0
secondary1=255,255,0
secondary2=0,255,255
secondary3=255,0,255
black=255,255,200
white=25,80,150

Puedes usar comentarios para documentar el tema; estos deben comenzar con el símbolo numeral (#). Y la sintaxis del tema es nombreEtiqueta=valor. Pero que significa cada etiqueta:

name=PruebaLF indica que el tema se llama PruebaLF.
primary1=200,0,66 es el color del borde externo
primary2=0,0,255 es el color del borde interno y el foco
primary3=0,255,0 es el color de la barra de titulo
secondary1=255,255,0 color del borde de los botones, labels, etc
secondary2=0,255,255 color de los botones al estar presionados
secondary3=255,0,255 color de fondo de los botones, labels, etc
black=255,255,200 color de las letras sobre las cajas de texto, etc
white=25,80,150 color del fondo de las cajas de texto, areas, etc

Todos estos colores están en escala RGB

Para utilizar este archivo en un programa, se haría de la siguiente forma:
Código:
//Ejemplo2Look .java
import javax.swing.*;
import java.awt.event.*;
import java.awt.*;
import java.io.*;
import javax.swing.plaf.metal.*;
public class Ejemplo2Look extends JFrame
{
   public Ejemplo2Look()
   {
      super("Ejemplo 2");
      JButton boton = new JButton("Cargar tema");
      boton.addActionListener(new ActionListener(){
            public void actionPerformed(ActionEvent e){
               cargarTema();
            }
         }
      );
      getContentPane().add(boton);
      getContentPane().add(new JLabel("Así  se ven las labels"), BorderLayout.NORTH);
      getContentPane().add(new JTextField("Y así  las cajas de texto"), BorderLayout.SOUTH);
      getContentPane().add(new JScrollPane(new JTextArea("Area de texto")), BorderLayout.WEST);
      getContentPane().add(new JSlider(SwingConstants.VERTICAL, 0, 200, 10 ),  BorderLayout.EAST);
      setSize(400,200);
      UIManager.addPropertyChangeListener(new UISwitchListener((JComponent)getRootPane()));
      setVisible(true);
   }
   public void cargarTema()
   {
      MetalTheme miTema = null;
      try{
         InputStream flujoEntrada = getClass().getResourceAsStream("Tema.theme");
         miTema =  new PropertiesMetalTheme(flujoEntrada);
      }catch(NullPointerException npe) {System.out.println(npe);}
      MetalLookAndFeel.setCurrentTheme(miTema);
      try{
         UIManager.setLookAndFeel("javax.swing.plaf.metal.MetalLookAndFeel");
      }catch (Exception ex){
         System.out.println("Falló la carga del tema");
         System.out.println(ex);
      }
   }
   public static void main(String args[])
   {
      UIManager.put("swing.boldMetal", Boolean.FALSE);
      JFrame.setDefaultLookAndFeelDecorated(true);
      JDialog.setDefaultLookAndFeelDecorated(true);
      Toolkit.getDefaultToolkit().setDynamicLayout(true);
      System.setProperty("sun.awt.noerasebackground","true");
      try{
         UIManager.setLookAndFeel(new MetalLookAndFeel());
      }catch ( UnsupportedLookAndFeelException e ){
         System.out.println ("Metal Look & Feel no es soportada en esta plataforma");
         System.exit(0);
      }
      Ejemplo2Look ventana = new Ejemplo2Look();
      ventana.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
   }
}

el programa tendrá una salida un poco desagradable, pero solo así se podía mostrar para que es cada etiqueta:

Importante: Para que el programa funcione perfectamente tenemos que usar dos programas creados por el maestro Steve Wilson: PropertiesMetalTheme.java y UISwitchListener.java.

PropertiesMetalTheme, es una clase que crea un flujo hacia el archivo de texto que contenga la configuración del tema, y carga las propiedades que se encuentren allí .

UISwitchListener, es una clase que permite cambiar la interfaz de usuario en tiempo de ejecución, sin problemas inesperados.

Creando estilos visuales simples a partir de clases

Bueno, ahora vamos a ver una manera de crear estilos visuales a partir de un programa (una clase) hecho en Java. El primer programa que vamos a tomar como ejemplo es EstiloHalloween.java:
Código:
//Ejemplo simple de tema en clase
//EstiloHalloween .java
import javax.swing.plaf.*;
import javax.swing.plaf.metal.*;
import javax.swing.*;
public class EstiloHalloween extends DefaultMetalTheme
{
   public String getName() { return "Halloween"; }
   
   private final ColorUIResource primary1 = new ColorUIResource(204, 102, 0);
   private final ColorUIResource primary2 = new ColorUIResource(255, 0, 0);
   private final ColorUIResource primary3 = new ColorUIResource(255, 204,102);
   
   protected ColorUIResource getPrimary1() { return primary1; }
   protected ColorUIResource getPrimary2() { return primary2; }
   protected ColorUIResource getPrimary3() { return primary3; }
}

Como ven todo es muy fácil; simplemente declaramos una clase que herede de DefaultMetalTheme, y creamos tres objetos de la clase ColorUIResource que sean privados (private) y constantes (final). Cada uno vá a simbolizar un color para la interfaz de usuario. Si no comprendes bien es porque no has leí do lo de primary1, primary2, etc. allí  arriba. Los objetos ColorUIResource los inicializamos con tres números separados por comas que representan un color en formato RGB.
Por último declaramos tres métodos protected que retornen el valor de los  objetos ColorUIResource.

El programa que utiliza la clase EstiloHalloween es:
Código:
// Ejemplo3Look .java
import javax.swing.*;
import java.awt.event.*;
import java.awt.*;
import javax.swing.plaf.metal.*;
public class Ejemplo3Look extends JFrame
{
   public Ejemplo3Look()
   {
      super("Ejemplo 3 - Halloween");
      JButton boton = new JButton("Mostrar dialogo");
      boton.addActionListener(new ActionListener(){
            public void actionPerformed(ActionEvent e){
               JOptionPane.showMessageDialog(null,
               "Y así  se ve un cuadro de dialogo de alerta",
               "Mensaje",JOptionPane.WARNING_MESSAGE);
            }
         }
      );
      getContentPane().add(new JLabel("Asi se ve un Frame"), BorderLayout.NORTH);
      getContentPane().add(boton);
      setSize(200,80);
      setVisible(true);
   }
   public static void main(String args[])
   {
      MetalLookAndFeel.setCurrentTheme(new EstiloHalloween());
      try{
         UIManager.setLookAndFeel("javax.swing.plaf.metal.MetalLookAndFeel");
      } catch (Exception ex) {
         System.out.println("Falló la carga del tema");
         System.out.println(ex);
      }
      JFrame.setDefaultLookAndFeelDecorated(true);
      JDialog.setDefaultLookAndFeelDecorated(true);
      Ejemplo3Look ventana = new Ejemplo3Look();
      ventana.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
   }
}

Como puedes ver estamos utilizando únicamente los tres colores primarios para interfaces gráficas (primary1, primary2 y primary3). La salida del programa es:

Pero ahora vamos a ver un programa que utiliza todas las combinaciones de colores; la clase es EstiloHalloween2:
Código:
// EstiloHalloween2.java
import javax.swing.plaf.*;
import javax.swing.plaf.metal.*;
import javax.swing.*;
public class EstiloHalloween2 extends DefaultMetalTheme
{
   public String getName() { return "Halloween"; }
   
   private final ColorUIResource primary1 = new ColorUIResource(204, 102, 0);
   private final ColorUIResource primary2 = new ColorUIResource(255, 0, 0);
   private final ColorUIResource primary3 = new ColorUIResource(255, 204, 102);

   private final ColorUIResource secondary1 = new ColorUIResource( 111,  111,  111);
   private final ColorUIResource secondary2 = new ColorUIResource(255, 204, 102);
   private final ColorUIResource secondary3 = new ColorUIResource(204, 102, 0);

   private final ColorUIResource black = new ColorUIResource(255, 255, 255);
   private final ColorUIResource white = new ColorUIResource(0, 0, 0);

   protected ColorUIResource getPrimary1() { return primary1; }
   protected ColorUIResource getPrimary2() { return primary2; }
   protected ColorUIResource getPrimary3() { return primary3; }

   protected ColorUIResource getSecondary1() { return secondary1; }
   protected ColorUIResource getSecondary2() { return secondary2; }
   protected ColorUIResource getSecondary3() { return secondary3; }
   
   protected ColorUIResource getBlack() { return black; }
   protected ColorUIResource getWhite() { return white; }
}

No explico esta clase ya que tiene la misma escencia de la primera, solo que, aparte de utilizar primary1, primary2 y primary3, utiliza secondary1, secondary2 y secondary3, y el color de fondo de las cajas de texto (white) y de las letras (black).

La aplicación que utiliza la clase EstiloHalloween2 es esta:
Código:
// Ejemplo4Look .java
import javax.swing.*;
import java.awt.event.*;
import java.awt.*;
import java.io.*;
import javax.swing.plaf.metal.*;
public class Ejemplo4Look extends JFrame
{
   public Ejemplo4Look()
   {
      super("Ejemplo 4 - Halloween");
      getContentPane().add(new JButton("Los botones"));
      getContentPane().add(new Jlabel("Estos son labels"), BorderLayout.NORTH);
      getContentPane().add(new JTextField("Y así  las cajas de texto"), BorderLayout.SOUTH);
      getContentPane().add(new JScrollPane(new JTextArea("Area de\ntexto")), BorderLayout.WEST);
      getContentPane().add(new JSlider(SwingConstants.VERTICAL, 0, 200, 10 ),  BorderLayout.EAST);
      setSize(400,200);
      setVisible(true);
   }
   public static void main(String args[])
   {
      MetalLookAndFeel.setCurrentTheme(new EstiloHalloween2());
      try{
         UIManager.setLookAndFeel("javax.swing.plaf.metal.MetalLookAndFeel");
      }catch (Exception ex){
         System.out.println("Falló la carga del tema");
         System.out.println(ex);
      }
      JFrame.setDefaultLookAndFeelDecorated(true);
      JDialog.setDefaultLookAndFeelDecorated(true);
      try{
         UIManager.setLookAndFeel(new MetalLookAndFeel());
      }catch ( UnsupportedLookAndFeelException e ){
         System.out.println ("Metal Look & Feel no es soportada en esta plataforma");
         System.exit(0);
      }
      Ejemplo4Look ventana = new Ejemplo4Look();
      ventana.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
   }
}

Esta es la apariencia del programa:

Pero por supuesto que estos códigos dan una salida que se ve un poco desagradable ya que están hechos solo para ejemplificar el uso de Look and Feel, pero veran que si se toman un poco de tiempo para imaginarse un buen look & Feel lograran aspectos realmente llamativos,

Saludos!