📄 java9.htm
字号:
<HTML>
<HEAD>
<TITLE>Java desde Cero</TITLE>
</HEAD>
<BODY background=/iconos/1.gif TEXT=000000 LINK=FF0000 VLINK=A62A2A>
<H1>Completando la ventana<BR>
</H1>
<P>
Vamos a empezar por completar nuestro método <FONT FACE="Arial">ActualizaBoton</FONT>,
que modificará el texto del botón <FONT FACE="Arial">ok</FONT>
a medida que seleccionemos las ciudades y la fecha:<BR>
<PRE>
<FONT SIZE=2>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());
}<BR>
</FONT>
</PRE>
<P>
Nuestro método comienza por crear un <FONT FACE="Arial">StringBuffer</FONT>
con las palabras "Viaje: de ", y va agregando el resto:
<UL>
<LI>la ciudad de partida, llamando al método <FONT FACE="Arial">getDescription</FONT>
de <FONT FACE="Arial">cs</FONT> (ciudad de salida)
<LI>el texto constante " a "
<LI>la ciudad de llegada, llamando al método <FONT FACE="Arial">getDescription</FONT>
de <FONT FACE="Arial">cl</FONT> (ciudad de llegada)
<LI>el texto constante " el "
<LI>la fecha seleccionada, llamando al método <FONT FACE="Arial">getDescription</FONT>
de <FONT FACE="Arial">dp</FONT> (día de partida)
</UL>
<P>
Si en cualquier caso recibe un string nulo, pone un signo de pregunta
(o ?/?/? para la fecha).
<P>
El método <FONT FACE="Arial">setLabel</FONT>, sobre el
objeto <FONT FACE="Arial">ok</FONT> de tipo <I>Label</I>, modifica
la "etiqueta" del botón.
<P>
Realmente nos devuelven <FONT FACE="Arial">null</FONT> los métodos
que llamamos si no hay selección hecha?
<P>
Veamos:<BR>
<PRE>
<FONT SIZE=2>class SelecPueblo extends Panel {
private List listaPueblos;
............................
public String getDescription() {
return listaPueblos.getSelectedItem();
}
}<BR>
</FONT>
</PRE>
<P>
El método <FONT FACE="Arial">getSelectedItem</FONT> de
la clase <I>List</I> devuelve <FONT FACE="Arial">null</FONT> si
no hay ítems seleccionados, así que acá andamos
bien. En cuanto a la clase <FONT FACE="Arial">DiaPartida</FONT>,
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 <FONT FACE="Arial">Ventana8</FONT> estamos poniendo
un texto fijo en el botón, y no el que devuelve el objeto
<FONT FACE="Arial">dp</FONT>.
<P>
Sería mejor, para ser más consistente, modificar
el constructor de <FONT FACE="Arial">Ventana8</FONT> para que
arme el texto mediante el método <FONT FACE="Arial">ActualizaBotón</FONT>:
<BR>
<PRE>
<FONT SIZE=2>Ventana8 (String titulo, boolean enApplet) {
........................................
<B> ok = new Button("cualquiera");
ActualizaBoton();
add("South",ok);
</B> pack();
show();
}<BR>
</FONT>
</PRE>
<P>
Esto ya se ve mejor! Y de paso probamos el método...<BR>
<H3>Un poquito de actividad</H3>
<P>
Ahora sí, pasemos a completar nuestro manejador de eventos:
<BR>
<PRE>
<FONT SIZE=2>public boolean handleEvent(Event e) {
if (e.id == Event.WINDOW_DESTROY) {
if (enApplet) dispose();
else System.exit(0);
}
<B>if ( (e.target==dp)||(e.target==cs)||(e.target==cl) )
ActualizaBoton();
if (e.target==ok)
Activar();
}
</B>return super.handleEvent(e);
}<BR>
</FONT>
</PRE>
<P>
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.
<P>
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 <B>Hoy</B> o <B>Mañana</B>
<I>sin</I> mover el mouse, el texto del botón <FONT FACE="Arial">ok</FONT>
no se actualiza ya que el evento va dirigido al botón presionado
y no al panel.
<P>
Una forma de filtrar sólo los eventos que nos interesan
sería usar, por ejemplo:<BR>
<PRE>
<FONT SIZE=2>if ((e.target=cs.listaPueblos) && (e.id==Event.LIST_SELECT)) ActualizaBoton();
<BR>
</FONT>
</PRE>
<P>
que está dirigida a la <I>lista</I> y no al <I>panel</I>
en general, y tiene en cuenta el tipo de evento.
<P>
Lamentablemente, <FONT FACE="Arial">listaPueblos</FONT> es privada
dentro de la clase <FONT FACE="Arial">SelecPueblo</FONT> y por
lo tanto dentro de <FONT FACE="Arial">cs</FONT>. Pero es mejor
así, porque declararla pública y leerla desde afuera
sería bastante sucio (así como la leemos podríamos
escribirla).
<P>
Hay varias formas de mejorar esto sin cometer la torpeza de declarar
pública a <FONT FACE="Arial">listaPueblos</FONT>. Una posibilidad
es verificar, usando <FONT FACE="Arial">cs.getDescription()</FONT>,
si el texto cambió (y sólo en ese caso modificar
el texto del botón).
<P>
Otra, es hacer que los objetos de la clase <FONT FACE="Arial">SelecPueblo</FONT>
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 <FONT FACE="Arial">SelecPueblo</FONT>:<BR>
<PRE>
<FONT SIZE=2>public boolean handleEvent(Event e) {
if ((e.target==listaPueblos) && (e.id==Event.LIST_SELECT)) {
e.target=this;
}
return super.handleEvent(e);
}<BR>
</FONT>
</PRE>
<P>
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 <FONT FACE="Arial">target</FONT> del evento
para que indique el <I>panel</I> (y no la <I>lista</I>); si no,
lo paso a la clase antecesora.
<P>
Lo mismo podemos hacer con <FONT FACE="Arial">handleEvent</FONT>
para la clase <FONT FACE="Arial">DiaPartida</FONT>:<BR>
<PRE>
<FONT SIZE=2>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);
}<BR>
</FONT>
</PRE>
<P>
Esto no anda como esperaríamos! El campo de texto no se
comporta muy bien...
<P>
Esto es porque el código dependiente de la plataforma procesa
los eventos de mouse <I>antes</I> de llamar a <FONT FACE="Arial">handleEvent</FONT>,
pero procesa los de teclado <I>después</I> de llamar a
<FONT FACE="Arial">handleEvent</FONT>.
<P>
Lo que significa que, en el caso del campo de texto, <FONT FACE="Arial">handleEvent</FONT>
(y por lo tanto <FONT FACE="Arial">ActualizaBotón</FONT>)
se llama <I>antes</I> de modificar el texto!
<P>
Para corregir esto, deberíamos procesar nosotros las teclas
presionadas (lo que podríamos aprovechar para verificar
que se presiona una tecla válida).
<P>
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.
<P>
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 <B>Canvas</B> pasa todos los eventos.
<P>
Para simplificar, sólo actualizaremos el texto del botón
cuando se presiona <B>Enter</B> (Event.key=10):<BR>
<PRE>
<FONT SIZE=2>if ((e.target == elDia)&&(e.id==Event.KEY_PRESS)) {
if (e.key==10) e.target=this;
}<BR>
</FONT>
</PRE>
<P>
Ahora debemos modificar el método <FONT FACE="Arial">handleEvent</FONT>
en nuestra clase <FONT FACE="Arial">Ventana8</FONT> para que soporte
todos estos eventos:<BR>
<PRE>
<FONT SIZE=2>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);
}<BR>
</FONT>
</PRE>
<P>
Obviamente, procesar todas las teclas nosotros sería bastante
más complicado... de todos modos, el método en <FONT FACE="Arial">DiaPartida</FONT>
sería más o menos así:<BR>
<PRE>
<FONT SIZE=2>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: <B>getParent().deliverEvent(e)
</B> // 6- evitar proceso posterior del evento mediante: <B>result(true)
</B>}
</FONT>
</PRE>
<P>
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!
<H3>Y para terminar...</H3>
<P>
Bueno, sólo nos queda por definir el método <FONT FACE="Arial">Activar()</FONT>.
Primero vamos a llamar a <FONT FACE="Arial">ActualizaBoton()</FONT>
por si alguien lo último que hizo fue entrar un texto sin
presionar <B>Enter</B>, 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.
<P>
Por ahora simplemente vamos a mostrar una ventana con la selección
y un lindo botón de OK.
<P>
Primero vamos a hacer una muy pequeña modificación
a <FONT FACE="Arial">ActualizaBoton()</FONT> para que nos devuelva
el valor del texto del botón (para no calcularlo de nuevo):
<BR>
<PRE>
<FONT SIZE=2>String ActualizaBoton() {
StringBuffer b = new StringBuffer("Viaje: de ");
..............................................
ok.setLabel(b.toString());
}<BR>
</FONT>
</PRE>
<P>
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:<BR>
<PRE>
<FONT SIZE=2>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!");
}<BR>
</FONT>
</PRE>
<P>
Sólo nos falta definir una sencilla clase <FONT FACE="Arial">Result8</FONT>
para nuestra ventanita resultado:<BR>
<PRE>
<FONT SIZE=2>// 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);
}
}<BR>
</FONT>
</PRE>
<P>
Noten que usé <FONT FACE="Arial">dispose</FONT> y no <FONT FACE="Arial">System.exit</FONT>!
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.<BR>
<H3>Finale con tutto</H3>
<P>
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.
<P>
Si no... avísenme, y subo también los fuentes o
las clases.<BR>
<P>
Por las dudas, pueden probar esta aplicación como applet
cargando:<BR>
<PRE>
<FONT SIZE=2>http://www.amarillas.com/rock/java/Ejemplo8.htm<BR>
<BR>
</FONT>
</PRE>
<P>
Y basta por hoy. En el capítulo 10 vamos a empezar a manejar
el I/O de Java.
<P>
Todavía nos queda mucho por ver! (Y no hemos terminado
con el AWT...)<BR>
<BR>
<P>
Jorge Bourdette
<P>
<A HREF="mailto:jpb@amarillas.com" >jpb@amarillas.com</A>
</BODY>
</HTML>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -