viernes, 21 de agosto de 2009

Diseño de interfaces gráficas en Java, Parte 5: Completando el Modelo


Prepárense para construir el Modelo

De regreso, bien, en este punto ya debe estar corriendo el servidor de base de datos PostgreSQL y la base de datos con su respectiva tabla debe estar lista y ésta última debe contar con algunos registros para la prueba. También los programas en Java de nuestro proyecto deben contar con la referencia al archivo JAR que descargamos e instalamos en el artículo anterior. Ahora solo queda concluir con la construcción del Modelo programando las capas de negocio y de acceso a datos de la aplicación.

Una vez más, retomamos el mismo proyecto que hemos ido construyendo en esta serie de post sobre interfaces gráficas en Java.

Primero, programaremos una clase para crear objetos de datos durante la ejecución del programa, es decir objetos cuyo único propósito sea transportar información a través de las capas de la aplicación. En este caso la clase se llamará InfoUsuario, y la utilizaremos para que sus objetos almacenen los datos extraídos de la tabla usuario de la base de datos:

InfoUsuario.java
/**
* @Desc Clase utilizada como una estructura de datos para alamacenar la información de los registros que
* consultemos de la base de datos
* @author Beto González
*/
public class InfoUsuario {
    public String nombre;
    public String correoElectronico;
    public String domicilio;
    public String telefono;
    public int edad;
}


Después crearemos una clase para manejar el acceso a la base de datos desde nuestra aplicación, incluyendo las consultas a la tabla requerida. El código fuente es el siguiente:

AccesoADatos.java
import java.sql.*;
import java.util.*;
/**
* @Desc Clase utilizada para acceder a la base de datos y realizar consultas sobre sus tablas
* @author Beto González
*/
public class AccesoADatos {
    private String usuario;
    private String contraseña;
    private String url;
    private Connection conexion;
    private Vector<InfoUsuario> resultado; //Vector en el que se almacenarán la lista de registros que resulten en una cosulta, con el <InfoUsuario> se especifica que cada uno de sus elementos será un objeto de la clase InfoUsuario
    private String mensaje;
    AccesoADatos(String url, String usuario, String contraseña)     {
        this.usuario = usuario;
        this.contraseña = contraseña;
        this.url = url;
        this.resultado = new Vector<InfoUsuario>();
        this.mensaje = "";
    }
    /**
     * @Desc
     * @return Verdadero si se realizó correctamente, falso en caso contrario
     */
    public boolean conecta() {
        boolean estado = true;
        Properties props = new Properties();
        props.setProperty("user",usuario);
        props.setProperty("password",contraseña);
        try{
            conexion = DriverManager.getConnection(url, props);
        }catch(SQLException e){
            estado = false;
            mensaje = e.toString();
        }
        return estado;
    }
    /**
     * @Desc Método que ejecuta una consulta SQL sobre la tabla usuario
     * @param condicion Condición para la consulta SQL
     * @return Verdadero si se realizó correctamente, falso en caso contrario
     */
    public boolean consultaUsuario(String condicion) {
        boolean estado = true;
        if(!condicion.isEmpty()) {
            try{
                PreparedStatement st = conexion.prepareStatement("SELECT * FROM usuario WHERE " + condicion);
                ResultSet rs = st.executeQuery();
                InfoUsuario iUsuario = new InfoUsuario(); //Objeto para almacenar la información de cada registro de la tabla
                while (rs.next()) {
                    iUsuario.correoElectronico = rs.getString("correoElectronico");
                    iUsuario.nombre = rs.getString("nombre");
                    iUsuario.domicilio = rs.getString("domicilio");
                    iUsuario.telefono = rs.getString("telefono");
                    iUsuario.edad = rs.getInt("edad");
                    resultado.add(iUsuario);
                }
                rs.close();
                st.close();
            }catch(SQLException e){
                estado = false;
                mensaje = e.toString();
            }
        }
        else{
            estado = false;
            mensaje = "Consulta vacía";
        }
        return estado;
    }
    /**
     * @Desc Método que realiza la búsqueda de un usuario a partir de un correo electrónico
     * @param correo Correo electrónico buscado
     * @return Verdadero si se realizó correctamente, falso en caso contrario
     */
    public boolean buscaCorreo(String correo) {
        boolean estado = consultaUsuario("\"correoElectronico\" = '" + correo + "'");
        return estado;
    }
    /**
     * @Desc Método que realiza la búsqueda de un usuario a partir de un nombre
     * @param correo Correo electrónico buscado
     * @return Verdadero si se realizó correctamente, falso en caso contrario
     */
    public boolean buscaNombre(String nombre) {
        boolean estado = consultaUsuario("nombre LIKE '%" + nombre + "%'");
        return estado;
    }
    /**
     * @Desc Devuelve un elemento específicado que se encuentre dentro del Vector de resultados
     * @param indice Indice del elemento dentro del vector
     * @return
     */
    public InfoUsuario regresaInfoUsuario(int indice) {
        if(resultado.size() > 0)
            return resultado.get(indice);
        else
            return null;
    }
    /**
     * @Desc Devuelve el último mensaje de erro generado por el controlador de la base de datos
     * @return Mensaje devuelto por el controlador de la base de datos
     */
    public String regresaMensaje() {
        return mensaje;
    }
    /**
     * @Desc Devuelve el número de elementos del Vector de resultados
     * @return Número de elementos
     */
    public int regresaNumDeRegistros() {
        return resultado.size();
    }
    /**
     * @Desc Método que cierra la conexión con el sistema manejador de base de datos
     */
    public void cierraConexion() {
        try{
            conexion.close();
        }catch(SQLException e){}
    }
}


Esta clase tiene como propósito abstraer al resto de la aplicación de los detalles del acceso a la base de datos, en este caso a través del SMBD PostgreSQL. Como vemos, se incluyen métodos para abrir y cerrar la conexión con el servidor, un método llamado consultaUsuario(), para ejecutar consultas sobre la tabla usuario a partir de la condición especificada, almacenando la información de los registros resultantes en la propiedad resultado, así como el método regresaInfoUsuario() que precisamente extrae la información de los registros almacenados en esta propiedad. El objeto resultado pertenece a la clase Vector, y funciona como un arreglo en el que se almacenará una lista de objetos de la clase InfoUsuario que ya definimos previamente. De forma que a cada registro resultante le corresponderá un nodo en el vector.

La tercera clase que crearemos se encargará de implementar una pequeña lógica de negocio, se llamará Buscador y en ésta se recibirá la información capturada por el usuario, con la cual se decidirá qué método de la capa de acceso a datos deberá ejecutarse para llevar a cabo la búsqueda de un usuario y retornar su información a la capa de presentación.

Buscador.java
import java.awt.Component;
import javax.swing.JOptionPane;
/**
* @Desc Clase que implementa las busquedas de información en el sistema a través de un objeto de la clase AccesoADatos
* @author Beto González
*/
public class Buscador {
    /**
     * @Desc Método que busca la información de un usuario a través de un objeto de la clase AccesoADatos
     * @param correoElectronico Correo electrónico del usuario
     * @param nombre Nombre del usuario
     * @return Un objeto de la clase InfoUsuario, que contendrá la información del usuario solicitado
     */
    public InfoUsuario buscaUsuario(String correoElectronico, String nombre) {
        InfoUsuario info = new InfoUsuario();
        AccesoADatos a = new AccesoADatos("jdbc:postgresql://localhost/pruebas","postgres","myhouse6");
        boolean estado = true;
        if(a.conecta()) {
            if(!nombre.isEmpty()) {
                if((estado = a.buscaNombre(nombre)) && a.regresaNumDeRegistros() > 0)
                    info = a.regresaInfoUsuario(0);
            }
            else if(!correoElectronico.isEmpty()) {
                if((estado = a.buscaCorreo(correoElectronico)) && a.regresaNumDeRegistros() > 0)
                    info = a.regresaInfoUsuario(0);
            }
            a.cierraConexion();
        }
        if(!estado) {
            JOptionPane.showMessageDialog((Component)null,"Error de acceso a la base de datos " + a.regresaMensaje(),"Error",JOptionPane.OK_OPTION);
        }
        if(a.regresaNumDeRegistros() == 0) {
            JOptionPane.showMessageDialog((Component)null,"No se encontraron registros con los datos capturados","Alerta",JOptionPane.WARNING_MESSAGE);
        }
        return info;
    }
}


En el método buscaUsuario() vemos que se instancía la clase AccesoADatos y sus métodos son utilizándolos según el contenido de la información recibida como parámetro, así si se recibió un nombre de usuario válido para la búsqueda se utilizará el método buscaNombre(), en caso contrario si el correo electrónico es válido, se empleará el método buscaCorreo(). Ambos métodos devolverán el objeto de datos de la clase InfoUsuario, cuyas propiedades contienen la información del usuario consultado. Finalmente se incluye un pequeño control de los mensajes de error generados, para indicar los errores de acceso a datos y las consultas que no arrojaron resultados.

La información devuelta por el método buscaUsuario() deberá llegar hasta la capa de presentación de la aplicación, por lo que dentro de los programas Controlador.java y PanelDeDatos.java realizaremos los siguientes cambios:

PanelDeDatos.java
import javax.swing.*;
import java.awt.*;
/**
* @Desc Clase utilizada para desplegar ventanas emergentes dentro del sistema que contendrán la información establecida
* en un objeto JPanel pasado por el constructor
* @author Beto González
*
*/
public class PanelDeDatos extends JPanel {
    private JLabel lblNombre;
    private JLabel lblCorreoElectronico;
    private JLabel lblDomicilio;
    private JLabel lblEdad;
    private JLabel lblTelefono;
    //Constructor de la clase
    PanelDeDatos () {
        this.lblNombre = new JLabel("Nombre del usuario: ");
        this.lblCorreoElectronico = new JLabel("Correo electronico: ");
        this.lblDomicilio = new JLabel("Domicilio: ");
        this.lblEdad = new JLabel("Edad: ");
        this.lblTelefono = new JLabel("Telefono: ");
        this.setLayout(new GridBagLayout());
    }
    /**
     * @Desc Método que agrega al panel principal cada una de las etiquetas declaradas
     */
    public void estableceContenido(InfoUsuario datos) {
        lblNombre.setText(lblNombre.getText() + datos.nombre);
        lblCorreoElectronico.setText(lblCorreoElectronico.getText() + datos.correoElectronico);
        lblDomicilio.setText(lblDomicilio.getText() + datos.domicilio);
        lblEdad.setText(lblEdad.getText() + Integer.toString(datos.edad));
        lblTelefono.setText(lblTelefono.getText() + datos.telefono);

        GridBagConstraints delimitador = new GridBagConstraints(); //Creamos un delimitador que usaremos para especificar la ubicación de los componentes dentro del panel
        delimitador.gridx = 0;
        delimitador.gridy = 0;
        delimitador.anchor = GridBagConstraints.EAST;
        this.add(lblNombre, delimitador);
        delimitador.gridy = 1;
        this.add(lblCorreoElectronico, delimitador);
        delimitador.gridy = 2;
        this.add(lblDomicilio, delimitador);
        delimitador.gridy = 3;
        this.add(lblEdad, delimitador);
        delimitador.gridy = 4;
        this.add(lblTelefono, delimitador);
    }
}


En la clase PanelDeDatos adecuamos el método estableceContenido() para que reciba un objeto de la clase InfoUsuario y actualice el contenido de sus etiquetas con los datos de éste.

Controlador.java
import javax.swing.JFrame;
import java.awt.event.*;
/**
* @Desc Clase que maneja los eventos realizados en la Vista implementando una serie de interfaces nativas del lenguaje
* @author Beto González
*
*/
public class Controlador implements ActionListener, MouseMotionListener {
    public Ventana ventana;
    Controlador(Ventana ventana) {
        this.ventana = ventana;
    }
    /**
     * @Desc método perteneciente a la interface ActionListener que es implementado para controlar las acciones sobre los botones
     * ya que se ejecutará si se hace clic sobre el botón que este atendiendo
     */
    public void actionPerformed(ActionEvent ie) {
        if(ie.getActionCommand() == "Buscar usuario") { //Validación para asegurarse que la acción fue realizada sobre el botón Buscar
            ventana.panelPrincipal.lblMensaje.setText("Has hecho clic en el bóton de búsqueda");
            //Desplegamos una ventana emergente que contendrá la información de un panel de la clase PanelDeDatos
            PanelDeDatos panel = new PanelDeDatos();
            Buscador buscador = new Buscador();
            InfoUsuario datos = buscador.buscaUsuario(ventana.panelPrincipal.txtCampo2.getText().trim(), ventana.panelPrincipal.txtCampo1.getText().trim()); //Buscamos la información del usuario según los datos capturados
            panel.estableceContenido(datos);
//Pasamos la información consultada al panel de datos
            VentanaEmergente emergente = new VentanaEmergente("Información del usuario", panel);
            emergente.setBounds(180, 180, 300, 250);
            emergente.setVisible(true);
            emergente.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
            emergente.addWindowListener(new WindowAdapter()
            {
                public void WindowCloser(WindowEvent e)                 {
                    System.exit(0);
                }
            });
        }
    }
     /**
     * @Desc Método ejecutado cuando el usuario arrastra el ratón presionando el botón de acción
     */
    public void mouseDragged(MouseEvent e) {
        ventana.panelPrincipal.lblMensaje.setText("Ratón arrastrado");
    }
    /**
     * @Desc Método ejecutado cuando el usuario solo arrastra el ratón
     */
    public void mouseMoved(MouseEvent e) {
        ventana.panelPrincipal.lblMensaje.setText("Posición del puntero X:"
         + Integer.toString(e.getX()) + " Y:"
         + Integer.toString(e.getY()));
    }
}


En la clase Controlador modificamos el método actionPerformed() para que se incluya un objeto de la clase Buscador que proporcione la información del usuario al objeto panel.

Ahora solo queda probar todos estos cambios, el resultado al ejecutar la aplicación deberá verse al capturar un nombre un un correo en el formulario y presionar el botón "Buscar", tal y como se muestra en la siguiente imagen:


En este caso, buscamos un usuario con nombre María. Debido a que en la base de datos existe una registro con nombre 'María Rodríguez' la aplicación nos arrojó un resultado satisfactorio, gracias a que en el nivel de acceso a datos la búsqueda se realiza utilizando un LIKE '%%', aunque claro que el usuario será ajeno a todos esos detalles.

En caso de buscar algún nombre inexistente, el sistema nos debe desplegar un mensaje como el siguiente:


También pueden probar la búsqueda por correo electrónico, en donde la coincidencia con la información almacenada sí deberá ser exacta.

Claro que a este programa se le pueden hacer múltiples mejoras, como por ejemplo en la búsqueda por nombre desplegar una lista con la información de todos los usuarios, en lugar del primer usuario consultado, sin embargo el ejemplo cumple con el propósito de mostrar una forma para separar nuestras aplicaciones en una Vista, un Controlador y un Modelo, así como trabajar con capas verticalmente, yendo desde la base de datos hasta la presentación. Un aspecto que me agrada mucho de trabajar con objetos y que aquí se ve reflejado, es el encapsulamiento que proporcionan, es decir que los detalles de las tareas realizadas por una capa de más bajo nivel, quedan fuera del conocimiento de las capas superiores, a estas solo les interesarán los datos o mensajes que las capas de abajo arrojen al realizar sus tareas.

Con este último post es que finalizamos con el pequeño proyecto que fuimos construyendo a partir de la parte 2 de la programación de interfaces gráficas en Java. Ya vimos algunos detalles interesantes sobre la presentación de los componentes y el manejo de eventos, complementándolos con el acceso a una base de datos. En los siguientes post veremos algunos otros componentes de la interfaz gráfica, y cómo utilizarlos para crear una nueva aplicación para la manipulación de imágenes, que será el proyecto con el que concluiremos con esta serie de artículos sobre Java.

Hasta la próxima.

lunes, 17 de agosto de 2009

Diseño de interfaces gráficas en Java, Parte 4: Creación del Modelo de la aplicación


Primero la capa de datos

¡Saludos nuevamente!

En esta ocasión dejaremos un poco la parte de la presentación y nos enfocaremos en la creación de una pequeña lógica de negocio junto con el acceso a un sistema manejador de base de datos. Aunque podría parecer que me salgo del tema original relacionado con interfaces gráficas, considero que es importante tocar el punto en el que uniríamos el Modelo de la aplicación con la Vista y el Controlador, ya que es el punto al que finalmente se tiene que llegar en todo desarrollo de aplicaciones.

Para lograr este objetivo primero tenemos que considerar la comunicación horizontal de las capas de la aplicación, es decir la forma en que la información llegará desde la capa de acceso a datos hasta la capa de presentación. En el nivel más bajo tendremos un sistema manejador de bases de datos en que estará almacenada nuestra información, en este caso opte por el PostgreSQL, ya que es un gestor en el que apenas me estoy adentrando y nos servirá a todos los novatos para comenzar a dominar un nuevo administrador de base de datos.

Además es un SMBD open source rubusto y por si fuera poco, su mascota es un elefante, y todos sabemos que los elefantes son geniales.

Pueden descargar este sistema desde la sección downloads de la página oficial de PostgreSQL, eligiendo el sistema operativo de su preferencia.

Si descargan la versión para Windows notarán que además hay una aplicación visual de escritorio para administrar las base de datos en PostgreSQL, llamado pgAdmin. Esta aplicación viene incluida dentro del instalador de postgreSQL para este sistema operativo.

Dentro de las características de postgreSQL encontramos que:
- Es un manejador de base de datos dual al ser relacional y orientado a objetos, esto último es una características muy interesante ya que permite manejar conceptos como la herencia entre las tablas.
- Es un sistema multiusuario y multiplataforma.
- Soporta replicación.
- Nos permite utilizar un conjunto de datos complejos, como vectores, xml, figuras geométricas o arreglos, entre otros.
- Permite utilizar varios conceptos avanzados como llaves foráneas, constraints, vistas, transacciones y la propia herencia.
- Implementa un gran conjunto de instrucciones del lenguaje SQL.
- También permite implementar triggers en diferentes lenguajes de programación ajenos al lenguaje nativo procedimental.

La documentación completa de este sistema la encontraremos en la sección Documentation de la página oficial de la plataforma.

Para este ejemplo utilizaremos las funcionalidades estándar, como son crear una base de datos, crear una tabla, añadir registros y consultar.

Iniciando el servidor

Primero asegúrense de que el servidor de PostgreSQL está corriendo, en el caso de un escritorio de Windows pueden echarlo a andar desde Inicio > Todos los programas > PostgreSQL 8.4 > Start Server. Para un S.O. GNU/Linux en la consola de comandos pueden utilizar la instrucción:

#su -
Ingresa tu contraseña de root
#service postgresql initdb
Iniciamos por primera vez el cluster de postgreSQL
#service postgresql start
Iniciamos el servicio de postgreSQL


Si hemos instalado el pgAmin en nuestro escritorio podemos realizar cada uno de los siguientes pasos a través de su interfaz gráfica. En caso contrario también podemos utilizar el Shell de PostgreSQL con los comando. Dentro de Windows pueden encontrar este Shell en Inicio > Todos los programas > PostgreSQL 8.4 > Shell, para Linux basta que desde la consola de comando tenemos que estar logeados como root (tal y como quedamos en el paso anterior) e ingresar los siguientes comandos:

#su - postgres
Nos cambiamos a la cuenta del administrador de postgreSQL
#psql
Entramos al Shell de postgreSQL


Creación de la base de datos

Crearemos una nueva base de datos llamada pruebas. Desde el Shell sería:

postgres=#CREATE DATABASE pruebas WITH OWNER = postgres;


Nota: El siguiente paso es solo necesario para los que esten utilizando el Shell:

Salimos del Shell

postgres=#\q


y volvemos a ingresar, con los pasos que vimos antes, solo que ahora debemos seleccionar la base de datos pruebas que recién creamos. Desde el Shell de Windows bastará con en los parámetros que nos pide capturar en un inicio:

Server [localhost]:
Database [postgres]: pruebas
Port [5432]:
Username[postgres]:


Dejando el resto de los campos sin cambio para que conserven los valores por defecto que se indican. Mientras que en Linux bastará con el comando :

#psql -d pruebas


desde la consola de comandos. Recuerden que tenemos que estar logeados como el usuario postgres.

Creación de la tabla

Ahora, creamos una nueva tabla para esta base de datos llamada usuario, con las características que se muestran en el script:

CREATE TABLE usuario
(
"correoElectronico" character varying(50) NOT NULL,
nombre character varying(60),
domicilio character varying(60),
edad integer,
telefono character varying(20),
CONSTRAINT correo PRIMARY KEY ("correoElectronico")
)
WITH (
OIDS=FALSE
);
ALTER TABLE usuario OWNER TO postgres;


Desde el pgAdmin la creación de tablas se hace accediendo a la base de datos y dentro de Schemas > public seleccionamos la opción New Object > New Table...



Inserción de los registros

Ahora, solo queda insertar algunos registros en la tabla para hacer las pruebas desde nuestra aplicación en Java:

INSERT INTO usuario(
"correoElectronico", nombre, domicilio, edad, telefono)
VALUES
('maria@gmail.com', 'María Rodríguez', 'Av. Libertadores #5464', 19, '(611)6766676'),
('antonio@gmail.com', 'Antonio Márquez', 'Misiones #786', 22, '(611)9898981'),
('estela@gmail.com', 'Estela Saldivar', 'Av. Ruta de la Seda #9004', 25, '1184564');


Controlador de PostgreSQL para Java

De esta forma la base de datos esta preparada para ser utilizada. Ahora solo tenemos que incluir en nuestro proyecto de Java el controlador para conectarse con bases de datos de PostgreSQL. Este lo pueden descargar desde la página oficial del proyecto PostgreSQL > JDBC Driver, el cual consiste en un archivo .jar que contiene una biblioteca de clases para implementar el acceso a las bases de datos de este servidor. Una vez descargado el archivo, para incluirlas en nuestro proyecto de Eclipse, bastará con que configuremos el Build Path del JRE System Library (biblioteca del sistema de JRE) del proyecto, haciendo clic derecho sobre el JRE System Library y seleccionando:



Y dentro de la ventana que aparece seleccionamos el botón "Add External JARs..." con lo cual podremos agregar el archivo .jar descargado a la lista de referencia del proyecto:



Si están utilizando el entorno de desarrollo diferente de Eclipse entonces pueden usar la herramienta que posea para agregar archivos JAR externos.

Si por el contrario, están compilando y probando todos los programas desde la consola de comandos, una solución será modificar el CLASSPATH del programa para incluir la referencia al archivo .jar. En la documentación de la biblioteca que encuentran en la misma página de PostgreSQL > JDBC Driver se especifican los pasos, pero para ahorrar un poco el trabajo sería más o menos así:

Como ejemplo, si tenemos un archivo fuente llamado myapp.jar que contiene la definición de la clase MyApp, este archivo se encuentra en /usr/local/lib/myapp.jar y la biblioteca .jar está en /usr/local/pgsql/share/java/postgresql.jar, la ejecución de la aplicación sería:

#export CLASSPATH=/usr/local/lib/myapp.jar:/usr/local/pgsql/share/java/postgresql.jar:.
#java MyApp


Todo desde la consola de comandos. Otra solución, -que me pareció más sencilla- la encontré en este blog, y consisten en copiar el archivo en el directorio de extensiones de JRE, es decir JRE\lib\ext. Por ejemplo, la ruta absoluta en mi S.O. Windows es C:\Archivos de programa\Java\jre6\lib\ext, y variará según el sistema operativo en el que estén trabajando.

Y con esta referencia ya estaremos listos para pasar a la programación de la aplicación en Java, pero debido a lo mucho que se extendió este post, esa parte quedará pendiente para el siguiente artículo.

Recuerden que para cualquier duda u observación pueden dejar un comentario en este y todos los otros artículos.

Hasta la próxima.

martes, 11 de agosto de 2009

Diseño de interfaces gráficas en Java, Parte 3: Manejo de eventos


El manejo de eventos siempre estará bajo nuestro control

¡Saludos nuevamente blogueros!

La gestión o manejo de eventos en Java, es un mecanismo que podemos incluir en nuestros programas para reconocer las acciones realizadas por los usuarios en las interfaces gráficas, y programar una respuesta en consecuencia.

Algunos de los eventos comunes que nos encontramos en la interfaces son el hacer clic con el ratón sobre un botón o un elemento del menú, utilizar el menú contextual del ratón, o utilizar una combinación de teclas para acciones rápidas, para los cuales podemos preparar una respuesta de nuestro sistema, de modo que gestionaremos estos eventos de la interfaz gráfica. Además, es posible incluir manejo de eventos más especializados, como validar el texto que el usuario está capturando a través del teclado, obtener la ubicación del ratón dentro de la pantalla, u obtener los valores capturados en algunos controles especiales como los componentes JSlider, JTree o JSpinner, entre otros.

Como ya había mencionado en el primer post de esta serie, la implementación de la gestión de eventos recae en el componente de la aplicación conocido como Controlador. Dentro del ejemplo que exploraremos a continuación, este aparece representado por una única clase que se encarga de atender diferentes eventos ocurridos en la ventana principal de nuestra aplicación, a través de la implementación de 2 interfaces nativas del lenguaje Java: ActionListener y MouseMotionListener. Las acciones que recibirán nuestra atención son que el usuario haga un un clic con el ratón sobre el botón de búsqueda titulado "Buscar", y el movimiento del ratón sobre la pantalla, de modo que al seleccionar este botón se deberá desplegar una ventana emergente que contendrá una serie de datos, y al mover el ratón deberán mostrarse en una de las etiquetas de la pantalla principal las coordenadas de la ubicación del ratón.

Notas: Además de las interfaces, Java cuenta con un grupo de clases nativas que también podemos usar para incluir la gestión de eventos en nuestro programas, llamadas clases adaptadoras. Sin embargo, para efectos prácticos, nos enfocaremos en las interfaces, ya que Java nos permiten implementar múltiples interfaces en una misma clase, con lo que podemos tener una sola clase controladora de todos los eventos.

Retomaremos el proyecto del post anterior, agregando nuevas clases y modificando parte del código de la clase Ventana para echar a andar la gestión de eventos.

El Controlador

Para incluir este componente dentro de nuestro programa crearemos el archivo de la clase que lo implementará, y después agregaremos una instancia a ésta en el código de la ventana principal. El código para el Controlador es el que aparece a continuación:

Controlador.java
import javax.swing.JFrame;
import java.awt.event.*;
/**
* @Desc Clase que maneja los eventos realizados en la Vista implementando una serie de interfaces nativas del lenguaje
* @author Beto González
*
*/
public class Controlador implements ActionListener, MouseMotionListener {
    public Ventana ventana;
    Controlador(Ventana ventana) {
        this.ventana = ventana;
    }
    /**
    * @Desc método perteneciente a la interface ActionListener que es implementado para controlar las acciones sobre los botones
    * ya que se ejecutará si se hace clic sobre el botón que este atendiendo
    */
    public void actionPerformed(ActionEvent ie)
    {
        if(ie.getActionCommand() == "Buscar usuario") { //Validación para asegurarse que la acción fue realizada sobre el botón Buscar
            ventana.panelPrincipal.lblMensaje.setText("Has hecho clic en el bóton de búsqueda");
            //Desplegamos una ventana emergente que contendrá la información de un panel de la clas PanelDeDatos
            PanelDeDatos panel = new PanelDeDatos();
            panel.estableceContenido();
            VentanaEmergente emergente = new VentanaEmergente("Información del usuario", panel);
            emergente.setBounds(180, 180, 300, 250);
            emergente.setVisible(true);
            emergente.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
            emergente.addWindowListener(new WindowAdapter()
            {
                public void WindowCloser(WindowEvent e)
                {
                    System.exit(0);
                }
            });
        }
    }
    /**
    * @Desc Método ejecutado cuando el usuario arrastra el ratón presionando el botón de acción
    */
    public void mouseDragged(MouseEvent e) {
        ventana.panelPrincipal.lblMensaje.setText("Ratón arrastrado");
    }
    /**
    * @Desc Método ejecutado cuando el usuario solo arrastra el ratón
    */
    public void mouseMoved(MouseEvent e) {
        ventana.panelPrincipal.lblMensaje.setText("Posición del puntero X:" + Integer.toString(e.getX()) + " Y:" + Integer.toString(e.getY()));
    }
}


Como vemos, es el código de una clase que implementa las interfaces ActionListener y MouseMotionListener, gracias a las cuales hereda una serie de métodos que le permitirán controlar los eventos sobre algunos de los componentes de la pantalla, en este caso particular presionar el botón "Buscar" y el movimiento del ratón sobre la pantalla. El método actionPerformed() que viene de la interface ActionListener se ejecuta cada vez que se hace clic sobre un botón que haya sido habilitado para recibir eventos y este reciendolos a través de la clase Controlador. De una manera similar, los métodos mouseDragged() y mouseMoved() son heredados de la interface MouseMotionListener, y se ejecutarán con las acciones del ratón correspondientes, siempre y cuando la ventana donde deseamos que actue este configurada para recibir los eventos del movimiento del ratón con esta misma clase.

El método mouseMoved obtiene las coordenadas del ratón a través del parámetro de la clase MouseEvent que recibe, para después desplegarlos incluyendolos en el valor de una de las etiquetas de la pantalla principal. Tanto la ejecución de este método como la creación del objeto que recibe son responsabilidad de Java, por lo que no tenemos que preocuparnos por estos detalles. Algo similar ocurre con el método actionPerformed, ya que las especificaciones del evento ocurrido pueden ser obtenidas a traves de parámetro de la clase ActionEvent recibido, este objeto puede ser utilizada para obtener una referencia al componente desde el que se originó el evento, y de esa forma podemos validar que acción deberemos realizar en consecuencia. En este caso se valida que la propiedad actionCommand del objeto origen sea igual a "Buscar usuario". Después de que se realiza la validación aparece el código que permitirá desplegar una ventana emergente con datos incluidos, para esto se utilizarán 2 clases más definidas por nosotros mismos, cuyo código aparece a continuación.

VentanaEmergente.java
import java.awt.Container;
import javax.swing.JFrame;
import javax.swing.JPanel;
/**
* @Desc Clase utilizada para desplegar ventanas emergentes dentro del sistema que contendrán la información establecida
* en un objeto JPanel pasado por el constructor
* @author Beto González
*/
public class VentanaEmergente extends JFrame {
    //Constructor de la clase
    VentanaEmergente(String titulo, JPanel panelDeDatos) {
        super(titulo);
        Container contentPane = getContentPane();
        contentPane.add(panelDeDatos);
    }
}


PanelDeDatos.java
import javax.swing.*;
import java.awt.*;
/**
* @Desc Clase utilizada para desplegar ventanas emergentes dentro del sistema que contendrán la información establecida
* en un objeto JPanel pasado por el constructor
* @author Beto González
*
*/
public class PanelDeDatos extends JPanel {
    private JLabel lblNombre;
    private JLabel lblCorreoElectronico;
    private JLabel lblDomicilio;
    private JLabel lblEdad;
    private JLabel lblTelefono;
    //Constructor de la clase
    PanelDeDatos () {
        this.lblNombre = new JLabel("Nombre del usuario: ");
        this.lblCorreoElectronico = new JLabel("Correo electronico: ");
        this.lblDomicilio = new JLabel("Domicilio: ");
        this.lblEdad = new JLabel("Edad: ");
        this.lblTelefono = new JLabel("Telefono: ");
        this.setLayout(new GridBagLayout());
    }
    /**
    * @Desc Método que agrega al panel principal cada una de las etiquetas declaradas
    */
    public void estableceContenido() {
        GridBagConstraints delimitador = new GridBagConstraints(); //Creamos un delimitador que usaremos para especificar la ubicación de los componentes dentro del panel
        delimitador.gridx = 0;
        delimitador.gridy = 0;
        delimitador.anchor = GridBagConstraints.EAST;
        this.add(lblNombre, delimitador);
        delimitador.gridy = 1;
        this.add(lblCorreoElectronico, delimitador);
        delimitador.gridy = 2;
        this.add(lblDomicilio, delimitador);
        delimitador.gridy = 3;
        this.add(lblEdad, delimitador);
        delimitador.gridy = 4;
        this.add(lblTelefono, delimitador);
    }
}


La clase VentanaEmergente -que extiende a JFrame- se utilizará para poder crear y lanzar ventanas personalizadas en tiempo de ejecución, mientras que la clase PanelDeDatos -que exiende a JPanel- se utilizará para incluir información en forma de etiquetas dentro estas ventanas.

Por ahora la información que contendrán los objetos de PanelDeDatos será constante, ya que no poseemos una fuente de datos externa con la cual alimentarla. Ese será precisamente el trabajo que realizaremos en el siguiente post, el acceder a una base de datos para extraer la información que se esté consultando, al buscar algunos de los datos caputados por el usuario en la pantalla principal. La razón por la que ese detalle queda fuera de ese post es porque la responsabilidad del acceso a datos recae sobre el Modelo de la aplicación, no sobre el Controlador.

Interacción entre el Controlador y la Vista


Una vez creados los archivos Cotrolador.java, VentanaEmergente.java y PanelDeDatos.java, solo queda modificar el código de la clase Ventana.java para poner a trabajar el manejo de eventos, agregando una referencia a un objeto de la clase Controlador en los objetos correspondientes de la Vista:

Ventana.java
import java.awt.Container;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JFrame;
import javax.swing.*;
/**
* @Desc Clase que utilizamos para crear una ventana que contendrá el panel principal de la aplicación
* @author Beto González
*/
public class Ventana extends JFrame {
    PanelSwing panelPrincipal;
    /**
    * @param args
    */
    public static void main(String[] args) {
        Ventana ventana = new Ventana();
        ventana.setBounds(120, 120, 800, 600);
        ventana.setVisible(true);
        ventana.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
        ventana.addWindowListener(new WindowAdapter()
        {
            public void WindowCloser(WindowEvent e)
            {
                System.exit(0);
            }
        });
    }
    //Constructor de la clase
    Ventana(){
        super("Primer paso en la interfaces gráficas de Java"); //Llamada al constructor de la clase padre
        Container contentPane = getContentPane();
        panelPrincipal = new PanelSwing();
        panelPrincipal.estableceContenedores();
        contentPane.add(panelPrincipal);
        try{
            UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
        }catch(Exception ex){}
        SwingUtilities.updateComponentTreeUI(getContentPane());
        panelPrincipal.btnAccion1.setActionCommand("Buscar usuario"); //Establecemos un Action Command para el boton btnAccion1, con el objetivo de poder identificar sus eventos en el Controlador
        Controlador controlador = new Controlador(this); //Instanciamos a la clase Controlador
        panelPrincipal.btnAccion1.addActionListener(controlador); //Indicamos que el objeto controlador manejará los eventos sobre btnAccion1
        this.addMouseMotionListener(controlador); //Indicamos que el objeto controlador manejará los eventos de movimiento del ratón sobre la ventana

    }
}


Vemos que dentro del código establecemos el valor de la propiedad actionCommand del objeto btnAccion1 como "Buscar usuario", ya que es el valor que se valida del lado del Controlador para comprobar que la acción se realizó sobre este botón. Justo después de esa línea se instancia la clase Controlador y aparece finalmente la referencia del componente con el Controlador de eventos, al escribir la sentencia:

panelPrincipal.btnAccion1.addActionListener(controlador);


Así, establecemos que los todos los eventos recibidos por el botón btnAccion1 serán gestionados por el objeto controlador. En este punto quizás podrían pensar que la validación de la propiedad actionCommand en el método actionPerformed está de más, ya que solo contamos con un botón en la aplicación, pero recuerden que de esta forma, en caso de contar con multiples botones de acción en la aplicación, sería mucho más sencillo manejar los eventos de todos, al recibirlos con un solo objeto en un mismo método.

Para que los eventos del movimiento del ratón sean atendidos por el Controlador, haremos una referencia desde objeto ventana:

this.addMouseMotionListener(controlador);


De modo que todos estos eventos que ocurran en la venta principal serán manejados por este mismo objeto.

Y esto es todo lo que tenemos que hacer para que el manejo de evento funcione, al echar a andar el programa debemos obtener un resultado como el que se observa a continuación:



Y como podemos apreciar, en la etiqueta lblMensaje se despliega el valor de la posición del ratón en la pantalla, y también nos informa cuando lo estamos arrastrando.

Si probamos hacer clic sobre el botón "Buscar" debe aparecer la ventana emergente que mencionamos, tal y como pueden ver en la imagen:



Con la implementación de este ejemplo es que concluímos el post de hoy, el objetivo principal es que aprecieran que agregar un manejo de eventos a nuestros programas en Java es relativamente sencillo, tan solo debemos conocer que clase o interface nativa le corresponde al tipo de componente que deseamos controlar, agregar las definiciones de los métodos necesarios dentro de una clase que que herede o implemente las clases o interfaces, y realizar la vinculación entre el componente y el manejador de eventos. El otro objetivo -que me falto mencionar al principio- era diseñar un esqueleto funcional al que agregaremos el acceso a un manejador de base de datos y una pequeña lógica de negocios, los cuales integrarán el Modelo de nuestra aplicación, completando de esta forma el MVC de que habiamos hablado en un inicio, pero que quedará pendiente para el siguiente artículo.

Hasta la próxima.

viernes, 7 de agosto de 2009

Diseño de interfaces gráficas en Java, parte 2: Swing y los contenedores (Layout)


¿Tocamos Swing?

¡Saludos nuevamente blogueros!

En este post veremos algunas de las características de la biblioteca de clases Swing que posee Java, la cual utilizaremos para el diseño de una interfaz de usuario básica. También revisaremos la gestión de posicionamiento de los componentes gráficos en la pantalla, a través de las diferentes clases de contenedores (Layout) de componentes que posee el lenguaje, en particular le dedicaremos mucha atención a la clase GridBagLayout, que nos permite crear una presentación más elaborada.

También hay que señalar que en esta ocasión nos encargaremos únicamente de la aparición de los elementos gráficos en la pantalla, por lo que trabajaremos con el nivel de Vista, dejando la implementación del Controlador y el Modelo para los siguientes artículos.

La Vista

Bueno, dicho esto entremos en acción. El primer paso consistirá en la creación de una clase que funcionará como un panel que contendrá a los diferentes elementos de la interface que iremos incorporando. Para este propósito es que incluí el siguiente código fuente:

PanelSwing.java
import javax.swing.*;
import java.awt.*;
/**
* @Desc Clase que crea un panel con una serie de componentes visuales que podemos incluir en una ventana de la clase JFrame
* @author Beto González
*/
public class PanelSwing extends JPanel {
    //Atributos de la clase que consisten en una serie de componentes visuales
    public JPanel panelCentral, panelSuperior, panelInferior;
    public JLabel lblTitulo, lblPie, lblMensaje, lblInfo,lblCampo1, lblCampo2;
    public JTextField txtCampo1, txtCampo2;
    public JButton btnAccion1;

    //Constructor de la clase
    PanelSwing() {
        this.setLayout(new BorderLayout());
        this.panelCentral = new JPanel(new GridBagLayout());
        this.panelSuperior = new JPanel(new FlowLayout(FlowLayout.LEFT));
        this.panelInferior = new JPanel(new GridLayout(1,3)); //Este panel consistirá en una tabla de 1 x 3 (1 renglon con 3 columnas)
        this.lblTitulo = new JLabel("Encabezado o Banner de la ventana");
        this.lblPie = new JLabel("Barra de estado");
        this.lblInfo = new JLabel("Autor: Beto González");
        this.lblMensaje = new JLabel("Bienvenido a esta práctica");
        this.lblCampo1 = new JLabel("Nombre completo:");
        this.lblCampo2 = new JLabel("Correo electrónico:");
        this.txtCampo1 = new JTextField("", 30);
        this.txtCampo2 = new JTextField("",20);
        this.btnAccion1 = new JButton("Buscar");
    }
    /**
    * @Desc Método en el que se lleva a cabo la colocación de cada uno de los componentes en el panel principal
    */
    public void estableceContenedores() {
        //Establecemos el panel superior
        panelSuperior.add(this.lblTitulo);
        this.add(panelSuperior, BorderLayout.NORTH);
        //Establecemos el panel central
        GridBagConstraints delimitador = new GridBagConstraints(); //Creamos un delimitador que usaremos para especificar la ubicación de los componentes dentro del panel
        delimitador.ipadx = 8; //Espacio mínimo entre los componentes de un mismo renglon
        delimitador.ipady = 5; //Altura de los componentes
        delimitador.gridx = 0;
        delimitador.gridy = 0;
        delimitador.anchor = GridBagConstraints.EAST;
        panelCentral.add(lblCampo1, delimitador);
        delimitador.gridx = 1;
        delimitador.anchor = GridBagConstraints.WEST;
        panelCentral.add(txtCampo1, delimitador);
        delimitador.gridy = 1;
        delimitador.gridx = 0;
        delimitador.anchor = GridBagConstraints.EAST;
        panelCentral.add(lblCampo2, delimitador);
        delimitador.gridx = 1;
        delimitador.anchor = GridBagConstraints.WEST;
        panelCentral.add(txtCampo2, delimitador);
        delimitador.gridx = 3;
        delimitador.gridy = 3;
        panelCentral.add(btnAccion1, delimitador);
        this.add(panelCentral, BorderLayout.CENTER);
        //Establecemos el panel inferior
        panelInferior.add(lblPie, 0); //Primera celda
        panelInferior.add(lblMensaje,1); //Segunda celda
        panelInferior.add(lblInfo,2); //Tercera celda
        this.add(panelInferior, BorderLayout.SOUTH);
    }
}


La clase recibe el nombre de PanelSwing, ya que básicamente consistirá en un panel que extenderá a la clase JPanel, la cual se encuentra contenida en la biblioteca swing. Este panel representa todo el contenido de la ventana de nuestra aplicación, ya que dentro de él se agregarán otra serie de paneles en los que se colocarán diferentes algunos de los componentes que utilizamos regularmente en el diseño de interfaces de usuario. Estos componentes aparecen declarados como atributos de nuestra clase como podemos ver:

public JPanel panelCentral, panelSuperior, panelInferior; 
public JLabel lblTitulo, lblPie, lblMensaje, lblInfo,lblCampo1, lblCampo2;
public JTextField txtCampo1, txtCampo2;
public JButton btnAccion1;


Entre ellos se incluyen tres paneles, etiquetas, campos de texto y un botón, todos de la biblioteca Swing. Estos objetos son creados dentro del constructor de la clase como podemos ver:

this.setLayout(new BorderLayout());
this.panelCentral = new JPanel(new GridBagLayout());
this.panelSuperior = new JPanel(new FlowLayout(FlowLayout.LEFT));
this.panelInferior = new JPanel(new GridLayout(1,3));
this.lblTitulo = new JLabel("Encabezado o Banner de la ventana");
this.lblPie = new JLabel("Barra de estado");
this.lblInfo = new JLabel("Autor: Beto González");
this.lblMensaje = new JLabel("Bienvenido a esta práctica");
this.lblCampo1 = new JLabel("Nombre completo:");
this.lblCampo2 = new JLabel("Correo electrónico:");
this.txtCampo1 = new JTextField("", 30);
this.txtCampo2 = new JTextField("",20);
this.btnAccion1 = new JButton("Buscar");


Donde además se declara que el Layout de la clase PanelSwing será BorderLayout; este tipo de contenedor nos permite colocar los elementos en cinco ubicaciones distintas dentro de la pantalla, utilizando como referencia los puntos cardinales:



Ya que al momento de agregar cada uno de los elementos a este contenedor se utilizan los valores NORTH, SOUTH, EAST, WEST y CENTER, que corresponden a cada una de las ubicaciones indicadas en la imagen y que se encuentran declarados como valores estáticos dentro de la clase BorderLayout.

El objeto panelSuperior será ubicado en el NORTH de este contenedor, panelCentral en CENTER y panelInferior en el SOUTH. Pueden darse cuenta que este tipo de delimitación está pensada especialmente para diseñar nuestras ventanas para aplicaciones, ya que cada una de las ubicaciones corresponde a un componente específico de la pantalla, por ejemplo la parte Norte puede llevar una barra de menú o de iconos, o una sección de encabezados, los menús también podrían aparecer en las ubicaciones Oeste y Este. Mientras que la parte Central está pensada para colocar el contenido principal de la pantalla, como pueden ser las imagenes, videos, formularios, texto, etc. Finalmente el área Sur por lo general será usada para llevar una barra de estado. Aunque al final la decisión sobre como distribuyamos los elementos de la interface será únicamente de los diseñadores.

El panelSuperior utilizará un Layout de tipo FlowLayout, que ordena los elementos como una secuencia horizontal de izquierda a derecha, similar a como escribimos texto en un editor común. Mientas que panelInferior estará delimitado por un Layout GridLayout, que hace aparecer a los componentes como si estuvieran dentro de una cuadricula de tamaño constante, es decir que en este contenedor se especifica el número de celdas que tendrá, al declarar el número de filas y columnas dentro de su constructor, como vemos que se escribió en el código:

this.panelInferior = new JPanel(new GridLayout(1,3));


Hasta aquí hemos hablado un poco de los tres Layout básicos para interfaces, aún falta ver el más poderoso de todos, es decir el GridBagLayout, que es utilizado por el panelCentral para ubicar los componentes de un formulario. Este contendedor nos permite colocar los componentes dentro de una estructura similar a una tabla HTML, es decir que también cuenta con renglones y columnas, pero con la gran diferencia de que el tamaño y número de las filas y columnas son variables, al mismo tiempo que podemos controlar la ubicación y el relleno que tendrán cada uno de los componentes dentro de la fila. La apariencia que se busca lograr para este ejemplo es la siguiente:


La cual es similar a la apariencia de un formulario básico como los que hemos manejado en las páginas HTML de los temas sobre aplicaciones Web.

Para lograr este propósito es que se incluye el código que vemos dentro del método estableceContenedores():

GridBagConstraints delimitador = new GridBagConstraints();
delimitador.ipadx = 8;
delimitador.ipady = 5;
delimitador.gridx = 0;
delimitador.gridy = 0;
delimitador.anchor = GridBagConstraints.EAST;
panelCentral.add(lblCampo1, delimitador);
delimitador.gridx = 1;
delimitador.anchor = GridBagConstraints.WEST;
panelCentral.add(txtCampo1, delimitador);
delimitador.gridy = 1;
delimitador.gridx = 0;
delimitador.anchor = GridBagConstraints.EAST;
panelCentral.add(lblCampo2, delimitador);
delimitador.gridx = 1;
delimitador.anchor = GridBagConstraints.WEST;
panelCentral.add(txtCampo2, delimitador);
delimitador.gridx = 3;
delimitador.gridy = 3;
panelCentral.add(btnAccion1, delimitador);


Dentro del cual es necesario crear un objeto de la clase GridBagConstraints, que se encarga de especificar la delimitación de cada uno de los componentes que serán colocados en el panelCentral, como vemos en el código, se declara el espaciado entre los elementos de un mismo renglón y la altura de los componentes a través de las propiedades ipadx e ipady respectivamente. Las propiedades gridx y gridy sirven para ubicar al componente en una celda específica. Finalmente el anchor se utiliza para especificar hacia que parte se cargarán los componentes en caso de que esta resulten más pequeños que la celda.

De esta forma después de agregar los componentes a sus paneles correspondientes, solo se agregan estos paneles en las ubicaciones que mencionamos dentro del objeto de la clase PanelSwing.

Para probar este sencillo código, es necesario contar con una clase que extienda a la clase JFrame para crear una ventana de aplicación, y además tener una clase que contenga el método estático main() para su ejecución. En este caso haremos realizaremos ambas tareas en una sola clase con el código que aparece a continuación:

Ventana.java
import java.awt.Container;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JFrame;
import javax.swing.*;
/**
* @Desc Clase que utilizamos para crear una ventana que contendrá el panel principal de la aplicación
* @author Beto González
*/
public class Ventana extends JFrame {
    PanelSwing panelPrincipal;
    /**
    * @param args
    */
    public static void main(String[] args) {
        Ventana ventana = new Ventana();
        ventana.setBounds(120, 120, 800, 600);
        ventana.setVisible(true);
        ventana.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
        ventana.addWindowListener(new WindowAdapter()
        {
            public void WindowCloser(WindowEvent e)
            {
                System.exit(0);
            }
        });
    }
    //Constructor de la clase
    Ventana(){
        super("Primer paso en la interfaces gráficas de Java"); //Llamada al constructor de la clase padre
        Container contentPane = getContentPane();
        panelPrincipal = new PanelSwing();
        panelPrincipal.estableceContenedores();
        contentPane.add(panelPrincipal);
    }
}


El código del método estático main lleva a cabo la instanciación de la clase Ventana, especifica sus dimensiones, la hace visible y finalmente habilita la operación de cierre para que el usuario pueda finalizar su ejecución.

Dentro del construcctor de la clase Ventana se instancia a la clase PanelSwing y después de establecer los elementos visuales que componen a este objeto, se agrega al objeto de la clase Container de la ventana para que se incluya en la presentación.

Solo tenemos que agregar este código a un nuevo archivo de clase dentro del proyecto y ejecutarlo. El resultado tiene que ser una ventana con la siguiente apariencia:



Ahora podemos apreciar el trabajo realizado del lado de la presentación de la aplicación, con el plus de que nuestra ventana es 100% redimencionable, ya que todos los componentes ocupan posiciones relativas dentro de ella.

Además de esta propiedad de posicionamiento, Swing nos ofrece la ventaja de poder modificar la apariencia de todos los elementos visuales de una ventana con un par de líneas de código. Por defecto la apariencia que poseen los componentes es Metal, que como podemos apreciar en las imagenes no se parece a las ventanas estandar de ningún sistema operativo, pero además se pueden elegir además las apariencias como Windows (apariencia de ventanas de Windows) y Motif (apariencia de ventanas de Unix); estás tres son lo que se denimona independientes de la plataforma, ya que pueden ser usadas en cualquier S.O. También hay una apariencia llamada Mac, para que nuestras ventanas luscan como las de los sistemas operativos de la Mac, pero a diferencia de las anteriores ésta es dependiente de la plataforma. Pueden encontrar más información sobre la modificación de la apariencia en este artículo de Java en castellano.

Si deseamos modificar la apariencia del ejemplo que acabamos de realizar para que lusca como una ventana de Windows, bastará con agregar unas cuantas líneas de código a la clase Ventana como aparece a continuación:

import java.awt.Container;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JFrame;
import javax.swing.*;
/**
* @Desc Clase que utilizamos para crear una ventana que contendrá el panel principal de la aplicación
* @author Beto González
*/
public class Ventana extends JFrame {
    PanelSwing panelPrincipal;
    /**
    * @param args
    */
    public static void main(String[] args) {
        Ventana ventana = new Ventana();
        ventana.setBounds(120, 120, 800, 600);
        ventana.setVisible(true);
        ventana.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
        ventana.addWindowListener(new WindowAdapter()
        {
            public void WindowCloser(WindowEvent e)
            {
                System.exit(0);
            }
        });
    }
    //Constructor de la clase
    Ventana(){
        super("Primer paso en la interfaces gráficas de Java"); //Llamada al constructor de la clase padre
        Container contentPane = getContentPane();
        panelPrincipal = new PanelSwing();
        panelPrincipal.estableceContenedores();
        contentPane.add(panelPrincipal);
        try{
            UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
        }catch(Exception ex){}
        SwingUtilities.updateComponentTreeUI(getContentPane());

    }
}


Para obener un resultado como el siguiente:



De esta forma es fácil modificar la apariencia general de nuestras aplicaciones de una manera muy fácil.

Y con este último ejemplo es que terminamos este pequeño artículo, espero que se hayan dado una pequeña idea de como pueden integrar la apariencia de una interface de usuario en Java, ya que la parte que sigue es el manejo de eventos del usuario, que corresponde al Controlador de la aplicación. Además de los controles vistos iremos revisando algunos más que nos pueden ser muy útiles, y finalizaremos con un pequeño proyecto que demostrará un poco el poder que tiene la plataforma Java.

Hasta la próxima.