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.

0 comentarios:

Publicar un comentario en la entrada