Práctica 4. Interfaces Gráficas de Usuario 1.- Programación

Anuncio
Práctica 4. Interfaces Gráficas de Usuario
Duración 1 sesión
Índice
1.- Programación dirigida por eventos
2.- Ejemplo. Una calculadora sencilla.
3.- Bibliografía.
1.- Programación dirigida por eventos.
La interacción con un programa a través de una interfaz gráfica, simplifica la labor del
usuario, pero complica la del programador. En su implementación podemos distinguir
dos tareas fundamentales: diseño y programación. El diseño supone determinar la
cantidad de componentes, su agrupación y su ubicación en el espacio, mientras que la
programación supone determinar las acciones que se llevarán a cabo cuando el usuario
interactúe con cada componente.
El modelo utilizado hasta el momento, entrada/salida por consola, establece una
línea de ejecución secuencial. El programa empieza con la primera instrucción del
método main y secuencialmente se van ejecutando una tras otra sus instrucciones. Si
alguna es de salida, por ejemplo System.out.println (“hola”), el programa muestra
en la consola el texto “hola”. Si se trata de una instrucción de entrada el programa
espera hasta que el usuario haya introducido el dato demandado, y una vez introducido
el programa sigue su ejecución.
El modelo de entrada/salida orientado a interfaz gráfica modifica ligeramente la idea
de secuencialidad. Cuando ejecutamos un programa en modo gráfico (todos somos
usuarios de aplicaciones con interfaz gráfica), el programa no esta esperando a que
introduzcamos un número o una frase, sino que gran cantidad de posibilidades se nos
brindan, una por cada componente de la interfaz. Consecuentemente a priori el
programa no sabe que acción va realizar el usuario. Por lo tanto, se tiene que programar
cada una de las acciones sin asumir a priori ninguna ordenación temporal de las
acciones.
Para entender los conceptos subyacentes al diseño y programación de interfaces
gráficas veamos un ejemplo sencillo. Supongamos un programa que presenta una
interfaz gráfica con dos botones y un campo de texto ( área para introducir y visualizar
texto). De forma que si el usuario pulsa el botón etiquetado con el número uno, en el
campo de texto aparece la frase “hola”, y si pulsa el botón etiquetado con el número
dos, en el campo de texto aparece la palabra “adiós”. En Java dicha interfaz tendría el
siguiente aspecto:
Ahora vamos a analizar cada uno de los elementos que vemos en la interfaz así
como las acciones asociadas.
El marco principal
La interfaz del programa esta delimitada por un marco o ventana, que en su parte
superior izquierda viene etiquetada con el texto “saludos”, y en su parte superior
derecha por los habituales botones de minimizar, maximizar y cerrar . En Java existe
una clase denominada Frame de la librería java.awt , que representa un marco en su
forma básica. En la librería java.awt, se encuentran la mayoría de las componentes de
interfaz. Un marco es un contenedor de componentes que a su vez puede contener otros
contenedores. En nuestro ejemplo, todas las componentes de la interfaz, están dentro de
un marco.
Un esqueleto de una clase que representa un marco principal de aplicación es:
class Igu extends Frame
{
// Los atributos pueden ser componentes de la interfaz como
//botones y campos de texto
// En el constructor podemos tener un parámetro que sea el
//título que le queremos dar al marco, dicho parámetro se lo
//pasaremos al constructor
Igu (String nombre)
{
}}
super(nombre);
// a continuación se definen y añaden las componentes de la
//interfaz
Hay que tener en cuenta que la componente Frame, tiene programados los botones
de minimización y maximización pero no el de cierre (su programación la veremos en el
ejemplo del apartado siguiente).
Las componentes del marco
Siguiendo con el ejemplo, dentro del marco principal tenemos dos botones y un campo
de texto. Al igual que ocurre con los marcos, existen clases predefinidas en Java que
representan la forma básica de una componente. En general para cada componente de
interfaz que estáis acostumbrados a utilizar existe una clase predefinida en Java. La
clase Button representa los botones y la clase TextField los campos de texto, ambas
de la librería java.awt. Cuando se necesite tener un botón o campo de texto muy
personalizado se puede diseñar una clase que extienda de Button o TextField. El
código para definir uno de los botones del ejemplo es:
Button hola= new Button(“1”);
donde el “1” indica el texto que aparecerá dentro del botón.
Un vez creada una componente se puede modificar su apariencia utilizando los
métodos apropiados. Para poder visualizar una componente tendremos que añadirla a un
contenedor, por ejemplo a un marco, y luego hacer visible el contenedor. Para añadir
una componente a un contenedor hay que invocar el método add del contenedor. Por
ejemplo:
add (hola);
que añade el botón “hola” al marco.
El código en Java para diseñar la interfaz del ejemplo es:
import java.awt.*;
class Igu extends Frame
{
// Los atributos
Button hola, adios;
TextField campo_texto;
Igu (String nombre)
{
super(nombre);
hola= new Button(“1”);
adios= new Button(“2”);
campo_texto= new TextField(12);
add(hola);
add(adios);
add(campo_texto);
}}
class Principal
{
// Los atributos
static void main (String []args)
{
Igu interfaz=new Igu(“Saludos”);
// colocamos el tamaño inicial de la ventana
interfaz.setSize(500,500);
// y la visualizamos
interfaz.setVisible(true);}}
La clase Principal se usa para lanzar a ejecución la interfaz gráfica. Crea un objeto
de la clase Igu, establece su tamaño inicial y lo visualiza.
Ubicación de las componentes en el marco
Hasta el momento hemos visto como añadir componentes a la ventana o marco
principal, pero hemos pasado por alto su ubicación dentro del espacio. Esto es, cada vez
que ejecutamos el método add del marco ¿dónde se coloca la componente?. En Java
esto se resuelve asociando un diseñador del espacio al marco. Existen clases
predefinidas que hacen la función de diseñadores, el más sencillo de todos, es el
FlowLayout que coloca las componentes de izquierda a derecha y de arriba abajo según
el orden de ejecución del método add. El siguiente código añade un diseñador a un
contenedor:
FlowLayout diseñador= new FlowLayout();
setLayout(diseñador);
Este código se tiene que poner antes de las instrucciones add del contendor.
De momento sólo nos hemos centrado en la parte visual de la interfaz no en la
programación de las componentes. De manera que si implementamos y ejecutamos el
código descrito, la interfaz no responde a casi nada.
Dispositivos físicos de interacción con la interfaz y eventos
Cuando el usuario utiliza el ratón, el teclado o cualquier otros dispositivo físico para
interaccionar con las componentes de una interfaz gráfica, la máquina “virtual” (en
ejecución) produce un evento que representa dicha interacción, o en términos Java se
crea un objeto de la clase AWTEvent en la librería java.awt.event. Por ejemplo, si el
usuario presiona un botón, el evento es de la clase ActionEvent, subclase de
AWTEvent; si el usuario cierra un marco, el evento es de la clase WindowEvent; si el
usuario toca el botón del ratón, el evento es de la clase MouseEvent; etc. Además
del tipo de interacción, un objeto evento almacena datos como la posición de pantalla
donde se produjo (para consultarla, getX() y getY()), y la componente de la interfaz
involucrada o fuente del evento (para consultarla, getSource()).
Tratamiento de los eventos
Cuando se produce una interacción pero no se ha programado nada para tratarla,
simplemente se ignora. Es más, para que se llegue a crear un objeto evento la máquina
virtual tiene que tener registrado un responsable de su tratamiento, i.e. una clase oyente.
Para que una clase oyente tenga la capacidad de efectivamente tratar un tipo de evento
dado, por ejemplo un ActionEvent, tiene que implementar la interfaz de escucha de
dicho tipo de evento, por ejemplo ActionListener para eventos ActionEvent. Así, la
clase oyente deberá implementar los distintos métodos del interfaz de escucha, por
ejemplo implementará actionPerformed que es él único método del interfaz
ActionListener indicando qué tratamiento se da a cada evento ActionEvent en
función de su fuente.
Siguiendo con el ejemplo, la clase Oyente responsable del botón ”hola” es:
class Oyente implements ActionListener
{
// clase oyente de eventos de botón
public void actionPerformed(ActionEvent e)
{
String res =”hola”;
campo_texto.setText(res);
}}
Para que la clase Oyente pueda acceder al botón hola y al campo de texto
campo_texto, se declara como clase interna de la clase Igu. Pese a que el concepto de
clase interna no ha sido desarrollado en clase de teoría es fácil entender que su
visibilidad esta restringido a la clase que la contiene y en este caso a la propia máquina
virtual. De forma alternativa se podría otorgar funcionalidad de oyente a la propia clase
Igu, si ésta implementara la interfaz ActionListener. En el apartado siguiente se
presenta una solución que hace uso de esta estrategia.
Una vez definida la clase Oyente, el código para añadirlo al marco es:
Oyente oy=new Oyente();
hola.addActionListener(oy);
De forma similar al botón hola, se realiza la programación del botón adiós. Como
hemos comentado anteriormente, los botones de minimizar y maximizar del marco ya
están programados. Sin embargo, el botón de cierre del marco no. Pese a que también se
trata de un botón, su programación es un poco diferente al del resto de botones, puesto
que el evento que se genera cuando se interactúa con él es WindowEvent. Para que una
clase sea oyente de estos eventos tiene que implementar la interfaz WindowListener,
que define 7 métodos. Como únicamente estamos interesados en programar el botón de
cierre y el método que se ejecuta cuando se pulsa es windowClosing, sólo escribiremos
código para ese método. Del resto de métodos sólo pondremos su cabecera. Para evitar
esto último, la API de Java tiene unas clases denominadas adaptadores, que
implementan las interfaces oyentes, dejando el cuerpo de los métodos vacíos. De
manera que si una clase oyente la hereda no necesita poner la cabecera de los métodos
que no quiere programar. En el ejemplo, el oyente del cierre del marco es:
class Oyente_marco extends WindowAdapter implements WindowListener
{
/
public void windowClosing(WindowEvent e)
{
dispose();
System.exit(0);
}}
Y el código para añadirlo el oyente de marco al marco es:
Oyente_marco oy1=new Oyente_marco();
this.addWindowListener(oy1);
2.- Ejemplo. Una calculadora sencilla.
Visualmente nuestra calculadora en su versión más básica tendrá el siguiente aspecto:
Primera solución
La primera solución del ejercicio se fundamenta en las siguientes directrices:
•
La lógica del programa esta fusionada con la programación de eventos
•
Una única clase Oyente interna que atienda los eventos de los dos botones.
•
El diseñador del marco es el FlowLayout.
•
No se realiza ningún tratamiento de errores debidos a la inserción de datos no
numéricos.
import java.awt.*;
import java.awt.event.*;
/**
* La clase calculadora representa una calculadora básica.
public class Calculadora extends Frame
{
//
Label eti1,eti2,eti3;
TextField t1,t2,t3;
Button b1,b2;
public Calculadora(String nombre)
{
super(nombre);
// el gestionador de diseño es el BorderLayout
setLayout(new FlowLayout());
// Añadimos las componentes propias de la interfaz
eti1=new Label("primer elemento");
eti2=new Label("segundo elemento");
eti3=new Label("resultado");
t1=new TextField(12);
t2=new TextField(12);
t3=new TextField(12);
b1= new Button("+");
b2= new Button("*");
Oyente oy=new Oyente();
add(eti1);add(t1);
add(eti2);add(t2);
add(eti3); add(t3);
add(b1);
b1.addActionListener(oy);
add(b2);
b2.addActionListener(oy);
Oyente_marco oy1=new Oyente_marco();
this.addWindowListener(oy1);
}
class Oyente implements ActionListener
{
// clase oyente de evetos de raton
public void actionPerformed(ActionEvent e)
{
String n1,n2,res;
double num1, num2,result;
n1=t1.getText();
num1=Double.parseDouble(n1);
n2=t2.getText();
num2=Double.parseDouble(n2);
if (e.getSource()==b1)
result=num1+num2;
else result=num1*num2;
res=Double.toString(result);
t3.setText(res);
}
}
class Oyente_marco extends WindowAdapter implements WindowListener
{
public void windowClosing(WindowEvent e)
{
dispose();
System.exit(0);
}}
}
Segunda solución
La segunda solución del ejercicio se fundamenta en :
•
la lógica del programa se separa de la programación de eventos. De forma que
aparece una nueva clase Calculo sobre la que se realizan las operaciones
aritméticas.
•
La clase Igu asume el papel de oyente único para los dos botones.
•
No se realiza ningún tratamiento de errores debidos a la inserción de datos no
numéricos.
Por lo tanto aparecerá un clase Calculo que suma y multiplica los valores que se le
pasan como argumentos.
public class Calculo {
public static double suma(double x, double y) {
return x + y;}
public static double producto(double x, double y) {
return x * y;}}
En la cabecera de la clase Igu tendremos que indicar que se va a implementar la interfaz
ActionListener. La clase interna Oyente desaparece y el método actionPerformed
se reescribe de la siguiente forma:
public void actionPerformed(ActionEvent e)
{
String n1,n2,res;
double num1, num2,result;
n1=t1.getText();
num1=Double.parseDouble(n1);
n2=t2.getText();
num2=Double.parseDouble(n2);
if (e.getSource()==b1)
result=Calculo.sumar(num1,num2);
else result=Calculo.producto(num1,num2);
res=Double.toString(result);
t3.setText(res);}
Pese a que la modificación parece muy leve en el contexto del problema, la
aplicación sistemática del criterio de separar la lógica del programa de la programación
de la interfaz, dará como resultado programas más claros, modulares y acordes con los
principios de programación orientada a objetos.
Tercera solución
La tercera solución del ejercicio asume las directrices de la solución anterior y realiza
el tratamiento de errores debidos a la inserción de datos no numéricos.
Su aspecto será el siguiente:
Como se observa en al imagen, se ha añadido una nueva componente para la
visualización de los mensajes de error (provocados por la inserción incorrecta de datos).
Además de algunos detalles que vienen comentados directamente en el código, el
concepto más importante que presenta es la agrupación de componentes, a través del
contenedor de componentes Panel, de manera que el diseño del marco se divide en el
panel Norte (pNorte) y el panel Sur (pSur). En cada panel se añaden las componentes y
luego el panel se añade al marco. El diseñador que se ha utilizado es el BorderLayout,
que divide el espacio en 5 zonas: norte, sur, este y oeste.
El código de esta solución está en :/labos/asignaturas/EI/eda/practica4.
3.- Bibliografía.
Weiss. Estructuras de datos en Java. Apéndice D. Ed Addison-Wesley.
Descargar