miércoles, 25 de febrero de 2009
Veremos ejemplos de cómo se añaden botones a un panel para la interacción del usuario con la aplicación, pero antes vamos a ver la creación de botones como objetos.
Se pueden crear objetos Button con el operador new:
Button boton;
boton = new Button( "Botón");
La cadena utilizada en la creación del botón aparecerá en el botón cuando se visualice en pantalla. Esta cadena también se devolverá para utilizarla como identificación del botón cuando ocurra un evento.
Eventos Button
Cada vez que el usuario pulsa un botón, se produce un evento, de la misma forma que se produce un evento cuando se aprieta el botón del ratón. Los eventos de pulsación de un botón se pueden capturar sobrecargando el método action():
public boolean action( Event evt,Object obj ) {
if( evt.target instanceof Button )
System.out.println( (String)obj );
else
System.out.println( "Evento No-Button" );
}
La distinción entre todos los botones existentes se puede hacer utilizando el objeto destino pasado por el objeto Event y comparándolo con los objetos botón que hemos dispuesto en nuestro interface:
import java.awt.*;
import java.applet.Applet;
public class Botones extends Applet {
Button b1,b2,b3;
public void init() {
b1 = new Button( "Botón B1" );
b2 = new Button( "Botón B2" );
b3 = new Button( "Botón B3" );
this.add( b1 );
this.add( b2 );
this.add( b3 );
}
public boolean action( Event evt,Object obj ) {
if( evt.target.equals( b1 ) )
System.out.println( "Se ha pulsado el boton B1" );
if( evt.target.equals( b2 ) )
System.out.println( "Se ha pulsado el boton B2" );
if( evt.target.equals( b3 ) )
System.out.println( "Se ha pulsado el boton B3" );
return true;
}
}
En el applet anterior, Botones.java, observamos que se imprime el texto asociado al botón que hayamos pulsado.
Botones de Pulsación
Los botones presentados en el applet son los botones de pulsación estándar; no obstante, para variar la representación en pantalla y para conseguir una interfaz más limpia, AWT ofrece a los programadores otros tipos de botones.
Botones de Lista
Los botones de selección en una lista (Choice) permiten el rápido acceso a una lista de elementos. Por ejemplo, podríamos implementar una selección de colores y mantenerla en un botón Choice:
import java.awt.*;
import java.applet.Applet;
public class BotonSeleccion extends Applet {
Choice Selector;
public void init() {
Selector = new Choice();
Selector.addItem( "Rojo" );
Selector.addItem( "Verde" );
Selector.addItem( "Azul" );
add( Selector );
}
public boolean action( Event evt,Object obj ) {
if( evt.target instanceof Choice )
{
String color = (String)obj;
System.out.println( "El color elegido es el " + color );
}
return true;
}
}
En este ejemplo, BotonSeleccion.java, la cadena proporcionada al método addItem() será devuelta en el argumento Object de un evento Choice, por ello en el manejador del botón de selección, comprobamos en primer lugar que se trate efectivamente de un evento generado en un botón de tipo Choice.
Botones de Marcación
Los botones de comprobación (Checkbox) se utilizan frecuentemente como botones de estado. Proporcionan información del tipo Sí o No (true o false). El estado del botón se devuelve en el argumento Object de los eventos Checkbox; el argumento es de tipo booleano: verdadero (true) si la caja se ha seleccionado y falso (false) en otro caso.
Tanto el nombre como el estado se devuelven en el argumento del evento, aunque se pueden obtener a través de los métodos getLabel() y getState() del objeto Checkbox.
import java.awt.*;
import java.applet.Applet;
public class BotonComprobacion extends Applet {
Checkbox Relleno;
public void init() {
Relleno = new Checkbox( "Relleno");
add( Relleno );
}
public boolean action( Event evt,Object obj ) {
if( evt.target instanceof Checkbox )
System.out.println( "CheckBox: " + evt.arg.toString() );
return true;
}
}
El sencillo ejemplo anterior, BotonComprobacion.java, muestra los cambios que se producen en el estado del botón cuando la caja está o no seleccionada.
Botones de Selección
Los botones de comprobación se pueden agrupar para formar una interfaz de botón de radio (CheckboxGroup), que son agrupaciones de botones Checkbox en las que siempre hay un único botón activo.
import java.awt.*;
import java.applet.Applet;
public class BotonRadio extends Applet {
CheckboxGroup Radio;
public void init() {
Radio = new CheckboxGroup();
add( new Checkbox( "Primero",Radio,true) );
add( new Checkbox( "Segundo",Radio,false) );
add( new Checkbox( "Tercero",Radio,false) );
}
}
En el ejemplo anterior, BotonRadio.java, observamos que siempre hay un botón activo entre los que conforman el interfaz de comprobación que se ha implementado.
Botones Autocontenidos
La naturaleza orientada a objetos de Java nos da la posibilidad de crear botones completamente autocontenidos. En este tipo de botón, se construye el manejador de eventos dentro de la propia clase extendida de botón. Se pueden añadir estos botones a la aplicación, sin tener que preocuparse de los eventos que pudieran generar.
En el ejemplo siguiente, BotonAuto.java, creamos el botón que muestra la figura, un botón que genera el texto "Boton Aceptar" por la salida estándar:
import java.awt.*;
import java.applet.Applet;
class BotonAceptar extends Button {
public BotonAceptar() {
setLabel( "Aceptar" );
}
public boolean action( Event evt,Object obj ) {
System.out.println( "Boton Aceptar" );
return true;
}
}
public class BotonAuto extends Applet {
BotonAceptar boton;
public void init() {
boton = new BotonAceptar();
add( boton );
}
}
Es de hacer notar que no hay un método action() en la clase applet BotonAuto, la clase BotonAceptar manejará sus eventos. Si se hubiesen colocado otros objetos en el applet, se podría haber usado un método action() para tratar los eventos de esos objetos.
Las indicaciones que se proporcionan a continuación, van a permitirnos fijar los parámetros de Windows '95 para que se puedan ejecutar programas cliente y servidor, sin necesidad de que el ordenador en el cual se está ejecutando Windows '95 esté conectado a una red de área local. Esto puede resultar útil para mucha gente que está probando Java en su casa y no dispone de red, o incluso para aquellos programadores o estudiosos que quieren probar sus nuevos programas distribuidos pero no disponen de red o de Internet.
Advertencia: Si los siguientes parámetros se van a fijar en un ordenador portátil que en ocasiones sí se conecte a una red, sería conveniente anotar los parámetros actuales de la configuración de Windows '95, para que sean fácilmente recuperables cuando este ordenador se vuelva a conectar a la red.
Configuración del TCP/IP de Windows '95
Hay que seguir los pasos que vamos a relatar a continuación, suponemos que se está ejecutando la versión española de Windows '95, en otras versiones puede que las opciones tengan nombre diferente:
1. En el Panel de Control, seleccionar Red
2. Picar Agregar, luego Protocolo y luego Agregar
3. Seleccionar Microsoft y luego TCP/IP y picar en Aceptar
4. En este momento probablemente se solicite la introducción de los discos de Windows '95, o del CD-ROM
5. Seleccionar la pestaña Configuración de la ventana Red
6. Seleccionar TCP/IP en la lista que aparece y picar Propiedades
7. Seleccionar la pestaña Dirección IP
o Crearse una dirección IP, como por ejemplo: 102.102.102.102
o Crearse una Máscara de subred, como: 255.255.255.0
8. Seleccionar la pestaña Configuración DNS y desactivar DNS
9. Los valores correspondientes a las otras cuatro pestañas pueden dejarse los que hay por defecto
10. Picar Aceptar
11. (Opcional) Seleccionar la pestaña Identificación de la ventana Red
12. (Opcional) Introducir un nombre para el ordenador, como por ejemplo: breogan
13. (Opcional) Picar Aceptar
Crear una entrada en la "red"
1. Editar el fichero hosts.sam que está en el directorio de Windows
2. Al final del fichero incorporar la dirección IP y el nombre del ordenador que se han introducido antes, en nuestro caso: 102.102.102.102 breogan
3. Asegurarse de que la dirección IP y el nombre coinciden con la dirección IP que se ha fijado en el paso 7a de antes y que el nombre es el mismo que el indicado en el paso 12 anterior
4. Salvar el fichero con el nombre "hosts" y reiniciar Windows '95
Comprobación de la red
1. Abrir una sesión MS-DOS
2. Teclear "ping breogan"
3. Debería aparecer:
4. Pinging breogan [102.102.102.102] with 32 bytes of data:
5. Reply from 102.102.102.102: bytes=32 time=1ms TTL=32
6. Reply from 102.102.102.102: bytes=32 time<10ms TTL=32
7. Reply from 102.102.102.102: bytes=32 time<10ms TTL=32
8. Reply from 102.102.102.102: bytes=32 time<10ms TTL=32
9. Teclear "tracert 102.102.102.102"
10. Debería aparecer:
11. Tracing route to 102.102.102.102 over a maximum of 30 hops
12. 1 <10 ms 1 ms <10 ms 102.102.102.102
13. Trace complete.
En este instante, si todo ha ido bien, el ordenador está listo para funcionar como si estuviera en red. Dos o más programas que se comuniquen en red a través de sockets debería poder ejecutarse ahora dentro de los dominios del ordenador que acabamos de configurar
Problemas más frecuentes
Los tres problemas que pueden presentarse cuando intentemos comprobar el funcionamiento correcto de la red interna que acabamos de montar son:
Cuando hacemos "ping" obtenemos "Bad IP address breogan"
Intentar teclear "ping 102.102.102.102". Si ahora sí se obtiene réplica, la causa del problema es que el los pasos 12 de la Configuración y 3 de la Creación de la entrada en la tabla de hosts, no se ha introducido correctamente el nombre de la máquina. Comprobar esos pasos y que todo coincide.
El programa cliente o el servidor fallan al intentar el "connect"
La causa podría estar en que se produzca un fallo por fichero no encontrado en el directorio Windows/System de las librerías WINSOCK.DLL o WSOCK32.DLL. Muchos programas que se utilizan en Internet reemplazan estos ficheros cuando se instalan. Asegurarse de que están estos ficheros y que son los originales que vienen con la distribución de Windows '95.
El programa servidor dice que no puede "bind" a un socket
Esto sucede porque tiene el DNS activado y no puede encontrar ese DNS o servidor de direcciones, porque estamos solos en la red. Asegurarse de que en el paso 8 de la Configuración la opción de DNS está deshabilitada.
CLASES UTILES EN COMUNICACIONES
Vamos a exponer otras clases que resultan útiles cuando estamos desarrollando programas de comunicaciones, aparte de las que ya se han visto. El problema es que la mayoría de estas clases se prestan a discusión, porque se encuentran bajo el directorio sun. Esto quiere decir que son implementaciones Solaris y, por tanto, específicas del Unix Solaris. Además su API no está garantizada, pudiendo cambiar. Pero, a pesar de todo, resultan muy interesantes y vamos a comentar un grupo de ellas solamente que se encuentran en el paquete sun.net
Socket
Es el objeto básico en toda comunicación a través de Internet, bajo el protocolo TCP. Esta clase proporciona métodos para la entrada/salida a través de streams que hacen la lectura y escritura a través de sockets muy sencilla.
ServerSocket
Es un objeto utilizado en las aplicaciones servidor para escuchar las peticiones que realicen los clientes conectados a ese servidor. Este objeto no realiza el servicio, sino que crea un objeto Socket en función del cliente para realizar toda la comunicación a través de él.
DatagramSocket
La clase de sockets datagrama puede ser utilizada para implementar datagramas no fiables (sockets UDP), no ordenados. Aunque la comunicación por estos sockets es muy rápida porque no hay que perder tiempo estableciendo la conexión entre cliente y servidor.
DatagramPacket
Clase que representa un paquete datagrama conteniendo información de paquete, longitud de paquete, direcciones Internet y números de puerto.
MulticastSocket
Clase utilizada para crear una versión multicast de las clase socket datagrama. Múltiples clientes/servidores pueden transmitir a un grupo multicast (un grupo de direcciones IP compartiendo el mismo número de puerto).
NetworkServer
Una clase creada para implementar métodos y variables utilizadas en la creación de un servidor TCP/IP.
NetworkClient
Una clase creada para implementar métodos y variables utilizadas en la creación de un cliente TCP/IP.
SocketImpl
Es un Interface que nos permite crearnos nuestro propio modelo de comunicación. Tendremos que implementar sus métodos cuando la usemos. Si vamos a desarrollar una aplicación con requerimientos especiales de comunicaciones, como pueden se la implementación de un cortafuegos (TCP es un protocolo no seguro), o acceder a equipos especiales (como un lector de código de barras o un GPS diferencial), necesitaremos nuestra propia clase Socket.
Vamos a ver un ejemplo de utilización, presentando un sencillo ejemplo, servidorUDP.java, de implementación de sockets UDP utilizando la clase DatagramSocket.
import java.net.*;
import java.io.*;
import sun.net.*;
// Implementación del servidor de datagramas UDP. Envía una cadena
// tras petición
//
class servidorUDP {
public static void main( String args[] ) {
DatagramSocket s = (DatagramSocket)null;
DatagramPacket enviap,recibep;
byte ibuffer[] = new byte[100];
String cadena = "Hola Tutorial de Java!\n";
InetAddress IP = (InetAddress)null;
int longitud = sendString.length();
int puertoEnvio = 4321;
int puertoRecep = 4322;
int puertoRemoto;
// Intentamos conseguir la dirección IP del host
try {
IP = InetAddress.getByName( "bregogan" );
} catch( UnknownHostException e ) {
System.out.println( "No encuentro al host breogan" );
System.exit( -1 );
}
// Establecemos el servidor para escuchar en el socket 4322
try {
s = new DatagramSocket( puertoRecep );
} catch( SocketException e ) {
System.out.println( "Error - "+e.toString() );
}
// Creamos un paquete de solicitud en el cliente
// y nos quedamos esperando a sus peticiones
recibep = new DatagramPacket( ibuffer,longitud );
try {
s.receive( recibep );
} catch( IOException e ) {
System.out.println( "Error - "+e.toString() );
}
// Creamos un paquete para enviar al cliente y lo enviamos
sendString.getBytes( 0,longitud,ibuffer,0 );
enviap = new DatagramPacket( ibuffer,longitud,IP,puertoEnvio );
try {
s.send( enviap );
} catch( IOException e ) {
System.out.println( "Error - "+e.toString() );
System.exit( -1 );
}
// Cerramos el socket
s.close();
}
}
Y también vamos a implementar el cliente, clienteUDP.java, del socket UDP correspondiente al servidor que acabamos de presentar:
import java.net.*;
import java.io.*;
import sun.net.*;
// Implementación del cliente de datagramas UDP. Devuelve la salida
// de los servidores
//
class clienteUDP {
public static void main( String args[] ) {
int longitud = 100;
DatagramSocket s = (DatagramSocket)null;
DatagramPacket enviap,recibep;
byte ibuffer[] = new byte[100];
InetAddress IP = (InetAddress)null;
int puertoEnvio = 4321;
int puertoRecep = 4322;
// Abre una conexión y establece el cliente para recibir
// una petición en el socket 4321
try {
s = new DatagramSocket( puertoRecep );
} catch( SocketException e ) {
System.out.println( "Error - "+e.toString() );
}
// Crea una petición para enviar bytes. Intenta conseguir
// la dirección IP del host
try {
IP = InetAddress.getByName( "depserver" );
} catch( UnknownHostException e ) {
System.out.println( "No encuentro el host depserver" );
System.exit( -1 );
}
// Envía una petición para que responda el servidor
try {
enviap = new DatagramPacket( ibuffer,ibuffer.length,
IP,4322 );
s.send( enviap );
} catch( IOException e ) {
System.out.println( "Error - "+e.toString() );
}
// Consigue un controlador de fichero de entrada del socket y lee
// dicha entrada. Creamos un paquete descriptor para recibir el
// paquete UDP
recibep = new DatagramPacket( ibuffer,longitud );
// Espera a recibir un paquete
try {
s.receive( recibep );
} catch( IOException e ) {
System.out.println( "Error - "+e.toString() );
System.exit( -1 );
}
// Imprimimos los resultados de lo que conseguimos
System.out.println( "Recibido: "+recibep.getLength()+" bytes" );
String datos = new String( recibep.getData(),0 );
System.out.println( "Datos: "+datos );
System.out.println( "Recibido por puerto: "+recibep.getPort() );
// Cerramos la conexión y abandonamos
s.close();
}
}
La salida que se producirá cuando ejecutemos primero el servidor y luego el cliente será la misma que reproducimos a continuación:
%java clienteUDP
Recibido: 17 bytes
Datos: Hola Tutorial de Java!
Recibido por puerto: 4322
El lenguaje de programación Java proporciona soporte para la arquitectura MVC mediante dos clases:
• Observer: Es cualquier objeto que desee ser notificado cuando el estado de otro objeto sea alterado
• Observable: Es cualquier objeto cuyo estado puede representar interés y sobre el cual otro objeto ha demostrado ese interés
Estas dos clases se pueden utilizar para muchas más cosas que la implementación de la arquitectura MVC. Serán útiles en cualquier sistema en que se necesite que algunos objetos sean notificados cuando ocurran cambios en otros objetos.
El Modelo es un subtipo de Observable y la Vista es un subtipo de Observer. Estas dos clases manejan adecuadamente la función de notificación de cambios que necesita la arquitectura MVC. Proporcionan el mecanismo por el cual las Vistas pueden ser notificadas automáticamente de los cambios producidos en el Modelo. Referencias al objeto Modelo tanto en el Controlador como en la Vista permiten acceder a los datos de ese objeto Modelo.
Funciones Observer y Observable
Vamos a enumerar las funciones que intervienen en el control de Observador y Observable:
Observer
public void update( Observableobs,Object obj )
Llamada cuando se produce un cambio en el estado del objeto Observable
Observable
public void addObserver( Observer obs )
Añade un observador a la lista interna de observadores
public void deleteObserver( Observer obs )
Borra un observador de la lista interna de observadores
public void deleteObservers()
Borra todos los observadores de la lista interna
public int countObserver()
Devuelve el número de observadores en la lista interna
protected void setChanged()
Levanta el flag interno que indica que el Observable ha cambiado de estado
protected void clearChanged()
Baja el flag interno que indica que el Observable ha cambiado de estado
protected boolean hasChanged()
Devuelve un valor booleano indicando si el Observable ha cambiado de estado
public void notifyObservers()
Comprueba el flag interno para ver si el Observable ha cambiado de estado y lo notifica a todos los observadores
public void notifyObservers( Object obj )
Comprueba el flag interno para ver si el Observable ha cambiado de estado y lo notifica a todos los observadores. Les pasa el objeto especificado en la llamada para que lo usen los observadores en su método notify().
UTILIZAR OBSERVADOR Y OBSERVABLE
Vamos a describir en los siguientes apartados, como crear una nueva clase Observable y una nueva clase Observer y como utilizar las dos conjuntamente.
Extender un Observable
Una nueva clase de objetos observables se crea extendiendo la clase Observable. Como la clase Observable ya implementa todos los métodos necesarios para proporcionar el funcionamiento de tipo Observador/Observable, la clase derivada solamente necesita proporcionar algún tipo de mecanismo que lo ajuste a su funcionamiento particular y proporcionar acceso al estado interno del objeto Observable.
En la clase ValorObservable que mostramos a continuación, el estado interno del Modelo es capturado en el entero n. A este valor se accede (y más importante todavía, se modifica) solamente a través de sus métodos públicos. Si el valor cambia, el objeto invoca a su propio método setChanged() para indicar que el estado del Modelo ha cambiado. Luego, invoca a su propio método notifyObservers() para actualizar a todos los observadores registrados.
import java.util.Observable;
public class ValorObservable extends Observable {
private int nValor = 0;
// Constructor al que indicamos el valor en que comenzamos y los
// limites inferior y superior que no deben sobrepasarse
public ValorObservable( int nValor,int nInferior,int nSuperior ) {
this.nValor = nValor;
}
// Fija el valor que le pasamos y notifica a los observadores que
// estan pendientes del cambio de estado de los objetos de esta
// clase, que su etado se ha visto alterado
public void setValor(int nValor) {
this.nValor = nValor;
setChanged();
notifyObservers();
}
// Devuelve el valor actual que tiene el objeto
public int getValor() {
return( nValor );
}
}
sábado, 21 de febrero de 2009
Una nueva clase de objetos que observe los cambios en el estado de otro objeto se puede crear implementando la interface Observer. Esta interface necesita un método update() que se debe proporcionar en la nueva clase. Este método será llamado siempre que el Observable cambie de estado, que anuncia este cambio llamando a su método notifyObservers(). El observador entonces, debería interrogar al objeto Observable para determinar su nuevo estado; y, en el caso de la arquitectura MVC, ajustar su Vista adecuadamente.
En la clase ObservadorDeTexto, que muestra el código siguiente, el método notify() primero realiza una comprobación para asegurarse de que el Observable que ha anunciado un cambio es el Observable que él esta observando. Si lo es, entonces lee su estado e imprime el nuevo valor.
import java.util.Observer;
import java.util.Observable;
public class TextoObservador extends Frame implements Observer {
private ValorObservable vo = null;
public TextoObservador( ValorObservable vo ) {
this.vo = vo;
}
public void update( Observable obs,Object obj ) {
if( obs == vo )
tf.setText( String.valueOf( vo.getValor() ) );
}
}
Usando Observador y Observable
Un programa indica a un objeto Observable que hay un objeto observador que debe ser notificado cuando se produzca un cambio en su estado, llamando al método addObserver() del objeto Observable. Este método añade el Observador a la lista de observadores que el objeto Observable ha de notificar cuando su estado se altere.
En el ejemplo siguiente, en donde mostramos la clase ControlValor, ControlValor.java, vemos como se usa el método addObserver() para añadir una instancia de la clase TextoObservador a la lista que mantiene la clase ValorObservable.
// Constructor de la clase que nos permite crear los objetos de
// observador y observable
public ControlValor() {
ValorObservable vo = new ValorObservable( 100,0,500 );
TextoObservador to = new TextoObservador( vo );
vo.addObserver( to );
}
public static void main( String args[] ) {
ControlValor m = new ControlValor();
}
}
En la siguiente secuencia, vamos a describir como se realiza la interacción entre un Observador y un objeto Observable, durante la ejecución de un programa:
En primer lugar el usuario manipula un elemento del interface de usuario representado por el Controlador. Este Controlador realiza un cambio en el Modelo a través de uno de sus métodos públicos de acceso; en nuestro caso, llama a setValue().
El método público de acceso modifica el dato privado, ajusta el estado interno del Modelo y llama al método setChanged() para indicar que su estado ha cambiado. Luego llama al método notifyObservers() para notificar a los observadores que su estado no es el mismo. La llamada a este método puede realizarse en cualquier lugar, incluso desde un bucle de actualización que se esté ejecutando en otro thread.
Se llama a los métodos update() de cada Observador, indicando que hay un cambio en el estado del objeto que estaban observando. El Observador accede entonces a los datos del Modelo a través del método público del Observable y actualiza las Vistas.
EJEMPLO DE APLICACION MVC
En el ejemplo siguiente, vemos como colaboran juntos Observador y Observable en la arquitectura MVC: el Modelo de este ejemplo es muy simple. Su estado interno consta de un valor entero. Este valor, o estado, es manipulado exclusivamente a través de métodos públicos de acceso. El código del modelo se encuentra implementado en ValorObservable.java.
Inicialmente, hemos escrito una clase simple de Vista/Controlador. La clase combina las características de una Vista (presenta el valor que corresponde al estado actual del Modelo) y un Controlador (permite al usuario introducir un nuevo valor para alterar el estado del Modelo).
El código se encuentra en el fichero TextoObservador.java. Podemos crear instancias de esta vista pulsando el botón superior que aparece en el applet.A través de este diseño utilizando la arquitectura MVC (en lugar de colocar el código para que el Modelo, la Vista y el Controlador de texto en una clase monolítica), el sistema puede ser fácilmente rediseñado para manejar otra Vista y otro Controlador.
En este caso, hemos visto una clase Vista/Controlador con una barra de desplazamiento. La posición del marcador en la barra representa el valor actual que corresponde con el estado del Modelo y puede ser alterado a través de movimientos del marcador sobre la barra por acción del usuario. El código de esta clase se encuentra en BarraObservador.java. Se pueden crear instancias de esta clase pulsando el botón inferior del applet de esta página.