Universidad Carlos III de Madrid

Ingeniería de Telecomunicación

Enero-Mayo 2010 / January-May 2010

Interfaces Gráficas de Usuario

Lab Section1. Laboratorio: Interfaces Gráficas de Usuario

Time90 min

Exercise Section1.1. Gestión de eventos

Time40 min

El código que se muestra a continuación construye una interfaz gráfica muy simple, que contiene un botón (javax.swing.JButton) que puede ser pulsado. El resto de los elementos de la interfaz los deberías conocer ya.

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JButton;
import java.awt.BorderLayout;

class Simple00GUI {

	private static final String TITULO_FRAME = "Simple00GUI";
	private static final String TEXTO_BOTON = "Púlsame!";

	private void creaGUI() {
		JFrame.setDefaultLookAndFeelDecorated(true);
		JFrame frame = new JFrame(TITULO_FRAME);
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		JPanel contentPane = (JPanel) frame.getContentPane();
		JButton button = new JButton(TEXTO_BOTON);
		contentPane.add(button, BorderLayout.CENTER);
		frame.pack();
		frame.setVisible(true);
	}

	public static void main(String args[]) {
		Simple00GUI p = new Simple00GUI();
		p.creaGUI();
	}
}

          

Descargar este código

  1. Compila y prueba este programa. ¿Qué ocurre al pulsar sobre el botón?

  2. Abre el API de Java y localiza la clase JButton. Este punto ya lo deberías haber realizado por iniciativa propia, esto es un mero recordatorio.

  3. Escribe un programa Simple01GUI, basado en Simple00GUI, en el que al pulsar el botón se muestre un texto por salida estándar. Te será útil el método javax.swing.AbstractButton.addActionListener(ActionListener l), así como el siguiente esqueleto de código.

    import java.awt.event.ActionListener;
    import java.awt.event.ActionEvent;
    
    class ButtonActionListener implements ActionListener {
    
        public void actionPerformed(ActionEvent e) {
    
        }
    }
    
              

    Descargar este código

  4. Escribe un programa Simple02GUI, basado en Simple00GUI, en el que al pulsar el botón se modifique el texto que aparece sobre el mismo.

  5. Escribe un programa Simple03GUI, basado en Simple00GUI, que además contenga un JLabel cuyo texto aparecerá con la primera pulsación del botón.

  6. Escribe un programa TwoButtons que contenga dos botones A y B. Al comenzar el programa, sólamente A estará activado. Al pulsar sobre el botón A, éste se desactivará y será B el que pase a estar activo. El comportamiento de B será simétrico al de A. Aquí tienes un ejemplo: TwoButtons.class.(Nota: Este ejemplo está compuesto de una única clase, pero tu programa no tiene por qué seguir este patrón. Elige la forma de trabajo que más te guste.)

  7. Con respecto al apartado anterior, si lo hiciste con una única clase, repite el ejercicio utilizando varias clases. Si por el contrario utilizaste varias clases, haz las modificaciones necesarias para que sólo haya una clase (un único .class resultante de la compilación).

Exercise Section1.2. Gestión de Eventos en GUIs I -(Tres En Raya)

Time90 min

Este ejercicio vamos a practicar con un ejemplo completo de implementación de una Interfaz Gráfica de Usuario (GUI) que integre la correspondiente Gestión de Eventos (Event Management) asociada a los elementos gráficos que la componen.

Para todo esto, nos basaremos en el famoso juego del “Tres en Raya”. Según la Wikipedia, “el tres en línea, también conocido como Tres En Raya, juego del gato, tatetí, triqui, tres en gallo, michi o la vieja, es un juego de lápiz y papel entre dos jugadores: O y X, que marcan los espacios de un tablero de 3×3 alternadamente. Un jugador gana si consigue tener una línea de tres de sus símbolos: la línea puede ser horizontal, vertical o diagonal.”

Para simular el tablero del juego, usaremos los siguientes elementos gráficos del paquete javax.Swing que se citan a continuación:

  • Los espacios del tablero se simulan mediante el uso de botones de tipo javax.swing.JButton con una imagen (javax.swing.ImageIcon) asociada a los mismos.

    • Si el espacio esta libre, sin haber sido marcado, el botón no tendrá asociada ninguna imagen.

    • El marcado del espacio se simula asociando al botón una imagen vinculada, respectivamente, al turno de cada jugador: O y X

      Las imágenes se muestran a continuación para poder descargarlas:

      Nota

      A la hora de inicalizar un objeto ImageIcon con la ruta del fichero de la imagen del botón, asegúrate de que dicha ruta sea correcta para que pueda mostrarse al usuario.

  • El tablero se modelará mediante un elemento contenedor de tipo javax.swing.Jpanel y cuyo layout asociado será de tipo java.awt.GridLayout , un array bidimensional (3x3).

    • El array bidimensional 3x3 se rellena con botones del tipo anterior.

La siguiente imagen muestra la ventana inicial del juego constituida por los siguientes paneles (JPanel): un panel superior (panel_info) con información sobre el turno del jugador actual; y un panel central (panel_board) que contiene inicialmente el array de botones sin ninguna imagen asociada, es decir, el tablero inicial del juego sin marcar y preparado para empezar a jugar:

A medida que se suceden los turnos de los jugadores, cada jugador “marca el espacio" al pulsar el botón correspondiente de la misma. Para simular el marcado del espacio, se cambia la imagen asociada al botón con el símbolo de cada jugador como puede observarse en la siguiente imagen:

En el caso de que algún espacio no se pueda marcar (bien porque no le queden símbolos restantes al jugador o por no estar en el turno correspondiente), debe aparecer un mensaje advirtiendo del error cometido.

Por último, si se consiguen las tres en raya, se muestra un mensaje de enhorabuena al jugador como se muestra en la figura.

La clase TicTacToeButton.

Time30 min

Vamos a programar la clase TicTacToeButton que representa el espacio o casilla que puede marcar cada jugador. Para simular el marcado de la misma, el botón actualizará la imagen asociada al mismo en función de si está libre (no tiene imagen asociada) o marcada (con la imagen asociada al jugador de cada turno). Para ello, la clase contiene los siguientes atributos:

  • Un atributo entero (turnPlayer) para almacenar el turno del jugador correspondiente y representa el estado del espacio (casilla).

  • Tres constantes enteras (FREE, PLAYER_1, PLAYER_2) que indican los posibles estados correspondientes asociados al botón .

  • Un array de imágenes (javax.swing.ImageIcon[]) que almacena los símbolos de los respectivos jugadores: O y X.

Descarga el esqueleto de la clase del siguiente enlace TicTacToeButton.java e implementa los siguientes métodos de la clase que se citan a continuación:

  • El constructor de la clase public TicTacToeButton() que inicializa el botón y lo deja en estado libre (FREE), es decir, sin turno de jugador y, por consiguiente, sin ninguna imagen asociada al mismo.

  • El método public void setPlayer(int newPlayer) que actualiza el estado del botón con el turno del nuevo jugador y su imagen correspondiente.

Prueba el funcionamiento de la clase ejecutando el método main de la misma y pasándole como argumento uno cualquiera de los posibles estados del botón (FREE(-1), PLAYER_1(0), PLAYER_2(1)) para ver que el comportamiento del mismo es correcto.

La clase TicTacToe.

Time20 min

En este apartado no se va a programar nada, símplemente se pretende observar cómo se ha construido el Interfaz Gráfico correspondiente al tablero del "Tres en Raya". Descarga el esqueleto de la clase TicTacToe del siguiente enlace TicTacToe.java y observa los siguiente métodos que permiten construir el Interfaz Gráfico.

Nota

El método que realiza la configuración de la gestión de eventos, private void createEventListener(), se ha dejado vacío hasta el siguiente apartado.

  • El método private void buildGraphicElements() inicializa todos los elementos gráficos necesarios para construir el Interfaz Gráfico (etiquetas y array de botones) y los establece en el estado inicial libre (FREE), tal y como se muestra en el enunciado del ejercicio.

  • El método private void addElementsToContainers() añade respectivamente los elementos gráficos anteriores a sus correspondientes paneles (información y tablero). Por último, añade dichos paneles respectivamente al contenedor de alto nivel de la ventana del GUI.

Para hacer visible la ventana con el GUI del juego en el estado inicial del mismo, ejecuta el método main de la misma. Dicho método se encarga de crearla, inicializarla y hacerla visible invocando a los anteriores métodos.

La clase TicTacToeButtonListener.

Time40 min

En este apartado vamos a "dar vida" a nuestro juego para que sea capaz de interaccionar con el usuario de acuerdo a la lógica del juego. Todo eso se consigue con el diseño e implementación de la “Gestión de Eventos” asociada al GUI, para lo cual, normalmente necesitamos hacer las siguientes tareas:

  1. Decidir qué tipo de componentes gráficos de nuestro Interfaz Gráfico constituyen la "Fuente de Eventos" (Event Source), es decir, qué componentes serán los generadores de eventos que queramos a tratar posteriormente. En nuestro caso, dichos componentes gráficos serán los botones (JButton) del tablero que representan los espacios o casillas del mismo.

  2. Decidir qué tipos de eventos, de los que pueden generar los anteriores componentes, van a ser posteriormente tratados por el/los escuchador/es correspondiente/s. En este caso, todos los eventos que vamos a tratar son del tipo ActionEvent y que está relacionado con el evento de pulsación del botón por parte del usuario.

  3. Definir el número de Clases Escuchadoras (Listeneres) que van a reaccionar a los anteriores eventos. El número de clases es variable, puede ser una o varias en función de la estrategia de diseño para la Gestión de Eventos. En este caso, implementaremos una única clase escuchadora, TicTacToeButtonListener, que va a ser la responsable de gestionar cualquier evento de tipo ActionEvent que se generen al pulsar cualquier botón. Para conseguirlo, la clase escuchadora debe implementar la interfaz (interface) correspondiente asociado a dicho tipo de evento, en nuestro caso se trata de la interfaz ActionListener.

  4. Para cada elemento gráfico que actúe como una "Fuente de Eventos" (Event Source) que quieran ser tratados posteriormente por una o varias clase Escuchadoras (Listeners), se necesita realizar un registro previo de dicha/s clase/s en el componente. En nuestro caso, una vez creado el objeto de la clase escuchadora (TicTacToeButtonListener), este se ha de registrar respectivamente en cada uno de los botones del tablero mediante la invocación al método public void addActionListener(ActionListener l) de la clase Component.

  5. Por último, implementar la clase Escuchadora de los eventos, TicTacToeButtonListener, esto es, tanto el constructor como el/los método/s correspondiente/s de la interfaz o interfaces que se implementen en la/s clase/s escuchadoras de acuerdo a la anterior estrategia elegida (punto 2). Es en estos métodos es donde ser realiza la auténtica gestión de eventos asociada del GUI.

En relación a los tres primeros puntos, (1. 2. y 3.), todo se resume en que los únicos componentes gráficos que van a actuar como fuentes de eventos serán los botones (JButton) del tablero. Respecto a los distintos tipos de eventos posibles, únicamente van a gestionarse eventos de tipo ActionEvent que se generen tras las pulsaciones de dichos botones. Se ha decidido crear una única clase escuchadora, la clase, TicTacToeButtonListener, que se encarga de gestionar los eventos del tipo ActionEvent implementando su interfaz asociada, ActionListener y, por consiguiente, el siguiente único método de la misma: public void actionPerformed(ActionEvent event).

En este apartado del ejercicio, implementaremos el código de los dos últimos puntos (4. y 5.):

  • El punto 4. se implementa mediante el método private void createEventListener() de la clase TicTacToe. Se encarga de realizar la creación y el posterior registro de la clase escuchadora para cada elemento gráfico generador de eventos, es decir, para cada botón del tablero.

    Programa el código del método en el esqueleto de la clase TicTacToe de acuerdo a lo especificado en el punto.

  • El punto 5. nos obliga a implementar el método public void actionPerformed(ActionEvent event) en la clase escuchadora TicTacToeButtonListener. Es, precisamente, en este método donde se realiza la verdadera Gestión de Eventos del GUI. El evento generado se pasa al método a través del parámetro event del mismo.

El diagrama de flujo correspondiente al método public void actionPerformed(ActionEvent event) se muestra a continuación. Está particularizado para un jugador "JUGADOR_X" y representa el flujo del programa que se realiza cuando dicho jugador pulsa el botón correspondiente al espacio que quiere marcar. En el diagrama de flujo, básicamente se realizan tres comprobaciones previas antes de verificar si el jugador ha ganado la partida:

  • Primero, se comprubeba si al jugador le quedan símbolos libres para poder marcar el espacio (recuerda, el jugador posee solo tres símbolos para usar).

    • En caso afirmativo, se comprueba si el espacio esta libre (FREE) o no y, en cuyo caso se debe mostrar el mensaje de error correspondiente.

    • En caso negativo, se comprueba si el turno actual pertenece al jugador "PLAYER_X" o no antes de actualizar el símbolo de la casilla y, en cuyo caso se debe mostrar el mensaje de error correspondiente.

Nota

Para mostrar una Ventana de Diálogo con el mensaje de error del usuario, puedes usar el método estático showMessageDialog de la clase javax.swing.JOptionPane.

Homework Section2. Actividades para casa

Time180 min

Exercise Section2.1. Gestión de Eventos en GUIs II - (Contador de tiempo)

Time180 min

Para mejorar nuestros conocimientos sobre el modelo de Gestión de Eventos en Java, vamos a proponer el siguiente ejercicio para hacer en casa. Nos basaremos en el código del GUI del Tres En Raya que hemos programado en la sesión de laboratorio y vamos a añadir una nueva funcionalidad que permita controlar el tiempo disponible de cada turno para cada jugador del juego. Es por ello que incorporaremos un nuevo componente que proporciona la librería gráfica de Java (javax.swing) que actua como un "temporizador".

El componente al que hacemos referencia está implementado en la clase javax.swing.Timer y, básicamente, genera eventos del tipo ActionEvent a intervalos determinados constantes y configurables previamente. La forma de gestionar los eventos de este componente se integra en el "Modelo General de Delegación de Gestión de Eventos" de Java que, recordemos, se basa en la existencia de tres clases de objetos:

  • Un conjunto de Fuentes de Eventos (Source Events) que generan eventos de algún tipo como consecuencia de las acciones del usuario al interaccionar con ellos.

  • Un conjunto de Eventos que pueden organizarse en una jerarquía de clases en base a la información almacenada y el origen diverso de los mismos. Por ejemplo, el tipo ActionEvent para acciones sobre un componente; el tipo MouseEvent para movimientos de ratón; el tipo KeyEvent para pulsaciones del teclado, etc…

  • Un conjunto de Escuchadores (Listeners) que reaccionan ante los eventos que generan las anteriores fuentes en base a los distintos tipos de eventos a los que estén subscritos. Para ello, deben implementar la interfaz asociada a cada tipo de evento que quieran gestionar: ActionListener para eventos de tipo ActionEvent; MouseListener para eventos de tipo MouseEvent, etc..

Este modelo tiene muchas ventajas: es entendible, flexible, extensible y robusto. Una buena prueba de esto es lo relativamente sencillo que va a resultar la incorporación de código con lógica adicional de Gestión de Eventos a nuestro GUI del Tres en Raya. Para ello, vamos a hacer la analogía correspondiente de la Gestion de Eventos asociada al nuevo componente que integraremos en nuestra aplicación

  • La Fuente de Eventos será el componente Timer (javax.swing.Timer).

  • El tipo de Evento que se genera y que va a tratarse es del tipo ActionEvent.

  • La clase Escuchadora (TimeListener), tiene que implementar la interfaz asociada al tipo del Evento: ActionListener.

A continuación, se muestra una imagen con la pantalla inicial en la que se muestra la etiqueta con información del contador de tiempo restante del turno del jugador en el panel superior de información (10 segundos en este juego):

El contador de tiempo empieza a descontar segundos hasta que se acaba el tiempo del turno del jugador, en cuyo caso, se muestra la ventana de diálogo con el mensaje de error correspondiente al usuario.

Tras pulsar el botón de Aceptar, se cambia el turno del jugador y se pone el contador de tiempo al valor inicial, tras lo cual, empezará a descontar segundos de nuevo.

Partiendo del código fuente del ejercicio del Tres En Raya de laboratorio que has implementado previamente, realiza las siguientes tareas sobre el mismo para conseguir la incorporación del contador de tiempo:

  • Añade los siguientes atributos a la clase TicTacToe que serán necesarios para integrar el contador de tiempo:

    • Una etiqueta (Jlabel) que contiene el texto con la información el tiempo restante del turno.

    • Una constante entera (final static int) que representa el máximo tiempo para cada turno.

    • Una referencia al objeto Timer (javax.swing.Timer) que será el contador de tiempo.

  • Implementa la clase TimeListenter que actua como clase Escuchadora de los eventos del objeto contador de tiempo.

    • Añade un atributo que sea una referencia a la ventana principal del juego (Jframe) y que se inicializará en el constructor de la clase.

    • Añade otro atributo entero estático que almacenará el número de segundos restantes del turno del jugador.

    • Implementa el método public void actionPerformed(ActionEvent event) que se ejecutará periódicamente por el objeto Timer. Dicho método decrementará el contador de segundos y, en caso de que dicho valor llegue a cero, mostrará el mensaje de error correspondiente al usuario. Por último, cambiará el turno del juego.

  • Crea una instancia del objeto Timer en el constructor de la clase TicTacToe para que el tiempo restante del turno comience a descontar desde ese momento.

    • Como argumento del constructor, pasa una referencia del objeto de la clase Escuchadora TimeListener inicializado con la referencia de la ventana del juego, es decir, con this.

  • Por último, modifica el método que sirve para cambiar el turno del jugador, public void changeTurn(), para que ajuste el valor del contador estático de segundos de la clase Escuchadora (TimeListener) a cero. De esa manera, el contador empezará a descontar el tiempo disponible del nuevo turno del jugador.