Completando la ventana

Vamos a empezar por completar nuestro método ActualizaBoton, que modificará el texto del botón ok a medida que seleccionemos las ciudades y la fecha:

void ActualizaBoton() {
    StringBuffer b = new StringBuffer("Viaje: de ");
    if (cs.getDescription() != null) b.append(cs.getDescription());
    else b.append("?");
    b.append(" a ");
    if (cl.getDescription() != null) b.append(cl.getDescription());
    else b.append("?");
    b.append(" el ");
    if (dp.getDescription() != null) b.append(dp.getDescription());
    else b.append("?/?/?");
    ok.setLabel(b.toString());
  }


Nuestro método comienza por crear un StringBuffer con las palabras "Viaje: de ", y va agregando el resto:

la ciudad de partida, llamando al método getDescription de cs (ciudad de salida)
el texto constante " a "
la ciudad de llegada, llamando al método getDescription de cl (ciudad de llegada)
el texto constante " el "
la fecha seleccionada, llamando al método getDescription de dp (día de partida)

Si en cualquier caso recibe un string nulo, pone un signo de pregunta (o ?/?/? para la fecha).

El método setLabel, sobre el objeto ok de tipo Label, modifica la "etiqueta" del botón.

Realmente nos devuelven null los métodos que llamamos si no hay selección hecha?

Veamos:

class SelecPueblo extends Panel {
private List	listaPueblos;
............................
public String getDescription() {
return listaPueblos.getSelectedItem();
}
}


El método getSelectedItem de la clase List devuelve null si no hay ítems seleccionados, así que acá andamos bien. En cuanto a la clase DiaPartida, de entrada inicializa el valor del texto en la fecha actual, así que aquí no se daría nunca este caso... Aunque al crear el objeto Ventana8 estamos poniendo un texto fijo en el botón, y no el que devuelve el objeto dp.

Sería mejor, para ser más consistente, modificar el constructor de Ventana8 para que arme el texto mediante el método ActualizaBotón:

Ventana8 (String titulo, boolean enApplet) {
........................................
	ok = new Button("cualquiera");
	ActualizaBoton();
	add("South",ok);
	pack();
	show();
  }


Esto ya se ve mejor! Y de paso probamos el método...

Un poquito de actividad

Ahora sí, pasemos a completar nuestro manejador de eventos:

public boolean handleEvent(Event e) {
	if (e.id == Event.WINDOW_DESTROY) {
		if (enApplet) dispose();
		else System.exit(0);
	}
if ( (e.target==dp)||(e.target==cs)||(e.target==cl) )
ActualizaBoton();
if (e.target==ok)
Activar();
}
return super.handleEvent(e);
}


Simplemente, si detectamos un evento sobre alguno de nuestros paneles actualizamos el texto del botón; y si se presiona dicho botón llamamos al método Activar que se supone que va a tomar los datos de la base de datos, indicarnos servicios disponibles, etc.

Algo importante a notar es que el simple hecho de mover el mouse sobre uno de los paneles ya llama a ActualizaBoton (se nota porque titila el texto, sobre todo en una máquina lenta). Además, si hacen click sobre el botón Hoy o Mañana sin mover el mouse, el texto del botón ok no se actualiza ya que el evento va dirigido al botón presionado y no al panel.

Una forma de filtrar sólo los eventos que nos interesan sería usar, por ejemplo:

if ((e.target=cs.listaPueblos) && (e.id==Event.LIST_SELECT)) ActualizaBoton();



que está dirigida a la lista y no al panel en general, y tiene en cuenta el tipo de evento.

Lamentablemente, listaPueblos es privada dentro de la clase SelecPueblo y por lo tanto dentro de cs. Pero es mejor así, porque declararla pública y leerla desde afuera sería bastante sucio (así como la leemos podríamos escribirla).

Hay varias formas de mejorar esto sin cometer la torpeza de declarar pública a listaPueblos. Una posibilidad es verificar, usando cs.getDescription(), si el texto cambió (y sólo en ese caso modificar el texto del botón).

Otra, es hacer que los objetos de la clase SelecPueblo pasen a sus padres cualquier evento sobre ellos, o mejor solamente la selección de un elemento de la lista; para eso basta agregar a la clase SelecPueblo:

public boolean handleEvent(Event e) {
if ((e.target==listaPueblos) && (e.id==Event.LIST_SELECT)) {
e.target=this;
}
return super.handleEvent(e);
}


En resumen: si el evento en el panel es una selección de la lista (tanto con mouse como moviendo la selección con las flechas), cambio el target del evento para que indique el panel (y no la lista); si no, lo paso a la clase antecesora.

Lo mismo podemos hacer con handleEvent para la clase DiaPartida:

public boolean handleEvent (Event e) {
if (e.target == hoy) {
elDia.setText(GetHoy());
e.target=this;
}
if (e.target == diasiguiente) {
elDia.setText(GetManana());
e.target=this;
}
if (e.target == elDia) {
e.target=this;
}
return super.handleEvent(e);
}


Esto no anda como esperaríamos! El campo de texto no se comporta muy bien...

Esto es porque el código dependiente de la plataforma procesa los eventos de mouse antes de llamar a handleEvent, pero procesa los de teclado después de llamar a handleEvent.

Lo que significa que, en el caso del campo de texto, handleEvent (y por lo tanto ActualizaBotón) se llama antes de modificar el texto!

Para corregir esto, deberíamos procesar nosotros las teclas presionadas (lo que podríamos aprovechar para verificar que se presiona una tecla válida).

Cuidado! En futuras versiones de Java podría implementarse el mismo comportamiento para el mouse, y por lo tanto tendríamos que repensar la estrategia.

Para colmo, sólo los eventos que la plataforma envía llegan a Java; por ejemplo, Motif no envía eventos de movimiento de mouse dentro de un campo de texto... lo que significa que nunca podríamos capturar ese tipo de eventos. Sólo el componente Canvas pasa todos los eventos.

Para simplificar, sólo actualizaremos el texto del botón cuando se presiona Enter (Event.key=10):

if ((e.target == elDia)&&(e.id==Event.KEY_PRESS)) {
	if (e.key==10) e.target=this;
}


Ahora debemos modificar el método handleEvent en nuestra clase Ventana8 para que soporte todos estos eventos:

public boolean handleEvent(Event e) {
	if (e.id == Event.WINDOW_DESTROY) {
		if (enApplet) dispose();
		else System.exit(0);
	}
	if ( ((e.target==dp)&&((e.id==Event.ACTION_EVENT)||(e.id==Event.KEY_PRESS)))
||((e.target==cs)&&(e.id==Event.LIST_SELECT))
||((e.target==cl)&&(e.id==Event.LIST_SELECT)) )
			ActualizaBoton();
	if (e.target==ok)
Activar();
	return super.handleEvent(e);
}


Obviamente, procesar todas las teclas nosotros sería bastante más complicado... de todos modos, el método en DiaPartida sería más o menos así:

if ((e.target == elDia)&&(e.id==Event.KEY_PRESS)) {
	// 1- leer el contenido del campo con: elDia.getText()
	// 2- modificarlo de acuerdo a la tecla presionada: e.key
	// 3- poner el resultado en el campo con: elDia.setText(texto)
	// 4- modificar el objeto del evento al panel con: e.target=this;
	// 5- enviar el evento al objeto padre (no la clase padre),
	//     en este caso Ventana8, mediante: getParent().deliverEvent(e)
	// 6- evitar proceso posterior del evento mediante: result(true)
}

Me ahorro explicar estos dos últimos pasos; se complica bastante todo porque hay que manejar la posición del cursor dentro del campo de texto, etcétera. Con lo que hicimos es bastante... creo!

Y para terminar...

Bueno, sólo nos queda por definir el método Activar(). Primero vamos a llamar a ActualizaBoton() por si alguien lo último que hizo fue entrar un texto sin presionar Enter, y dejo para otro día más tranquilo consultar un archivo o base de datos con lo que vamos a mostrar al usuario de nuestro programa.

Por ahora simplemente vamos a mostrar una ventana con la selección y un lindo botón de OK.

Primero vamos a hacer una muy pequeña modificación a ActualizaBoton() para que nos devuelva el valor del texto del botón (para no calcularlo de nuevo):

String ActualizaBoton() {
    StringBuffer b = new StringBuffer("Viaje: de ");
..............................................
    ok.setLabel(b.toString());
  }


Y ahora vamos a definir nuestro método, teniendo en cuenta que nuestro botón sólo actuará si se han entrado todos los datos:

void Activar() {
if ( (cs.getDescription() != null) && (cl.getDescription() != null) )
// también podríamos verificar que la fecha sea válida aquí
Result8 resultado = new Result8("Resultado",ActualizaBoton());
else ok.setLabel("Especificación incompleta!");
  }


Sólo nos falta definir una sencilla clase Result8 para nuestra ventanita resultado:

// archivo Result8.java, compilar con javac Result8.java
import java.awt.*;

class Result8 extends Frame {

Button	r_ok;

Result8 (String titulo, String texto) {	// constructor
super(titulo);
Label r_lbl = new Label(texto);
r_ok = new Button("Ok");
	add("Center", r_lbl);
		add("South", r_ok);
	pack();
		show();
}

public boolean handleEvent(Event e) {
if ((e.id == Event.WINDOW_DESTROY)||(e.target==r_ok))
dispose();	// cierra esta ventana pero no la aplicación
	return super.handleEvent(e);
}
}


Noten que usé dispose y no System.exit! Esto permite cerrar sólo la ventana de resultado, y seguir usando la aplicación hasta que se nos ocurra cerrarla mediante meta-F4, alt-F4, el menú de sistema de la ventana, la cruz de Windows 95 o lo que le resulte a su sistema operativo.

Finale con tutto

Espero que se haya entendido! Esta aplicación costó bastante pero en el camino hemos tenido oportunidad de aprender unas cuantas cosas... Si logran juntar todo el código y generar las varias clases que definimos, todo tiene que andar sobre rieles e independientemente de la plataforma.

Si no... avísenme, y subo también los fuentes o las clases.

Por las dudas, pueden probar esta aplicación como applet cargando:

http://www.amarillas.com/rock/java/Ejemplo8.htm




Y basta por hoy. En el capítulo 10 vamos a empezar a manejar el I/O de Java.

Todavía nos queda mucho por ver! (Y no hemos terminado con el AWT...)

 

<> Página de tutoriales <> Página Anterior <> Siguiente Capitulo <>