Librojava 08
Librojava 08
Librojava 08
ACLARATORIO
Este manual ha sido recopilado con el unico objetivo de ser utilizado, en la materia programacin III, Universidad de el Salvador, Facultad Multidisciplinaria Paracentral.
Se han utilizado como apuntes bases los siguientes manuales: 1. El lenguaje de programacin java TM. Java Sun. 2. Java2, Abraham Otero. 2003 3. Fundamentos de Java. Mc GrawHill. En este manual no estan contemplados todos los metodos y clases de Java, se recomienda consultar la documentacin de java Sdk en www.sun.com.
3.4.3 Operadores de Desplazamiento ........................................................................ 25 3.4.4 Operadores de Asignacin ............................................................................... 27 3.4.5 Expresiones ..................................................................................................... 27 3.5 CADENAS DE CARACTERES ............................................................................ 29 3.5.1 Concatenacin ................................................................................................. 29 3.5.2 Subcadenas ...................................................................................................... 29 3.5.3 Comparacin de cadenas.................................................................................. 30
7.3 Checkboxes ........................................................................................................ 60 7.5 TextField ........................................................................................................... 63 7.7 TextArea ............................................................................................................ 66 7.8 Componentes avanzados ..................................................................................... 68 7.8.1 Paneles ............................................................................................................ 68 7.8.2 Canvas (Lienzos) ............................................................................................. 70 7.8.3 reas de texto .................................................................................................. 72 7.8.4 Listas ............................................................................................................... 75 7.8.5 Frames ............................................................................................................. 78 7.8.6 Dilogos .......................................................................................................... 80
11.4.4 suspend(). .................................................................................................... 133 11.4.5 wait(). .......................................................................................................... 134 11.4.6 yield(). ......................................................................................................... 134 11.4.7 join()............................................................................................................ 135 11.5 Threads y sincronismo. ....................................................................................... 136 11.5.1 El paradigma productor/consumidor ............................................................ 136 11.5.2 Seccin crtica. ............................................................................................ 136 11.5.3 Monitores. ................................................................................................... 139 11.6 Prioridad de un thread......................................................................................... 144 11.7 Threads Daemon. ............................................................................................... 145 11.8 Grupos de threads. .............................................................................................. 146 11.8.1 Creacin de grupos de threads...................................................................... 146
Interfaces de usuario en Java: otras ................... 208 caractersticas de Swing ....................................... 208
Introduccin ............................................................................................................... 208 El gestor de diseo BoxLayout ................................................................................... 209
6
1.2 Qu es Java?
Java es un lenguaje de desarrollo de propsito general, y como tal es vlido para realizar todo tipo de aplicaciones profesionales. Entonces, es simplemente otro lenguaje ms? Definitivamente no. Incluye una combinacin de caractersticas que lo hacen nico y est siendo adoptado por multitud de fabricantes como herramienta bsica para el desarrollo de aplicaciones comerciales de gran repercusin.
Se pueden escribir aplicaciones para intraredes, aplicaciones cliente/servidor, aplicaciones distribuidas en redes locales y en Internet. Es fcil de aprender y est bien estructurado. Las aplicaciones son fiables. Puede controlarse su seguridad frente al acceso a recursos del sistema y es capaz de gestionar permisos y criptografa. Tambin, segn Sun, la seguridad frente a virus a travs de redes locales e Internet est garantizada. Aunque al igual que ha ocurrido con otras tecnologas y aplicaciones, se han descubierto, y posteriormente subsanado, agujeros en la seguridad3 de Java.
10
El manejo de la memoria no es un problema, la gestiona el propio lenguaje y no el programador. Genera aplicaciones con pocos errores posibles. Incorpora Multi-Threading (para permitir la ejecucin de tareas concurrentes dentro de un mismo programa). El lenguaje Java puede considerarse como una evolucin del C++. La sintaxis es parecida a la de este lenguaje, por lo que en este libro se har referencia a dicho lenguaje frecuentemente. A pesar de que puede considerarse como una evolucin del C++ no acarrea los inconvenientes del mismo, ya que Java fue diseado partiendo de cero, es decir, no necesitaba ser compatible con versiones anteriores de ningn lenguaje como ocurre con C++ y C. Gracias a que fue diseado partiendo de cero ha conseguido convertirse en un lenguaje orientado a objetos puro, limpio y prctico. No permite programar mediante otra tcnica que no sea la programacin orientada a objetos (POO en adelante) y, una vez superado el aprendizaje de la programacin orientada a objetos, es realmente sencillo aprender Java. El lenguaje es Compilado o Interpretado? Ni una cosa ni la otra. Aunque estrictamente hablando es interpretado, necesita de un proceso previo de compilacin. Una vez compilado el programa, se crea un fichero que almacena lo que se denomina bytecodes o j_code (pseudocdigo prcticamente al nivel de cdigo mquina). Para ejecutarlo, es necesario un intrprete, la JVM (Java Virtual Machine) mquina virtual Java. De esta forma, es posible compilar el programa en una estacin UNIX y ejecutarlo en otra con Windows95 utilizando la mquina virtual Java para Windows95. Esta JVM se encarga de leer los bytecodes y traducirlos a instrucciones ejecutables directamente en un determinado microprocesador, de una forma bastante eficiente.
Que el programa deba ser interpretado no hace que sea poco eficiente en cuanto a velocidad, ya que la interpretacin se hace prcticamente al nivel de cdigo mquina. Por ejemplo, es
11
mucho ms rpido que cualquier otro programa interpretado como por ejemplo Visual Basic, aunque es ms lento que el mismo programa escrito en C++. Esta deficiencia en cuanto a la velocidad, puede ser aminorada por los compiladores Just-InTime (JIT). Un compilador JIT transforma los bytecodes de un programa o un applet en cdigo nativo de la plataforma donde se ejecuta, por lo que es ms rpido. Suelen ser incorporados por los navegadores, como Netscape o Internet Explorer. El lenguaje Java es robusto. Las aplicaciones creadas en este lenguaje son susceptibles de contener pocos errores, principalmente porque la gestin de memoria y punteros es realizada por el propio lenguaje y no por el programador. Bien es sabido que la mayora de los errores en las aplicaciones vienen producidos por fallos en la gestin de punteros o la asignacin y liberacin de memoria. Adems, el lenguaje contiene estructuras para la deteccin de excepciones (errores de ejecucin previstos) y permite obligar al programador a escribir cdigo fiable mediante la declaracin de excepciones posibles para una determinada clase reutilizable.
12
Un mismo programa fuente compilado en distintas plataformas o sistemas operativos, genera el mismo fichero en byte-code. Esto es lgico, ya que se supone que el compilador de Java traduce el fichero fuente a cdigo ejecutable por una mquina que nicamente existe en forma virtual (aunque se trabaja en la construccin de microprocesadores que ejecuten directamente el byte-code). Evidentemente, si un mismo programa en byte-code puede ser ejecutado en distintas plataformas es porque existe un traductor de ese byte-code a cdigo nativo de la mquina sobre la que se ejecuta. Esta tarea es realizada por la JVM. Existe una versin distinta de esta JVM para cada plataforma. Esta JVM se carga en memoria y va traduciendo al vuelo, los byte-codes a cdigo mquina. La JVM no ocupa mucho espacio en memoria, pinsese que fue diseada para poder ejecutarse sobre pequeos electrodomsticos como telfonos, televisores, etc.
1.6.1 Simple
Es un lenguaje sencillo de aprender. Su sintaxis es la de C++ simplificada. Los creadores de Java partieron de la sintaxis de C++ y trataron de eliminar de este todo lo que resultase complicado o fuente de errores en este lenguaje.
13
1.6.3 Distribuido
Java est muy orientado al trabajo en red, soportando protocolos como TCP/IP, UDP, HTTP y FTP. Por otro lado el uso de estos protocolos es bastante sencillo comparandolo con otros lenguajes que los soportan.
1.6.4 Robusto
El compilador Java detecta muchos errores que otros compiladores solo detectaran en tiempo de ejecucin o incluso nunca. (ej: if(a=b) then ... el compilador Java no nos dejara compilar este cdigo.
1.6.5 Seguro
Sobre todo un tipo de desarrollo: los Applet. Estos son programas diseados para ser ejecutados en una pgina web. Java garantiza que ningn Applet puede escribir o leer de nuestro disco o mandar informacin del usuario que accede a la pgina a travs de la red (como, por ejemplo, la direccin de correo electrnico). En general no permite realizar cualquier accin que pudiera daar la mquina o violar la intimidad del que visita la pgina web.
1.6.6 Portable
En Java no hay aspectos dependientes de la implementacin, todas las implementaciones de Java siguen los mismos estndares en cuanto a tamao y almacenamiento de los datos. Esto no ocurre as en C++, por ejemplo. En ste un entero, por ejemplo, puede tener un tamao de 16, 32 o ms bits, siendo lo nica limitacin que el entero sea mayor que un short y menor que un long int. As mismo C++ bajo UNIX almacena los datos en formato little endian, mientas que bajo Windows lo hace en big endian. Java lo hace siempre en little edian para evitar confusiones.
1.6.9 Multithread
Soporta de modo nativo los threads, sin necesidad del uso de de libreras especficas (como es el caso de C++). Esto le permite adems que cada Thread de una aplicacin java pueda correr en una CPU distinta, si la aplicacin se ejecuta en una mquina que posee varias CPU. Las aplicaciones de C++ no son capaces de distribuir, de modo transparente para el programador, la carga entre varias CPU.
2.2 Qu es un objeto?
Un objeto es una coleccin de datos, junto con las funciones asociadas, utilizadas para operar sobre esos datos. La potencia real de los objetos reside en las propiedades que soportan: herencia, encapsulacin (o encapsulamiento) y polimorfismo junto con los conceptos de objetos, clases, mtodos y mensajes.
15
2.3 CLASES
Una clase es un tipo definido por el usuario que determina las estructuras de datos y las operaciones asociadas con ese tipo. Son como plantillas o modelos. Cuando se construye un objeto de una clase, se crea una instancia de clase. Instancia de clase = objeto
2.4 Mensaje
Un mensaje es simplemente la peticin de un objeto a otro objeto para que se se comporte de una determinada manera, ejecutando uno de sus mtodos. Paso de mensajes: invocacin o llamada de un mtodo de un objeto.
2.5 Herencia
Es la propiedad que permite a los objetos construirse a partir de otros objetos. Ejemplo:
El principio de este tipo de divisin es que cada subclase comparte caractersticas comunes con la clase de la que se deriva. Adems de las caractersticas compartidas con otros miembros de la clase, cada subclase tiene sus propias caractersticas particulares.
16
La herencia permite definir nuevas clases a partir de clases ya existentes. La herencia impone una relacin jerrquica entre clases en la cual una clase hija hereda de su clase padre. Herencia simple: la clase solo puede recibir caractersticas de otra clase base. Herencia mltiple: la clase recibe propiedades de mas de una clase base.
2.6 Polimorfismo
Polimorfismo: significa la cualidad de tener ms de una forma. Polimorfismo es la capacidad del cdigo de un programa para ser utilizado con diferentes tipos de datos u objetos. Tambin se puede aplicar a la propiedad que poseen algunas operaciones de tener un comportamiento diferente dependiendo del objeto (o tipo de dato) sobre el que se aplican. Ejemplo: Operacin sumar. En un lenguaje de programacin el operador + representa la suma de dos nmeros ( x + y ) de diferentes tipos: enteros, flotantes. O adems, se puede definir la operacin de sumar dos cadenas: concatenacin. De modo similar: suponiendo un nmero de figuras geomtricas que responden todas al mensaje Calcular _ superficie. Cada objeto reacciona a este mensaje haciendo el clculo correspondiente de la superficie, el cual difiere de una figura a otra.
17
El concepto de polimorfismo se puede aplicar tanto a funciones como a tipos de datos. As nacen los conceptos de funciones polimrficas y tipos polimrficos. Las funciones polimrficas son aquellas funciones que pueden evaluarse y/o ser aplicadas a diferentes tipos de datos de forma indistinta. Los tipos polimrficos, por su parte, son aquellos tipos de datos que contienen al menos un elemento cuyo tipo no est especificado.
2.7 Encapsulacion
El encapsulamiento consiste en la propiedad que tienen los objetos de ocultar sus atributos, e incluso los mtodos, a otras partes del programa u otros objetos. La forma natural de construir una clase es la de definir una serie de atributos que, en general, no son accesibles fuera del mismo objeto, sino que nicamente pueden modificarse a travs de los mtodos que son definidos como accesibles desde el exterior de esa clase.
Una clase est formada por una parte correspondiente a la declaracin, y otra correspondiente al cuerpo de la misma:
Declaracin de clase { Cuerpo de clase }
En la plantilla anterior se ha simplificado el aspecto de la Declaracin de clase, pero s que puede asegurarse que la misma contendr, como mnimo, la palabra reservada class y el nombre que recibe la clase. El cuerpo de las clases comienza con una llave abierta({) y termina con una llave cerrada (}). Dentro del cuerpo de la clase se declaran los atributos y los mtodos de la clase. Para que un programa se pueda ejecutar, debe contener una clase que tenga un mtodo main con la siguiente declaracin: public static void main( String args [] )
18
El lenguaje Java, al igual que C, distingue entre maysculas y minsculas, por lo que es importante transcribirlo literalmente. Tambin hay que comentar que en el nombre del fichero fuente tambin se hace distincin entre maysculas y minsculas a la hora de compilarlo, aunque el sistema operativo empleado no haga esta distincin. La extensin del mismo debe ser .java Para compilar el programa, hay que teclear: javac Hola.java El compilador crear un fichero que se llama Hola.class que contiene el cdigo bytecode (pseudoejecutable) Para ejecutarlo: java Hola Se escribe nicamente el nombre de la clase Hola y no Hola.class u Hola.java El resultado de la ejecucin ser la visualizacin en pantalla del mensaje:
Hola, este es mi primer programa
Explicacin del programa Hola.java lnea a lnea: Este simple programa, a pesar de no ser muy extenso, contiene muchos de los conceptos de la programacin orientada a objetos en Java. No se pretende que, a partir del mismo, se aprendan y comprendan la totalidad de los aspectos de la POO y la sintaxis del Java, pero s que puede afirmarse que una vez comprendida y asimilada su filosofa, se estar en un punto bastante cercano a los conocimientos bsicos necesarios para entenderlos. La primera lnea del programa declara una clase llamada Hola, que es descendiente, al no indicar nosotros que herede de otra clase, de la clase Object. Entre las llaves de la clase Hola, se declaran los atributos y los mtodos de la clase. Todo lo que se encuentre entre la llave abierta ( { ) y la llave cerrada ( } ) pertenece a la clase Hola. En nuestro ejemplo, nicamente tiene un mtodo: main. Las llaves no sirven nicamente para marcar el inicio y el fin de una clase. Se utilizan para marcar principio y final de bloques de cdigo y se interpretan mediante el mtodo LIFO (Last In First Out) donde el ltimo en entrar es el primero en salir. En el ejemplo existen, adems de las llaves abierta y cerrada de la clase, otras llaves abierta y cerrada dentro del mtodo main(). Que se interpreten mediante el mtodo LIFO significa que la llave cerrada ( } ) del mtodo main() parentiza el bloque abierto por la ltima llave ( { ) antes de sta.
19
Todo programa independiente escrito en Java empieza a ejecutarse(como en C) a partir del mtodo main(). Se pueden compilar clases que no posean mtodo main(), pero el intrprete Java no podr ejecutarlas inicialmente, aunque s pueden ejecutarse si son llamadas desde otro mtodo en ejecucin. Veremos en posteriores captulos que la forma de crear los applets es distinta y no necesita declarar este mtodo. Declaracin del mtodo main(): public: indica que el mtodo main() es pblico y, por tanto, puede ser llamado desde otras clases. Todo mtodo main() debe ser pblico para poder ejecutarse desde el intrprete Java (JVM). static: indica que la clase no necesita ser instanciada para poder utilizar el mtodo al que califica. (No se crea ninguna instancia u objeto de la clase Hola). Tambin indica que el mtodo es el mismo para todas las instancias que pudieran crearse. void: indica que la funcin main no devuelve ningn valor. El mtodo main debe aceptar siempre, como parmetro, un vector de strings, que contendr los posibles argumentos que se le pasen al programa en la lnea de comandos, aunque como es nuestro caso, no se utilice. Si no se han comprendido muy bien todos estos conceptos no importa, de momento. Hay una regla sencilla que nunca falla: El mtodo main() siempre se declara de la misma forma:
public static void main(String args[])
Este lnea indica que se va a ejecutar el mtodo println(), encargado de mostrar un valor a travs de la salida estndar (en nuestro caso, un String) y despus realizar un retorno de carro y nueva lnea. Este mtodo pertenece al atributo out. Este atributo se encuentra incluido dentro de la clase System. Como curiosidad (de momento), cabe mencionar que esta clase es static (no hemos declarado ningn objeto de la clase System). Un poco complicado para simplemente mostrar un mensaje en pantalla? Es posible que s lo sea para aquellos que an no conocen la programacin orientada a objetos, pero una vez conocidos y asimilados los conceptos de la POO (Programacin Orientada a Objetos) el programa es bastante sencillo y lgico. Para aquellos que conozcan C++ pensarn que es bastante parecido a lo que habran escrito en su lenguaje favorito. Este colectivo comprobar que es mucho ms fcil que C y tiene algunas ventajas.
3.2 Comentarios.
Los comentarios en los programas fuente son muy importantes en cualquier lenguaje. Sirven para aumentar la facilidad de comprensin del cdigo y para recordar ciertas cosas sobre el mismo. Son porciones del programa fuente que no sern compiladas, y, por tanto, no ocuparn espacio en el fichero ejecutable. nicamente sirven para documentar. Si un comentario debe ocupar ms de una lnea, hay que anteponerle /* y al final */. Ejemplo:
/* Esto es un comentario que
20
Si el comentario que se desea escribir es de una sola lnea, basta con poner dos barras inclinadas: // Ejemplo:
for (i=0; i<20;i++) // comentario de bucle { System.out.println(Adis); }
No puede ponerse cdigo despus de un comentario introducido por // en la misma lnea, ya que desde la aparicin de las dos barras inclinadas // hasta el final de la lnea es considerado como comentario e ignorado por el compilador. Existe otro tipo de comentario que sirve para generar documentacin automticamente en formato HTML mediante la herramienta javadoc .
Los tipos referenciados se llaman as porque el valor de una variable de referencia es una referencia (un puntero) hacia el valor real. En Java tenemos los arrays, las clases y los interfaces como tipos de datos referenciados.
21
Veamos una ejemplo para comprender su sintaxis: class Cast { public static void main(String[] args) { int i = 9,k;
22
float j = 47.9F; System.out.println("i: "+i + " j: " +j); k = (int)j; //empleo de un cast System.out.println("j: " + j + " k: " +k); j = k;//no necesita cast System.out.println("j: " + j + " k: " +k); float m = 2.3F; //float m = 2.3; dara error al compilar. System.out.println("m: "+m); } }
23
Operador Uso + op1 + op2 op1 - op2 * op1 * op2 / op1 / op2 % op1 % op2
Descripcin Suma op1 y op2 Resta op2 de op1 Multiplica op1 y op2 Divide op1 por op2 Obtiene el resto de dividir op1 por op2
Nota: El lenguaje Java extiende la definicin del operador + para incluir la concatenacin de cadenas. Los operadores + y - tienen versiones unarias que seleccionan el signo del operando:
Adems, existen dos operadores de atajos aritmticos, ++ que incrementa en uno su operando, y -- que decrementa en uno el valor de su operando.
Ejemplo:
class Incremento{ public static void main(String[] args) { int i = 1; prt("i : " + i); prt("++i : " + ++i); // Pre-incremento, primero //incrementa y luego imprime por consola prt("i++ : " + i++); // Post-incremento, primero imprime //2 por consola y luego incrementa i. prt("i : " + i);//i por lo tanto vale 3 prt("--i : " + --i); // Pre-decremento, primero //decrementa i y luego lo imprime por consola prt("i-- : " + i--); // Post-decremento, primero imprime //i por consola y luego de decrementa. prt("i : " + i);//Ahora i vale 1 } //esto nos ahorrara teclear. Invocando a prt podremos //imprimir por consola la cadena de caracteres que le pasemos static void prt(String s) { System.out.println(s); } }
24
Frecuentemente los operadores relacionales se utilizan con otro juego de operadores, los operadores condicionales, para construir expresiones de decisin ms complejas. Uno de estos operadores es && que realiza la operacin Y booleana . Por ejemplo puedes utilizar dos operadores relacionales diferentes junto con && para determinar si ambas relaciones son ciertas. La siguiente lnea de cdigo utiliza esta tcnica para determinar si un ndice de un array est entre dos lmites- esto es, para determinar si el ndice es mayor que 0 o menor que NUM_ENTRIES (que se ha definido prviamente como un valor constante):
El operador & se puede utilizar como un sinnimo de && si ambos operadores son booleanos. Similarmente, | es un sinonimo de || si ambos operandos son booleanos .
25
Los tres operadores de desplazamiento simplemente desplazan los bits del operando de la izquierda el nmero de posiciones indicadas por el operador de la derecha. Los desplazamientos ocurren en la direccin indicada por el propio operador. Por ejemplo: 13 >> 1 Desplaza los bits del entero 13 una posicin a la derecha. La representacin binaria del nmero 13 es 1101. El resultado de la operacin de desplazamiento es 110 o el 6 decimal. Observe que el bit situado ms a la derecha desaparece. Un desplazamiento a la derecha de un bit es equivalente, pero ms eficiente que, dividir el operando de la izquierda por dos. Un desplazamiento a la izquierda es equivalente a multiplicar por dos. Los otros operadores realizan las funciones lgicas para cada uno de los pares de bits de cada operando. La funcin "y" activa el bit resultante si los dos operandos son 1.
Si quieres evaluar los valores 12 "and" 13: 12 & 13 El resultado de esta operacin es 12. Por qu? Bien, la representacin binaria de 12 es 1100 y la de 13 es 1101. La funcin "and" activa los bits resultantes cuando los bits de los dos operandos son 1, de otra forma el resultado es 0. Entonces si colocas en lnea los dos operandos y realizas la funcin "and", puedes ver que los dos bits de mayor peso (los dos bits situados ms a la izquierda de cada nmero) son 1 as el bit resultante de cada uno es 1. Los dos bits de menor peso se evalan a 0 porque al menos uno de los dos operandos es 0:
26
O exclusiva significa que si los dos operandos son diferentes el resultado es 1, de otra forma el resultado es 0:
Y finalmente el operador complemento invierte el valor de cada uno de los bites del operando: si el bit del operando es 1 el resultado es 0 y si el bit del operando es 0 el resultado es 1.
3.4.5 Expresiones
Las expresiones realizan el trabajo de un programa Java. Entre otras cosas, las expresiones se utilizan para calcular y asignar valores a las variables y para controlar el flujo de un programa Java. El trabajo de una expresin se divide en dos partes: realizar los clculos indicados por los elementos de la expresin y devolver algn valor. Definicin: Una expresin es una serie de variables, operadores y llamadas a mtodos (construida de acuerdo a la sintaxis del lenguaje) que evala a un valor sencillo.
27
El tipo del dato devuelto por una expresin depende de los elementos utilizados en la expresin. La expresin count++ devuelve un entero porque ++ devuelve un valor del mismo tipo que su operando y count es un entero. Otras expresiones devuelven valores booleanos, cadenas, etc... Una expresin de llamada a un mtodo devuelve el valor del mtodo; as el tipo de dato de una expresin de llamada a un mtodo es el mismo tipo de dato que el valor de retorno del mtodo. El mtodo System.in.read() se ha declarado como un entero, por lo tanto, la expresin System.in.read() devuelve un entero. La segunda expresin contenida en la sentencia System.in.read() != -1 utiliza el operador !=. Recuerda que este operador comprueba si los dos operandos son distintos. En esta sentencia los operandos son System.in.read() y -1. System.in.read() es un operando vlido para != porque devuelve un entero. As System.in.read() != -1 compara dos enteros, el valor devuelto por System.in.read() y -1. El valor devuelto por != es true o false dependiendo de la salida de la comparacin. Como has podido ver, Java te permite construir expresiones compuestas y sentencias a partir de varias expresiones pequeas siempre que los tipos de datos requeridos por una parte de la expresin correspondan con los tipos de datos de la otra. Tambin habrs podido concluir del ejemplo anterior, el orden en que se evalan los componentes de una expresin compuesta. Por ejemplo, toma la siguiente expresin compuesta: x * y * z En este ejemplo particular, no importa el orden en que se evale la expresin porque el resultado de la multiplicacin es independiente del orden. La salida es siempre la misma sin importar el orden en que se apliquen las multiplicaciones. Sin embargo, esto no es cierto para todas las expresiones. Por ejemplo, esta expresin obtiene un resultado diferente dependiendo de si se realiza primero la suma o la divisin: x + y / 100 Puedes decirle directamente al compilador de Java cmo quieres que se evale una expresin utilizando los parntesis ( y ). Por ejemplo, para aclarar la sentencia anterior, se podra escribir: (x + y)/ 100. Si no le dices explcitamente al compilador el orden en el que quieres que se realicen las operaciones, l decide basndose en la precedencia asignada a los operadores y otros elementos que se utilizan dentro de una expresin. Los operadores con una precedencia ms alta se evalan primero. Por ejemplo. El operador divisin tiene una precedencia mayor que el operador suma, por eso, en la expresin anterior x + y / 100, el compilador evaluar primero y / 100. As x + y / 100 es equivalente a: x + (y / 100) Para hacer que tu cdigo sea ms fcil de leer y de mantener deberas explicar e indicar con parntesis los operadores que se deben evaluar primero. La tabla siguiente muestra la precedencia asignada a los operadores de Java. Los operadores se han listado por orden de precedencia de mayor a menor. Los operadores con mayor precedencia se evalan antes que los operadores con un precedencia relativamente menor. Lo operadores con la misma precedencia se evalan de izquierda a derecha.
28
3.5.1 Concatenacin
La concatenacin en Java es increblemente sencilla: se realiza con el operador +, es decir sumando cadenas de caracteres obtenemos la concatenacin de estas. Lo ilustraremos con un ejemplo: String saludo = hola; String nombre = Pepe; String saluda_pepe = ; saluda_pepe = saludo + nombre;// saluda_pepe toma el valor holaPepe La sencillez de Java en el manejo de cadenas de caracteres llega incluso ms all: si una cadena la intentamos encadenar con otro tipo de variable automticamente se convierte la otra variable a String, de tal modo que es perfectamente correcto: String saludo = hola; int n = 5; saludo = saludo + + n;// saludo toma el valor hola 5
3.5.2 Subcadenas
En la clase String hay un mtodo que permite la extraccin de una subcadena de caracteres de otra. Su sintaxis es: Nombre_String.substring((int)posicin_inicial,(int)posicin_final);
29
Donde posicin_inicial y posicin_final son respectivamente la posicin del primer carcter que se desea extraer y del primer carcter que ya no se desea extraer. String saludo = hola; String subsaludo = ; Subsaludo = saludo.substring(0,2);// subsaludo toma el valor ho Puede extraerse un char de una cadena, para ello se emplea el mtodo charAt(posicin), siendo posicin la posicin del carcter que se desea extraer.
class Cadena { public static void main(String[] args) { String saludo = "Hola"; String saludo2 ="hola"; int n = 5; //Imprime por consola la subcadena formada por los caracteres //comprendidos entre el caractero 0 de saludo y hasta el //carcter 2, sin incluir el ltimo prt(saludo.substring(0,2)); //Concatena saludo con un espacio en blanco y con el valor de //la variable n prt(saludo +" " + n); //Imprime el resultado del test de igualdad entre saludo y //saludo2. Son distintos, en Java se distingue entre //maysculas y minsculas. prt("saludo == saludo2 "+ saludo.equals(saludo2)); } static void prt(String s) { System.out.println(s); } }
3.5.4 Exponenciacin
En Java a diferencia que en otros lenguajes no existe el operador exponenciacin. Tampoco existen los operadores Seno o Coseno. Si se desea realizar una operacin de exponenciacin se deber invocar el mtodo correspondiente de la clase Math de Java.lang. Estos mtodos son estticos, por lo que no es necesario crear un objeto de dicha clase. Su sintaxis general es: Math.metodo(argumentos); En el siguiente cdigo aparecen ejemplos. Si alguna vez deseamos realizar algn otro tipo de operacin y queremos ver si la soporta Java podemos hacerlo consultando la ayuda on-line de la clase Math. Ejemplo:
30
class Exponente { public static void main(String[] args) { int i = 45,j=2; //Imprime por consola la cadena de caracteres Cos i : //concatenado con el resultado de calcular el coseno de i prt("Cos i : " + Math.cos(i)); prt("Sen i : " + Math.sin(i)); prt("j^i : " + Math.pow(j,i)); } //esto nos ahorrara teclear static void prt(String s) { System.out.println(s); } }
Si condicin toma el valor true se ejecuta Grupo de sentencias, si condicion2 toma el valor true se ejecuta Grupo2 de sentencias... y as sucesivamente hasta acabarse todas las condiciones. Si no se cumple ninguna se ejecuta Grupo_n de sentencias. Este ltimo else es opcional. En ambos casos se contina ejecutando el resto del cdigo. Ilustraremos esto con el siguiente ejemplo:
class Ejemplo1 { // Mtodo que podremos invovar como test(int a, int b) y que // devolver -1 si a < b, +1 si a > b y 0 si a == b. static int test(int val, int val2) { int result = 0; if(val > val2) result = +1; else if(val < val2) result = -1; else result = 0; return result; } public static void main(String[] args) { //Imprimimos por consola el resultado de realizar unos //cuantos test. System.out.println(test(10, 5)); System.out.println(test(5, 10)); System.out.println(test(5, 5)); } }
4.1.2 Switch
Los creadores de Java trataron de hacer de este lenguaje una versin simplificada y mejorada del lenguaje de C++. Su trabajo fue bastante bueno, pero no perfecto. Prueba de ello es esta sentencia: est tan poco flexible como en C++. Expliquemos su sintaxis antes de dar los motivos de esta crtica: switch(selector) { case valor1 : Grupo de sentencias1; break; case valor2 : Grupo de sentencias2; break; case valor3 : Grupo de sentencias3; break; case valor4 : Grupo de sentencias4; break; case valor5 : Grupo de sentencias5; break; // ... default: statement; } Se compara el valor de selector con sentencias_n. Si el valor coincide se ejecuta su respectivo grupo de secuencias. Si no se encuentra ninguna coincidencia se ejecutan las sentencias de default. Si no se pusieran los break una vez que se encontrase un valor que coincida con el selector se ejecutaran todos los grupos de sentencias, includa la del default. Ha llegado el momento de justificar la crtica hecha a los creadores de Java. Este tipo de estructura tiene sus posibilidades muy limitadas, ya que en las condiciones slo se admite la igualdad, no ingn otro tipo de condicin (sera fcil pensar ejemplos dnde, por poner un caso, se le sacara partido a esta sentencia si aceptase desigualdades). Adems para colmo esta comparacin de igualdad slo admite valores tipo char o cualquier tipo de valores enteros menos long. No podemos comparar contra reales, strings....
32
Tambin se le podra criticar el hecho de que una vez cumplidas una condicin se ejecuten todas las sentencias si no hay instrucciones break que lo impidan. Esto es en muchas ocasiones fuente de errores, aunque tambin hay que reconocer que a veces se le puede sacar partido, de hecho en el ejemplo que empleamos para ilustrar esta sentencia aprovechamos esta caracterstica:
class Ejemplo2 { public static void main(String[] args) { //Bucle for. Ejecutar 100 veces el cdigo que tiene dentro. for(int i = 0; i < 100; i++) { //Math.random() es un mtod esttico que genera un nmero real //aleatorio entre 0 y 1. //Math.random()*26 ser un nmero real aleatorio entre 0 y //26. Al sumarle un carcter, a el carcter se transforma a //un enteroy se le suma. a = 97. //Se transforma el nmero aleatorio entre 97y 97 + 26 en el //carcter correspodiente a su parte entera. Ser un carcter //aleatorio, que por la disposicin de los caracteres Unicode //ser un letra del abecedario. char c = (char)(Math.random() * 26 + 'a'); System.out.print(c + ": "); switch(c) { case 'a': case 'e': case 'i': case 'o': case 'u': //Si el carcter es a, e, i, o o u imprimimos //vocal. System.out.println("vocal"); break; default: //Si no era ninguna de las anteriores imprimimos consonante. System.out.println("consonante"); } } } }
4.2 BUCLES
Son instrucciones que nos permiten repetir un bloque de cdigo mientras se cumpla una determinada condicin. Pasemos a ver sus tipos.
33
class Ejemplo3 { public static void main(String[] args) { double r = 0; //Mientras que r < 0.99 sigue ejecutando el cuerpo del bucle. //La d significa double. No es necesaria while(r < 0.99d) { //Genera un nuevo r aleatario entre 0 y 1. r = Math.random(); //Lo imprime por consola. System.out.println(r); }}}
class Ejemplo4 { public static void main(String[] args) { double r; //Idntico al ejemplo anterior, solo que ahora la condicin //est al final del bucle. do { r = Math.random(); System.out.println(r); } while(r < 0.99d); } }
class Ejemplo5 { public static void main(String[] args) { for( char c = 0; c < 128; c++) //Imprime los caracteres correspondientes a los nmeros //enteros comprendidos entre 0 y 128. (int)c es el entero //correspondiente al carcter c.
34
class Ejemplo6 { public static void main(String[] args) { for(int i = 0; i < 100; i++) { if(i == 74) break; // teminamos aqui el bucle //Salto a la siguiente iteracin si i no es divisible entre 9 if(i % 9 != 0) continue; //Si I es divisible entre 9 se imprime System.out.println(i); } int i = 0; // Lazo infinito del cual se sale con break: while(true) { i++; int j = i * 27; if(j == 1269) break; // Salimos del lazo if(i%10 != 0) continue;//Salto a la siguiente iteracin System.out.println(i); } } }
4.3 RETURN
Sus funciones son las mismas que en C++. Cuando se llama a un procedimiento ( que en POO se denomin mtodo) al encontrarse con una sentencia return se pasa el valor especificado al cdigo que llam a dicho mtodo y se devuelve el control al cdigo invocador. Su misin tiene que ver con el control de flujo: se deja de ejecutar cdigo secuencialmente y se pasa al cdigo que invoc al mtodo. Esta sentencia tambin est profundamente relacionada con los mtodos, ya que es la sentencia que le permite devolver al mtodo un valor. Podamos haber esperado ha hablar de mtodos para introducir esta sentencia, pero hemos decidido introducirla aqu por tener una funcin relacionada, entre otras cosas, con control de flujo. En el ejemplo 1 ya se ha ejemplificado su uso.
35
5.1 INTRODUCCIN
En los aos 60 la programacin se realizaba de un modo clsico (no orientado a objetos). Un programa era un cdigo que se ejecutaba, los trozos de cdigo que se podan emplear en varias ocasiones a lo largo del programa (reusar) se escriban en forma de procedimientos que se invocaban desde el programa, y esta era la nica capacidad de reuso de cdigo posible. Segn los cdigos se fueron haciendo ms grandes y complejos este estilo de programacin se haca ms inviable: es difcil programar algo de grandes dimensiones con este estilo de programacin. La nica posibilidad de repartir trozos de cdigo relativamente independientes entre programadores son los procedimientos, y al final hay que juntar todos estos con el programa central que los llama, siendo frecuente encontrar problemas al unir estos trozos de cdigo. En los aos 70 se empez a imponer con fuerza otro estilo de programacin: POO, programacin orientada o objetos (en la literatura suele aparecer como OOP, Object Oriented Programing). Aqu un programa no es un cdigo que llama a procedimientos, aqu un programa es un montn de objetos, independientes entre si, que dialogan entre ellos pasndose mensajes para llegar a resolver el problema en cuestin. A un objeto no le importa en absoluto como est implementado otro objeto, que cdigo tiene o deja de tener, que variables usa.... slo le importa a que mensajes es capaz de responder. Un mensaje es la invocacin de un mtodo de otro objeto. Un mtodo es muy semejante a un procedimiento de la programacin clsica: a un mtodo se le pasan uno, varios o ningn dato y nos devuelve un dato a cambio. Si hay que repartir un programa de grandes dimensiones entre varios programadores a cada uno se le asignan unos cuantos objetos, y en lo nico que tendrn que ponerse de acuerdo entre ellos es en los mensajes que se van a pasar; la forma en que un programador implemente sus objetos no influye en absoluto en lo que los dems programadores hagan. Esto es as gracias a que los objetos son independientes unos de otros (cuanta mayor sea la independencia entre ellos de mayor calidad sern). Si analizamos lo que hemos dicho hasta aqu de los objetos veremos que estos parecen tener dos partes bastante diferenciadas: la parte que gestiona los mensajes, que ha de ser conocida por los dems, y que no podremos cambiar en el futuro sin modificar los dems objetos (s es posible aadir nuevos mtodos para dar nuevas funciones al objetos sin modificar los mtodos ya existentes). La otra parte es el mecanismo por el cual se generan las acciones requeridas por los mensajes el conjunto de variables que se emplean para lograr estas acciones. Esta segunda parte es, en principio, totalmente desconocida para los dems objetos (a veces no es as, pero es lo ideal en un buena OOP). Por ser desconocida para los dems objetos podemos en cualquier momento modificarla sin que a los dems les importe, y adems cada programador tendr total libertad para llevarla a cabo como l considere oportuno. La OOP permite abordar con ms posibilidades de xito y con un menor coste temporal grandes proyectos de software, simplificndole adems la tarea al programador.
derivarse de otra, en ese caso la clase derivada o clase hija hereda los mtodos y variables de la clase de la que se deriva o clase padre. En Java todas las clases tienen como primer padre una misma clase: la clase Object. Por motivos de simplicidad y dada la corta duracin de este curso ignoraremos la existencia del concepto de package en la explicacin de los siguientes conceptos, o haremos breves referencias a este concepto sin dar demasiadas explicaciones. En general en lo que al alumno respecta se recomienda ignorar, al menos durante este curso, toda referencia al trmino package. Vamos a continuacin a profundizar en todos estos conceptos y a explicar su sintaxis en Java.
class Animal{ int edad; String nombre; public void nace(){ System.out.println("Hola mundo"); } public void get_nombre(){ System.out.println(nombre); } public void get_nombre(int i){ System.out.println(nombre +" " +edad); } public void get_edad(){ System.out.println(edad); } }
5.2.1.3 Constructores
Constructores son mtodos cuyo nombre coincide con el nombre de la clase y que nunca devuelven ningn tipo de dato, no siendo necesario indicar que el tipo de dato devuelto es void. Los constructores se emplean para inicializar los valores de los objetos y realizar las operaciones que sean necesarias para la generacin de este objeto (crear otros objetos que puedan estar contenidos dentro de este objeto, abrir un archivo o una conexin de internet.....). Como cualquier mtodo, un constructor admite sobrecarga. Cuando creamos un objeto (ya se ver ms adelante como se hace) podemos invocar al constructor que ms nos convenga.
Ejemplo: Veamos la clase Animal y sus constructores. class Animal{ int edad; String nombre; public Animal(){ //constructor por defecto } public Animal(int _edad, String _nombre){ //constructor con 2 argumentos edad = _edad; nombre = _nombre; } public void nace(){ System.out.println("Hola mundo");} public void get_nombre(){ System.out.println(nombre); } public void get_nombre(int i){ System.out.println(nombre +" " +edad); } public void get_edad(){ System.out.println(edad); } }
38
5.2.3 Herencia
Cuando en Java indicamos que una clase extends otra clase estamos indicando que es una clase hija de esta y que, por lo tanto, hereda todos sus mtodos y variables. Este es un poderoso mecanismo para la reusabilidad del cdigo. Podemos heredar de una clase, por lo cual partimos de su estructura de variables y mtodos, y luego aadir lo que necesitemos o modificar lo que no se adapte a nuestros requerimientos. Veamos un ejemplo:
class Animal{ protected int edad; String nombre; public Animal(){ } public Animal(int _edad, String _nombre){ edad = _edad; nombre = _nombre; }
39
public void nace(){ System.out.println("Hola mundo"); } public void get_nombre(){ System.out.println(nombre); } public void get_nombre(int i){ System.out.println(nombre +" " +edad); } public void get_edad(){ System.out.println(edad); } } public class Perro extends Animal{ Perro(){ //constructor vacio edad = 0; nombre ="Tobi"; } Perro(int edad, String nombre){ //Esta sentencia invoca a un constructor de la clase padre. super(edad,nombre); } //Mtodo esttico que recibe un objeto de tipo perro e //invoca a su mtodo get_edad() static void get1(Perro dog){ //El mtodo get_edad() no est definido en perro, lo ha //heredado de animal. dog.get_edad(); } public static void main (String[] args){ //Creamos un objeto de tipo perro Perro dog = new Perro(8,"Bambi"); //Invocamos al mtodo esttioco get1 pasndole el objeto // Perro.get1(dog); } }
Fecha (int _dia, int _mes, int _anio){ dia= _dia; mes = _mes; ano = _anio; } }
Fecha hoy; hoy = new Fecha(); Con el primer comando hemos creado un puntero que apunta a una variable tipo fecha, como est sin inicializar apuntara a null. Con el segundo inicializamos el objeto al que apunta hoy, reservando espacio para sus variables. El constructor las inicializar, tomando el objeto hoy el valor 1-1-1900. Fecha un_dia = new Fecha(8,12,2005); Con esta sentencia creamos una variable que se llama un_dia con valor 8-12-2005. Una vez creado un objeto ser posible acceder a todas sus variables y mtodos pblicos, as por ejemplo en el ejemplo anterior un_dia.dia = 8. Si la variable fuese privada solo podran acceder a ella sus instancias:
class Fecha{ private int dia,mes,ano; Fecha(){ dia=1; mes = 1; ano = 1900; } Fecha (int ndia, nmes, nano){ dia= ndia; mes = nmes; ano = nano; } }
De este modo no podramos acceder a las variables de la clase fecha, para acceder a ella tendramos que hacerlo mediante mtodos que nos devolviesen su valor. A esto se le denomina encapsulacin. Esta es la forma correcta de programar OOP: no debemos dejar acceder a las variables de los objetos por otro procedimiento que no sea paso de mensajes entre mtodos.
5.2.5 this
Es una variable especial de slo lectura que proporciona Java. Contiene una referencia al objeto en el que se usa dicha variable. A veces es til que un objeto pueda referenciarse a si mismo:
class Cliente{ public Cliente(String n){ //Llamamos al otro constructor. El empleo de this ha de ser //siempre en la primera lnea dentro del constructor. this(n, Cuenta.nuevo_numero()); ..... } public Cliente (String n, int a){ nombre = n; numero_cuenta = a;
41
} ..... }
Otro posible uso de this, que ya se ha visto en ejemplos anteriores es diferenciar entre variables locales de un mtodo o constructor y variables del objeto.
public Animal(int edad, String nombre){ //this.edad = variable del objeto perro //edad = variable definida slo dentro del constructor this.edad =edad; this.nombre=nombre; }
5.2.6 super
Del mismo modo que this apunta al objeto actual tenemos otra variable super que apunta a la clase de la cual se deriva nuestra clase :
class Gato { void hablar(){ System.out.println("Miau"); } } class GatoMagico extends Gato { boolean gente_presente; void hablar(){ if(gente_presente) //Invoca al mtodo sobreescrito de la clase padre super.hablar(); else System.out.println("Hola"); } }
Uno de los principales usos de super es, como ya se emple en un ejemplo, llamar al constructor de la clase padre.
5.3 INTERFACES
En Java no est soportada la herencia mltiple, esto es, no est permitido que una misma clase pueda heredar las propiedades de varias clases padres. En principio esto pudiera parecer una propiedad interesante que le dara una mayor potencia al lenguaje de programacin, sin embargo los creadores de Java decidieron no implementar la herencia mltiple por considerar que esta aade al cdigo una gran complejidad (lo que hace que muchas veces los programadores que emplean programas que s la soportan no lleguen a usarla). Sin embargo para no privar a Java de la potencia de la herencia mltiple sus creadores introdujeron un nuevo concepto: el de interface. Una interface es formalmente como una clase, con dos diferencias: sus mtodos estn vacos, no hacen nada, y a la hora de definirla en vez de emplear la palabra clave class se emplea inteface. Vemoslo con un ejemplo:
interface Animal1{ public int edad = 10; public String nombre = "Bob";
42
interface Animal1{ public int edad = 10; public String nombre = "Bob"; public void nace(); public void get_nombre(); void get_nombre(int i); } ***************************************************** public class Perro1 implements Animal1{ Perro3(){ get_nombre(); get_nombre(8); } //Comprubese como si cambiamos el nombre del mtodo a nac() //no compila ya que no henos sobreescrito todos los mtodos //de la interfaz. public void nace(){ System.out.println("hola mundo"); } public void get_nombre(){ System.out.println(nombre ); } public void get_nombre(int i){ System.out.println(nombre +" " +i); } public static void main (String[] args){ Perro1 dog = new Perro1(); //Comprubese como esta lnea da un error al compilar debido //a intentar asignar un valor a una variable final // dog.edad = 8; } }
Las variables que se definen en una interface llevan todas ellas el atributo final, y es obligatorio darles un valor dentro del cuerpo de la interface. Adems no pueden llevar modificadores private ni protected, slo public. Su funcin es la de ser una especie de constantes para todos los objetos que implementen dicha interface.
43
Por ltimo decir que aunque una clase slo puede heredar propiedades de otra clase puede implementar cuantas interfaces se desee, recuperndose as en buena parte la potencia de la herencia mltiple. Veamos un ejemplo de mltiples interfaces
interface Animal1{ public int edad = 10; public String nombre = "Bob"; public void nace(); } ********************************* interface Animal2{ public void get_nombre(); } ********************************** interface Animal3{ void get_nombre(int i); } ********************************* public class Perro2 implements Animal1,Animal2,Animal3{ Perro2(){ get_nombre(); get_nombre(8); //edad = 10; no podemos cambiar este valor } public void nace(){ System.out.println("hola mundo"); } public void get_nombre(){ System.out.println(nombre ); } public void get_nombre(int i){ System.out.println(nombre +" " +i); } public static void main (String[] args){ Perro2 dog = new Perro2(); }}
// Un ejemplo sencillo de una clase abstracta. abstract class A { abstract void callme(); // en las clases abstractas se permiten mtodos concretos void callmetoo() {
44
System.out.println("Este es un mtodo concreto"); }} class B extends A { void callme() { System.out.println("implementacin de mtodo callme en B."); }} class AbstractApp { public static void main(String args[]) { B b = new B(); b.callme(); b.callmetoo(); }} observacin: No se declaran objetos de la clase A, ya que no es posible crear una instancia de una clase abstracta. Las clase abstractas pueden incluir tanta implementacin como sea necesario. Aunque no es posible crear instancia de las clases abstractas, si es posible crear referencias a objetos, ya que el polimorfismo de java en tiempo de ejecucin se implementa mediante la referencia a las superclases. // Mejora de la clase figura con una clase abstracta. abstract class Figura { double dim1; double dim2; Figura(double a, double b) { dim1 = a; dim2 = b; } // area es ahora un mtodo abstracto abstract double area(); } ************************ class Rectangulo extends Figura { Rectangulo(double a, double b) { super(a, b); } // se sobreescribe area para un rectangulo double area() { System.out.println("Dentro del mtodo area para un Rectangulo."); return dim1 * dim2; }} *********************************** class Triangulo extends Figura {
45
Triangulo(double a, double b) { super(a, b); } // sobreescribiendo el area para un triangulo double area() { System.out.println("Dentro del mtodo area para un Triangulo."); return dim1 * dim2 / 2; }} ************************************** class AbstractaApp { public static void main(String args[]) { // Figura f = new Figure(10, 10); // esto no es correcto ahora Rectangulo r = new Rectangulo(9, 5); Triangulo t = new Triangulo(10, 8); Figura figref; // esto es correcto no se crea ningun objeto figref = r; System.out.println("Area es: " + figref.area()); figref = t; System.out.println("Area es :" + figref.area()); }}
46
mtodos como a atributos como static. El ejemplo mas habitual de un miembro static es main(), este se declara static, ya que debe ser llamado antes de que exista cualquier objeto. class StaticDemo { static int a = 42; static int b = 99; static void callme() { System.out.println("a = " + a); }} class StaticApp { public static void main(String args[]) { StaticDemo.callme(); System.out.println("b = " + StaticDemo.b); }} Salida: a=42; b=99; En este ejemplo dentro de main() se accede al metodo static callme() y a la variable b fuera de su clase.
if(strOb1.equals(strOb2)) System.out.println("strOb1 == strOb2"); else System.out.println("strOb1 != strOb2"); if(strOb1.equals(strOb3)) System.out.println("strOb1 == strOb3"); else System.out.println("strOb1 != strOb3"); }} ********************************** // Ejemplo de matrices de cadena de caracteres. class StringApp1 { public static void main(String args[]) { String matriz[] = { "uno", "dos", "tres" }; for(int i=0; i<matriz.length; i++) System.out.println("matriz[" + i + "]: " + matriz[i]); }} ****************************************
// Presentacin de todos los argumentos de la linea de ordenes. class LineaApp { Java LineaApp esto es una prueba 100 public static void main(String args[]) { 1 for(int i=0; i<args.length; i++) Salida: System.out.println("args[" + i + "]: " +args[i]); args[0]:esto .... args[5]:-1 }} jgrasp opcion run ..... run arguments
48
6.0 Applets
6.1 Introduccin
Un applet es un programa en Java que se incrusta en una pgina HTML para agregarle funcionalidad. Como por seguridad los applets estn limitados a no poder accesar el medio ambiente de la computadora donde corren su principal utilidad es para hacer interfaces grficas de aplicaciones de bases de datos. Por supuesto la base de datos debe residir en la computadora de donde vino el applet. Tambin se pueden programar juegos, programas interactivos que complementen un texto en lnea o que ilustren un concepto en particular, simulaciones, etc.
Codigo html: <html> <head> <title>EjemploApplet Applet</title> </head> <body> <h1>EjemploApplet Applet</h1> <hr> <applet code="EjemploApplet.class" width=300 height=300 codebase="." archive="" alt="Your browser understands the <APPLET> tag but isn't running the applet, for some reason." > Your browser is ignoring the <APPLET> tag! </applet> <hr> </body> </html>
50
Como se puede ver en el listado anterior la etiqueta <APPLET> es la que incrusta el applet en la pgina HTML. Esta etiqueta puede tener varios argumentos, los principales son: CODE Indica el nombre del archivo compilado. Si el archivo de clase est en la misma carpeta que la pgina HTML (lo ms recomendable) basta con poner el nombre, pero se pueden ejecutar applets remotos poniendo la direccin http completa. WIDTH Indica el ancho en pixeles del rea dentro de la pgina HTML que el navegador apartar para uso del applet. HEIGHT Es la altura de dicha rea en pixeles. ALIGN Permite alinear el applet a la izquierda (Left), al centro (Center) o a la derecha (Right).
6.3 Argumentos
Los applets pueden recibir argumentos desde el archivo HTML con la etiqueta <PARAM>, la cual tiene un atributo para el nombre del argumento y otro para el valor. La etiqueta <PARAM> va dentro de la etiqueta <APPLET>. Los argumentos se pasan cuando el applet se inicializa. Dentro del mtodo init() se puede recuperar los valores de los argumentos usando el mtodo getParameter(). Este mtodo recibe como argumento un string con el nombre del argumento y devuelve un string con el valor correspondiente a ese argumento o el valor null si el argumento no existe. Ejemplo:Un applet que lee tres argumentos y escribe un mensaje en la pantalla..
import java.awt.Graphics; import java.awt.Font; import java.awt.Color; import java.applet.*; public class EjemploArgumento extends Applet { Font f = new Font ("TimesRoman", Font.BOLD, 36); String desde, para, mssg; public void init () {
51
desde = getParameter ("desde"); if (desde == null) desde = "aqui"; para = getParameter ("para"); if (para == null) para = "alla"; mssg = getParameter ("mensaje"); if (mssg == null) mssg = "no hay mensaje"; } public void paint (Graphics g) { g.setFont (f); g.setColor (Color.red); g.drawString ("Desde " + desde + " para " + para, 5, 40); g.setColor (Color.green); g.drawString ("El mensaje es: " + mssg, 5, 80); } } Codigo html: <HTML> <HEAD> <TITLE>Ejemplo con Argumentos !!!</TITLE> </HEAD> <BODY> <P><H2>Bienvenido a Programacion III:</H2><P> <APPLET CODE="EjemploArgumento.class" WIDTH=600 HEIGHT=400> <PARAM NAME=desde VALUE="San Vicente"> <PARAM NAME=para VALUE="todos"> <PARAM NAME=mensaje VALUE="Este es Java applet !!!"> </APPLET> </BODY> </HTML>
52
6.4 Grficas
La librera de Java incluye la clase Graphics para hacer dibujos sencillos. Esta clase tiene mtodos para dibujar lneas, rectngulos, crculos, polgonos y arcos. En los applets el mtodo paint recibe un objeto de tipo Graphics. Al dibujar sobre este objeto se dibuja dentro del applet y los resultados se desplegarn en la pantalla. El sistema de coordenadas est en pixeles. El origen, es decir, el punto (0,0) est en la esquina superior izquierda del rea reservada para el applet en la pgina HTML. La X crece hacia la derecha en forma horizontal y la Y crece hacia abajo en forma vertical. La clase Graphics tiene los siguientes mtodos. Hay que aclarar que todos los argumentos son de tipo int. drawString (s, x, y) Escribe el texto almacenado en el string s. El punto (x, y) indica la coordenada donde comienza el texto. drawLine (x1, y1, x2, y2) Dibuja una lnea recta entre los puntos (x1, y1) y (x2, y2). drawRect (x, y, ancho, alto) Dibuja un rectngulo hueco. La esquina superior izquierda est dada por el punto (x, y) y la anchura y altura por ancho y alto respectivamente. fillRect (x, y, ancho, alto) - Dibuja un rectngulo lleno con color actual. drawRoundRect (x, y, ancho, alto, r_hor, r_ver) Dibuja un rectngulo hueco con las esquinas redondeadas. Los primeros 4 argumentos tienen el mismo sentido que en los dos mtodos anteriores. Los argumentos r_hor y r_ver determinan que tan lejos de los limites del rectngulo debe comenzar el redondeo en las esquinas. fillRoundRect (x, y, ancho, alto, r_hor, r_ver) Dibuja un rectngulo lleno con el color actual y redondeado en las esquinas. Los argumentos son los mismos que en el mtodo anterior. drawPolygon (x[], y[], n) Dibuja un polgono hueco. Los arreglos x y y guardan respectivamente las coordenadas x y y de cada una de las esquinas del polgono. El argumento n es el nmero de puntos (el tamao de los arreglos x y y). El polgono se cierra en forma automtica porque siempre se dibuja una lnea entre el ltimo punto y el primero. fillPolygon (x[], y[], n) Dibuja un polgono lleno con el color actual. Los argumentos son los mismos que en el mtodo anterior. drawPolyline (x[], y[], n) Dibuja una lnea quebrada. El resultado se puede pensar que es un polgono sin cerrar. drawOval (x, y, ancho, alto) Dibuja un valo hueco. La forma ms fcil de entender el resultado de este mtodo es imaginar que se dibuja un rectngulo imaginario con una anchura de ancho pixeles y una altura de alto pixeles, con la esquina superior izquierda en el punto (x, y) y que el valo se dibuja inscrito en el rectngulo. Por lo tanto, el punto (x, y) est fuera del valo. Si ancho es igual que alto, el rectngulo se convierte en cuadrado y el valo se convierte en crculo. fillOval (x, y, ancho, alto) Dibuja un valo lleno con el color actual. Los argumentos son los mismos que en el mtodo anterior. drawArc (x, y, ancho, alto, grados_ini, largo_grados) Dibuja un arco hueco. Los primeros 4 argumentos tienen el mismo sentido que en los dos mtodos anteriores. El argumento grados_ini indica desde que grado se comienza a dibujar el arco. Si pensamos en los puntos cardinales el grado 0 est al este, el 90 al norte, el 180 al oeste y el 270 al sur. Por ltimo largo_grados indica la longitud del arco y la direccin de dibujo. Un valor de 90 significa que se dibujar un cuarto de arco en contra de las manecillas del reloj, en cambio un valor de 90 tambin indica un cuarto de arco pero en el sentido de las manecillas del
53
reloj. Igualmente un valor de 180 significa medio arco y dependiendo del signo es la direccin. fillArc (x, y, base, altura, grados_ini, largo_grados) Dibuja un arco lleno con el color actual. Los argumentos son los mismos que en el mtodo anterior.
Ejemplo: import java.awt.Graphics; import java.applet.*; public class Dibujo extends Applet { public void paint (Graphics g) { // la plataforma g.fillRect (0, 250, 290, 290); // La base g.drawLine (125, 250, 125, 160); g.drawLine (175, 250, 175, 160); // La cubierta g.drawArc (85, 157, 130, 50, -65, 312); g.drawArc (85, 87, 130, 50, 62, 58); g.drawLine (85, 177, 119, 89); g.drawLine (215, 177, 181, 89); // Las motas g.fillArc (78, 120, 40, 40, 63, -174); g.fillOval (120, 96, 40, 40); g.fillArc (173, 100, 40, 40, 110, 180); } }
54
55
6.6 Color
Para cambiar el color hay que crear una instancia de la clase Color: Color c = new Color (r, g, b); donde r, g y b es la cantidad de rojo, verde y azul respectivamente, en el rango de 0 a 255. El (0,0,0) representa el negro y el (255, 255, 255) el blanco. En total hay 256 3 = 16,777,216 colores distintos. Se puede usar alguno de los colores ya definidos en la clase Color como Color.white, Color.red, Color.black, Color.orange, Color.blue, Color.gray, Color.yellow, etc. Hay 3 funciones para cambio de color: setColor (c). Cambia el color actual para las operaciones de dibujo a partir de este momento. El argumento c es un objeto de tipo Color. setBackground (c). Cambia el color del fondo al color c. setForeground (c). Cambia el color de lo que se haya dibujado sin importar el color en el que se dibuj. Ejemplo:
import java.awt.Graphics; import java.applet.*; import java.awt.Color; public class CambioColor extends Applet { public void paint (Graphics g) { Color c1 = new Color (120, 30, 220); Color c2 = new Color (40, 240, 200); setBackground (Color.lightGray); g.setColor (c1); g.drawString ("Ovalo hueco", 5, 20); g.drawOval (5, 30, 100, 60); g.drawString ("Circulo hueco", 150, 20); g.drawOval (150, 30, 60, 60); g.setColor (c2); g.drawString ("Ovalo lleno", 5, 120); g.fillOval (5, 130, 100, 60); g.setColor (Color.red); g.drawString ("Circulo lleno", 150, 120); g.fillOval (150, 130, 60, 60); } }
6.7 Imgenes
Un applet pueden desplegar imgenes en formato GIF y JPEG. Lo primero es crear una instancia de la clase Image. Image mi_imagen = getImage (URL, nombre); donde URL es la direccin donde est la imagen y nombre es el nombre del archivo que contiene la imagen. Para la variable URL hay 3 opciones:
56
a) Poner la direccin absoluta. Si la imagen esta en: http://www.foo.com/images/foo.gif, entonces poner: Image imagen = getImage (new URL ("http://www.foo.com/images", "foo.gif"); b) Usar la direccin relativa con respecto a donde est la pgina HTML. Si el directorio images est abajo de donde est la pgina, entonces poner: Image imagen = getImage (getDocumentBase (), "images/foo.gif"); c) Usar la direccin relativa a donde est el applet compilado (archivo .class). Si el directorio images est abajo de donde est el cdigo objeto, entonces poner: Image imagen = getImage (getCodeBase (), "images/foo.gif"); Para desplegar la imagen: drawImage (imagen, x, y, ancho, alto, donde); donde imagen es un objeto de tipo Image, x y y son las coordenadas de la esquina superior izquierda de la imagen. Las variables ancho y alto indica el ancho y el alto en pixeles con que se va a desplegar la imagen. Por ltimo, la variable donde es el nombre del objeto que va a actuar como observador de la imagen que en el caso de los applets es el mismo applet.
Ejemplo: Para una imagen que se llama disco.gif import java.awt.Graphics; import java.applet.*; import java.awt.Image; public class Imagenes extends Applet { Image img; public void init () { img = getImage (getCodeBase (), "disco.gif"); } public void paint (Graphics g) { int ancho = img.getWidth (this); int alto = img.getHeight (this); int xpos = 10, ypos = 10; // 25 % g.drawImage (img, xpos, ypos, ancho / 4, alto / 4, this); // 50 % xpos += (ancho / 4) + 10; g.drawImage (img, xpos, ypos, ancho / 2, alto / 2, this); // 100 % xpos += (ancho / 2) + 10; g.drawImage (img, xpos, ypos, this); // 150 % xpos += ancho + 10; g.drawImage (img, xpos, ypos, (int) (ancho * 1.5), (int) (alto * 1.5), this); // distorsionado xpos += (int) (ancho * 1.5 + 10); g.drawImage (img, xpos, ypos, ancho / 2, (int) (alto * 1.5), this); } }
57
auditor que previamente haya registrado un inters por recibir informacin de eventos de accin que generan cada componente. Cada auditor implementa la interfaz ActionListener. Esta interfaz define el mtodo actionPerformed(), que es al que se llama cuando ocurre un evento. Como argumento a este mtodo se pasa un objeto ActionEvent. Cada vez que se pulsa un botn, se muestra un mensaje que indica que boton se ha pulsado. La etiqueta se obtiene llamando al mtodo getActionCommand() sobre el objeto ActionEvent que se pasa a actionPerformed(). Ejemplo:
import java.awt.*; import java.awt.event.*; import java.applet.*; public class Boton1Applet extends Applet implements ActionListener { String msg=""; Button btn1,btn2; public void init (){ btn1 = new Button ("Boton 1"); btn2 = new Button ("Boton 2"); add (btn1); add (btn2); btn1.addActionListener(this); btn2.addActionListener(this); } public void actionPerformed(ActionEvent ae){ String str=ae.getActionCommand(); if (str.equals("Boton 1")){ msg="Presiono el Boton 1"; } else { msg="Presiono el Boton 2"; } repaint(); } public void paint(Graphics g ){ g.drawString(msg,6,50); } }
59
7.3 Checkboxes
Un checkbox es un componente que tiene dos estados: marcado o no marcado. Los checkboxes se pueden agrupar de tal forma que solo un checkbox de ese grupo pueda estar activado a la vez. Para definir un checkbox hay que crear un objeto de tipo Checkbox indicando un texto como etiqueta y opcionalmente un booleano para indicar si de entrada est marcada o no. Los checkbox se pueden utilizar individualmente o como parte de grupo: Constructores: Checkbox(), Checkbox(String str), Checkbox(String str, boolean on), Checkbox(String str, boolean on, CheckboxGroup cbgroup), CheckboxGroup (String str, CheckboxGroup cbGroup, boolean on). Para obtener el estado inicial de un checkbox, hay que llamar a getState(). Para establecer un determinado estado, se puede llamar a setState(). Se puede obtener la etiqueta asociada al checkbox llamando al mtodo getLabel(). Llamado al mtodo setLabel() se establece la etiqueta. Para marcar (o desmarcar) desde un programa un checkbox se usa el mtodo setState() pasando un argumento booleano que indique el nuevo estado, cb1.setState (false); // desmarca el checkbox cb1. Ejemplo: dos grupos de checkbox
import java.awt.*; import java.applet.*; public class Checkbox1Applet extends Applet { Label lb1 = new Label ("Estado civil"); Label lb2 = new Label ("Nacionalidad"); CheckboxGroup cbg1 = new CheckboxGroup (); CheckboxGroup cbg2 = new CheckboxGroup (); Checkbox cbs = new Checkbox ("soltero", false, cbg1); Checkbox cbc = new Checkbox ("casado", true, cbg1); Checkbox cbv = new Checkbox ("viudo", false, cbg1); Checkbox cbsal = new Checkbox ("salvadoreo", true, cbg2); Checkbox cbext = new Checkbox ("extranjero", false, cbg2); public void init (){ add (lb1); add (cbs); add (cbc); add (cbv); add (lb2); add (cbsal); add (cbext); } }
60
Gestin de Checkbox Cada vez que se selecciona o se deselecciona un checkbox, se genera un evento que se enva a cualquier auditor que previamente se haya registrado para recibir informacin de los eventos que se producen desde ese componente. Cada auditor implementa la interfaz ItemListener. Esta interfaz define el metodo itemStateChanged(). Como argumento de este metodo se pasa un objeto ItemEvent, que encapsula la inforarmacion sobre el evento. Ejemplo:
import java.awt.*; import java.awt.event.*; import java.applet.*; public class Checkbox2Applet extends Applet implements ItemListener{ String msg=""; Checkbox win98, linux, solaris; CheckboxGroup cbg; public void init (){ cbg=new CheckboxGroup(); win98=new Checkbox("Window98",cbg,true); linux=new Checkbox("Linux",cbg,false); solaris=new Checkbox("Solaris",cbg,false); add (win98); add (linux); add (solaris); win98.addItemListener(this); linux.addItemListener(this); solaris.addItemListener(this); } public void itemStateChanged(ItemEvent ie){ repaint(); } public void paint(Graphics g){ msg="Seleccion Actual : "; msg+=cbg.getSelectedCheckbox().getLabel(); g.drawString(msg,6,50); } }
61
7.4 Choices Un choice se utiliza para crear una lista pop-up de elementos para qu el usuario pueda elegir uno de ellos. Por tanto un control choice es un men del cual se puede escoger solo una opcin. Para usar un choice hay que declarar un objeto de tipo Choice y luego usar el mtodo add para agregar la lista de opciones. Choice solo define un constructor por defecto que crea una lista vacia. Para aadir una seleccion a la lista, hay que llamar a addItem() o a add() que tienen los siguientes formatos: void addItem(String nombre) void add(String nombre) donde nombre corresponde al nombre del elemento que se aade. Los elementos se aaden a la lista en el orden en que se llama a add() o a addItem(). Para saber que opcin fue seleccionada se usa el mtodo getSelectedIndex() o al mtodo getSelectedItem(). El metodo getSelectedItem() devuelve un string que contiene el nombre del elemento, y getSelectedIndex() devuelve la posicin del elemento considerando que el primer elemento esta en la posicin 0. Por defecto se selecciona el primer elemento que se aade a la lista. Para obtener el numero de elementos que hay en la lista, hay que llamar al mtodo getItemcount(). Se puede establecer el elemento que tiene que estar seleccionado utilizando el mtodo select() tanto con un entero que indique la posicin del elemento (empezando a contar desde cero) como con un string que tenga el nombre que aparece en la lista estos mtodos son: int getItemCount(), void select(int index), void select (String nombre) Dada una posicin se puede obtener el nombre del elemento que esta en esa posicin llamando al mtodo getItem(), que tiene el siguiente formato: String getItem(int index) Gestin de listas Choice Cada vez que se hace una seleccin, se genera un evento que se enva a todo auditor que se haya registrado para recibir la informacin de los eventos que ese componente. Cada auditor debe implementar la interfaz ItemListener, que define el mtodo itemStateChanged(). Se pasa como argumento a este metodo un objeto de la clase ItemEvent. Ejemplo:
import java.awt.*; import java.awt.event.*; import java.applet.*;
62
public class Choice1Applet extends Applet implements ItemListener { Choice ch_deportes; String msg=""; public void init (){ ch_deportes=new Choice(); ch_deportes.addItem ("Ftbol"); ch_deportes.addItem ("Besbol"); ch_deportes.addItem ("Hockey"); ch_deportes.addItem ("Natacion"); add (ch_deportes); //se registran para recibir eventos de elemento ch_deportes.addItemListener(this); } public void itemStateChanged(ItemEvent ie){ repaint(); } public void paint(Graphics g){ msg="Deporte seleccionado : "; msg+=ch_deportes.getSelectedItem(); g.drawString(msg,6,100); } }
7.5 TextField
Un campo de texto es un componente que permite capturar y editar una lnea de texto. Para usar un campo de texto hay que crear un objeto de tipo TextField que es una subclase de TextComponent indicando el nmero de caracteres de ancho y opcionalmente un texto inicial, proporciona los siguientes constructores: TextField(), TextField(int numc), TextField(String str), TextField(String str, int numc) Donde: numc determina el nmero de caracteres del campo de texto. Para establecer un determinado texto se llama al mtodo setText() y para obtener un string del campo de texto se utiliza getText(). Se puede seleccionar una parte del texto con select(), y para obtener el texto completo seleccionado se llama s getSelectText().
63
Forma general: String getSelectText(), void select(int inicio, int fin) El usuario tambin puede modificar el contenido del campo de texto llamando a setEditable(). Tambin puede determinar si es editable o no llamando a isEditable(). Forma general: Booleam isEditable(), void setEditable(boolean valor) Puede que haya ocasiones en que interese que no se vea el texto que introduce, esto se hace llamando a setEdhoChar(). Se puede chequear un campo de texto para ver si esta en este modo con el metodo echoCharIsSet() y ver que caracter se va a ver llamando al metodo getEchoChar().
Ejemplo: import java.awt.*; import java.applet.*; public class TextFieldApplet extends Applet { TextField tf1 = new TextField (10); TextField tf2 = new TextField ("Nombre de la ciudad",30); TextField tf3 = new TextField (15); public void init (){ add (tf1); add (tf2); tf3.setEchoChar ('*'); add (tf3); }}
Gestin de un TextField Como los campos de texto gestionan sus propias funciones de edicion, generalmente el programa no tendra que ocuparse de los eventos de teclas individuales que ocurran dentro de un campo de texto. Pero puede que se quiera responder cuado el usuario pulse la tecla ENTER; y si esto ocurre, se genera un evento de accion. Ejemplo:
import java.awt.*; import java.awt.event.*; import java.applet.*; public class TextField1Applet extends Applet implements ActionListener { TextField tf1,tf2,tf3; public void init(){ Label lbl1=new Label("Departamento : ",Label.RIGHT); Label lbl2=new Label("Ciudad :",Label.RIGHT); Label lbl3=new Label("Codigo : ",Label.RIGHT); tf1= new TextField (10); tf2 = new TextField ("Nombre de la ciudad",30); tf3 = new TextField (15); tf3.setEchoChar ('*'); add (lbl1); add (tf1); add (lbl2); add (tf2); add (lbl3); add (tf3);
64
// se registran los eventos tf1.addActionListener(this); tf2.addActionListener(this); tf3.addActionListener(this); } public void actionPerformed(ActionEvent ae){ repaint(); } public void paint(Graphics g){ g.drawString ("Departamento :"+tf1.getText(),6,60); g.drawString ("Ciudad :"+tf2.getText(),6,80); g.drawString ("Texto seleccionado de Ciudad :"+tf2.getSelectedText(),6,100); g.drawString ("Codigo :"+tf3.getText(),6,120); } }
7.6 List. La clase List proporciona una lista de seleccin compacta, con desplazamiento, que permite realizar selecciones mltiples. A diferencia de Choice que solo muestra un nico objeto que se puede seleccionar en el men, se puede construir un objeto List que muestre cualquier numero de opciones en una ventana. Tambin se puede configurar de manera que puedan realizar selecciones mltiples. Constructors: List(), List(int num), List (int num, boolean multiple) Mtodos: void add(String nombre), void add(String nombre, int index): para aadir una lista. En listas que solo permiten seleccionar un nico elemento se determina si este esta seleccionado con los mtodos getSelectedItem() o con getSelectedIndex(), el primero devuelve un string con el nombre del elemento y el segundo devuelve la posicin del elemento. En las listas que permiten seleccionar mas de un elemento se utilizan los mtodos getSelectedItems() o getSelectedIndexes(). String [] getSelectedItems() int[] getSelectedIndexes() Para obtener el numero de elementos que hay en la lista hay que llamar a getItemCount(), que devuelve un array con las posiciones seleccionados actualmente. Se puede establecer el elemento que tiene que estar seleccionado con el mtodo select(), pasndole un entero que indique la posicin del elemento. Gestin de Listas: Es necesario implementar la interfaz ActionListener. Cada vez que se de dobleclick en un elemento List se genera un evento de la clase ActionEvent. Puede utilizarse getActionCommand(), para guardar el nombre del elemento recin seleccionado. Cada vez que se selecciona un evento con un solo click, se genera un objeto de la clase ItemEvent. Se puede utilizar su mtodo getStateChange() para determinar si una seleccin o deseleccion ha desencadenado ese evento. Si se utiliza getItemSelectable() devuelve una referencia al objeto que ha desencadenado ese evento.
65
Ejemplo:
//con doble click import java.awt.*; import java.awt.event.*; import java.applet.*; public class ListaApplet extends Applet implements ActionListener { List lista; final Color[] colores={Color.red, Color.green, Color.blue}; int indice; public void init() { lista=new List(3,false); //se aaden los elementos de la lista lista.add("Rojo"); lista.add("Verde"); lista.add("Azul"); lista.select(0); //se aaden la listas al contenedor add(lista); //registrando eventos lista.addActionListener(this); } public void actionPerformed(ActionEvent ae){ indice=lista.getSelectedIndex(); repaint(); } public void paint(Graphics g){ g.setColor(colores[indice]); g.fillRect(2, 2, 100, 50); } }
TextArea es una subclase de TextComponent. Por tanto, soporta los metodos getText(), setText(), getSelectedText(), select(), isEditable() y setEditable(). TextArea aade los siguientes metodos: void append(String str), void insert(String str, int index), void replaceRange(String str, int inicio, int fin). El metodo append() aade el string especificado en str al final del texto actual. El metodo insert() inserta el string que se pasa en str en el punto especificado en index. Para reemplazar texto hay que llamar a replaceRange(), que reemplaza los caracteres desde inicio a fin con el texto que esta en str.
Ejemplo: import java.awt.*; import java.applet.*; import java.awt.event.*; // Presentamos dos areas de texto y un boton para imprimir el contenido public class AreaTexto extends Applet implements ActionListener{ String texto=""; TextArea t1,t2; public void init() { Button boton = new Button( "Aceptar" ); // Creamos las dos areas de texto, una con el constructor de // defecto del Componente y otra con un texto por defecto y // limitada su longitud a 40 columnas t1 = new TextArea(); t2 = new TextArea( "Aqui no se puede editar",5,40 ); // Hacemos que no sea editable t2.setEditable( false ); add( t1 ); add( t2 ); add( boton ); boton.addActionListener(this); } // Solo controlaremos el evento generado por el boton public void actionPerformed(ActionEvent ae ) { // Si pulsamos el boton, imprimimos en la consola el contenido // del campo de texto que se haya escrito String str=ae.getActionCommand(); if( str.equals("Aceptar") ) { texto = t1.getText(); } repaint(); } public void paint(Graphics g){ g.setColor(Color.red); g.drawString(texto,20,300); } }
67
public class testPanel extends Applet { Panel pnl_1 = new Panel (); Panel pnl_2 = new Panel (); Panel pnl_3 = new Panel (); Panel pnl_4 = new Panel (); Panel pnl_5 = new Panel (); Checkbox cb_1 = new Checkbox ("Opcion uno"); Checkbox cb_2 = new Checkbox ("Opcion dos"); Checkbox cb_a = new Checkbox ("Opcion A"); Checkbox cb_b = new Checkbox ("Opcion B"); Checkbox cb_c = new Checkbox ("Opcion C"); Label lbl_1 = new Label ("Soy etiqueta"); Choice ch_1 = new Choice (); Choice ch_2 = new Choice (); Button btn_1 = new Button ("Soy un boton"); Button btn_2 = new Button ("Soy otro boton"); public void init () { setBackground (Color.lightGray); setLayout (new GridLayout (3, 1)); add (pnl_1); add (pnl_2); add (pnl_5); // panel 1 pnl_1.setLayout (new FlowLayout (FlowLayout.LEFT)); pnl_1.add (cb_1); pnl_1.add (cb_2); // panel 2 pnl_2.setLayout (new BorderLayout ()); // panel 3 pnl_3.setLayout (new GridLayout (3, 1)); pnl_3.add (cb_a); pnl_3.add (cb_b); pnl_3.add (cb_c); pnl_2.add ("West", pnl_3); // panel 4 pnl_4.setLayout (new GridLayout (1, 3, 30, 10)); pnl_4.add (lbl_1); ch_1.add ("A"); ch_1.add ("B"); ch_1.add ("C"); ch_2.add ("1"); ch_2.add ("2"); pnl_4.add (ch_1); pnl_4.add (ch_2); pnl_2.add ("East", pnl_4); // panel 5 pnl_5.setLayout (new FlowLayout (FlowLayout.CENTER, 10, 30)); pnl_5.add (btn_1); pnl_5.add (btn_2);
69
} }
70
import java.applet.*; import java.awt.*; import java.awt.event.*; public class testCanvas extends Applet { miCanvas mc; Panel pnl_1; Label lbl_1 = new Label ("La lampara de Aladino"); Label lbl_2 = new Label ("Color de la mesa"); Choice color_mesa = new Choice (); Label lbl_3 = new Label ("Color de la lampara"); Choice color_lampara = new Choice (); public void init () { setLayout (new BorderLayout ()); lbl_1.setFont (new Font ("TimesRoman", Font.BOLD, 18)); add ("North", lbl_1); add ("Center", mc = new miCanvas ()); add ("East", pnl_1 = new Panel ()); pnl_1.setLayout (new GridLayout (4, 1)); pnl_1.add (lbl_2); color_mesa.add ("Negro"); color_mesa.add ("Rojo"); color_mesa.add ("Azul"); color_mesa.add ("Verde"); pnl_1.add (color_mesa); pnl_1.add (lbl_3); color_lampara.add ("Negro"); color_lampara.add ("Rojo"); color_lampara.add ("Azul"); color_lampara.add ("Verde"); pnl_1.add (color_lampara); // oidores color_mesa.addItemListener ( new java.awt.event.ItemListener () { public void itemStateChanged (ItemEvent e) { mc.cambiaColorMesa (color_mesa.getSelectedIndex ()); } }); color_lampara.addItemListener ( new java.awt.event.ItemListener () { public void itemStateChanged (ItemEvent e) { mc.cambiaColorLampara (color_lampara.getSelectedIndex ()); } }); } }
71
class miCanvas extends Canvas { int color_mesa = 0; int color_lampara = 0; public void paint (Graphics g) { switch (color_mesa) { case 0 : g.setColor (Color.black); break; case 1 : g.setColor (Color.red); break; case 2 : g.setColor (Color.blue); break; case 3 : g.setColor (Color.green); break; } g.fillRect (0, 250, 290, 290); // dibuja la mesa switch (color_lampara) { case 0 : g.setColor (Color.black); break; case 1 : g.setColor (Color.red); break; case 2 : g.setColor (Color.blue); break; case 3 : g.setColor (Color.green); break; } g.drawLine (125, 250, 125, 160); g.drawLine (175, 250, 175, 160); // dibuja la base g.drawArc (85, 157, 130, 50, -65, 312); // dibuja la cubierta g.drawArc (85, 87, 130, 50, 62, 58); g.drawLine (85, 177, 119, 89); g.drawLine (215, 177, 181, 89); g.fillArc (78, 120, 40, 40, 63, -174); // dibuja las motas g.fillOval (120, 96, 40, 40); g.fillArc (173, 100, 40, 40, 110, 180); } public void cambiaColorMesa (int c) { color_mesa = c; repaint (); } public void cambiaColorLampara (int c) { color_lampara = c; repaint (); } }
72
Para utiliza un rea de texto hay que crear un objeto de tipo TextArea pasando como argumentos al constructor en forma opcional un string inicial, un tamao preferido de desplegado en renglones y columnas y un indicador para que despliegue o no barras de scroll de inicio. TextArea ta1 = new TextArea (); // el tamao se deja al diseo TextArea ta2 = new TextArea (10, 60); // 10 renglones y 60 columnas TextArea ta3 = new TextArea ("hola mundo", 10, 60); // texto inicial TextArea ta4 = new TextArea ("hola", 10, 60, TextArea.SCROLLBARS_NONE); A pesar de que el texto est en varias lneas, el rea de texto lo trata como un string, es decir, como si fuera una sola cadena de caracteres. Cuando desde un programa se altera el texto de un rea de texto es responsabilidad del programador insertar saltos de lnea ('\n') dentro del string para que el texto se visualice por renglones. El texto capturado se puede recuperar llamando al mtodo getText. String s = ta1.getText (); Se puede asignar texto desde el programa con el mtodo setText. ta2.setText ("Esto es una lnea\nEsto es otra lnea"); El siguiente ejemplo presenta un applet con dos reas de texto. Despus de escribir un texto cualquiera en la primera rea de texto, al oprimir el botn de Cuenta, la segunda rea de texto despliega el nmero de lneas y de caracteres que se escribieron. La figura muestra la salida del programa.
73
import java.awt.*; import java.awt.event.*; import java.applet.*; public class ta extends Applet { GridBagLayout gbl = new GridBagLayout (); GridBagConstraints gbc = new GridBagConstraints (); TextArea ta1 = new TextArea ("Escribe algo"); TextArea ta2 = new TextArea ("Soy una textarea", 5, 25); Button btn = new Button ("Cuenta"); public void init () { setLayout (gbl); // text area de arriba gbc.gridx = 0; gbc.gridy = 0; gbc.gridwidth = 1; gbc.gridheight = 1; gbc.weightx = 100; gbc.weighty = 33; gbc.fill = GridBagConstraints.NONE; gbc.anchor = GridBagConstraints.CENTER; gbl.setConstraints (ta1, gbc); add (ta1); // boton gbc.gridx = 0; gbc.gridy = 1; gbc.gridwidth = 1; gbc.gridheight = 1; gbc.weightx = 0; gbc.weighty = 33; gbc.fill = GridBagConstraints.NONE; gbc.anchor = GridBagConstraints.CENTER; gbl.setConstraints (btn, gbc); add (btn); // text area de abajo gbc.gridx = 0; gbc.gridy = 2; gbc.gridwidth = 1; gbc.gridheight = 1; gbc.weightx = 0; gbc.weighty = 33; gbc.fill = GridBagConstraints.NONE; gbc.anchor = GridBagConstraints.CENTER; gbl.setConstraints (ta2, gbc); add (ta2); btn.addActionListener ( new java.awt.event.ActionListener () { public void actionPerformed (ActionEvent e) { String s = ta1.getText (); int k = 0; for (int i = 0; i < s.length (); i++) if (s.charAt (i) == '\n') k++; ta2.setText ("Escribiste\n" + k + " renglones\n" + s.length () + " caracteres");
74
} }); } }
7.8.4 Listas
Una lista es similar al choice que permite seleccionar de entre varias opciones de una lista con dos diferencias: su presentacin es en forma de lista y permite seleccionar ms de una opcin. Para usar una lista hay que crear un objeto de tipo List pasando como argumentos opcionales el nmero de opciones que estarn visibles y un indicador si la liste permite o no opciones mltiples. List lst1 = new List (); // permite escoger una opcin List lst2 = new List (5); // cinco opciones visibles a la vez List lst3 = new List (5, true); // permite opciones mltiples Las opciones se agregan con el mtodo add. lst2.add ("opcin uno"); lst2.add ("opcin dos"); En las listas que permiten la seleccin de nicamente un elemento el mtodo getSelectedIndex devuelve la posicin del elemento seleccionado. int opcin = lst1.getSelectedIndex (); En las listas que permiten mltiples selecciones el mtodo getSelectedIndexes devuelve un arreglo de enteros con las posiciones de los elementos seleccionados. int opciones[] = lst3.getSelectedIndexes (); El listado ejemplo presenta un applet con un choice, una lista que permite seleccionar un solo elemento y una lista que permite seleccin mltiple. Al oprimir el botn, en la parte inferior se despliegan las opciones que fueron seleccionadas en el choice y en las listas. La figura muestra la salida del programa.
75
import java.awt.*; import java.awt.event.*; import java.applet.*; public class lst extends Applet { GridBagLayout gbl = new GridBagLayout (); GridBagConstraints gbc = new GridBagConstraints (); Choice chs_1 = new Choice (); List lst_1 = new List (); // una sola seleccion List lst_2 = new List (4, true); // seleccion multiple Button btn_revisar = new Button ("Revisar"); Label lbl_1 = new Label ("En el choice la opcion es: "); Label lbl_2 = new Label ("En la lista 1 la opcion es: "); Label lbl_3 = new Label ("En la lista 2 las opciones son: "); public void init () { setLayout (gbl); // choice chs_1.add ("opcion 1"); chs_1.add ("opcion 2"); chs_1.add ("opcion 3"); gbc.gridx = 0; gbc.gridy = 0; gbc.gridwidth = 1; gbc.gridheight = 1; gbc.weightx = 33; gbc.weighty = 40; gbc.fill = GridBagConstraints.NONE; gbc.anchor = GridBagConstraints.CENTER; gbl.setConstraints (chs_1, gbc); add (chs_1); // lista 1 lst_1.add ("opcion 1"); lst_1.add ("opcion 2"); lst_1.add ("opcion 3"); lst_1.add ("opcion 4"); lst_1.add ("opcion 5"); gbc.gridx = 1; gbc.gridy = 0; gbc.gridwidth = 1; gbc.gridheight = 1; gbc.weightx = 33; gbc.weighty = 0; gbc.fill = GridBagConstraints.NONE; gbc.anchor = GridBagConstraints.CENTER; gbl.setConstraints (lst_1, gbc); add (lst_1); // lista 2 gbc.gridx = 2; gbc.gridy = 0; gbc.gridwidth = 1; gbc.gridheight = 1; gbc.weightx = 33; gbc.weighty = 0; gbc.fill = GridBagConstraints.NONE; gbc.anchor = GridBagConstraints.CENTER; gbl.setConstraints (lst_2, gbc); lst_2.add ("opcion 1"); lst_2.add ("opcion 2"); lst_2.add ("opcion 3"); lst_2.add ("opcion 4"); add (lst_2); // boton de revisar gbc.gridx = 0; gbc.gridy = 1;
76
gbc.gridwidth = 3; gbc.gridheight = 1; gbc.weightx = 0; gbc.weighty = 20; gbc.fill = GridBagConstraints.NONE; gbc.anchor = GridBagConstraints.CENTER; gbl.setConstraints (btn_revisar, gbc); add (btn_revisar); // etiqueta 1 gbc.gridx = 0; gbc.gridy = 2; gbc.gridwidth = 1; gbc.gridheight = 1; gbc.weightx = 0; gbc.weighty = 40; gbc.fill = GridBagConstraints.NONE; gbc.anchor = GridBagConstraints.CENTER; gbl.setConstraints (lbl_1, gbc); add (lbl_1); // etiqueta 2 gbc.gridx = 1; gbc.gridy = 2; gbc.gridwidth = 1; gbc.gridheight = 1; gbc.weightx = 0; gbc.weighty = 0; gbc.fill = GridBagConstraints.NONE; gbc.anchor = GridBagConstraints.CENTER; gbl.setConstraints (lbl_2, gbc); add (lbl_2); // etiqueta 3 gbc.gridx = 2; gbc.gridy = 2; gbc.gridwidth = 1; gbc.gridheight = 1; gbc.weightx = 0; gbc.weighty = 0; gbc.fill = GridBagConstraints.NONE; gbc.anchor = GridBagConstraints.CENTER; gbl.setConstraints (lbl_3, gbc); add (lbl_3); // oidor btn_revisar.addActionListener ( new java.awt.event.ActionListener () { public void actionPerformed (ActionEvent e) { lbl_1.setText ("En el choice la opcion es: " + chs_1.getSelectedIndex ()); lbl_2.setText ("En la lista 1 la opcion es: " + lst_1.getSelectedIndex ()); int idx[] = lst_2.getSelectedIndexes (); StringBuffer mssg = new StringBuffer ("En la lista 2 las opciones son: "); for (int i = 0; i < idx.length; i++) mssg.append (idx[i] + " "); lbl_3.setText (mssg.toString ()); } }); } }
77
7.8.5 Frames
Un frame es una ventana que, dependiendo del sistema operativo, cuenta con un ttulo, un men de barra, indicadores para minimizar, maximizar o cerrar, y otras caractersticas tpicas de las ventanas. Un frame es un componente contenedor y como tal puede tener su propio diseo y contener otros componentes de AWT. Para usar un frame hay que crear un objeto de tipo Frame pasando como parmetro el ttulo de la ventana. Frame frm = new Frame ("titulo de la ventana"); El frame puede tener su propio diseo y componentes. frm.setLayout (new GridLayout (4, 1)); frm.add (new Button ("soy un botn")); frm.add (new Label ("soy una etiqueta")); Se le puede asignar un tamao en pixeles usando el mtodo setSize o indicarle las coordenadas de su esquina superior izquierda con setLocation. frm.setSize (400, 300); // 400 pixeles de ancho y 300 pixeles de alto frm.setLocation (200, 100); // 200 en x y 100 en y Cuando se crea un nuevo frame, ste es invisible. El mtodo setVisible hace que el frame sea visible o de vuelta invisible mediante un argumento booleano que puede valer true (la ventana se hace visible) o false (el frame se hace invisible). frm.setVisible (true); // hace visible el frame El mtodo dispose elimina el frame y libera la memoria. Se invoca cuando el frame ya no va a ser utilizado. frm.dispose (); Por default el indicador de cerrar ventana, que en Windows tiene una marca de cruz y est en la parte superior derecha del frame, esta inactivo. Es necesario crear un oidor de la clase WindowAdapter y registrarlo usando el mtodo addWindowListener para indicarle al frame que se destruya al oprimir ese indicador. frm.addWindowListener ( new java.awt.event.WindowAdapter () { public void windowClosing (WindowEvent e) { frm.dispose (); } }); El siguiente ejemplo presenta un applet que crea un frame, le agrega una etiqueta, un campo de texto y un botn y luego lo hace visible. La figura muestra la salida de este programa. import java.awt.*; import java.awt.event.*; import java.applet.*; public class frm extends Applet {
78
Frame frm_1 = new Frame ("Mi ventanita"); Panel pnl_1 = new Panel (); GridBagLayout gbl = new GridBagLayout (); GridBagConstraints gbc = new GridBagConstraints (); Label lbl_nombre = new Label ("Nombre"); TextField tf_nombre = new TextField (20); Button btn_ok = new Button ("OK"); Label lbl_applet = new Label ("aqui es el applet"); public void init () { // layout del applet setBackground (Color.lightGray); setLayout (new FlowLayout (FlowLayout.LEFT)); add (lbl_applet); // layout de la ventana frm_1.setLayout (gbl); // etiqueta de nombre gbc.gridx = 0; gbc.gridy = 0; gbc.gridwidth = 1; gbc.gridheight = 1; gbc.weightx = 30; gbc.weighty = 80; gbc.fill = GridBagConstraints.NONE; gbc.anchor = GridBagConstraints.WEST; gbl.setConstraints (lbl_nombre, gbc); frm_1.add (lbl_nombre); // textfield de nombre gbc.gridx = 1; gbc.gridy = 0; gbc.gridwidth = 1; gbc.gridheight = 1; gbc.weightx = 70; gbc.weighty = 0; gbc.fill = GridBagConstraints.NONE; gbc.anchor = GridBagConstraints.WEST; gbl.setConstraints (tf_nombre, gbc); frm_1.add (tf_nombre); // boton de ok gbc.gridx = 0; gbc.gridy = 1; gbc.gridwidth = 2; gbc.gridheight = 1; gbc.weightx = 0; gbc.weighty = 20; gbc.fill = GridBagConstraints.NONE; gbc.anchor = GridBagConstraints.CENTER; gbl.setConstraints (btn_ok, gbc); frm_1.add (btn_ok); frm_1.setLocation (100, 150); frm_1.setSize (400, 300); frm_1.setVisible (true); // oidor frm_1.addWindowListener ( new java.awt.event.WindowAdapter () {
79
7.8.6 Dilogos
Un dilogo es semejante a un frame en el sentido que es una ventana que puede tener su propio diseo y contener a otros componentes. Las diferencias son que los dilogos necesitan tener un frame ligado a ellos, no tienen indicadores de maximizar ni de minimizar y pueden operar en forma modal. Un dilogo modal impide que sean accesadas otras ventanas hasta que el dilogo sea cerrado. Por estos motivos y por lo general, los dilogos se utilizan en forma modal y para avisar de errores en el programa o pedir datos al usuario. Para usar un dilogo hay que crear un objeto de tipo Dialog pasando como argumento obligatorio el frame al cual estn ligados y opcionalmente un argumento string con el ttulo del dilogo y una variable booleana para indicar si el dilogo es modal o no modal. Dialog dlg1 = new Dialog (frame); // el dilogo es no modal por default Dialog dlg2 = new Dialog (frame, true); // el dilogo es modal Dialog dlg3 = new Dialog (frame, "titulo", true); // tiene ttulo y es modal Los mtodos setLayout, add, setSize, setLocation, setVisible, dispose y addWindowListener
80
tienen la misma funcionalidad que en los frames. Para que un applet pueda usar un dilogo necesita hacer referencia a la ventana del navegador que lo contiene. Una forma de conseguirlo es obtener los componentes padres del applet, usando el mtodo getParent, hasta encontrar un objeto de tipo Frame y pasar como argumento ese objeto al constructor del dilogo. Object tmp = getParent (); while (! (tmp instanceof Frame)) tmp = ((Component) tmp).getParent (); dlg1 = new Dialog ((Frame) tmp, "ttulo", true); El ejemplo presenta un applet que abre un dilogo modal y por lo tanto se bloquea. Al cerrar el dilogo el programa puede continuar y abre un frame. La figura muestra la salida del programa antes de cerrar el dilogo.
import java.awt.*; import java.awt.event.*; import java.applet.*; public class dlgm extends Applet { GridBagLayout gbl = new GridBagLayout (); GridBagConstraints gbc = new GridBagConstraints (); Label lbl_error = new Label ("El frame no se abrira hasta cerrar este dialogo"); Label lbl_hola = new Label ("Hola"); Button btn_ok = new Button ("Cerrar"); Frame frm_1 = new Frame ("Ventanita"); Dialog dlg_1; public void init () { // Dialog Object tmp = getParent (); while (! (tmp instanceof Frame)) tmp = ((Component) tmp).getParent (); dlg_1 = new Dialog ((Frame) tmp, "Dialogo modal", true);
81
dlg_1.setLayout (gbl); // Etiqueta de error gbc.gridx = 0; gbc.gridy = 0; gbc.gridwidth = 1; gbc.gridheight = 1; gbc.weightx = 100; gbc.weighty = 90; gbc.fill = GridBagConstraints.NONE; gbc.anchor = GridBagConstraints.CENTER; gbl.setConstraints (lbl_error, gbc); dlg_1.add (lbl_error); // Boton de ok gbc.gridx = 0; gbc.gridy = 1; gbc.gridwidth = 1; gbc.gridheight = 1; gbc.weightx = 0; gbc.weighty = 10; gbc.fill = GridBagConstraints.NONE; gbc.anchor = GridBagConstraints.CENTER; gbl.setConstraints (btn_ok, gbc); dlg_1.add (btn_ok); // Agrega los oidores btn_ok.addActionListener ( new java.awt.event.ActionListener () { public void actionPerformed (ActionEvent e) { dlg_1.dispose (); } }); dlg_1.pack (); dlg_1.setLocation (90, 120); dlg_1.setResizable (false); dlg_1.setVisible (true); // Frame frm_1.setForeground (Color.green); frm_1.setLayout (new BorderLayout ()); lbl_hola.setFont (new Font ("Times Roman", Font.PLAIN, 36)); frm_1.add ("Center", lbl_hola); frm_1.setLocation (100, 150); frm_1.setSize (400, 300); frm_1.setVisible (true); frm_1.addWindowListener ( new java.awt.event.WindowAdapter () { public void windowClosing (WindowEvent e) { frm_1.dispose (); } }); }}
82
Introduccin
A lo largo de este captulo, vamos a continuar con la creacin de interfaces de usuario en Java, vamos a tratar dos caractersticas del interfaz de usuario en los programas Java, los gestores de diseo y los eventos. Lo que vamos a ver en este captulo es aplicable tanto a componentes AWT como a componentes Swing (que los veremos en el siguiente captulo), aunque muchas de las clases que implementan los gestores de diseo y el tratamiento de eventos se encuentran dentro del paquete JAVA.AWT y del subpaquete java.awt.events. Los eventos y gestores de diseo especficos de los componentes Swing los veremos en los prximos captulos en los que se abordar Swing, en ste veremos lo comn a los dos tipos de componentes.
El operador new devuelve un gestor de diseo del tipo FlowLayout, ste objeto es recogido por el mtodo setLayout() de la clase Container, quien a partir de ahora ya sabe con qu gestor deseamos trabajar.
83
La clase Container dispone del mtodo add(), que permite aadir componentes. Como ya hemos comentado, a un objeto de la clase Container le podemos aadir bien controles o bien otros gestores de diseo. Para ello algo tan simple como lo que aparece aqu:
this.add( new Button( "Uno" ) );
Los layouts por defecto para los contenedores de swing son los mismos que para sus homnimos de AWT. Si queremos cambiar el layout manager de un contenedor en un momento dado, tan slo tendremos que llamar al mtodo: contenedor.setLayout(LayoutManager layout); Si en cualquier momento decidimos que estamos hartos de un layout manager y queremos encargarnos nosotros mismos de la gestin de componentes tan slo tendremos que escribir: contenedor.setLayout(null); Los componentes como hemos dicho son objetos que forman parte de nuestro interfaz grfico. Cada componente tiene asignada una coordenada horizontal, una coordenada vertical, una longitud y una anchura determinadas. Estos valores sern los que se utilizarn para renderizar el componente en pantalla. La clase java.awt.Component nos ofrece una serie de mtodos para poder modificar los valores de los atributos anteriores: public void setSize(Dimension size); public void setBounds(Rectangle r); Hay pequeas variaciones de estos mtodos pero su funcin es la misma. Por su parte la clase javax.swing.JComponent tiene mtodos diferentes: public void setPreferredSize(Dimension size); public void setMinimumSize(Dimenstion size); public void setMaximumSize(Dimension size); Es muy frustrante, cuando conoces el funcionamiento de los layouts, ver que aunque utilizas estos mtodos, el intrprete Java no les hace ningn caso y los objetos muestran un tamao que nosotros no queramos. Veamos un ejemplo: .... JButton button = new JButton(); button.setMaximumSize(new Dimension(80,25)); JFrame frame = new JFrame(); frame.getContentPane().add(button); frame.setSize(400,300); frame.setVisible(true); ....
84
Aunque no lo he dicho, todos los layout managers implementan la interfaz LayoutManager directa o indirectamente (a travs de LayoutManager). Si creamos un pequeo programa que contenga las lneas de cdigo anteriores y lo ejecutamos, inicialmente podramos pensar que se ver un botn de 80 puntos de largo por 25 de ancho. Sin embargo, tras ejecutar el programa veremos como nuestro botn es considerablemente ms grande. Qu es lo que ha pasado? Por qu no nos ha hecho caso? La realidad es que cuando utilizamos layout managers no sirve de nada intentar establecer a la fuerza el tamao de los componentes ya que ser el layout el que siempre tenga la ltima palabra1. En el ejemplo anterior, si que se establece el tamao mximo de 80x25 para el botn, lo que pasa es que despus de aadirlo al frame es el layout manager de ste el que toma el control del interfaz y el que pasa a decidir el nuevo tamao del botn, por lo que nuestra llamada a setMaximumSize() no hace ningn efecto. El ltimo aspecto importante a tener en cuenta es el significado del atributo preferredSize. Este atributo indica el tamao que a un componente le gustara tener. Este tamao suele ser dependiente de la plataforma y el layout manager intentar respetarlo siempre que sea posible y siempre que lo permitan sus polticas de layout.
A continuacin vamos a comentar los distintos gestores de diseo que podemos encontrar dentro del paquete JAVA.AWT. En los distintos ejemplos se han utilizado botones para poner de manifiesto las diferentes distribuciones que los distintos gestores de diseo hacen del espacio ya que el gestor es invisible. Los cinco gestores de diseo son los que se comentan a continuacin.
8.1.1 FlowLayout
El gestor de diseo FlowLayout, adems de ser el gestor de diseo por defecto de los applets, es tambin el ms simple de todos. Este gestor, distribuye los controles de izquierda a derecha llenando el ancho del contenedor de arriba a abajo. Es decir, cuando acaba con una fila de controles, sigue con la siguiente, actuando con los controles como lo hace un editor de textos con las palabras. Es decir, los controles fluyen (Flow). Ejemplo:
import java.awt.*; import javax.swing.*; import java.awt.event.*; class EjemploFlowLayout extends JFrame{ public EjemploFlowLayout( ) { Container c = getContentPane ( ) ; JButton b1 = new JButton ( "A" ) ; JButton b2 = new JButton ( "B" ) ; JButton b3 = new JButton ( "Botn largo " ) ; JButton b4 = new JButton ( "D" ) ; JButton b5 = new JButton ( "E" ) ; c.setLayout (new FlowLayout ( ) ) ; c.add ( b1 ) ; c.add ( b2 ) ; c.add ( b3 ) ; c.add ( b4 ) ; c.add ( b5 ) ; }
85
public static void main (String [ ] args ) { EjemploFlowLayout f = new EjemploFlowLayout ( ) ; f.setSize (200 ,200) ; f.setVisible(true); f.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0);} }); } }
Como se puede observar, se utiliza primero el mtodo setLayout() pasndole por parmetro una instancia del gestor de diseo que se va a utilizar en nuestra ventana. En el bucle se van aadiendo botones a la ventana, en este caso se utiliza el mtodo add() de la clase Container, en la versin que ya conocemos, es decir pasndole por parmetro la instancia del componente AWT que queremos aadir.
8.1.2 BorderLayout
BorderLayout es el layout manager por defecto para frames por lo que al igual que FlowLayout su aprendizaje es indispensable. BorderLayout divide el espacio de un contenedor en cinco regiones diferentes. Estas regiones son: North, South, East, West y Center, y se corresponden con su situacin dentro del contenedor en el que se encuentran. Veamos ms claramente lo que quiere decir esto con un ejemplo sencillo: import javax.swing.*; import java.awt.*; public class TestBorderLayout extends JFrame { public static void main(String[] args) { TestBorderLayout frame = new TestBorderLayout(); Container panel = frame.getContentPane(); JButton norte = new JButton("Norte"); JButton sur= new JButton("Sur"); JButton este= new JButton("Este"); JButton oeste = new JButton("Oeste"); JButton centro = new JButton("Centro"); panel.add(norte, BorderLayout.NORTH); panel.add(sur, BorderLayout.SOUTH); panel.add(este, BorderLayout.EAST); panel.add(oeste, BorderLayout.WEST); panel.add(centro, BorderLayout.CENTER); frame.setSize(400,300); frame.setTitle("Prueba de BorderLayoutLayout"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } }
86
Como podemos ver en el cdigo f uente anterior, al aadi r un elemento al BorderLayout tenemos que especificar la regin en la cual lo queremos aadir. Si no especificamos ninguna regin por defecto el componente se inserta en el centro del contenedor. Aunque ya las hemos visto en el cdigo las resumiremos de nuevo:
BorderLayout.NORTH BorderLayout.SOUTH BorderLayout.EAST BorderLayout.WEST BorderLayout.CENTER // Por defecto Existe una manera alternativa de especificar donde queremos colocar los componentes y es pasando como parmetros al mtodo add en lugar de los atributos anteriores las cadenas siguientes: North South East West Center Este mtodo no est recomendado porque es demasiado propenso a errores, ya que si nos equivocamos en una simple letra el compilador no nos avisar de nuestro error y por lo tanto podremos llevarnos sorpresas al ejecutar el programa. Si insertamos un componente en una regin donde haba otro componente previamente, el que se encontraba en el contenedor desaparecer y el nuevo ocupar su lugar. Por lo tanto tenemos que tener cuidado con donde insertamos los componentes. Para finalizar con este layout manager vamos a hablar de como trata el tamao de los componentes. Cuando aadimos un componente en las posiciones norte o sur, BorderLayout respeta su alto mientras que la longitud del mismo se ajusta hasta ocupar todo el ancho del contenedor. Con los componentes de las posiciones este y oeste pasa lo contrario, Border-Layout respeta su longitud y ajusta su altura hasta que ocupe la totalidad de la altura del contenedor o hasta que se encuentre con los componentes del norte o del sur. Por ltimo el objeto que se situe en la zona central ocupar el resto de espacio disponible. BorderLayout, por su estructura, es muy til para muchas aplicaciones. Por ejemplo, sera bastante normal colocar una barra de herramientas en el panel norte de nuestra ventana, una barra de estado en el panel sur y quizs un rbol de navegacin en el panel izquierdo o derecho, dejando el panel central para el resto del interfaz.
87
8.1.3 CardLayout
CardLayout es un layout manager ligeramente diferente a todos los dems ya que tan slo muestra en un instante dado un nico componente. Un contenedor que tenga asignado un CardLayout podr tener cualquier nmero de componentes en su interior pero slo uno se ver en un instante dado. En este layout manager los componentes ocuparn todo el tamao disponible en el contenedor. Los componentes a medida que se insertan en el contenedor van formando una secuencia. Para seleccionar el componente que queremos mostrar en cada momento disponemos de varios mtodos: public void first(Container contenedor); public void last(Container contenedor); public void next(Container contenedor); public void previous(Container contenedor); public void show(Container container, String nombre); El mtodo ms comn para aadir un componente es: public void add(Component componente, String nombre); Este mtodo inserta un componente dentro de un contenedor y le asigna un nombre, este nombre lo podremos utilizar con el mtodo show para mostrar el componente directamente. Por ltimo, al aadir componentes tendremos que fijarnos en que el orden en el que los aadamos al contenedor ser el orden en el que sern recorridos por el layout manager. Veamos un ejemplo simple: import javax.swing.*; import java.awt.*; import java.awt.event.*; public class TestCardLayout extends JFrame { public static void main(String[] args) { TestCardLayout frame = new TestCardLayout(); Container container = frame.getContentPane(); JButton siguiente = new JButton(siguiente); container.add(siguiente, BorderLayout.NORTH); JLabel label1 = new JLabel(Componente 1); JLabel label2 = new JLabel(Componente 2); JLabel label3 = new JLabel(Componente 3); JLabel label4 = new JLabel(Componente 4); JPanel panelComponentes = new JPanel(); CardLayout layout = new CardLayout(); panelComponentes.setLayout(layout); panelComponentes.add(label1,1); panelComponentes.add(label2,2); panelComponentes.add(label3,3); panelComponentes.add(label4,4); container.add(panelComponentes, BorderLayout.CENTER); siguiente.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { layout.next(panelComponentes); } }); frame.setSize(400,300); frame.setTitle("Prueba de BorderLayoutLayout"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
88
frame.setVisible(true); } } El ejemplo es muy sencillo y muestra un contenedor con varias etiquetas. Con un botn podemos ir avanzando de etiqueta. Al pulsar el botn se muestra la etiqueta siguiente llamando al mtodo next() de CardLayout. Este layout manager es muy sencillo y muy til especialmente cuando tenemos un panel que variar su contenido en funcin de alguna parte de nuestro interfaz (una combo box por ejemplo). En lugar de eliminar el panel e insertar otro nuevo, o en lugar de eliminar los componentes e insertar otros nuevos, podemos utilizar un CardLayout que nos ahorra gran cantidad de trabajo.
Este tercer gestor permite manejar varias "fichas intercambiables", de forma tal que slo una est visible a la vez y ocupando todo el rea del contenedor. Es similar a los controles de pestaa o "tabs" de Windows. El problema que este gestor de diseo tiene es que la implementacin que en Java se ha hecho de l, no es completa, y las fichas (o tarjetas si se prefiere) no tienen la correspondiente pestaa asociada, por lo que no existe una forma preestablecida de interactuar con los controles para cambiar de unas fichas a otras; siendo necesario crear este mecanismo programticamente con los inconvenientes de prdida de tiempo y esfuerzo que esto supone. En el captulo siguiente veremos que los componentes Swing ofrecen una clase llamada JTabbedPane que representa un componente en el que se pueden elegir las distintas fichas (paneles) a travs de los paneles que presenta.
8.1.4 GridLayout
GridLayout divide el espacio de un contenedor en forma de tabla, es decir, en un conjunto de filas y columnas. Cada fila y cada columna tiene el mismo tamao y el rea del contenedor se distribuye equitativamente entre todas las celdas. De todo esto se deduce que GridLayout no respetar el tamao preferido de los componentes que insertemos en cada una de las celdas. El nmero de filas y columnas se especifica en el constructor. Si pasamos cero como el nmero de filas o columnas el layout manager ir creando las filas y columnas en funcin del nmero de componentes y del valor de la otra dimensin, es decir, si creamos un GridLayout con cero filas y tres columnas e insertamos cuatro componentes el GridLayout ser lo suficientemente inteligente como para saber que tiene que crear dos filas. Veamos un ejemplo simple de funcionamiento: import javax.swing.*; import java.awt.*; public class TestGridLayout extends JFrame { public static void main(String[] args) { TestGridLayout frame = new TestGridLayout(); Container container = frame.getContentPane(); int X = 3; int Y = 3; container.setLayout(new GridLayout(X,Y)); for (int i = 0; i < X; i++) {
89
for (int j = 0; j < Y; j++) { container.add(new JButton(i + x + j)); } } frame.setSize(400,300); frame.setTitle("Prueba de BorderLayoutLayout"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } }
En la figura anterior vemos el resultado de ejecutar el ejemplo. Como se puede ver todas las celdas tienen el mismo tamao y los botones ocupan la totalidad de la celda. GridLayout es un layout manager realmente muy simple y como tal su utilidad se encuentra bastante reducida. Suele ser til a la hora de crear partes del interfaz de usuario que necesiten representar una matriz de componentes o incluso para interfaces que tengan en si una forma matricial. Lo realmente interesante sera que algunas celdas pudiesen tener tamaos diferentes a las dems, o que una celda pudiese ocupar varias posiciones, o dejar celdas vacas, etc... Afortunadamente, esto es justamente lo que nos permite el prximo layout manager que veremos, GridBagLayout.
8.1.5 GridBagLayout
GridBagLayout es el layout manager ms poderoso y eficaz con mucha diferencia. Con GridBagLayout podemos imitar facilmente el comportamiento del resto de layout managers a parte de poder crear con el interfaces mucho ms complejas. Ventajas y desventajas GridBagLayout es el layout manager que ms pavor causa entre los programadores Java. Odiado por unos y alabado por otros, este layout manager proporciona una serie de ventajas sobre el resto: Permite la creacin de interfaces de usuario complejos. Con este layout manager tenemos control absoluto sobre las posiciones que ocuparn los objetos en el interfaz final.
90
Las interfaces construidas son ms ligeras. Cuando queremos crear un interfaz de usuario combinando el resto de layout managers vistos hasta el momento a menudo terminamos con un nmero grande de paneles anidados. Los paneles son objetos bastante pesados y tener una gran cantidad de los mismos puede influir perjudicialmente en el rendimiento de nuestro programa. Con GridBagLayout se pueden crear interfaces exactamente iguales pero con un nico panel con lo que nuestra interfaz ser mucho ms ligera. Pero como todo, tambin tiene sus inconvenientes: Requiere un tiempo de aprendizaje bastante grande. No slo es necesario comprender su funcionamiento sino que tambin es necesario haber hecho bastantes ejemplos para llegar a dominarlo. El cdigo necesario para crear un interfaz de usuario es considerablemente ms grande que con el resto de layout managers y adems suele ser un cdigo bastante complicado y dficil de comprender y por lo tanto de mantener. En la ltima parte de este apartado veremos como podemos crear una serie de clases auxiliares con las que solucionaremos estos dos inconvenientes y que harn que utilizar GridBag- Layout sea un juego de nios. GridBagLayout a fondo GridBagLayout basa su funcionamiento en una clase auxiliar que establece restricciones a los componentes, GridBagConstraints. Estas restricciones especifican exactamente como se mostrar cada elemento dentro del contenedor. La clase GridBagConstraints posee bastantes atributos que nos permiten configurar el layout de un contenedor a nuestro gusto. A continuacin vamos a ver los atributos importantes; no voy a mostrar la totalidad de atributos para no complicar las cosas a los menos experimentados. Vamos a ir viendo los atributos ms importantes de esta clase y de una forma grfica para que se entienda mejor: gridx y gridy Estos dos atributos especifican las coordenadas horizontal y vertical del componente que vamos a insertar en el grid. Realmente no siempre es necesario establecer su valor ya que en los casos ms simples nos llegara con gridwidth y gridheight, sin embargo la experiencia dice que poner este atributo es recomendable ya que permite saber en que elemento nos encontramos de una manera visual. La siguiente figura muestra grficamente lo que indican los atributos gridx y gridy:
gridwidth y gridheight Este otro par de elementos junto con gridx y gridy son la base de GridBagLayout. Comprendiendo a la perfeccin su significado no habr ningn interfaz que se nos resista. Bsicamente lo que indican
91
gridwidth y gridheight es el nmero de celdas que ocupar un componente dentro del GridBagLayout, su valor puede ser: Un nmero cardinal, en este caso indica exactamente el nmero de filas o columnas que ocupar el componente. GridBagConstraints.RELATIVE, indica que el componente ocupar el espacio disponible desde la fila o columna actual hasta la ltima fila o columna disponibles. GridBagConstraints.REMAINDER, indica que el componente es el ltimo de la fila actual o columna actual.
Analicemos la figura anterior para comprender el significado de estos dos atributos. En la primera fila tenemos tres componentes. Todos los componentes ocupan una celda en horizontal y en vertical luego su gridwidth y gridheight ha de ser igual a uno. En el segundo y tercer componente en lugar de ponerle uno como valor de gridwidth hemos de poner RELATIVE y REMAINDER. Tcnicamente slo sera necesario poner REMAINDER ya que es el nico que necesita conocer el GridBagLayout para saber que se ha acabado la fila.1 En la segunda fila tenemos dos componentes. Ambos tienen como gridheight RELATIVE ya que se encuentran en la penltima fila. El primer componente tiene de gridwidth REMAINDER, es decir, ocupar todo el espacio hasta el ltimo componente. En la figura se puede ver como se cumple esto que acabo de decir ya que el componente abarca dos celdas y la ltima se deja para el ltimo cuyo gridwidth es RELATIVE. 1.Siempre y cuando no se use gridx y gridy. c.gridwidth=1 c.gridheight=1 c.gridwidth=GridBagConstraints.RELATIVE c.gridheight=1 c.gridwidth=GridBagConstraints.REMAINDER c.gridheight=1 c.gridwidth=GridBagConstraints.RELATIVE c.gridheight=GridBagConstraints.RELATIVE
92
c.gridwidth=GridBagConstraints.REMAINDER c.gridheight=GridBagConstraints.REMAINDER c.gridwidth=GridBagConstraints.REMAINDER c.gridheight=GridBagConstraints.RELATIVE Por ltimo en la tercera fila tan slo tenemos un componente con gridwidth REMAINDER y gridheight REMAINDER. Como veis es bastante sencillo, gridwidth y gridheight especifican el nmero de celdas horizontal y vertical que abarcar un componente. Adems podemos utilizar los valores especiales REMAINDER y RELATIVE para indicar que un componente ha de ocupar todo el espacio restante o todo el espacio hasta el ltimo componente. anchor Este atributo es mucho ms sencillo; anchor especifica la posicin que ocupar un componente dentro de una celda. Los valores que puede tomar este atributo estn definidos como variables estticas dentro de la clase GridBagConstraints y son: NORTH, SOUTH, EAST, WEAST, NORTHWEST, SOUTHWEST, NORTHEAST, SOUTHEAST y CENTER. Como intuiris indican la orientacin de los componentes dentro de la celda que ocupan. Veamos una figura que nos aclare las cosas:
En la imgen anterior las lneas punteadas marcan el espacio que ocupa cada una de las celdas del contenedor. En este ejemplo he colocado cada uno de los componentes con un anchor diferente para que podis apreciar fcilmente el efecto que tiene este atributo. fill El atributo fill especifica el espacio que ocupar el componente dentro de la celda. Los valores que puede tomar tambin son variables estticas de la clase GridBagConstraints: NONE: El componente ocupar exactamente el tamao que tenga como preferido HORIZONTAL: El componente ocupar todo el espacio horizontal de la celda mientras que su altura ser la que tenga como preferida. VERTICAL: El componente ocupar todo el espacio vertical de la celda mientras que su longitud ser la que tenga como preferida. BOTH: El componente ocupar la totalidad de la celda Vemoslo en una imgen:
93
Como antes, las lneas marcan los bordes de las celdas. Como se puede apreciar en la figura segn el valor del atributo anchor los componentes abarcarn ms o menos espacio. weightx y weighty Estos dos atributos son la clave de los dolores de cabeza que GridBagLayout le da a tanta gente. Como habis podido observar hasta ahora no ha habido nada complicado. Todos los atributos que hemos visto son bastante sencillos de comprender y prcticamente ya estaramos en disposicin de crear un interfaz complejo con GridBagLayout. Sin embargo existe un pequeo problema que veremos a continuacin. A medida que vamos aadiendo componentes a un contenedor el layout manager va determinando en funcin del tamao de los componentes el espacio que ocupan las celdas. Hay que tener mucho cuidado porque al contrario de lo que pueda parecer si no indicamos nada las celdas no ocuparn la totalidad del contenedor. Fijaros en la figura anterior, en este caso las celdas si que ocupan todo el contenedor, pero eso es debido a que he utilizado los atributos weightx y weighty. Mirar lo que sucede si hago el mismo ejemplo anterior sin estos atributos:
He puesto las lneas que marcan el espacio ocupado por las celdas en rojo para que se vea mejor. Como veis las celdas tienen de largo la longitud mxima de los componentes y de alto la altura mxima de los componentes. Esto suele desconcertar a la gente ya que por mucho que se utilicen correctamente los otros atributos este comportamiento por defecto acaba por hacer que nuestros interfaces no salgan como planeabamos. Cmo hacemos entonces para que las celdas ocupen la totalidad del contenedor?, la respuesta como caba esperar son los atributos weightx y weighty.
94
Los atributos weightx y weighty especifican el porcentaje de espacio libre que ocupar una celda eterminada. En el ejemplo anterior una vez aadidos los componentes queda una determinada cantidad de espacio libre tanto horizontal como vertical. Veamoslo:
Este espacio libre (flechas) se dividir entre todas las celdas que especifiquen valores dentro de los atributos weightx y weighty. La forma de especificar el espacio que quiere ocupar cada componente es mediante un nmero entre 0.0 y 1.0. Este nmero representa al porcentaje de espacio libre que ocupar cada celda. Un ejemplo: Espacio libre 250 puntos en horizontal y 150 en vertical Componente 1: c.weightx=1.0, c.weighty=1.0 Componente 2: c.weightx=0.4, c.weighty=1.0 Veamos como se asigna el espacio horizontal. Como ambos componentes han pedido espacio libre, ste se divide entre ambos, por lo tanto a cada uno le tocan 125 puntos. Sin embargo como el componente 2 tan slo quiere el 40% del espacio libre se le asignan 50 puntos y el resto de los puntos pasan al otro componente que recibir sus 125 puntos ms los 75 puntos que sobraron del segundo componente, en total 200 puntos. El espacio vertical es ms sencillo. Como vimos antes se divide el espacio libre entre los componentes que lo han pedido. En este caso como los dos componentes han pedido la totalidad de su parte a ambos les corresponden 75 puntos. Obviamente cuando estemos diseando el interfaz no estaremos pensando en si este componente va a tener unos puntos y otro otros, sin embargo los atributos weightx y weighty son de una ayuda inestimable para hacer que determinadas partes de nuestra interfaz sean ms grandes que las otras. Veamos como queda el ejemplo anterior pero utilizando los atributos weightx y weighty:
95
Como se puede apreciar en la figura anterior cuanto mayor sea el porcentaje ms espacio libre ocupar nuestra celda. Lo ms importante es comprender que se los porcentajes se refieren al espacio libre y que el espacio libre se determina despus de insertar los componentes. Por lo tanto, que no os extrae que poniendo 0.1 como valor de weightx las celdas de la derecha ocupen tanto espacio. En este caso, las celdas de la derecha ocuparn el tamao preferido del botn ms grande ( HORIZONTAL ) ms su porcentaje del espacio libre horizontal. insets Con todo lo que hemos visto hasta ahora tenemos ms que de sobra para crear una interfaz compleja. Vamos a ver un ltimo atributo que nos servir de gran ayuda, insets. Si os fijis en la figura anterior, cuando se insertan componentes que ocupan la totalidad de la celda ya sea en horizontal, en vertical o en ambas direcciones, el componente se pega literalmente al borde de la celda. Muy comnmente desearemos que los componentes no estn tan pegados, es decir, que haya un mrgen entre el borde de la celda y los componentes. Esto lo conseguimos con el atributo insets. El atributo insets es un objeto de la clase java.awt.Insets cuyo constructor es: Insets(int top, int left, int bottom, int right) Como intuiris los parmetros del constructor especifican el espacio que se dejar de mrgen. Veamos un ejemplo:
96
Vamos a empezar con el ms sencillo, la ventana de entrada al sistema. Veamos como sera el cdigo para crear este interfaz: import javax.swing.*; import java.awt.*; public class A { public static void main(String[] args) { JFrame f = new JFrame(); Container container = f.getContentPane(); container.setLayout(new GridBagLayout()); ((JPanel)container).setBorder(BorderFactory.createTitledBorder( "Entrada al sistema")); GridBagConstraints c = new GridBagConstraints(); c.weightx=0.4; c.weighty=1.0; c.gridwidth=GridBagConstraints.RELATIVE; c.gridheight=GridBagConstraints.RELATIVE; c.fill=GridBagConstraints.BOTH; c.anchor = GridBagConstraints.WEST; c.insets = new Insets(2,5,2,0); container.add(new JLabel("Usuario"),c); c.gridwidth=GridBagConstraints.REMAINDER; c.gridheight=GridBagConstraints.RELATIVE; c.weightx=1.0; c.insets = new Insets(2,0,2,5); container.add(new JTextField(),c); c.gridwidth=GridBagConstraints.RELATIVE; c.gridheight=GridBagConstraints.REMAINDER; c.weightx=0.4; c.insets = new Insets(2,5,2,0); container.add(new JLabel("Contrasea"),c); c.gridwidth=GridBagConstraints.REMAINDER; c.gridheight=GridBagConstraints.REMAINDER; c.weightx=1.0; c.insets = new Insets(2,0,2,5); container.add(new JTextField(),c); f.setSize(220,110); f.setTitle("Login"); f.setVisible(true); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } } Como se puede ver cada vez que aadimos un componente al contenedor hemos de pasar una variable de tipo GridBagConstraints al mtodo add. Tened mucho cuidado de no olvidaros pasar la variable de constraints porque en otro caso el interfaz no saldr como esperbais. Esta variable puede ser reutilizada, tal como se ve en el cdigo, para no tener que estar crendolas continuamente. Si compilais y ejecutis el ejemplo os deberais encontrar con la ventana siguiente:
97
Un ltimo punto que quera resaltar del cdigo fuente anterior es que como podis observar no he utilizado para nada los atributos gridx y gridy. Como ya os he comentado, estos atributos no son estrictamente necesarios al igual que tampoco lo son gridwidth o gridheight. A menudo con utilizar una de las dos alternativas ser suficiente pero habr ocasiones en las que tendremos que utilizar ambos atributos.
9.0 EVENTOS
El sistema de gestin de eventos de Java de la AWT es similar al de SWING, algunos diseadores pensaron que se necesitaba dejar a un lado las libreras AWT e introducir las Swing no sintieron lo mismo del sistema de gestin de eventos, consideraron que era lo suficientemente bueno. Realmente este sistema de gestin de eventos es bastante elegante y sencillo, sobre todo si se compara con el sistema de gestin de eventos de Java 1.0, mucho ms engorroso de usar y menos elegante.
Qu es un evento?
Todos los sistemas operativos estn constantemente atendiendo a los eventos generados por los usuarios. Estos eventos pueden ser pulsar una tecla, mover el ratn, hacer clic con el ratn, pulsar el ratn sobre un botn o men (Java distingue entre simplemente pulsar el ratn en un sitio cualquiera o hacerlo, por ejemplo, en un botn). El sistema operativo notifica a las aplicaciones que estn ocurriendo estos eventos, y ellas deciden si han de responder o no de algn modo a este evento.
98
Lo que la fuente de eventos le pasa al objeto encargado de escuchar los eventos es, como no, otro objeto. Es un objeto tipo Event. En este objeto va toda la informacin necesaria para la correcta gestin del evento por parte del objeto que escucha los eventos. El objeto que escucha los eventos ha de implementar para ello una interface. El nombre de esta interface es siempre el nombre del evento ms Listener: para que un objeto escuche eventos de ratn ha de implementar la interface MouseListener, para que escuche eventos de teclado KeyListener..... Para hacer que un objeto escuche los eventos de otro objeto se emplea el mtodo add[nombre_evento]Listener, as si tuvisemos un Jframe llamado frame y quisisemos que el objeto llamado manejador escuchase los eventos de ratn de frame lo haramos del siguiente modo: frame.addMouseListener(manejador); manejador ha de pertenecer a una clase que implemente la interface MouseListener, que tiene un total de 7 mtodos que ha de implementar. A continuacin en la siguiente tabla mostramos los
99
eventos ms comunes, junto a la interface que debe implementar el objeto que escuche esos eventos y el mtodo para asociar un objeto para escuchar dichos eventos. En la columna de la derecha se presentarn diversos componentes que pueden generar dichos eventos.
100
Cabe preguntarse ahora por que mtodos tiene cada interface, ya que hemos de implementar todos ellos, incluso aunque no los usemos, sino la clase que se encargara de escuchar los eventos sera abstracta y no podramos crear ningn objeto de ella. Parece un poco estpido implementar mtodos que no hagan nada slo porque la interface de la que heredamos los tenga.
101
As por ejemplo si estamos interesados en escuchar clics de ratn hemos de crear una clase que implemente MouseListener, pero nosotros slo estaremos interesados en un mtodo de dicha interfase: mouseClicked. Los creadores de Java tambin pensaron en esto y por ello para cada interface que tiene ms de un mtodo crearon una clase llamada [nombre_evento]Adapter (MouseAdapter), que lo que hace es implementar todos los mtodos de la interface sin hacer nada en ellos. Nosotros lo nico que tendremos que hacer es que nuestra clase que escuche eventos extienda esta clase y sobrescriba los mtodos que nos interesen. A continuacin en la siguiente tabla damos un listado de las principales interfaces junto a sus respectivas clases Adapter y los mtodos que poseen:
102
Todos estos conceptos que estamos introduciendo acerca del tratamiento de eventos en Java, se ver mucho ms claro cuando se muestren algunos ejemplos ms adelante. As por ejemplo, si tenemos el siguiente GUI: un botn y una caja de texto dentro de una ventana, y queremos que al pulsar el botn aparezca un mensaje en la caja de texto, el oyente ser la ventana que contiene al botn y a la caja de texto, y la fuente ser el botn que lanzar el evento correspondiente. Este modelo de eventos ofrece las siguientes caractersticas: Ofrece un estructura robusta para soportar programas Java complejos. simple y fcil de aprender. Es Ofrece una clara separacin entre el cdigo de la aplicacin y del interfaz de usuario, en lo que al tratamiento de eventos se refiere. Facilita la creacin de un cdigo de tratamiento de eventos robusto y menos propenso a errores. Un oyente en lugar de implementar un interfaz, puede utilizar una clase que herede de una clase adaptadora de eventos del paquete java.awt.event. Las clases adaptadoras permiten sobrescribir solamente los mtodos del interfaz en los que se est interesado. As por ejemplo, si queremos atrapar un click del ratn, en lugar de implementar el interfaz MouseListener, heredamos de la clase adaptadora de los eventos de ratn, es decir de la clase MouseAdapter, solamente deberemos sobrescribir el mtodo mouseClicked(). Esto es posible debido a que las clases adaptadoras implementan el interfaz correspondiente y simplemente tienen implementaciones vacas de todos los mtodos del interfaz EventListener. De esta forma se consigue un cdigo ms claro y limpio. Estas clases se suelen utilizar cuando se quiere hacer uso de un interfaz muy complejo del que slo interesan un par de mtodos. Dentro de la jerarqua de clases de Java hay una serie de clases para representar todos los eventos y otra serie de interfaces que definen una serie de mtodos que deben implementar las clases que van a tratar los eventos, es decir, lo que hemos llamado oyentes. Otras clases que tambin hemos comentado y que se utilizan dentro del tratamiento de eventos en Java son las clases adaptadoras. Las clases adaptadoras las utilizaremos para simplificar nuestro cdigo, ya que implementan de forma vaca todos los mtodos de un interfaz de tratamiento de eventos determinado. Cada conjunto de eventos tiene asociado un interfaz, y como ya hemos dicho cada uno de estos interfaces declara una serie de mtodos para cada uno de los eventos lgicos asociados al tipo de evento de que se trate. Interfaz Mtodos Clase Adaptadora ActionListener actionPerformed(ActionEvent) --AdjustmentListener adjustmentValueChanged(AdjustmentEvent) --ComponentListener componentHidden(ComponentEvent) componentMoved(ComponentEvent) componentResized(ComponentEvent) componentShown(ComponentEvent) ComponentAdapter ContainerListener componentAdded(ContainerEvent) componentRemoved(ContainerEvent) ContainerAdapter FocusListener focusGained(FocusEvent) focusLost(FocusEvent) FocusAdapter ItemListener ItemStateChanged(ItemEvent) --103
KeyListener KeyPressed(KeyEvent) keyReleased(KeyEvent) keyTyped(KeyEvent) KeyAdapter MouseListener mouseClicked(MouseEvent) mouseEntered(MouseEvent) mouseExited(MouseEvent) mousePressed(MouseEvent) mouseReleased(MouseEvent) MouseAdapter MouseMotionListener mouseDragged(MouseEvent) mouseMoved(MouseEvent) MouseMotionAdapter TextListener textValueChanged(TextEvent) --WindowListener windowActivated(WindowEvent) windowClosed(WindowEvent) windowClosing(WindowEvent) windowDeactivated(WindowEvent) windowDeiconified(WindowEvent) windowIconified(WindowEvent) windowOpened(WindowEvent) WindowAdapter Como se puede apreciar en la tabla anterior, los interfaces que nicamente poseen un mtodo no tienen clase adaptadora correspondiente, ya que no tiene ningn sentido, siempre que utilizamos un interfaz con un nico mtodo vamos a implementarlo. A continuacin vamos a comentar los pasos genricos que se deben seguir a la hora de realizar el tratamiento de eventos en Java. Aunque los pasos son genricos, para poder hacer referencia a un interfaz concreto vamos a suponer que queremos realizar el tratamiento de eventos que se corresponde con la pulsacin de un botn. Lo primero es importar el paquete java.awt.event:
import java.awt.event.*;
A continuacin escribiremos la declaracin de la clase para que implemente el interfaz adecuado (listener interface). Por ejemplo si se est tratando de atrapar un evento ActionEvent (es decir, una pulsacin de un botn) generado por un botn, ser necesario implementar el interfaz ActionListener.
public class MiClase extends ClasePadre implements ActionListener{
Debemos determinar que componentes van a generar los eventos. Se registra cada uno de ellos con el tipo adecuado de oyente, si tenemos en cuenta el ejemplo anterior del botn, se debera escribir lo que objetoBoton.addActionListener(this); Una vez hecho esto debemos crear las implementaciones de todos los mtodos del interfaz que la clase debe implementar.
public void actionPerformed(ActionEvent evento){ //cuerpo del mtodo }
Una vez comentado el tratamiento de eventos en Java de forma ms o menos terica vamos a comentar una serie de ejemplos para aplicar la teora a la prctica. Estos ejemplos adems nos van a servir para repasar distintos puntos del lenguaje Java comentados hasta ahora.
104
Vamos a realizar una aplicacin que vamos a ir modificando y complicando para mostrar las diferentes facetas del tratamiento de eventos en Java. No vamos a tratar todos los interfaces, slo vamos a tratar tres de los ms representativos, el que se encarga de las pulsaciones de botones, ActionListener, el que se encarga del ratn, MouseListener, y el que se encarga de las ventanas, WindowListener, estos tres nos ofrecen ejemplos bastante prcticos. Tambin veremos los adaptadores MouseAdapter y WindowAdapter. Primero vamos a comenzar creando una aplicacin sencilla que va a consistir simplemente en una ventana que nos va a indicar si se encuentra minimizada o no, y cuando pulsemos sobre el aspa de cerrar la ventana esta se cierre y finalice la ejecucin de la aplicacin. El cdigo de esta aplicacin de ejemplo es:
import java.awt.*; //paquete necesario para el tratamiento de eventos import java.awt.event.*; public class Ventana extends Frame implements WindowListener{ //constructor de nuestra clase public Ventana(String titulo){ //constructor de la clase padre super(titulo); //tamao de la ventana setSize(150,150); //se muestra la ventana setVisible(trae); //se registra nuestra clase como oyente addWindowListener(this); } public void windowClosed(WindowEvent evento){ System.out.println("La ventana se ha cerrado"); //finaliza la aplicacin System.exit(0); } public void windowIconified(WindowEvent evento){ System.out.println("Estoy minimizada"); } public void windowDeiconified(WindowEvent evento){ System.out.println("No estoy minimizada"); } public void windowClosing(WindowEvent evento){ //se cierra la ventana dispose(); } public void windowDeactivated(WindowEvent evento){} public void windowActivated(WindowEvent evento){} public void windowOpened(WindowEvent evento){} public static void main (String[] args){ Ventana miVentana=new Ventana("Eventos"); } }
Vamos a comentar algunos puntos importantes acerca del cdigo anterior. Vamos a comentar algunas consideraciones de carcter general, en primer lugar, al ser una aplicacin debe tener un mtodo main() de arranque, y como hemos dicho que es una ventana, hereda de la clase Frame. Como se puede observar hemos creado tambin un mtodo constructor para nuestra clase Ventana. En lo concerniente al tratamiento de eventos, como se puede ver importamos el paquete correspondiente e indicamos que nuestra clase implementa el interfaz WindowListener. En el constructor de nuestra clase indicamos quien va a ser el oyente de nuestra clase, es decir, quien va a
105
tratar los eventos, en este caso el oyente es nuestra misma clase. Para ello utilizamos la lnea de cdigo
addWindowListener(this);
Es decir, indicamos que el oyente de los eventos de la ventana va a ser nuestra propia clase, es decir, estamos registrando nuestra clase como oyente mediante la referencia this. Al ser nuestra clase fuente y oyente de los eventos, tambin debe implementar el interfaz que define los mtodos que se van a ejecutar atendiendo al evento que se produzca. En nuestro caso hay tres mtodos que no nos interesan, pero como implementamos el interfaz WindowListener debemos implementarlos aunque no tengan contenido. Los mtodos windowIconified() y windowDeiconified() se ejecutarn cuando se minimice la ventana y cuando se restaure, respectivamente, el mtodo windowClosing() se ejecuta en el momento de cerrar la ventana y el mtodo windowClosed() cuando ya se ha cerrado. El resto del cdigo es bastante sencillo y considero que no necesita una mayor explicacin. Como ya hemos comentado hay tres mtodos del interfaz WindowListener que no nos interesan y que por lo tanto nos gustara suprimir, en este momento entran en juego las clases adaptadoras. Como ya debe saber el alumno, las clases adaptadoras realizan una implementacin vaca de todos los mtodos del interfaz con el que se corresponden, y al heredar de ellas utilizaremos nicamente los mtodos del interfaz correspondiente que nos interese. En nuestro caso no podemos heredar de una clase adaptadora, ya que heredamos de la clase Frame y la herencia mltiple no se permite en Java. Por lo tanto deberemos crear y definir una nueva clase que herede de la clase adaptadora del interfaz WindowListener, es decir, que herede de la clase WindowAdapter. Por lo tanto si queremos hacer uso de la clase adaptadora WindowAdapter, el cdigo de nuestra aplicacin se debe modificar como indica :
import java.awt.*; import java.awt.event.*; //nuestra clase ya no implementa el interfaz WindowListener public class Ventana extends Frame{
public Ventana(String titulo){ super(titulo); setSize(150,150); setVisible(true); //se registra como oyente la clase que hereda la clase adaptadora addWindowListener(new AdaptadorVentana(this)); } public static void main (String[] args){ Ventana miVentana=new Ventana("Eventos"); } } //clase que hereda de la clase adaptadora class AdaptadorVentana extends WindowAdapter{ //atributo que se utiliza como referencia a la clase //que es la fuente del evento que queremos tratar private Ventana fuente; //constructor de nuestra clase adaptadora //recibe como parmetro la clase fuente del evento public AdaptadorVentana(Ventana fuente){ this.fuente=fuente; } //contiene la implementacin de los mtodos que nos interesan //del interfaz WindowListener
106
public void windowClosed(WindowEvent evento){ System.out.println("La ventana se ha cerrado"); System.exit(0); } public void windowIconified(WindowEvent evento){ System.out.println("Estoy minimizada"); } public void windowDeiconified(WindowEvent evento){ System.out.println("No estoy minimizada"); } public void windowClosing(WindowEvent evento){ fuente.dispose(); } }
Los mayores cambios se aprecian en que existe una nueva clase que va a ser la encargada de tratar los eventos, en este caso si que existe una separacin clara entre clase fuente y clase oyente. El oyente sera la clase AdaptadorVentana y la clase fuente sera la clase principal Ventana. Nuestra clase Ventana, al ser slo fuente de eventos no va a implementar el interfaz WindowListener y por lo tanto nicamente va a tener en su cuerpo el mtodo constructor y el mtodo main(). Otra cosa que cambia tambin en el cdigo de nuestra aplicacin es la forma en la que se registra el oyente. Se sigue utilizando el mtodo addWindowListener() pero en este caso se pasa por parmetro una instancia de la clase adaptadora que hemos creado nosotros.
addWindowListener(new AdaptadorVentana(this));
Ahora vamos a detenernos en la clase AdaptadorVentana. Esta clase hereda de la clase WindowAdapter y posee un atributo denominado fuente que es de la clase Ventana. Este atributo es necesario para tener una referencia a la clase fuente del evento. Esta referencia es necesaria ,en este caso, dentro del mtodo windowClosing(), a la hora de cerrar la ventana.
public void windowClosing(WindowEvent evento){ fuente.dispose(); }
Para conseguir la referencia a la fuente del evento se pasa una instancia de la clase Ventana actual al constructor de la clase adaptadora. De esta forma la clase adaptadora puede manipular y acceder al objeto de clase Ventana.
public AdaptadorVentana(Ventana fuente){ this.fuente=fuente; }
Como se puede comprobar esta nueva clase contiene todos los mtodos que nos interesan del interfaz WindowListener. Si queremos tratar distintos eventos mediante clases adaptadoras, deberemos crear una clase que herede de la clase adaptadora de cada tipo de evento, siempre que exista una clase adaptadora para el evento en cuestin. Tambin existe la posibilidad de utilizar la clases adaptadoras como clases internas (inner classes). Una clase interna es una clase definida dentro de otra. El beneficio que podemos obtener de estas clases adaptadoras, es que no tenemos porque llevar la referencia de la clase fuente. La clase interna va a tener acceso a todos los mtodos y atributos de la clase en la que est declarada, aunque estos sean privados. Realmente una clase interna se sale fuera de los principios de la POO, pero puede ser bastante prctica.
107
Ahora, vamos a ofrecer una posibilidad distinta a la hora de tratar los eventos en Java, se puede utilizar una clase interna annima:
import java.awt.*; import java.awt.event.*; public class Ventana extends Frame{ public Ventana(String titulo){ super(titulo); setSize(150,150) setVisible(true); //Utilizamos una clase interna annima addWindowListener(new WindowAdapter(){ //mtodos de la clase annima public void windowClosed(WindowEvent evento){ System.out.println("La ventana se ha cerrado"); System.exit(0); } public void windowIconified(WindowEvent evento){ System.out.println("Estoy minimizada"); } public void windowDeiconified(WindowEvent evento){ System.out.println("No estoy minimizada"); } public void windowClosing(WindowEvent evento){ dispose(); } });//se cierra la clase interna annima } public static void main (String[] args){
108
A la vista del cdigo se puede ver que se trata de instanciar una clase sin indicar un objeto que contenga una referencia a la misma, ya que en realidad esta clase slo se va a utilizar para el tratamiento de eventos y no se va a querer hacer una referencia a ella. El mecanismo es muy sencillo, se utiliza el nombre de la clase adaptadora correspondiente, WindowAdapter en nuestro caso, y se implementan los mtodos que se consideren necesarios, por lo dems funciona exactamente igual a una clase interna. Una vez comentados las distintas opciones que tenemos a la hora de tratar los eventos, ahora vamos a aadir a nuestra aplicacin un botn para que al pulsarlo escriba un mensaje en la pantalla. En este caso deberemos implementar el interfaz ActionListener, ya que este interfaz no posee una clase adaptadora, al tener un nico mtodo llamado actionPerformed(). El nuevo aspecto de nuestra aplicacin de ejemplo se puede observar en la Figura.
import java.awt.*; import java.awt.event.*; //implementamos el interfaz para tratar la pulsacin del botn public class Ventana extends Frame implements ActionListener{ private Button boton; public Ventana(String titulo){ super(titulo); setSize(150,150); setLayout(new FlowLayout()); boton=new Button("Plsame"); add(boton); setVisible(true); addWindowListener(new AdaptadorVentana()); //oyente del botn, en este caso la clase Ventana boton.addActionListener(this); } public void actionPerformed(ActionEvent evento){ System.out.println("Buenas tardes"); } public static void main (String[] args){ Ventana miVentana=new Ventana("Eventos"); } class AdaptadorVentana extends WindowAdapter{ public void windowClosed(WindowEvent evento){ System.out.println("La ventana se ha cerrado"); System.exit(0); } public void windowIconified(WindowEvent evento){ System.out.println("Estoy minimizada"); } public void windowDeiconified(WindowEvent evento){ System.out.println("No estoy minimizada"); } public void windowClosing(WindowEvent evento){
109
dispose(); } } }
Como se puede observar la fuente del evento en este nuevo caso va a ser por un lado, el botn y su oyente la clase Ventana, y por otro lado la fuente de eventos va a ser la clase Ventana y el oyente la clase interna AdaptadorVentana. Las lneas utilizadas para aadir el botn se encuentran en el constructor, la nica lnea digna de mencin es la que registra el oyente del botn.
boton.addActionListener(this);
La clase Ventana al implementar el interfaz ActionListener debe facilitar el mtodo actionPerformed(), que se ejecutar al pulsar el botn. Al pulsar el botn veremos en pantalla el saludo "Buenas tardes".
public void actionPerformed(ActionEvent evento){ System.out.println("Buenas tardes"); }
Para finalizar el presente captulo modificaremos de nuevo nuestra aplicacin, aadiendo un rea de texto, en la que se escribir si el puntero del ratn se encuentra en el rea de texto, o por el contrario ha salido. Aqu vamos a tratar un nuevo tipo de evento, en este caso los eventos del ratn, y lo vamos a realizar a travs de la clase adaptadora MouseAdapter. La clase MouseAdapter es la clase adaptadora del interfaz MouseListener. Utilizamos la clase adaptadora, porque slo nos van a interesar un par de mtodos del interfaz MouseListener.
import java.awt.*; import java.awt.event.*; public class Ventana extends Frame implements ActionListener{ private Button boton; private TextArea area; public Ventana(String titulo){ super(titulo); setLayout(new FlowLayout()); boton=new Button("Plsame"); area=new TextArea(); add(boton); add(area); show(); pack(); addWindowListener(new AdaptadorVentana()); boton.addActionListener(this); //El oyente del ratn es una nueva clase interna area.addMouseListener(new AdaptadorRaton()); } public void actionPerformed(ActionEvent evento){ System.out.println("Buenas tardes"); } public static void main (String[] args){ Ventana miVentana=new Ventana("Eventos"); } //clase adaptadora para los eventos del ratn class AdaptadorRaton extends MouseAdapter{ public void mouseEntered(MouseEvent evento){ area.setText("El ratn ha entrado"); } public void mouseExited(MouseEvent evento){ area.setText("El ratn ha salido"); } } class AdaptadorVentana extends WindowAdapter{
110
public void windowClosed(WindowEvent evento){ System.out.println("La ventana se ha cerrado"); System.exit(0); } public void windowIconified(WindowEvent evento){ System.out.println("Estoy minimizada"); } public void windowDeiconified(WindowEvent evento){ System.out.println("No estoy minimizada"); } public void windowClosing(WindowEvent evento){ dispose(); } } }
En esta ltima versin de la aplicacin de prueba tenemos una nueva fuente de eventos, que va a ser el rea de texto, y un nuevo oyente que es la clase adaptadora AdaptadorRaton. La clase Ventana ofrece un nuevo atributo que va a representar el rea de texto. Para registrar el oyente del rea de texto se utiliza el mtodo addMouseListener(), a este mtodo se le pasa una instancia de la clase adaptadora que va a tratar los eventos del ratn, en este caso se trata de la clase AdaptadorRaton. Esta clase implementa los mtodos mouseEntered() y mouseExited() que se lanzarn cuando en ratn entre en el rea de texto o salga del mismo, respectivamente. Como se puede apreciar cada oyente se encarga de los eventos para los cuales se ha registrado, sin tener en cuenta el resto de los que se produzca y sin interferir entre s.
Un evento es una accin que se genera al cambiar el estado de un componente de AWT. Los eventos es la forma que tiene un programa para enterarse de que ocurri algo y reaccionar conforme al suceso. Un evento puede ser una entrada de usuario (movimientos o clicks del ratn, pulsaciones de teclas), cambios en el medio ambiente del sistema (abrir, cerrar o mover una ventana) o cualquier otra actividad que pudiera afectar la operacin del programa. Java soporta varios tipos de eventos. Cada vez que ocurre alguna accin de las que se listan a continuacin se genera un evento que puede ser atrapado por un programa: Eventos de ratn. El ratn esta sobre un componente. El ratn sali de un componente. El botn del ratn est oprimido o fue liberado. El ratn se movi. El ratn est siendo arrastrado. Eventos de teclado. La tecla est oprimida o fue liberada. Eventos de botn. El botn fue oprimido. Eventos de choice. Alguna de las opciones del choice fue seleccionada. Eventos de checkbox. El checkbox fue seleccionado. Eventos de campo de texto. Se est escribiendo sobre el campo de texto. Se dio un enter sobre el campo de texto. Eventos de foco. El componente obtuvo o perdi el foco. El foco se obtiene cuando se da un click al ratn sobre un componente. Para que un programa atienda un evento se necesita asignar al componente un oidor o escuchador (listener en la terminologa de Java). Dentro del oidor se escribe el cdigo que se desea ejecutar cuando ocurra el evento.
111
Hay dos estrategias para definir oidores: la primera es usar una clase que tenga un solo mtodo por cada tipo de evento que atienda todos los eventos que se generen de ese tipo. La segunda estrategia consiste en declarar oidores annimos, uno por cada componente que pueda generar el evento en particular. La ventaja de la primera estrategia es que el manejo de eventos se centraliza en un solo mtodo. La desventaja es que ese mtodo debe ser capaz de distinguir cul componente fue el que gener el evento. Respecto a la segunda estrategia, la ventaja es que no es necesario distinguir nada, cada componente tiene su propio oidor y el cdigo se vuelve ms legible y elegante. La desventaja es que la cantidad de cdigo generado se vuelve mayor. En mi opinin la ventaja de los oidores annimos sobrepasa la desventaja y por eso en este curso se definen oidores annimos para el manejo de eventos. En las secciones siguientes vamos a revisar los eventos ms utilizados que son los de botn, de choice y de checkbox.
10.0 EXCEPCIONES
10.1- QU SON LAS EXCEPCIONES?
Definicin: Una excepcin es un evento excepcional que ocurre durante la ejecucin del programa y que interrumpe el flujo normal de las sentencias. Si una operacin no puede completarse debido a un error, el programa deber: volver a un estado estable y permitir otras operaciones. intentar guardar el trabajo y finalizar. Esto es difcil debido a que generalmente, el cdigo que detecta el error no es el que puede realizar dichas tareas. El que detecta el error debe informar al que pueda manejarlo. La solucin ms habitual a esto son los cdigos de error, es decir, que un mtodo devuelva un cdigo de error como valor de retorno. Esto presenta una serie de inconvenientes: No siempre queda sitio para cdigos de error (p. ej.: getchar). No se garantiza que se vayan a consultar. Si se contemplan todos los errores, el cdigo crece considerablemente. La lgica del programa queda oscurecida. No sirven en los constructores. En definitiva, hacen que el tratamiento de errores sea complejo y que, por ello, muchas veces no se tenga en cuenta y no se traten todos los errores. Esto impide la construccin de programas robustos. Java ofrece una solucin, otra forma de tratar con los errores: las excepciones.
Ejemplo:
class Excepcion { public static void main(String argumentos[]) { int i=5, j=0; int k=i/j; // Divisin por cero } }
catch( IOException ref ){ System.out.println( Error de E/S ); } Si alguna de las operaciones del bloque produce una excepcin, se interrumpe el bloque try y se ejecuta el catch. Al finalizar ste, se contina normalmente. Si no se produce ninguna excepcin el bloque catch se ignora. Un bloque try puede tener varios catch asociados, uno para cada tipo de excepcin que se pueda producir. Por ejemplo, try{ a.abreFichero(); a.leeCabecera(); a.actualizaDatos(); } catch( FileNotFoundException ref ){ System.out.println( Error de apertura ); } catch( IOException ref ){ System.out.println( Error de E/S ); } Si se produce una excepcin que no se corresponde con ningn catch indicado, la excepcin se propaga hacia atrs en la secuencia de invocaciones hasta encontrar un catch adecuado. Por ejemplo: void f1( int accion ) throws EOFException { try{ if( accion == 1 ) throw new FileNotFoundException(); else if( accion == 2 ) throw new EOFException(); } catch( FileNotFoundException e ){ System.out.println( Error corregido ); } System.out.println( Finalizacin normal de f1 ); } void f2( int accion ) { try{ f1( accion ): } catch( EOFException e ){ System.out.println( Error corregido ); } System.out.println( Finalizacin normal de f2 ); } Qu pasa si se llama a f2 pasndole los valores 1 2 para el parmetro accin? En qu mtodo se producen las excepciones y en cul se tratan?
114
Cuando se invoca a un mtodo que lanza excepciones, es obligatorio: Que se maneje el error (en un catch). Que se indique mediante throws su propagacin. Es decir, o se trata el error o se avisa que se va a continuar sin haberlo corregido para que otro mtodo lo corrija. Por ejemplo: void f1() throws IOException { ... } void f2() { try{ // Alternativa 1: tratar el error en cuanto se produce f1() } catch( IOException e ){ // Tratamiento del error } } // Alternativa 2: se contina propagando el error para que lo gestione // otro mtodo. void f2() throws IOException { f1(); } Es mejor intentar tratar los errores en cuanto sea posible (alternativa 1), en vez de dejarlos para que los gestione alguien por encima (alternativa 2). De todos modos, no siempre es posible tratarlos en el mismo momento en que se producen y por tanto, a menudo hay que recurrir a la segunda alternativa.
10.4.- FINALLY
Puede haber ocasiones en que se desea realizar alguna operacin tanto si se producen excepciones como si no. Dichas operaciones se pueden situar dentro de un bloque finally, de la siguiente forma: try{ // Operaciones con posibles excepciones } catch( <tipoDeExcepcion> ref ){ // Tratamiento de la excepcin } finally{ // Operaciones comunes } Ejemplo: class Recurso { void reserva(){ ... } void libera(){ ... }
115
} class Ejemplo { void prueba() { Recurso recurso = new Recurso(); try{ recurso.reserva(); // Operaciones con posibles excepciones recurso.libera(); } catch( ExceptionA a ){ // Tratamiento del error recurso.libera(); } catch( ExceptionB b ){ // Tratamiento del error recurso.libera(); } catch( ExceptionC c ){ // Tratamiento del error recurso.libera(); } } } En el ejemplo anterior queda suficientemente claro que independientemente de que se produzca una excepcin o no, e independientemente de que sta sea capturada o no, la liberacin del recurso siempre debe llevarse a cabo (instruccin recurso.libera()). Para ello existe una solucin mucho ms elegante. Solucin con finally: class Ejemplo { void prueba() { Recurso recurso = new Recurso(); try{ recurso.reserva(); // Operaciones con posibles excepciones } catch( ExceptionA a ){ // Tratamiento del error } catch( ExceptionB b ){ // Tratamiento del error } catch( ExceptionC c ){ // Tratamiento del error } finally{
116
recurso.libera(); } } } Si no se produce ninguna excepcin, se ejecutarn el bloque try y el finally. En cambio, si se produce alguna excepcin: Si es atrapada por un catch del mismo try, se ejecuta ste y luego el finally. La ejecucin contina despus del finally con normalidad. Si no es atrapada, se ejecuta el finally y la excepcin se propaga al contexto anterior.
La jerarqua de excepciones en Java es la siguiente: Throwable Error (AWTError, LinkageError, ThreadDeath, VirtualMachineError) Exception (ClassNotFoundException, CloneNotSupportedException, DataFormatException, GeneralSecurityException, IllegalAccessException, IOException, NoSuchFieldException, NoSuchMethodException, PrinterException, etc.) RuntimeException (ArithmeticException, ArrayStoreException, CannotRedoException, CannotUndoException, ClassCastException, EmptyStackException, IllegalArgumentException, IndexOutOfBoundsException, NullPointerException, etc.) Que representada en forma grfica da lugar a:
Figura Jerarqua de clases de las excepciones Java Todas las excepciones descienden de la clase Throwable, la cual se divide en dos subclases: Error y Exception. Las clases derivadas de Error describen errores internos de la JVM e indican errores serios que normalmente la aplicacin no debera intentar gestionar. Tampoco deberan ser lanzadas por las clases del usuario. Estas excepciones rara vez ocurren, y cuando as sea, lo nico que se puede hacer es intentar cerrar el programa sin perder datos. Ejemplos de este tipo de excepciones son OutOfMemoryError, StackOverflowError, etc. Los programas en Java trabajarn con las excepciones de la rama Exception. Esta clase, a su vez, tiene una subclase llamada RuntimeException que, a su vez, tiene otras clases derivadas. Estas excepciones se clasifican en: Explcitas: las que derivan directamente de Exception. Implcitas: las que derivan de RunTimeException. Se utilizan las RunTimeException para indicar un error de programacin. Si se produce una
118
excepcin de este tipo hay que arreglar el cdigo. Por ejemplo: un cast incorrecto, acceso a un array fuera de rango, uso de un puntero null, etc. El resto de las Exception indican que ha ocurrido algn error debido a alguna causa ajena al programa (est correcto). Por ejemplo: un error de E/S, error de conexin, etc. Los mtodos deben declarar slo las excepciones explcitas. Las implcitas no deben declararse (el compilador no lo exige, aunque pueden producirse igualmente). Por tanto, cuando un mtodo declara una excepcin, est avisando de que puede producirse dicho error (por causas externas al mtodo) adems de cualquier error implcito (consecuencia de un error en el cdigo que debera ser subsanado).
void setMes( int mes ) { if( mes < 1 || mes > 12 ) throw new MesInvalidoException(); } } En este ejemplo no se avisa que se lanza una excepcin porque es de tipo RuntimeException (implcita).
Otro ejemplo con una excepcin descendiente de RuntimeException: class MesInvalidoException extends RuntimeException { private int mes; MesInvalidoException( int m ) { mes = m; } getMesInvalido() { return mes; } public String toString() // Mtodo heredado de la clase Throwable, que devuelve // la representacin String del error. { return Mes invlido: + mes; } } class Fecha { void setMes( int m ) { if( m < 1 || m > 12 ) throw new MesInvalidoException( m ); } } class Ejemplo { public static void main( String[] args ) { Fecha fecha = new Fecha(); fecha.setMes( 14 ); } } Java obliga a atrapar las excepciones explcitas, qu pasa si no se atrapa una implcita, como en este caso? El compilador no da ningn error por tratarse de una excepcin implcita y el programa compila correctamente. La excepcin MesInvalidoException se produce al invocar el mtodo setMes() con el valor 14 y al no haber sido capturada por el cdigo que realiza la invocacin (el main), el programa termina de manera anormal. Por defecto, se visualizar el mensaje Mes invlido: 14 y el programador deber darse cuenta de que existe un error en el cdigo que llama al mtodo setMes(). La clase Throwable tiene una serie de mtodos que tienen ya implementados todas las excepciones de Java (por herencia), y que pueden ser implementados por las nuevas excepciones que se creen. Algunos de estos mtodos son: public String getMessage() Devuelve el mensaje detallado de error de este objeto (o null si el objeto no tiene mensaje)..
121
public String getLocalizedMessage() Lo mismo, pero pensado para devolver el mensaje localizado de una forma ms especfica. Hay que redefinirlo, si no simplemente se comporta como getMessage() public String toString() Devuelve la representacin String del error (la clase del error, bsicamente). public void printStackTrace() Visualiza en el flujo de error estndar la traza de llamadas (backtrace) que se almacena automticamente en todo error. public void printStackTrace(PrintStream s) Lo mismo, pero indicando el flujo al que se manda la informacin. public void printStackTrace(PrintWriter s) Idem. public native Throwable fillInStackTrace() "Rellena" la pila de llamadas; es decir, devuelve el error cambiando su traza de llamadas como si se hubiera producido ahora. Es til cuando un error que se ha producido en un lugar queremos que se indique en otro.
122
123
10.12.- EJEMPLOS
Ejemplo 1: Lectura de un nmero por teclado class LeerTeclado { static BufferedReader inp = new BufferedReader( new InputStreamReader(System.in)); public static String leerLinea() { String s = ""; try { s = inp.readLine(); } catch (java.io.IOException e) { } return s; // Otra forma de hacerlo // // char c; // try { // while( (c = (char)System.in.read()) != '\n') // s += c; // } // catch (java.io.IOException e) { } } public static String leerLinea( String mens ) { // con prompt System.out.print( mens ); return leerLinea(); } /* leerDoble() Devuelve NaN en caso de error */ public static double leerDoble() { String s = leerLinea(); double d; try { d = Double.valueOf( s ).doubleValue(); } catch (java.lang.NumberFormatException e) { d = Double.NaN; } return d; } public static double leerDoble( String mens ) { // con prompt System.out.print( mens ); return leerDoble(); } public static void main (String[] a) { double d1 = leerDoble( "Introduce un nmero real: " ); double d2 = leerDoble( "Introduce otro: " ); System.out.println( "La suma de ambos es: " + (d1+d2) ); } }
124
Ejemplo 2: Incorporacin de excepciones a ListaEnlazada. public class FueraDeListaException extends IndexOutOfBoundsException { FueraDeListaException( ) { } FueraDeListaException( String s ) { super( s ); // Llama al constructor del padre } } public class ListaEnlazada { ... public void insertar( Object o, int posicion ) throws FueraDeListaException { NodoLista aux = l; for (int i = 1; aux != null && i < posicion-1; i++ ) { aux = aux.siguiente; } if (posicion == 1) l = new NodoLista( o, l ); else { if (aux == null) throw new FueraDeListaException("intentando insertar elemento ms all del fin de la lista" ); else aux.siguiente = new NodoLista( o, aux.siguiente ); } } public void borrarPrimero() throws FueraDeListaException { if (l == null) throw new FueraDeListaException("intento de borrar primero en lista vaca" ); else l = l.siguiente; } public static void main( String[] a ) { ListaEnlazada l = new ListaEnlazada(); try { l.borrarPrimero(); } catch (FueraDeListaException e) { System.out.println( "Error: " + e.getMessage() + ". Continuamos..." ); } l.insertarPrimero( new Integer( 1 ) ); l.insertarPrimero( new Integer( 2 ) ); l.insertar( new Integer( 4 ), 1 ); l.insertar( new Integer( 3 ), 2 ); // no obligatorio recoger el error // pq es RuntimeException l.insertar( new Double( 2.5 ), 3 ); try { l.insertar( new Integer( 18 ), 10 );
125
} catch (FueraDeListaException e) { System.out.println( "Error: " + e.getMessage() + ". Continuamos..." ); } finally { l.insertarPrimero( new Integer(18) ); } System.out.println( l ); l.destruir(); l.borrarPrimero(); // de este error no se recupera el programa System.out.println( "Aqu no se llega... error en ejecucin" ); } } Salida: La salida resultado de ejecutar este main() sera la siguiente: Error: intento de borrar primero en lista vaca. Continuamos... Error: intentando insertar elemento ms all del fin de la lista. Continuamos... ( 4 3 2.5 2 1 ) FueraDeListaException: intento de borrar primero en lista vaca at ListaEnlazada.borrarPrimero(ListaEnlazada.java:42) at ListaEnlazada.main(ListaEnlazada.java:66)
126
11.0 THREADS.
11.1 Qu son los threads?
Todos los programadores conocen lo que es un proceso, la mayora dira que es un programa en ejecucin: tiene un principio, una secuencia de instrucciones y tiene un final. Un thread39 es un flujo simple de ejecucin dentro de un programa. Hasta el momento, todos los programas creados contenan un nico thread, pero un programa (o proceso) puede iniciar la ejecucin de varios de ellos concurrentemente. Puede parecer que es lo mismo que la ejecucin de varios procesos concurrentemente a modo del fork() en UNIX, pero existe una diferencia. Mientras en los procesos concurrentes cada uno de ellos dispone de su propia memoria y contexto, en los threads lanzados desde un mismo programa, la memoria se comparte, utilizando el mismo contexto y recursos asignados al programa (tambin disponen de variables y atributos locales al thread). Un thread no puede existir independientemente de un programa, sino que se ejecuta dentro de un programa o proceso.
La salida de este programa es una serie alternativa de Ses y Noes: SI NO NO NO SI SI SI NO NO NO NO NO NO NO NO NO NO NO NO NO NO NO NO NO SI SI SI SI SI SI SI SI SI SI SI SI SI SI SI SI En el ejemplo se declara una clase principal (UnThread) que inicia su ejecucin como un proceso con un nico thread mediante su mtodo main(), como ocurra en todos los programas vistos hasta ahora. En este proceso, se declara y se crea un thread (NoThread t = new NoThread(); ). Despus se inicia su ejecucin mediante la llamada al mtodo de la clase
127
Thread start() (t.start() ), con lo cul comienza a ejecutarse el mtodo run() redefinido en la clase NoThread (el mtodo start() llama al mtodo run() ). Tenemos
dos threads ejecutndose. Una vez se inicia la ejecucin del thread, el tiempo de la CPU se reparte entre todos los procesos y threads del sistema, con lo cul, se intercalan instrucciones del mtodo main() con instrucciones del mtodo run() entre otras correspondientes a otros procesos (del sistema operativo y otros procesos de usuario que pudieran estar ejecutndose).
class DosThreads { public static void main(String args[] ) { NoThread n = new NoThread(); SiThread s = new SiThread(); n.start(); s.start(); } } class NoThread extends Thread { public void run() { int i; for (i=1;i<=20; i++) System.out.print("NO "); } }
128
class SiThread extends Thread { public void run() { int i; for (i=1;i<=20; i++) System.out.print("SI "); } }
La salida del programa, al igual que en el ejemplo anterior, es: NO NO NO NO NO NO SI SI SI SI SI SI SI SI SI SI SI SI SI SI SI SI SI NO NO SI SI NO NO SI NO NO NO NO NO NO NO NO NO NO En este caso se instancian dos threads y se llama a su ejecucin mediante los mtodos start(). Estos dos threads se reparten el tiempo de la CPU y se ejecutan concurrentemente. Una vez que finalizan su ejecucin, el programa termina.
Produce la siguiente salida: 1:SI 2:SI 3:SI 5:NO 6:NO 4:SI 8:SI 9:SI 10:SI 11:SI 12:SI 13:SI 14:SI 15:SI 7:NO 17:NO 16:SI 19:SI 20:SI 21:SI 22:SI 23:SI 24:SI 25:SI 18:NO 26:NO 27:NO 28:NO 29:NO 30:NO 31:NO 32:NO 33:NO 34:NO 35:NO 36:NO 37:NO 38:NO 39:NO 40:NO En este caso se declaran dos instancias de una misma clase ( SiNoThread) y se ejecutan concurrentemente. Cada una de ellas con sus propios atributos de objeto (String SiNo), pero comparten los atributos de clase40 (int Contador). Puede comprobarse que el contador no ha funcionado todo lo correctamente que pudiera esperarse: 1,2,3,5,6,4,8, Esto es debido a que se ha accedido concurrentemente a una misma zona de memoria sin que se produzca exclusin mutua.
129
Cuando se instancia un thread, se inicializa sin asignarle recursos. Est en el estado nuevo Thread. Un thread en este estado nicamente acepta las llamadas a los mtodos start() o stop(). La llamada al mtodo start() asigna los recursos necesarios al objeto, lo sita en el estado ejecutable y llama al mtodo run() del objeto. Esto no significa que el thread est jecutndose (existen multitud de sistemas que poseen una sola CPU que debe ser compartida por todos los threads y procesos) sino que est en disposicin de ejecutarse en cuanto la CPU le conceda su tiempo. Un thread en estado ejecutable puede pasar al estado no ejecutable por alguna de las siguientes razones: que sean invocados alguno de sus mtodos sleep() o suspend(), que el thread haga uso de su mtodo wait() o que el thread est bloqueado esperando una operacin de entrada/salida o que se le asigne algn recurso. Un thread puede pasar al estado muerto por dos motivos: que finalice normalmente su mtodo run() o que se llame a su mtodo stop() desde cualquiera de sus posibles estados (nuevo thread, ejecutable o no ejecutable). Un thread pasa del estado no ejecutable a ejecutable por alguna de las siguientes razones: dormido: que pase el tiempo de espera indicado por su mtodo sleep(), momento en el cual, el thread pasar al estado ejecutable y, si se le asigna la CPU, proseguir su ejecucin.
130
suspendido: que, despus de haber sido suspendido mediante el mtodo suspend(), sea continuado mediante la llamada a su mtodo resume(). Nota: Estos dos metodos, ya casi no se usan en java2. esperando: que despus de una llamada a wait() se contine su ejecucin con notify() o notifyAll(). bloqueado: una vez finalizada una espera sobre una operacin de E/S o sobre algn recurso.
La forma en que se crean los threads como subclase de Thread ya se ha visto en puntos anteriores. Creacin de threads utilizando la interface Runnable: class PrimerThread implements Runnable { Thread t; public void start() { t = new Thread(this); t.start(); } public void run() { int i; for (i=1;i<=50;i++) System.out.println(i); } } class SegundoThread extends PrimerThread { } class ThreadRunnable {
131
public static void main(String args[]) { SegundoThread s = new SegundoThread(); SegundoThread ss = new SegundoThread(); s.start(); ss.start(); } }
En este caso se utiliza el constructor de la clase Thread:
public Thread(Runnable destino); Metodologa para la creacin del thread: 1) La clase creada debe implementar el interface Runnable: class
Runnable {
PrimerThread implements
2) La clase ha de crear un atributo (o variable local en el mtodo start() ) de la clase Thread: Thread t; 3) Hay que redefinir el mtodo start(): Instanciar el atributo de la clase Thread llamando a su constructor pasndole como parmetro la propia instancia de clase ( this):
t = new Thread(this);
Iniciar el thread:
t.start();
4) Redefinir el mtodo run() tal y como se hace en la otra alternativa de creacin de threads (mediante subclases de Thread). Una vez declarada la clase que implementa la interface Runnable, ya puede ser instanciada e iniciada como cualquier thread. Es ms, cualquier subclase descendiente de esta clase poseer tambin las caractersticas propias de los threads, como ocurre en el ejemplo: SegundoThread es subclase de PrimerThread, y por lo tanto, ambas son Thread. Ambas formas de crear threads admiten un parmetro de tipo String que identifica al thread por su nombre: public Thread(String nombre); public Thread(Runnable destino, String nombre); Para poder despus averiguarlo mediante el mtodo: public final String getName(); Si no se ha asignado nombre a los threads, la clase Thread se los asigna por defecto como Thread-1, Thread-2,
11.4.2 isAlive().
public final boolean isAlive(); Devuelve false si el thread est en el estado nuevo thread o muerto y true en caso contrario.
132
class MiThread extends Thread { public void run() { try { sleep(2000); } catch (InterruptedException e) { } } } class Vivo { public static void main(String args[]) { MiThread t = new MiThread(); System.out.println("isAlive() antes de iniciar: "+ t.isAlive()); t.start(); System.out.println("isAlive() en ejecucin: "+ t.isAlive()); try { // Hace "dormir" al thread actual Thread.currentThread().sleep(3000); // de esta forma, le da tiempo a terminar al // thread t } catch (InterruptedException e) { } System.out.println("isAlive() despus de ejecucin: "+ t.isAlive()); } }
La salida correspondiente al programa es: Estado antes de iniciar: false Estado en ejecucin: true Estado despus de ejecucin: false
11.4.3 sleep().
1) public static void sleep(long milisegundos) throws InterruptedException; 2) public static void sleep(long milisegundos, int nanosegundos) throws InterruptedException; 1) Hace que el thread actual pase del estado ejecutable a dormido y permanezca en dicho estado durante los milisegundos especificados como parmetro. Una vez que se ha cumplido el tiempo, el thread despierta y pasa automticamente al estado de ejecutable. 2) Acepta un parmetro ms: un nmero entero de nanosegundos (0 999999 ) que se sumaran a los milisegundos de espera. En ambos casos se lanzar la excepcin InterruptedException si se llama al mtodo interrupt() del thread.
11.4.4 suspend().
public final void suspend(); Llamando al mtodo suspend() de un thread, el estado del mismo pasa de ejecutable a suspendido inmediatamente y slo puede ser reactivado (pasado al estado ejecutable) llamando a su mtodo resume(). La llamada al mtodo resume() de un thread que est dormido no tiene ningn efecto.
133
11.4.5 wait().
El mtodo wait() es heredado de la superclase Object, de la cul heredan todos sus mtodos todas las clases creadas en Java. 1) public final void wait() throws InterruptedException; 2) public final void wait(long milisegundos) throws InterruptedException; 3) public final void wait(long milisegundos, int nanosegundos) throws InterruptedException 1) Causa el paso al estado esperando del thread indefinidamente hasta que el thread sea notificado mediante notify() o notifyAll(). 2) El thread estar esperando hasta que sea notificado o expire el timeout (milisegundos). 3) El thread estar esperando hasta que sea notificado o expire el timeout (milisegundos + nanosegundos).
11.4.6 yield().
public static void yield(); Transfiere el control al siguiente Thread en el scheduler 41 (con la misma prioridad que el actual) que se encuentre en estado ejecutable.
class UnThreadDosInstanciasAmables { public static void main(String args[] ) { SiNoThreadAmable s = new SiNoThreadAmable("SI"); SiNoThreadAmable n = new SiNoThreadAmable("NO"); s.start(); n.start(); } } class SiNoThreadAmable extends Thread { private String SiNo; static int Contador=0; public SiNoThreadAmable(String s) { super(); SiNo=s; } public void run() { int i; for (i=1;i<=20; i++) { System.out.print(++Contador+":"+SiNo+" "); yield(); } } }
En este caso hay dos threads que se ejecutan concurrentemente. Para cada salida por pantalla, (System.out.println(++Contador+:SiNo+ ); ) el thread cede la CPU al otro thread mediante la llamada a yield(). Esto no significa que el otro thread contine su ejecucin hasta llegar a su propio yield(), ya que puede perder la CPU antes y por consiguiente no tienen porqu salir los Ses y Noes alternativamente, pero s que est ms repartida la CPU: 1:NO 2:SI 3:SI 4:NO 5:SI 6:NO 7:NO 8:SI 9:NO 10:SI 11:NO 12:NO 14:NO 13:SI 15:NO 16:SI 17:SI 18:NO 19:SI 20:NO 21:SI 22:NO 23:SI 24:SI 25:NO 26:SI 27:SI 28:NO 29:NO 30:NO 31:SI 32:NO 33:SI 34:SI 35:SI 36:NO 37:SI 38:NO 39:SI 40:NO
134
11.4.7 join().
public final void join() throws InterruptedException; Hace que el thread que se est ejecutando actualmente pase al estado esperando indefinidamente hasta que muera el thread sobre el que se realiza el join().
class MiThread extends Thread { public void run() { int i; for (i=1;i<=100; i++) System.out.print(i+" "); } } class Join1 { public static void main (String args[]) throws InterruptedException { MiThread t = new MiThread(); t.start(); t.join(); System.out.println("El thread ha terminado"); } }
El thread de la clase Join1 espera indefinidamente hasta que finalice el thread t. La salida por pantalla es: 1 2 3 4 5 6 7 8 9 22 23 24 25 26 27 40 41 42 43 44 45 58 59 60 61 62 63 76 77 78 79 80 81 94 95 96 97 98 99 10 11 12 13 14 15 16 17 18 28 29 30 31 32 33 34 35 36 46 47 48 49 50 51 52 53 54 64 65 66 67 68 69 70 71 72 82 83 84 85 86 87 88 89 90 100 El thread ha terminado 19 37 55 73 91 20 38 56 74 92 21 39 57 75 93
Si no se hubiera realizado el t.join(), el thread de la clase Join1 no habra esperado la finalizacin de t y la salida por pantalla habra sido otra: 1 2 3 El thread ha terminado 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 Existen dos mtodos join() ms: public final synchronized void join(long miliseg) throws InterruptedException; public final synchronized void join(long miliseg, int nanoseg) throws InterruptedException; En estos dos casos, el thread actual no espera indefinidamente sino que reinicia su ejecucin en el instante en que se finalice el thread sobre el que se hace el join() o pase el tiempo especificado por los parmetros miliseg (milisegundos) y opcionalmente nanoseg (nanosegundos).
135
En este caso, debe existir un mecanismo que frene al productor o al consumidor en los casos necesarios.
136
En Java, las secciones crticas se marcan con la palabra reservada synchronized. Aunque est permitido marcar bloques de cdigo ms pequeos que un mtodo como synchronized, para seguir una buena metodologa de programacin, es preferible hacerlo a nivel de mtodo.
class Contador { private long valor=0; public void incrementa() { long aux; aux=valor; aux++; valor=aux; } public long getValor() { return valor; } } class Contable extends Thread { Contador contador; public Contable (Contador c) { contador=c; } public void run () { int i; long aux; for (i=1;i<=100000; i++) { contador.incrementa(); } System.out.println("Contado hasta ahora: "+ contador.getValor()); } } class ThreadSinSync { public static void main(String arg[]) { Contable c1 , c2 ; Contador c = new Contador(); c1 = new Contable(c); c2 = new Contable(c); c1.start(); c2.start(); } }
137
Para realizar el incremento en el mtodo incrementa(), a efectos didcticos, se ha empleado una variable auxiliar intermedia (aux), aunque el efecto de haber codificado valor++ habra sido similar. En este caso, la seccin crtica es la lnea: contador.incrementa(); ya que desde esta instruccin se accede a un objeto (contador) que es compartido por ambos threads (c1 y c2). La salida por pantalla es: Contado hasta ahora: 124739 Contado hasta ahora: 158049 cuando debera haber sido: en la primera lnea un nmero comprendido entre 100000 y 200000; y en la segunda lnea el valor 200000. Lo que ha ocurrido en realidad para que se produzca este resultado equivocado es lo siguiente
Evidentemente, esto mismo ha ocurrido un gran nmero de veces durante la ejecucin de los threads. Simplemente cambiando la lnea de cdigo:
public void incrementa() {
por
public synchronized void incrementa() {
se habra conseguido bloquear el mtodo incrementa, de forma que no pudieran ejecutarlo simultneamente dos threads sobre el mismo objeto (contador).
138
La salida del programa habra sido (correcta): Contado hasta ahora: 186837 Contado hasta ahora: 200000
11.5.3 Monitores.
En Java, cada objeto que posee un mtodo synchronized posee un nico monitor para ese objeto. Cuando un thread est ejecutando un mtodo synchronized de un objeto, se convierte en el propietario del monitor, evitando que cualquier otro thread ejecute ningn otro mtodo synchronized sobre ese mismo objeto. Esto no impide que el propietario del monitor ejecute otro mtodo synchronized de ese mismo objeto, adquiriendo de nuevo el monitor (llamada a un mtodo synchronized desde otro mtodo synchronized).
class Caja { private int valor; public synchronized void meter(int nv) { valor = nv; System.out.println("metido el valor: "+valor); } public synchronized void sacar() { System.out.println("sacado el valor: "+valor); valor=0; } } class Productor extends Thread { Caja c; public Productor(Caja nc) { c = nc; } public void run() { int i; for (i=1; i<=10; i++) c.meter(i); } } class Consumidor extends Thread { Caja c; public Consumidor(Caja nc) { c = nc; } public void run() {
139
int i; for (i=1; i<=10; i++) c.sacar(); } } class ProdCons { public static void main(String argum[]) { Caja cj = new Caja(); Productor p = new Productor(cj); Consumidor c = new Consumidor(cj); p.start(); c.start(); } }
En este ejemplo existen dos threads que se comportan siguiendo el paradigma productor/consumidor. Existe un objeto de la clase Caja que es capaz de almacenar un nico nmero entero. Sobre esta caja se pueden realizar dos tipos de operaciones: meter(int): Almacena el entero que se le pasa como parmetro y muestra el valor en pantalla. sacar(): Muestra en pantalla el valor almacenado y pone ese valor a cero. Tanto el thread productor como el consumidor comparten el mismo objeto de la clase Caja. Para asegurar la exclusin mutua en la zona crtica (la que accede a valor), se declaran los mtodos meter y sacar como synchronized. Pero esto no es suficiente para que el programa funcione correctamente, ya que el productor puede almacenar varios valores antes de que el consumidor extraiga el valor o, tambin, que el consumidor intente sacar varios valores consecutivamente, por lo que la salida por pantalla podra ser la siguiente: metido el valor: 1 sacado el valor: 1 sacado el valor: 0 sacado el valor: 0 metido el valor: 2 metido el valor: 3 sacado el valor: 3 sacado el valor: 0 sacado el valor: 0 metido el valor: 4 metido el valor: 5 sacado el valor: 5 sacado el valor: 0 metido el valor: 6 metido el valor: 7 metido el valor: 8 sacado el valor: 8 sacado el valor: 0 metido el valor: 9 metido el valor: 10 De alguna forma habra que asegurar que no se meta ningn valor si la caja est llena y que no se saque ningn valor si la caja est vaca.
140
class Caja { private int valor; private boolean disponible=false; public synchronized void meter(int nv) { if (!disponible) { valor = nv; disponible=true; System.out.println("metido el valor: "+valor); } } public synchronized void sacar() { if (disponible) { System.out.println("sacado el valor: "+valor); valor=0; disponible=false; } } } class Productor extends Thread { Caja c; public Productor(Caja nc) { c = nc; } public void run() { int i; for (i=1; i<=10; i++) c.meter(i); } } class Consumidor extends Thread { Caja c; public Consumidor(Caja nc) { c = nc; } public void run() { int i; for (i=1; i<=10; i++) c.sacar(); } } class ProdCons2 { public static void main(String argum[]) { Caja cj = new Caja(); Productor p = new Productor(cj); Consumidor c = new Consumidor(cj); p.start(); c.start(); } }
Salida por pantalla: metido el valor: 1 sacado el valor: 1 metido el valor: 2 Despus de sacar el valor 1, el consumidor ha seguido su ejecucin en el bucle y como no se mete ningn valor por el consumidor, termina su ejecucin, el productor introduce el siguiente valor (el 2), y sigue su ejecucin hasta terminar el bucle; como el consumidor ya ha terminado su ejecucin no se pueden introducir ms valores y el programa termina.
141
Lo que hace falta es un mecanismo que produzca la espera del productor si la caja tiene algn valor (disponible) y otro que frene al consumidor si la caja est vaca (no disponible):
class Caja { private int valor; private boolean disponible=false; public synchronized void meter(int nv) { if (disponible) { try { wait(); } catch (InterruptedException e) { } } valor = nv; disponible=true; System.out.println("metido el valor: "+valor); notify(); } public synchronized void sacar() { if (!disponible) { try { wait(); } catch (InterruptedException e) { } } System.out.println("sacado el valor: "+valor); valor=0; disponible=false; notify(); } } class Productor extends Thread { Caja c; public Productor(Caja nc) { c = nc; } public void run() { int i; for (i=1; i<=10; i++) c.meter(i); } } class Consumidor extends Thread { Caja c; public Consumidor(Caja nc) { c = nc; } public void run() { int i; for (i=1; i<=10; i++) c.sacar(); } } class ProdCons3 { public static void main(String argum[]) { Caja cj = new Caja(); Productor p = new Productor(cj); Consumidor c = new Consumidor(cj); p.start(); c.start(); } }
sacado el valor: 1 metido el valor: 2 sacado el valor: 2 metido el valor: 3 sacado el valor: 3 metido el valor: 4 sacado el valor: 4 metido el valor: 5 sacado el valor: 5 metido el valor: 6 sacado el valor: 6 metido el valor: 7 sacado el valor: 7 metido el valor: 8 sacado el valor: 8 metido el valor: 9 sacado el valor: 9 metido el valor: 10 sacado el valor: 10 Si el primer thread en adquirir el monitor del objeto Caja es el Productor al ejecutar el mtodo meter(i): la variable disponible contendr el valor false y no se ejecutar wait(). Realiza la operacin correspondiente y ejecuta el mtodo notify() que no tiene ningn efecto. Si el primer thread en adquirir el monitor del objeto Caja es el Consumidor al ejecutar el mtodo sacar(): La variable disponible contendr el valor false y por lo tanto, se ejecuta el mtodo wait() el mtodo wait hace que el thread que actualmente posee el monitor lo abandone el monitor es tomado por el otro thread al llamar a meter(i) como disponible sigue siendo false, no se ejecuta el wait(), se completa el mtodo, se cambia el valor de disponible a true y se llama al mtodo notify() el mtodo notify() hace que el thread que est en estado de espera pase a ejecutable y tome el monitor del objeto.
143
225
nombre=n; setPriority(pri); } public void run() { int i; for(i=1;i<100;i++) { System.out.print(nombre); yield(); } } } class Prioridad1 { public static void main(String args[]) { ThreadConPrioridad t1 = new ThreadConPrioridad("1",5); ThreadConPrioridad t2 = new ThreadConPrioridad("2",5); t1.start(); t2.start(); } }
144
Salida por pantalla: 221211122122212121212121112221212121212121212121212121 212121212121212121212122121212121212121121212121221212 121212121212121212112121212121212121212121221212211212 121212121211212121212121212121221111 En este caso, los threads t1 y t2 tienen la misma prioridad (5), por lo que al llamar al mtodo yield() se cede la ejecucin al otro thread. Si las prioridades hubieran sido otras. Por ejemplo:
ThreadConPrioridad t1 = new ThreadConPrioridad("1",5); ThreadConPrioridad t2 = new ThreadConPrioridad("2",4);
La salida por pantalla habra sido diferente: 111111111111111111111111111111111111111111111111111111 111111111111111111121212121212121212111111111111111111 222222222222222222222222222222222222222222222222222222 222222222222222222222222222222222222 Como puede observarse, la mayora del tiempo de la CPU es acaparada por el thread de mayor prioridad.
145
Ejemplo: ThreadGroup tg = new ThreadGroup(MiGrupo); 2) Crea un grupo cuyo nombre se pasa como parmetro. Este grupo pertenecer al grupo que se pasa como parmetro (padre). Ejemplo: ThreadGroup tg2 = new ThreadGroup( tg ,
MiGrupo2);
Es posible obtener el grupo al que pertenece un thread mediante la llamada al mtodo getThreadGroup() de la clase Thread:
class Grupo1 { public static void main(String args[]) { ThreadGroup tg = Thread.currentThread().getThreadGroup(); System.out.println(tg.getName()); } }
El mtodo getName() devuelve un String que contiene el nombre del grupo. La salida por pantalla, evidentemente es: main
11.8.2 Operaciones sobre threads agrupados. 11.8.2.1 Operaciones sobre threads. 11.8.2.1.1 Asignacin de un thread a un grupo.
Como se ha visto, los threads se insertan por defecto en el mismo grupo que el thread actual (el thread que lo crea) a no ser que se indique explcitamente a qu grupo debe pertenecer. Esto se realiza en la llamada al constructor del thread: public Thread(ThreadGroup grupo, String nombreDelThread); public Thread(ThreadGroup grupo, Runnable destino, String nombreDelThread); 11.8.2.1.2 Operaciones sobre un conjunto de threads. public final void suspend(); Pasan al estado de suspendido todos los threads descendientes de este grupo (todos los threads pertenecientes a este grupo) y se llama al mtodo suspend() de todos los grupos pertenecientes a este grupo ( valga la redundancia). public final void resume(); Pasan al estado de ejecutable todos los threadsdescendientes de este grupo que estaban en estado suspendido. public final void stop(); Pasan al estado muerto todos los threads descendientes de este grupo. 11.8.2.2 Operaciones sobre grupos. public final void setDaemon(boolean daemon); Convierte al grupo en daemon si el parmetro es true43. Un grupo daemon es destruido automticamente cuando no contiene ningn thread o grupo. public final boolean isDaemon(); Devuelve true si el grupo es un daemon y false en caso contrario. public final void setMaxPriority(int pri); Establece la mxima prioridad que puede tener un thread descendiente de este grupo, aunque los threads descendientes que ya pertenecan al grupo antes de llamar al mtodo setMaxPriority() pueden conservar su prioridad mayor que el parmetro (pri).
147
public final void destroy() throws IllegalThreadStateException; Destruye el grupo (pero no los threads descendientes). Este grupo debe estar vaco: no debe contener threads activos, de lo contrario se genera una excepcin de la clase IllegalThreadStateException. Si el grupo no est vaco, hay que llamar antes al mtodo stop() del grupo44 para que todos los threads descendientes pasen al estado muerto. public final boolean parentOf(ThreadGroup g); Devuelve true si este grupo es padre (o es el mismo) que el grupo que se pasa como parmetro (g) y false en caso contrario. 11.8.2.3 Obtencin del contenido de un grupo. 1) public int enumerate(Thread lista[]); 2) public int enumerate(Thread lista[], boolean recurr); 3) public int enumerate(ThreadGroup lista[]); 4) public int enumerate(ThreadGroup lista[], boolean recurr); El mtodo enumerate puede obtener un vector con los threads pertenecientes al grupo (los casos 1 y 2) o un vector con los grupos pertenecientes a un grupo (casos 3 y 4) se almacena en el parmetro lista[]. Por defecto, se obtienen listas de threads (y grupos) recursivas, aadiendo los threads o grupos del grupo sobre el que se realiza la llamada y de los grupos descendientes del mismo. Para obtener nicamente los threads pertenecientes al grupo en cuestin es necesario pasar un nuevo parmetro de tipo bolean (recurr) con el valor false.
class MiThread extends Thread { public MiThread(ThreadGroup tg, String nom) { super(tg,nom); } public void run() { System.out.println("Ejecutado "+getName()); } } class Grupo2 { public static void main(String args[]) throws InterruptedException { ThreadGroup tg = new ThreadGroup("grupo uno"); ThreadGroup tg2 = new ThreadGroup(tg,"grupo dos"); MiThread t1 = new MiThread(tg,"Thread 1.1"); MiThread t12 = new MiThread(tg,"Thread 1.2"); MiThread t21 = new MiThread(tg2,"Thread 2.1"); MiThread t22 = new MiThread(tg2,"Thread 2.2"); Thread t[] = new Thread[4]; int i; tg.enumerate(t); for (i=0;i<t.length;i++) System.out.println(t[i]); } }
148
En este caso, la salida por pantalla es la siguiente: Thread[Thread 1.1,5,grupo uno] Thread[Thread 1.2,5,grupo uno] Thread[Thread 2.1,5,grupo dos] Thread[Thread 2.2,5,grupo dos] Si en lugar de enumerate(t) se hubiera utilizado la frmula enumerate(t, false), la salida por pantalla habra sido: Thread[Thread 1.1,5,grupo uno] Thread[Thread 1.2,5,grupo uno] null null
149
12.0 Swing
JFC y Swing
JFC (Java Foundation Classes) es el nombre que recibe el conjunto de un gran nmero de caractersticas cuya funcin primordial es la de permitir la construccin de complejos interfaces de usuario. El primer anuncio de JFC se realiz en la conferencia de desarrolladores JavaOne y se defini mediante las caractersticas que iba a ofrecer: Componentes Swing: comprenden todos los componentes utilizados para interfaces de usuario desde botones, barras de men, dilogos y ventanas hasta cajas de texto, barras de progreso, paneles con pestaas y listas. A lo largo de este captulo describiremos algunos de los componentes Swing, no todos ya que Swing ofrece un gran nmero de clases agrupadas en 15 paquetes distintos.
150
Soporte para Pluggable Look and Feel: es decir, soporte para una apariencia y comportamiento configurables. Permite a cualquier programa que utilice componentes Swing definir un tipo de apariencia y comportamiento (Look and Feel). As por ejemplo una misma aplicacin puede presentar una apariencia y comportamiento al estilo de Java o bien tipo Windows. En el apartado correspondiente volveremos a retomar esta caracterstica y la trataremos en detalle. API de accesibilidad: permite a tecnologas de rehabilitacin tales como lectores de pantalla y displays Braille obtener informacin acerca del interfaz de usuario. El API de accesibilidad se encuentra formando parte de las caractersticas avanzadas del lenguaje Java y por lo tanto no lo vamos a tratar en este curso. API Java 2D: al igual que ocurra con la caracterstica o funcionalidad anterior, se trata de un conjunto de clases especializadas, en este caso, en el tratamiento de grficos en dos dimensiones, imgenes y texto. El API Java 2D se escapa tambin del alcance y pretensiones de este curso. Soporte para arrastrar y soltar (Drag and Drop): permite la posibilidad de arrastrar y soltar componentes entra aplicaciones Java y aplicaciones en otros lenguajes. En muchos casos se utilizan indistintamente los trminos JFC y Swing sin hacer ningn tipo de distincin, Swing fue el nombre en clave que recibi el proyecto de Sun encargado de desarrollar los nuevos componentes para la construccin de interfaces de usuario. La denominacin swing aparece tambin en los paquetes correspondientes. Los componentes Swing se encuentran disponibles de dos formas distintas, como parte de la plataforma Java 2, tanto en la herramienta JDK 1.2 o como en el JDK 1.3, y como una extensin del JDK 1.1 (versin de Java 1.1) denominada JFC 1.1. En nuestro caso vamos a utilizar la versin que se encuentran integrada con la plataforma Java 2.
151
Los componentes Swing los podemos identificar porque los nombres de sus clases suelen ir precedidos por la letra J. As por ejemplo, la clase Button del AWT, tiene su clase correspondiente en Swing denominada JButton. Los componentes AWT se encuentran en el paquete JAVA.AWT y los Swing en el paquete javax.swing. La mayor diferencia entre los componentes AWT y los componentes Swing, es que los componentes Swing se encuentran implementados sin utilizar ningn tipo de cdigo nativo. Los componentes Swing se denominan componentes ligeros (lightweight) y los componentes AWT componentes pesados (heavyweight). Otra diferencia importante es la gran potencia y funcionalidad que ofrecen los componentes Swing. Incluso los componentes Swing ms sencillos ofrecen una serie de posibilidades que los componentes AWT no recogen, algunas de estas ventajas de los componentes Swing frente a los AWT son las que se enumeran a continuacin: botones Swing pueden mostrar imgenes y/o texto. Los posible situar bordes en torno a los componentes Swing. Es Los componentes Swing no tienen porque ser rectangulares, por ejemplo, los botones pueden ser redondos. tecnologas de rehabilitacin pueden obtener informacin de los componentes Swing Las de forma sencilla. Swing nos permite especificar el aspecto y comportamiento (look and feel) del interfaz de usuario de nuestra aplicacin (ms adelante veremos como), sin embargo los componentes AWT tienen el aspecto y comportamiento de la plataforma nativa sobre la que se ejecutan. Vamos a comentar brevemente algunas de las razones por las que nos puede interesar utilizar componentes Swing en lugar de componentes AWT. rico conjunto de componentes que ofrece Swing: botones con imgenes, barras de El herramientas, imgenes, elementos de men, selectores de color, etc. arquitectura Pluggable Look & Feel, es decir, aspecto y comportamiento configurable La y seleccionable para los elementos del interfaz de usuario. Posiblemente en un futuro se ampliarn los componentes Swing disponibles.
Parece hasta ahora que con los componentes Swing todo son ventajas, e incluso que ni siquiera debemos plantearnos que tipo de componentes debemos utilizar para construir interfaces de usuario, siempre elegiremos componentes Swing. Pero este razonamiento no es correcto, ya que en la prctica no resulta tan sencillo. Se debe sealar que, desgraciadamente, todava no existen navegadores Web que soporten Swing, ms claro todava, los navegadores Web actuales, incluso en sus ltimas versiones, no implementan la mquina virtual correspondiente a la plataforma Java 2. Por lo tanto si queremos construir applets que puedan ejecutarse en cualquier navegador deberemos construir su interfaz grfica mediante componentes AWT. Aunque se pueden generar parches que utilizaremos para que los navegadores soporten componentes Swing.
152
barraMenuCyan.setPreferredSize(new Dimension(200, 20)); //se aaden la etiqueta y la barra de men a la ventana ventana.setJMenuBar(barraMenuCyan); ventana.getContentPane().add(etiquetaAmarilla, BorderLayout.CENTER); //se muestra la ventana ventana.pack(); ventana.setVisible(true); ventana.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } }
A la vista de este cdigo podemos realizar los siguientes comentarios. Se han importado los paquetes JAVA.AWT y javax.swing. El paquete JAVA.AWT es necesario para utilizar las clases Dimension, Color y BorderLayout, como se puede observar aunque utilicemos componentes Swing vamos a necesitar de los componentes AWT. Tambin podemos comprobar la forma en la que se aade un componente a un panel de contenido de un contenedor de alto nivel como puede ser un objeto de la clase JFrame, y tambin como aadir una barra de men, en este mismo apartado veremos en ms detalle el cdigo que realiza estas funciones. Veamos un ejemplo ms para ilustrar las jerarquas de contenedores. Para ello nos debemos fijar en la Figura siguiente:
En este ejemplo se han utilizado los siguientes componentes Swing: Una ventana principal (JFrame). panel (JPanel). Un botn (JButton). Un Una etiqueta (JLabel). Al panel se le ha aadido un borde mediante la clase BorderFactory para mostrar el agrupamiento de forma ms clara entre el botn y la etiqueta. El cdigo fuente de este ejemplo es el que aparece en el Cdigo fuente :
import javax.swing.*; import java.awt.*; public class Ventana{ public static void main(String[] args) { //se establece el Look & Feel try { UIManager.setLookAndFeel( UIManager.getCrossPlatformLookAndFeelClassName()); } catch (Exception e) { } //se crean los componentes JFrame ventana = new JFrame("Ventana"); JLabel etiqueta = new JLabel(" Soy una etiqueta"); JButton boton = new JButton("Soy un botn");
154
JPanel panel = new JPanel(); //se asigna un borde y un gestor de diseo al panel panel.setBorder(BorderFactory.createLineBorder(Color.red,5)); panel.setLayout(new GridLayout(0, 1)); //se aaden al panel el botn y la etiqueta panel.add(boton); panel.add(etiqueta); //se aade el panel al panel de contenido de la ventana ventana.getContentPane().add(panel, BorderLayout.CENTER); //se muestra la ventana ventana.pack(); ventana.setVisible(true); ventana.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } }
Este cdigo adelanta algo que veremos en detalle en el apartado correspondiente, cmo asignar un aspecto y comportamiento (Look & Feel) determinado a nuestro interfaz de usuario, esto aparece en las primeras lneas del mtodo main() y en este caso se establece el aspecto y comportamiento de Java. Vamos a pasar a describir las funciones que presentan cada uno de los componentes Swing utilizados en el ejemplo. En este ejemplo la ventana representada mediante la clase JFrame es el contenedor de alto nivel, igualmente podra tratarse de otro componente de alto nivel como podra ser un objeto de la clase JApplet o JDialog. El panel es un contenedor intermedio, su propsito es el de simplificar la situacin del botn y la etiqueta, para que sea visible se le ha asignado un borde de color rojo. Otros contenedores intermedios pueden ser paneles con pestaas (JTabbedPane) y paneles de scroll (JScrollPane), que juegan un papel ms visible e interactivo dentro del interfaz de usuario. Y por ltimo y el botn y la etiqueta se denominan componentes atmicos, son componentes que no contienen otros componentes Swing, como ocurra con los anteriores, sino que tienen entidad suficiente para presentar por s mismos informacin al usuario. A menudo los componentes atmicos tienen como funcin obtener informacin del usuario. Swing ofrece un gran nmero de componentes atmicos (en este captulo veremos algunos de ellos) como pueden ser listas desplegables (JComboBox), cajas de texto (JTextField) o tablas (JTable).
155
Como se puede observar incluso el programa Swing ms sencillo presenta mltiples niveles en su jerarqua de contenedores, adems, como ya hemos dicho la raz de esta jerarqua siempre es un contenedor de alto nivel. Cada contenedor de alto nivel indirectamente contiene un contenedor intermedio llamado panel de contenido (content pane), y normalmente el panel de contenido contiene, directa o indirectamente, todos los componentes visibles de la ventana del interfaz de usuario. La excepcin a esta regla son las barras de men, que se sitan en un lugar especial fuera del panel de contenido, como vimos en el primer ejemplo de este apartado. Para aadir un componente a un contenedor se utiliza una de las distintas formas del mtodo add(), como ya vimos en el tema dedicado a los componentes AWT. Como regla general, una aplicacin Java con un interfaz de usuario basado en componentes Swing, tiene al menos una jerarqua de contenedores con un objeto de la clase JFrame como raz de la misma. Por ejemplo, si una aplicacin tiene una ventana principal y dos dilogos, la aplicacin presentar tres jerarquas de contenedores, y por lo tanto tres contenedores de alto nivel. Una de las jerarquas de contenedores tiene a un objeto de la clase JFrame como raz, y cada una de las otras dos jerarquas un objeto de la clase JDialog como raz. Un applet basado en Swing tiene al menos una jerarqua de contenedores y su raz es un objeto de la clase JApplet. Por ejemplo un applet que muestra un dilogo tiene dos jerarquas de contenedores, los componentes en la ventana del navegador tienen como raz un objeto JApplet, y los que se encuentran en el dilogo tienen como raz de su jerarqua de componentes un objeto de la clase JDialog. Hasta ahora hemos visto un par de ejemplos que por un lado nos han mostrado como funcionan las jerarquas de contenedores, y por otro, nos ha mostrado como utilizar algunos de los componentes Swing, aunque ms adelante veremos algunos de ellos con ms detalle. Para obtener una referencia al panel de contenido de un componente de alto nivel debemos utilizar el mtodo getContentPane(), que devuelve un objeto de la clase Container. Por defecto el panel de contenido es un contenedor intermedio que hereda de la clase JComponent y que tiene como gestor de diseo un BorderLayout. Una vez que tenemos una referencia al panel de contenido podremos aadir los componentes que consideremos necesarios utilizando una sentencia similar a la que muestra el siguiente fuente .
ventana.getContentPane().add(componente,BorderLayout.CENTER);
Todos los contenedores de alto nivel pueden tener, en teora, una barra de men, sin embargo en la prctica las barras de men se utilizan nicamente en ventanas y en raras ocasiones en applets. Para aadir una barra de men a un contenedor de alto nivel,
156
crearemos un objeto de la clase JMenuBar, aadiremos los elementos de men que se consideren necesarios y por ltimo se lanzar sobre el contenedor de alto nivel el mtodo setMenuBar(), pasndole por parmetro el objeto JMenuBar correspondiente.
ventana.setMenuBar(objMenuBar);
Todo contenedor de alto nivel adems de poseer un panel de contenido, poseen otro panel llamado panel raz (root pane). Normalmente este panel intermedio no se suele utilizar, su funcin es la de gestionar el panel de contenido y la barra de men junto con otros dos contenedores. Estos dos contenedores son el panel de capas (layered pane) y el panel de cristal (glass pane). El panel de capas contiene directamente la barra de men y el panel de contenido, y adems permite ordenar los componentes que se vayan aadir con detenimiento (Z-order), es decir, permite organizar los componentes del interfaz de usuario en capas. Esto puede ser til para mostrar mens de aparicin sbita (popup menus) por encima de otros componentes. El panel de cristal suele permanecer oculto y se encuentra por encima de todos los elementos del panel raz. Este panel es til para interceptar eventos o pintar sobre un rea que ya contiene otros componentes, se utiliza sobre todo para interceptar eventos de entrada que suceden sobre el contenedor de alto nivel. En la Figura 66 se puede ver un esquema que muestra la disposicin de los distintos paneles que podemos encontrar en un componente de alto nivel.
A continuacin vamos a comentar los distintos componentes de alto nivel: JFrame, JDialog y JApplet.
JFrame
Ya hemos visto este componente Swing realizando las labores de contenedor de alto nivel, un objeto de la clase JFrame representa a una ventana con bordes, ttulo y botones que permiten cerrar y maximizar la ventana. Las aplicaciones Java que poseen interfaz de usuario al menos utilizan un JFrame, y tambin lo hacen a veces los applets. Veamos la utilizacin de la clase JFrame mediante un sencillo ejemplo. Se trata de una ventana que contiene una etiqueta y que al pulsar el cierre de la misma se finalizar la ejecucin de la aplicacin.
157
import java.awt.*; import java.awt.event.*; import javax.swing.*; public class Ventana{ public static void main(String s[]) { JFrame ventana = new JFrame("Ventana Sencilla"); ventana.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); JLabel etiqueta = new JLabel("Soy una etiqueta"); etiqueta.setPreferredSize(new Dimension(175, 100)); ventana.getContentPane().add(etiqueta, BorderLayout.CENTER); ventana.pack(); ventana.setVisible(true); } }
En la primera lnea del mtodo main() se utiliza el constructor de la clase JFrame que nos permite indicar el ttulo de la ventana mediante una cadena que pasamos como parmetro, otra versin de este constructor es sin argumentos. A continuacin se aade un oyente para los eventos de la ventana, en este caso se implementa nicamente el mtodo windowClosing() para finalizar la ejecucin de la aplicacin. En las siguientes lneas se crea y aade una etiqueta (JLabel) al panel de contenido de la ventana. Por ltimo se lanzan los mtodos pack() y setVisible() sobre la instancia de la clase JFrame. El mtodo pack() de un tamao a la ventana de forma que todos sus contenidos tengan el tamao especificado o superior, una alternativa al mtodo pack() es le mtodo setSize() en el que se puede especificar de forma explcita las dimensiones de la ventana. Al mtodo setVisible() se le pasa el parmetro true para que muestre la ventana en la pantalla. Por defecto cuando se pulsa el botn de cierre de la ventana, la ventana se oculta, sin la necesidad de utilizar ningn tratamiento de eventos, para cambiar este comportamiento se puede utilizar el mtodo setDefaultCloseOperation() o bien implementar un tratamiento de eventos similar al del ejemplo. El parmetro que se le pasa al mtodo setDefaultCloseOperation() debe ser una de las siguientes constantes: DO_NOTHING_ON_CLOSE: en este caso cuando se pulsa el botn de cierre de la ventana no ocurre nada, la ventana sigue visible. HIDE_ON_CLOSE: es el valor por defecto, cuando se pulsa el botn de cierre la ventana se oculta, pero sigue existiendo, teniendo la posibilidad de mostrarse la ventana de nuevo si as lo indicamos en el programa.
158
DISPOSE_ON_CLOSE: al cerrar la ventana se elimina de la memoria y de la pantalla, es decir, en este caso se liberan todos los recursos que estaban siendo utilizados por la ventana. La clase JFrame hereda de la clase java.awt.Frame y por lo tanto hereda todos los mtodos de la clase Frame, como pueden ser setSize(), pack(), setTitle(), setVisible() y getTitle(). Adems de los mtodos de la clase java.awt.Frame, la clase JFrame tiene los siguientes mtodos propios: void setDefaultCloseOperation(int): asigna el tipo de operacin que va a realizar la ventana cuando el usuario pulse el botn de cierre de la ventana. Ya hemos visto las posibles acciones. getDefaultCloseOperation(): devuelve la operacin de cierre por defecto asignada a la int ventana. void setContentPane(Container): asigna a la ventana un panel de contenido, que va a ser el que contenga todos los componentes visibles del interfaz de usuario de la ventana. Este mtodo permite utilizar nuestro propio panel de contenido, lo normal es utilizar un objeto de la clase JPanel para crear el panel de contenido, en el Cdigo se puede observar como se crea un objeto JPanel que luego va a ser el panel de contenido de una ventana.
JPanel panelContenido=new JPanel(); panelContenido.setLayout(new BorderLayout()); panelContenido.add(componente,BorderLayout.CENTER); panelContenido.add(otroComponente,BorderLayout.SOUTH); ventana.setContentPane(panelContenido);
Container getContentPane(): devuelve el panel de contenido de la ventana, este mtodo se suele utilizar para una vez que tenemos una referencia al panel de contenido de la ventana aadir componentes a la misma. void setMenuBar(MenuBar): asigna a la ventana una barra de men determinada representada por un objeto de la clase MenuBar. JMenuBar getMenuBar(): devuelve la barra de men que tiene asignada la ventana. void setGlassPane(Component): permite asignar un panel de cristal a la ventana. Component getGlassPane(): devuelve el panel de cristal de la ventana.
JDialog, JOptionPane
Vamos a pasar ahora a comentar el segundo tipo de contenedores de alto nivel, los dilogos, que son un tipo de ventana ms limitada que las ventanas representadas por la clase JFrame. Hay varias clases que ofrecen dilogos: JOptionPane: permiten crear sencillos dilogos estndar. ProgressMonitor: muestra un dilogo que muestra el progreso de una operacin. JColorChooser: ofrece un dilogo estndar para la seleccin de colores. JFileChooser: ofrece un dilogo estndar para la seleccin de un fichero. JDialog: permite crear directamente dilogos completamente personalizados. Como se indica al principio de esta seccin nosotros nicamente nos vamos a encargar de las clase JOptionPane y JDialog.
159
Todo dilogo es dependiente de una ventana determinada, si la ventana se destruye tambin lo harn sus dilogos asociados. Cuando la ventana se transforma en icono sus dilogos desaparecen de la pantalla, cuando se maximiza la ventana los dilogos vuelven a aparecer. Los dilogos pueden ser modales, cuando un dilogo modal se encuentra visible se bloquea la entrada del usuario en todas las dems ventanas del programa. Los dilogos que ofrece la clase JOptionPane son modales, para crear un dilogo no modal se debe hacer uso de la clase JDialog que permitir indicar si el dilogo que se crea va a ser modal o no. La clase JDialog hereda de a clase AWT java.awt.Dialog, aade a la clase Dialog un panel raz (root pane) y soporte para la operacin por defecto de cierre. Como se puede observar son las mismas caractersticas que ofreca la clase JFrame sobre la clase Frame del AWT. Cuando se utiliza la clase JOptionPane en realidad tambin estamos haciendo uso de la clase JDialog de forma indirecta, ya que la clase JOptionPane es un contenedor que puede crear de forma automtica una instancia de la clase JDialog y aadirse al panel de contenido de ese dilogo. A continuacin vamos a comentar las distintas caractersticas de la clase JOptionPane. Mediante la clase JOptionPane podemos crear distintos tipos de dilogos, JOptionPane ofrece soporte para mostrar dilogos estndar, indicar los iconos, especificar el ttulo y texto del dilogo y los textos de los botones que aparecen. En cuanto a los iconos que se muestran en el dilogo facilitado por JOptionPane, podemos utilizar iconos personalizados, no utilizar ningn tipo de icono, o utilizar uno de los cuatro iconos estndar que ofrece la clase JOptionPane, estos iconos son los de pregunta, informacin, advertencia y error. El aspecto de estos iconos variar segn el Look & Feel (aspecto y comportamiento) que se aplique. Para mostrar dilogos modales sencillos se utilizar directamente uno de los mtodos showXXXDialog() de la clase JOptionPane. Este conjunto de mtodos son mtodos estticos y por lo tanto se lanzarn sobre una instancia concreta de la clase, sino que se lanzaran de forma directa sobre la clase. Si lo que necesitamos es controlar el comportamiento del dilogo cuando se cierre o si el dilogo no debe ser modal entonces instanciaremos un objeto de la clase JOptionPane y lo aadiremos a un objeto de la clase JDialog. Veamos a continuacin los principales mtodos showXXXDialog() de la clase JOptionPane. El primero de estos mtodos es el mtodo showMessageDialog(), que muestra un dilogo modal con un botn de aceptar. Se puede especificar el mensaje, el icono y el ttulo que muestra el dilogo. Este mtodo se encuentra sobrecargado y ofrece tres versiones distintas: void showMessageDialog(Component componentePadre, Object mensaje): el primer parmetro indica el componente padre del que depende, puede ser una ventana, un componente dentro de una ventana o nulo y el segundo el mensaje que se muestra, suele ser
160
una cadena de texto que podemos dividir en varias lneas utilizando el carcter de nueva lnea (\n). void showMessageDialog(Component componentePadre, Object mensaje, String ttulo, int tipoMensaje): en este caso se especifica tambin el ttulo, que ser una cadena, y el tipo de mensaje a mostrar. Este ltimo parmetro determinar el icono que se va a mostrar en el dilogo, para ello se utilizan una serie de constantes definidas en la clase JOptionPane, estas constantes son: PLAIN_MESSAGE (sin icono), ERROR_MESSAGE (icono de error), INFORMATION_MESSAGE (icono de informacin), WARNING_MESSAGE (icono de advertencia) y QUESTION_MESSAGE (icono de pregunta). void showMessageDialog(Component componentePadre, Object mensaje, String ttulo, int tipoMensaje, Icon icono): en esta ltima versin del mtodo podemos indicar un icono personalizado para que se muestre en el dilogo. Veamos algunos ejemplos con el mtodo showMessageDialog(). Podemos aadir las distintas sentencias de creacin de dilogos que vamos a ver ahora al ejemplo de la seccin anterior en la que tratbamos la clase JFrame, la instancia de la clase JFrame, llamada ventana va a ser el componente padre de los dilogos. Si aadimos la lnea que muestra el Cdigo, a nuestra clase Ventana de la seccin anterior, obtendremos el resultado que aparece en la Figura.
JOptionPane.showMessageDialog(ventana,"Esto es un mensaje", "Ttulo",JOptionPane.WARNING_MESSAGE);
Otras variaciones sobre la sentencia anterior se muestran a continuacin con sus correspondientes resultados.
JOptionPane.showMessageDialog(ventana,"Esto es un mensaje");
En este otro caso indicamos un icono personalizado para que se muestre en el dilogo.
161
Como se puede comprobar en todos los ejemplos, si pulsamos el botn etiquetado como OK, el dilogo se cerrar. Otro mtodo de la clase JOptionPane que muestra un dilogo es el mtodo showConfirmaDialog(). En este caso este mtodo muestra un dilogo de confirmacin, para que el usuario seleccione entre los botones correspondientes. Este mtodo, al igual que el anterior, se encuentra sobrecargado y por lo tanto ofrece tres versiones distintas, que pasamos a comentar a continuacin. showConfirmDialog(Component componentePadre, Object mensaje): este mtodo int muestra un dilogo modal con los botones, que representan las opciones disponibles, si, no y cancelar, y adems con el ttulo por defecto. showConfirmDialog(Component componentePadre, Object mensaje, String ttulo, int int tipoOpcin): en este caso podemos especificar el ttulo del dilogo de confirmacin y el tipo de opciones que se van a mostrar, para esto ltimo utilizaremos las siguientes constantes ofrecidas por la clase JOptionPane: DEFAULT_OPTION, YES_NO_OPTION, YES_NO_CANCEL_OPTION y OK_CANCEL_OPTION. showConfirmDialog(Component componentePadre, Object mensaje, String ttulo, int int tipoOpcin, int tipoMensaje): en esta versin del mtodo podemos indicar adems el tipo de mensaje que se muestra en el dilogo, de la misma forma que lo hacamos en el mtodo showMessageDialog(). showConfirmDialog(Component componentePadre, Object mensaje, String ttulo, int int tipoOpcin, int tipoMensaje, Icon icono): en esta ltima versin especificamos un icono personalizado que se va a mostrar en el dilogo de conformacin correspondiente. Como se puede observar en todas las versiones del mtodo showConfirmDialog() se devuelve un entero (int), este entero va a recoger la seleccin que ha realizado el usuario, es decir, indicar el botn que ha sido pulsado. El entero que devuelve este mtodo se corresponde con uno de los valores de las siguientes constantes de la clase JOptionPane:
162
YES_OPTION, NO_OPTION, CANCEL_ OPTION, OK_OPTION y CLOSED_OPTION (el usuario cierra el dilogo sin pulsar ninguna de las opciones disponibles). Al igual que ocurra con el mtodo anterior, vamos a ver ejemplos de utilizacin del mtodo showConfirmDialog.
JOptionPane.showConfirmDialog(ventana,"Desea formatear el disco?");
El siguiente mtodo que vamos a tratar de la clase JOptionPane va a ser el mtodo showOptionDialog(). Este mtodo es mucho ms potente que los vistos anteriormente, ya que permite una mayor personalizacin del dilogo. La funcin de este mtodo es la de mostrar un dilogo modal con los botones, iconos, mensaje y ttulos especificados para que el usuario seleccione entre las distintas opciones que se le ofrecen, con este mtodo podremos indicar los botones que queremos aparezcan en el dilogo. El mtodo showOptionDialog() se diferencia del mtodo showConfirmDialog() principalmente en que permite especificar las etiquetas de los botones que aparecen y adems permite especificar la opcin seleccionada por defecto. La sintaxis de este mtodo es la siguiente: int showOptionDialog(Component componentePadre, Object mensaje,String ttulo, int tipoOpcin,int tipoMensaje, Icon icono, Object[] opciones, Object valorInicial) Vamos a comentar los distintos parmetros de este nuevo mtodo. Los tres primeros parmetros son los mismos que los vistos en el mtodo showMessageDialog() y adems tienen el mismo significado.
163
En el tipo de opcin se especifica el conjunto de opciones que se van a presentar al usuario, y por lo tanto se correspondern con las constantes vistas en el mtodo showConfirmDialog(). Los dos siguientes parmetros, tipo de mensaje y el icono personalizado, tiene el mismo cometido que el mtodo showMessageDialog(). El siguiente parmetro es un array de objetos que se va a corresponder con un array de cadenas que se van a mostrar en cada uno de los botones del dilogo, es decir, podemos utilizar nuestras propias etiquetas pera mostrar en los botones. El ltimo parmetro indica cual es la opcin que se encuentra seleccionada por defecto. Como se puede observar en la sintaxis del mtodo showOptionDialog() se devuelve un entero (int), este entero tiene la misma funcin que el que devolva el mtodo showConfirDialog(), es decir, va a recoger la seleccin que ha realizado el usuario, es decir, indicar el botn que ha sido pulsado. Se debe indicar que aunque utilicemos etiquetas personalizadas para nuestros botones, se siguen devolviendo los mismos valores de las constantes, as por ejemplo un dilogo del tipo YES_NO_OPTION siempre devolver los valores: YES_OPTION, NO_OPTION o CLOSED_OPTION. Ahora se va a mostrar ejemplos de uso del mtodo showOptionDialog(). Estos ejemplos son nicamente unas cuentas sentencias que podemos incluir, como ocurra con los mtodos anteriores, en nuestra ya conocida clase Ventana.
Object[] opciones={"Vale", "Ni hablar"}; ImageIcon icono = new ImageIcon("icono.gif"); JOptionPane.showOptionDialog(ventana,"Desea formatear el disco?","Confirme operacin", JOptionPane.YES_NO_OPTION,JOptionPane.WARNING_MESSAGE, icono, opciones, opciones[1]);
Se muestra un dilogo con las opciones si/no pero con unas etiquetas de botones personalizadas, as como con un icono personalizado, adems es el segundo botn el que se encuentra seleccionado por defecto.
El ltimo mtodo que vamos a ver de la clase JOptionPane es el mtodo showInputDialog(), este mtodo va a permitir obtener informacin de entrada del usuario a travs del dilogo, ya sea a travs de una caja de texto o a travs de un una lista de opciones. El dilogo que se muestra va a tener dos botones, uno de aceptar y otro de cancelar, si el usuario pulsa el botn de aceptar indicar que ha introducido una informacin que podemos recuperar. Este mtodo se encuentra sobrecargado, y ofrece las siguientes versiones: Object showInputDialog(Component componentePadre, Object mensaje): muestra un dilogo con el mensaje correspondiente que requiere una entrada del usuario a travs de una caja de texto.
164
Object showInputDialog(Component componentePadre, Object mensaje, String ttulo, int tipoMensaje): en esta caso nos permite especificar un ttulo y un tipo de mensaje (ya conocemos las constantes correspondientes). Object showInputDialog(Component componentePadre, Object mensaje, String ttulo, int tipoMensaje, Icon icono, Object opciones, Object opcinSeleccionada): en este mtodo se permite indicar un icono personalizado y un conjunto de opciones en forma de lista desplegable para que el usuario seleccione una de ellas adems se permite seleccionar una opcin por defecto. String showInputDialog(Object mensaje): en este caso no se indica nada ms que el mensaje que se va a mostrar al usuario, sin utilizar ningn componente padre, por lo que el dilogo se situar en el centro de la pantalla, ya que en los otros casos se centra siempre con respecto al componente padre, aunque de todas forma sigue bloqueando a la ventana que lo ha generado. Como se puede comprobar siempre se devuelve un objeto de la clase String, que se va a corresponder con una cadena que va a representar la informacin indicada por el usuario, ya sea a travs de una caja de texto una de una lista de opciones disponibles. En los siguientes ejemplos se muestra la utilizacin del mtodo showInputDialog(). En este caso se va a recoger el dato facilitado por el usuario y mostrarlo en la salida estndar de la aplicacin.
String respuesta=JOptionPane.showInputDialog(ventana,"Cul es tu nombre?"); System.out.println("El nombre del usuario es: "+respuesta);
Object[] valores={"Rojo","Verde","Azul","Negro","Amarillo"}; ImageIcon icono = new ImageIcon("icono.gif"); Object respuesta=JOptionPane.showInputDialog(ventana,"Cul es tu color favorito?","Seleccin de color",JOptionPane.QUESTION_MESSAGE, icono,valores,valores[2]); System.out.println("El color favorito del usuario es el: "+respuesta);
En este caso se muestra una lista con las opciones disponibles, para que el usuario seleccione la que desee. Tambin se ha utilizado un icono personalizado.
165
Hasta ahora hemos visto los mtodos que nos ofrece la clase JOptionPane para mostrar distintos tipos de dilogo. Estos dilogos tienen en comn una serie de caractersticas: al pulsar alguno de los botones que contienen se cierran, tambin se cierran cuando el usuario pulsa el cierre del dilogo y por ltimo son todos modales. En algunos casos estos dilogos nos servirn, ya que su comportamiento se puede adecuar a nuestras necesidades, pero en otros casos esto puede no ser as. En algunos casos nos puede interesar validar la informacin ofrecida por el usuario en un dilogo, o tambin nos puede interesar utilizar un dilogo que no sea modal. En estos casos en los que deseamos personalizar al mximo los dilogos utilizaremos la clase JDialog conjuntamente con la clase JOptionPane. Para ello necesitamos crear una instancia de la clase JOptionPane para aadirla al objeto JDialog correspondiente. La clase JOptionPane presenta mltiples constructores que permiten especificar el mensaje que se va a mostrar, el tipo de mensaje, el tipo de opciones, el icono personalizado, las opciones disponibles y la opcin seleccionada por defecto. Una vez creado el objeto JOptionPane, deberemos crear el objeto JDialog que lo va a contener, algunos de los constructores ofrecidos por la clase JDialog son: JDialog(): crea un dilogo no modal, sin ttulo definido y sin ninguna ventana propietaria. JDialog(Frame ventanaPropietaria): se indica la ventana a la que se encuentra asociado el dilogo. JDialog(Frame ventanaPropietaria, boolean modal): se indica si el dilogo va a ser modal o no. JDialog(Frame ventanaPropietaria, boolean modal, String ttulo): se especifica adems el ttulo que va a tener el dilogo. Si ya tenemos instanciados el objeto JOptionPane y el objeto JDialog, nicamente nos queda asignar al dilogo (JDialog) su contenido (JOptionPane), para ello utilizamos el mtodo setContentPane() de la clase JDialog, y que es comn, como ya hemos comentado, a todos los contenedores de alto nivel. A continuacin se muestra este proceso con un ejemplo que consiste en una sencilla aplicacin Java que muestra una ventana y un dilogo asociada a la misma.
import java.awt.*; import java.awt.event.*; import javax.swing.*; public class Dialogo{ public static void main(String s[]) { JFrame ventana = new JFrame("Ventana Sencilla"); ventana.setSize(175,100); ventana.setVisible(true); JOptionPane contenido=new JOptionPane("Esto es un mensaje", JOptionPane.INFORMATION_MESSAGE,JOptionPane.YES_NO_CANCEL_OPTION); JDialog dialogo=new JDialog(ventana,"Esto es un dilogo",true); dialogo.setContentPane(contenido); dialogo.setLocationRelativeTo(ventana); dialogo.pack(); dialogo.setVisible(true); } }
166
El resultado es :
Como se puede comprobar la pulsacin de los botones del dilogo no tiene el efecto que tenan anteriormente, si queremos que realicen alguna operacin cuando se pulsen, deberemos hacerlo a travs de una gestin de eventos a travs del cdigo de nuestra aplicacin. Aunque si pulsamos el cierre del dilogo ste se sigue cerrando, ya que la operacin tiene asignada por defecto para el cierre es HIDE_ON_CLOSE, es decir, ocultarse en el cierre, como ocurra con la clase JFrame. En el cdigo se puede observar que se utiliza el mtodo setLocationRealtiveTo(), este mtodo centra el dilogo de forma relativa al componente que le pasamos por parmetro. Otros mtodos de la clase JDialog son los siguientes: Container getContentPane(): devuelve el panel de contenido del dilogo. getDefaultCloseOperation(): devuelve la operacin de cierre por defecto que tiene int asignada el dilogo. void setDefaultCloseOperation(int operacion): asigna una operacin de cierre por defecto al dilogo.
JApplet
Este es el tercero de los contenedores de alto nivel. Esta clase hereda de la clase java.applet.Applet, es por lo tanto la versin Swing de la clase Applet. La clase JApplet aporta dos caractersticas esenciales a los applets, ofrece soporte para tecnologas de rehabilitacin y ofrece un panel raz, con todo lo que ello supone, es decir, aadir componentes al panel de contenido, posibilidad de tener una barra de men, etc.
Contenedores intermedios
Los contenedores intermedios son contenedores Swing, que aunque no son contenedores de alto nivel, su labor principal es la de contener otros componentes. Estos contenedores se siguen basando en la jerarqua de contenedores de Swing que ya hemos visto anteriormente. Swing ofrece una serie de contenedores de propsito general: JPanel: es le ms flexible y utilizado de todos ellos. Se utiliza normalmente para agrupar componentes, se le puede asignar gestores de diseo y bordes. Los paneles de contenido de los contenedores de alto nivel suelen ser de la clase JPanel. JScrollPane: ofrece una vista de scroll de un componente, suele contener componentes grandes o que pueden crecer. Se utilizan debido a las limitaciones del tamao de la pantalla.
167
JSplitPane: este panel agrupa dos componentes, uno al lado del otro. El usuario puede ajustar la lnea divisora que separa ambos componentes arrastrndola. El aspecto es similar al que ofrece, por ejemplo, el explorador de Windows, la vista del rbol de directorios se encuentra separada de la vista de contenidos de un directorio. JTabbedPane: contiene muchos componentes pero slo puede mostrar un conjunto de ellos a la vez, el usuario puede ir cambiando entre los distintos conjuntos de manera sencilla. Un ejemplo podran ser las distintas pestaas de una hoja de propiedades. JToolBar: grupa una serie de componentes (normalmente botones) en una fila o en una columna, pueden permitir al usuario arrastrar la barra a distintos lugares. Un ejemplo pueden ser las barras de herramientas de un procesador de textos como MS Word. Adems tambin existen los siguientes contenedores intermedios que se encuentran ms especializados: JInternalFrame: permite mostrar una ventana dentro de otra. Parece una ventana y ofrece toda su funcionalidad pero debe parecer siempre dentro de otra ventana (contenedor de alto nivel) que la contiene. JLayeredPane: este panel ofrece una tercera dimensin, la profundidad, para poder posicionar componentes de esta forma. Esta tercera dimensin se denomina tambin Zorder. Al aadir un componente a un panel de este tipo se especifica su profundidad mediante un entero, cuanto mayor sea el entero especificado mayor ser la profundidad en la que se sita el componente. JRootPane: esta clase representa el panel raz de un contenedor de alto nivel, como ya vimos en el apartado anterior el panel raz esta formado por las siguientes partes: panel de capas, panel de contenido, panel de cristal y barra de men. A continuacin vamos a comentar algunos de estos paneles intermedios, no vamos a tratar todos ya nos excederamos en la extensin del presente curso, no se debe olvidar que Swing tienen un gran nmero de componentes.
JPanel
Esta clase permite construir paneles de propsito general para contener componentes Swing. Por defecto un panel no muestra nada en pantalla a excepcin de su fondo, tambin por defecto los paneles son opacos, aunque se pueden hacer transparentes mediante el mtodo setOpaque() pasndole el valor false por parmetro. La clase JPanel es la versin Swing de la clase Panel de AWT. La clase JPanel permite asignar un gestor de diseo al panel para indicar la forma en la que se van aadiendo los distintos componentes al mismo, por defecto el gestor de diseo de un objeto JPanel es un FlowLayout. Swing utiliza los mismos gestores de diseo que vimos para los componentes AWT, de hecho se debe importar el paquete JAVA.AWT para poder utilizarlos, pero se aade un nuevo gestor mediante la clase BoxLayout, que veremos en el apartado correspondiente. Para aadir componentes lo haremos de la misma forma que veamos en los paneles AWT, es decir, con las distintas versiones de los mtodos add(). En el Cdigo siguiente se muestra la forma de utilizar esta clase.
import java.awt.*;
168
import java.awt.event.*; import javax.swing.*; public class Panel{ public static void main(String args[]) { JFrame ventana = new JFrame("Ventana Sencilla"); JPanel panel=new JPanel(); panel.setLayout(new BorderLayout()); JLabel etiqueta1 = new JLabel("Soy una etiqueta",JLabel.CENTER); etiqueta1.setPreferredSize(new Dimension(175, 100)); JLabel etiqueta2 = new JLabel("Soy otra etiqueta",JLabel.CENTER); etiqueta2.setPreferredSize(new Dimension(175, 100)); panel.add(etiqueta1, BorderLayout.NORTH); panel.add(etiqueta2, BorderLayout.SOUTH); ventana.getContentPane().add(panel, BorderLayout.CENTER); ventana.pack(); ventana.setVisible(true); } }
Se trata simplemente de aadir dos etiquetas a un panel (JPanel) y aadir este panel al panel de contenido de una ventana (JFrame), el resultado se puede apreciar en la Figura.
. La clase JPanel hereda de la clase JComponent, y debido a ello permite utilizar una nueva funcionalidad ofrecida por Swing, los bordes. Cada objeto de la clase JComponent puede tener uno o ms bordes. Los bordes no son realmente componentes, sino que se utilizan para delimitar visualmente una serie de componentes de otros. Para asignar un borde a un componente, en este caso un objeto de la clase JPanel, se utiliza el mtodo setBorder(). Para crear los distintos bordes que ofrece Swing se suele utilizar la clase BorderFactory. Esta clase ofrece un gran nmero de mtodos que permiten crear distintos tipos de bordes: de lneas, elevados, hundidos, marcados, con ttulos, una combinacin de dos bordes, etc. Veamos un ejemplo que asigna distintos bordes a varios paneles dentro de una ventana.
import import import public public java.awt.*; java.awt.event.*; javax.swing.*; class Panel{ static void main(String args[]) {
169
JFrame ventana = new JFrame("Ventana Sencilla"); ventana.getContentPane().setLayout(new GridLayout(7,1)); ((JPanel)ventana.getContentPane()).setBorder( BorderFactory.createEmptyBorder(10,10,10,10)); JPanel panel1=new JPanel(); JLabel etiqueta1 = new JLabel("Borde tipo lnea",JLabel.CENTER); panel1.setBorder(BorderFactory.createLineBorder(Color.black)); panel1.add(etiqueta1); ventana.getContentPane().add(panel1); JPanel panel2=new JPanel(); JLabel etiqueta2 = new JLabel("Borde elevado",JLabel.CENTER); panel2.setBorder(BorderFactory.createRaisedBevelBorder()); panel2.add(etiqueta2); ventana.getContentPane().add(panel2); JPanel panel3=new JPanel(); JLabel etiqueta3 = new JLabel("Borde hundido",JLabel.CENTER); panel3.setBorder(BorderFactory.createLoweredBevelBorder()); panel3.add(etiqueta3); ventana.getContentPane().add(panel3); JPanel panel4=new JPanel(); JLabel etiqueta4 = new JLabel("Borde decorado",JLabel.CENTER); panel4.setBorder(BorderFactory.createMatteBorder(5,5,5,5,Color.red)); panel4.add(etiqueta4); ventana.getContentPane().add(panel4); JPanel panel5=new JPanel(); JLabel etiqueta5 = new JLabel("Borde con ttulo",JLabel.CENTER); panel5.setBorder(BorderFactory.createTitledBorder( BorderFactory.createLineBorder(Color.blue),"Ttulo")); panel5.add(etiqueta5); ventana.getContentPane().add(panel5); JPanel panel6=new JPanel(); JLabel etiqueta6 = new JLabel("Borde grabado",JLabel.CENTER); panel6.setBorder(BorderFactory.createEtchedBorder()); panel6.add(etiqueta6); ventana.getContentPane().add(panel6); JPanel panel7=new JPanel(); JLabel etiqueta7 = new JLabel("Borde compuesto",JLabel.CENTER); panel7.setBorder(BorderFactory.createCompoundBorder( BorderFactory.createLineBorder(Color.yellow), BorderFactory.createRaisedBevelBorder())); panel7.add(etiqueta7); panel7.setPreferredSize(new Dimension(175,50)); ventana.getContentPane().add(panel7); ventana.pack(); ventana.setVisible(true); } }
170
Comentarios del cdigo: El primero de ellos es referente al tipo de borde utilizado en el panel de contenido, como se puede observar al recuperar el panel de contenido para asignarle un borde vaco se ha tenido que hacer un "cast" con la clase JPanel, este borde vaco se ha utilizado para crear un margen entre el panel de contenido y el contenedor, en este caso una instancia de la clase JFrame. Al panel de contenido de la ventana se le ha asignado un gestor de diseo GridLayout con siete filas y una nica columna. Algunos de los mtodos de la clase BorderFactory se encuentran sobrecargados, para permitir una mayor personalizacin de los distintos tipos de bordes que queremos asignar. A cada panel se le ha asignado un borde distinto y se le ha aadido una etiqueta (JLabel) con la descripcin del tipo de borde correspondiente.
JTabbedPane
Gracias a este contenedor intermedio podemos tener distintos componentes, normalmente otros paneles, compartiendo un mismo espacio. El usuario puede visualizar los componentes que desea ver seleccionando la pestaa correspondiente. Para crear un contenedor de este tipo primero debemos instanciar el objeto correspondiente de la clase JTabbedPane, luego se van creando los distintos componentes y se van aadiendo al contenedor mediante el mtodo addTab().
171
Antes de seguir con esta nueva clase vamos a verla en accin mediante un sencillo ejemplo. El ejemplo consiste simplemente en un objeto JTabbedPane al que se le van aadiendo distintos paneles, cada uno con sus componentes.
import java.awt.*; import java.awt.event.*; import javax.swing.*; public class PanelTab{ public static void main(String args[]) { JFrame ventana = new JFrame("Ventana Sencilla"); ImageIcon icono = new ImageIcon("icono.gif"); JTabbedPane panelTab = new JTabbedPane(); //tratamiento de eventos para el cierre de la ventana ventana.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); //se van creando paneles con componentes y se aaden al objeto JTabbedPane JPanel panel1=new JPanel(); panel1.add(new JButton("Botn",icono)); //se crea una nueva pestaa con el nuevo componente panelTab.addTab("Uno",icono,panel1,"Soy la primera pestaa"); panelTab.setSelectedIndex(0); JPanel panel2=new JPanel(); panel2.add(new JLabel("Soy una etiqueta",JLabel.CENTER)); panelTab.addTab("Dos",icono,panel2,"Soy la segunda pestaa"); JPanel panel3=new JPanel(); panel3.add(new JLabel("Soy otra etiqueta",JLabel.CENTER)); panel3.setBorder(BorderFactory.createLineBorder(Color.red)); panelTab.addTab("Tres",icono,panel3,"Soy la tercera pestaa"); JPanel panel4=new JPanel(); panel4.add(new JToggleButton("Otro botn",icono)); panelTab.addTab("Cuatro",icono,panel4,"Soy la cuarta pestaa"); JPanel panel5=new JPanel(); panel5.add(new JSlider(JSlider.HORIZONTAL,0,30,10)); panelTab.addTab("Cinco",icono,panel5,"Soy la ltima pestaa"); //se le da un tamao al contenedor de pestaas panelTab.setPreferredSize(new Dimension(400,200)); //se aade a la ventana ventana.getContentPane().add(panelTab); ventana.pack(); ventana.setVisible(true); } }
172
Si se prueba el ejemplo anterior se puede comprobar que no se ha escrito ningn cdigo para realizar el tratamiento de eventos, la clase JTabbedPane realiza este tratamiento (mostrar los componentes de la pestaa seleccionada) de forma automtica. En el ejemplo se ha utilizado un constructor de la clase JTabbedPane que no utiliza ningn parmetro, pero existe otro constructor que permite, mediante una constante que recibe como parmetro, especificar la localizacin de las pestaas. Estas constantes se encuentran definidas en la clase JTabbedPane y son las siguientes TOP, BOTTOM, LEFT y RIGHT. As si modificamos el ejemplo anterior cambiando el constructor utilizado para el panel JTabbedPane mediante la siguiente lnea de cdigo:e
JTabbedPane panelTab = new JTabbedPane(JTabbedPane.BOTTOM);
Para aadir una nueva pestaa ya sabemos que debemos utilizar el mtodo addTab(), este mtodo presenta las siguientes versiones: addTab(String texto, Component componente): el primer argumento indica el texto que va a parecer en la pestaa y el componente que va a contener. addTab(String texto, Icon icono, Component componente): en este caso adems se indica el icono que se va a mostrar en la pestaa. addTab(String texto, Icon icono, Component componente, String ayuda): en la ltima versin del mtodo addTab() se permite especificar el texto que aparecer a modo de ayuda (tooltip) cuando situemos el puntero del ratn sobre la pestaa.
173
En el ejemplo se ha utilizado el mtodo setSelectedIndex() para indicar la pestaa que por defecto se encuentra seleccionada, que ser la que mostrar sus componentes. Las pestaas comienzan a numerarse en cero. Para manipular las pestaas la clase JTabbedPane ofrece los siguientes mtodos: insertTab(String texto, Icon icono, Component componente, String ayuda, int ndice): inserta una nueva pestaa en la posicin indicada por el parmetro ndice. removeTabAt(int ndice): elimina la pestaa cuya posicin coincida con la indicada. remove(Component componente): elimina la pestaa que contenga el componente especificado como argumento. removeAll(): elimina todas las pestaas. indexOfTab(String texto): devuelve el ndice de la pestaa que posea el texto indicado int por parmetro. getSelectedIndex(): devuelve el ndice de la pestaa seleccionada actualmente. int Tambin es posible modificar la apariencia de las pestaas del panel JTabbedPane. Podemos indicar el icono que va a mostrar la pestaa segn se encuentre habilitada o deshabilitada, tambin el color de fondo y el del texto de la pestaa. As si una vez creado el panel de nuestro ejemplo aadimos las lneas de cdigo que se muestran en el Cdigo fuente siguientey obtenemos el resultado que aparece en la Figura.
//color de fondo cuando no est seleccionada la pestaa panelTab.setBackgroundAt(0,Color.red); //color del texto panelTab.setForegroundAt(0,Color.green); ImageIcon otroIcono = new ImageIcon("icono2.gif"); //icono que se muestra cuando la pestaa est desactivada panelTab.setDisabledIconAt(0,otroIcono); //se modifica el ttulo panelTab.setTitleAt(2,"Otro texto"); JPanel panelNuevo=new JPanel(); panelNuevo.add(new JTextArea(5,30)); //se cambia el componente que posee la pestaa panelTab.setComponentAt(3,panelNuevo);
Curioso resultado ya que no se muestra el icono que se utiliza para indicar que la pestaa est deshabilitada. Si utilizamos cualquiera de los mtodos anteriores ,que tiene como parmetro el ndice de la pestaa, y la pestaa no existe se producir una excepcin.
174
JToolBar
Esta clase representa una barra de herramientas, normalmente este tipo de contenedor va a contener botones con iconos dentro de una fila o columna. Estos botones cumplen las mismas funciones que las opciones de men. Por defecto el usuario puede arrastrar la barra de herramientas y situarla en los diferentes bordes del contenedor o bien como una ventana independiente. Para que este funcionamiento de arrastre de la barra sea correcto, el contenedor en el que se sita la barra de herramientas debe tener un gestor de diseo BorderLayout. Normalmente la barra de herramientas se aade en el norte del gestor de diseo y el componente al que afecta en el centro, no debe existir ningn componente ms en el centro del contenedor. En el Cdigo siguiente se puede observar como se utiliza un objeto de la clase JToolBar. En nuestro caso vamos a tener una ventana (JFrame) que va a tener un panel de contenido al que se va a aadir la barra de herramientas (JToolBar) y un rea de texto (JTextArea). Como se puede ver la barra de herramientas puede contener otros tipos de componentes, no slo botones.
import java.awt.*; import java.awt.event.*; import javax.swing.*; public class BarraHerramientas{ public static void main(String args[]) { JFrame ventana = new JFrame("Ventana con barra de herramientas"); //tratamiento de eventos para el cierre de la ventana ventana.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); //iconos ImageIcon icono1=new ImageIcon("icono1.gif"); ImageIcon icono2=new ImageIcon("icono2.gif"); ImageIcon icono3=new ImageIcon("icono3.gif"); //botones JButton boton1=new JButton(icono1); JButton boton2=new JButton(icono2); JButton boton3=new JButton(icono3); //lista con elementos JComboBox combo=new JComboBox(); combo.addItem("uno"); combo.addItem("dos"); combo.addItem("tres"); //caja de texto JTextField caja=new JTextField("caja de texto"); //barra de herramientas JToolBar barra=new JToolBar(); //se aaden los botones barra.add(boton1); barra.add(boton2); barra.add(boton3); //se aade el separador a la barra de herramientas barra.addSeparator(); barra.add(combo); barra.add(caja); //se aade la barra al panel de contenido
175
ventana.getContentPane().add(barra,BorderLayout.NORTH); //se crea un rea de texto JTextArea areaTexto = new JTextArea(5,30); //se aade a un panel de scroll JScrollPane scrollPane = new JScrollPane(areaTexto); //se aade el panel de scroll al panel de contenido ventana.getContentPane().add(scrollPane,BorderLayout.CENTER); //se asigna un tamao preferido a la ventana ((JPanel)ventana.getContentPane()).setPreferredSize(new Dimension(400, 100)); ventana.pack(); ventana.setVisible(true); } }
Como se puede comprobar en el cdigo anterior, para aadir un separador a la barra de herramientas se utiliza el mtodo addSeparator(). Si queremos que la barra de herramientas permanezca fija se puede lanzar sobre el objeto de la clase JToolBar el mtodo setFloatable() pasando como argumento el valor false. Para posicionar los distintos elementos que contiene, la clase JToolBar utiliza el gestor de diseo BoxLayout, que veremos ms adelante en el apartado dedicado al mismo.
JLayeredPane
Este contenedor ofrece una tercera dimensin que permite posicionar los componentes que contiene especificando una profundidad. La profundidad de un determinado componente se especifica mediante un entero, cuanto mayor sea este entero mayor ser la profundidad del componente correspondiente. Si los componentes que contiene el panel de capas se superponen los componentes que se encuentran a una mayor profundidad se muestran encima de los de una menor profundidad. Vimos que los contenedores de alto nivel de Swing contienen un panel de raz que a su vez contiene un panel de capas (layered pane), normalmente no se suele utilizar el JLayeredPane del JRootPane, sino que se crea un objeto JLayeredPane distinto para utilizarlo dentro de otro panel. Esto mismo ocurre en nuestro ejemplo, que se sita un objeto de la clase JLayeredPane dentro del panel de contenido de una ventana (JFrame). Para aadir un componente a un JLayeredPane se utiliza el mtodo add(), en este mtodo se debe indicar la profundidad del componente, es decir, la capa en la que se encuentra. En el siguiente ejemplo se van aadiendo a una instancia de la clase JLayeredPane etiquetas con color de fondo a distintas profundidades. Veamos el Cdigo:.
import import import public java.awt.*; java.awt.event.*; javax.swing.*; class PanelCapas{
176
static private String[] textosCapa = { "Amarillo(0)", "Magenta (1)", "Azul (2)", "Rojo (3)", "Verde (4)" }; static private Color[] coloresCapa = { Color.yellow, Color.magenta, Color.blue, Color.red, Color.green }; public static void main(String args[]) { JFrame ventana = new JFrame("Ventana con panel de capas"); JLayeredPane panelCapas=new JLayeredPane(); //tratamiento de eventos para el cierre de la ventana ventana.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); //punto de origen Point origen = new Point(10, 20); //separacin entre etiquetas int separacion = 35; //se van creando las etiquetas de color a distinta profundidad for (int i = 0; i < textosCapa.length; i++) { JLabel etiqueta = creaEtiqueta(textosCapa[i],coloresCapa[i],origen); //se aade la etiqueta al panel de capas panelCapas.add(etiqueta,new Integer(i)); origen.x += separacion; origen.y += separacion; } //se indica un tamao para le panel panelCapas.setPreferredSize(new Dimension(300, 310)); //se aade el panel creado al panel de contenido de la ventana ventana.getContentPane().add(panelCapas); ventana.pack(); ventana.setVisible(true); } //mtodo para la creacin de etiquetas con un texto y un color en un punto //de origen. private static JLabel creaEtiqueta(String texto,Color color,Point origen) { JLabel etiqueta = new JLabel(texto); //se alinea el texto en la etiqueta etiqueta.setVerticalAlignment(JLabel.TOP); etiqueta.setHorizontalAlignment(JLabel.CENTER); etiqueta.setOpaque(true); etiqueta.setBackground(color); //se asigna un borde a la etiqueta etiqueta.setBorder(BorderFactory.createLineBorder(Color.black)); etiqueta.setBounds(origen.x, origen.y, 140, 140); return etiqueta; } }
177
Como se puede comprobar a la vista del cdigo se han creado dos arrays que contienen por un lado los textos de las etiquetas y por otro los colores de las etiquetas. Se ha utilizado un bucle for para ir recorriendo estos arrays y crear las etiquetas mediante el mtodo crearEtiqueta(). Como se puede ver el mtodo y los atributos utilizado son estticos, ya que los utilizamos directamente en el mtodo main() si lanzarlos sobre una instancia de una clase. Dentro de una capa, es decir, a una profundidad determinada, se puede especificar la posicin de un componente. Es posible por lo tanto definir la posicin de un componente respecto al resto de componentes dentro de la misma capa, para ello existe una versin del mtodo add() que posee un tercer parmetro para indicar esta posicin dentro de la capa. El valor de esta posicin va desde -1 hasta n-1, dnde n es el nmero de componentes dentro de la capa. Al contrario que las capas, cuanto menor es el nmero mayor es la profundidad del componente dentro de la capa. Utilizar -1 es equivalente a utilizar n-1, indica la posicin ms en el fondo. Si utilizamos 0 el componente se encontrar por encima del resto. Si al ejemplo anterior le aadimos las lneas que muestra el Cdigo siguiente, una vez creadas las etiquetas en las distintas capas.
ImageIcon icono = new ImageIcon("imagen.gif"); JLabel nuevaEtiqueta=new JLabel(icono); nuevaEtiqueta.setBounds(180,75,icono.getIconWidth(),icono.getIconHeight()); panelCapas.add(nuevaEtiqueta,new Integer(2),0);
Lo que se hace es aadir una nueva etiqueta con un icono en la capa 2 de modo que quede por encima de la etiqueta de color que ya exista en esa misma capa. El nuevo aspecto del panel de capas es el que aparece en la Figura.
178
Tambin es posible mover un componente de una capa a otra, para ello se utiliza el mtodo setLayer(). As si queremos mover la etiqueta con el icono a la capa 4 escribiremos lo que indica la lnea de codigo siguiente:
panelCapas.setLayer(nuevaEtiqueta,4,0);
El ltimo argumento del mtodo setLayer() es la posicin del componente dentro de la nueva capa. Para mover un componente dentro de una capa a la primera posicin se utiliza el mtodo moveToFront(), y para enviarlo al fondo el mtodo moveToBack(), ambos mtodos tiene como parmetro el componente al que se quiere cambiar de posicin dentro de una capa. Si utilizamos el mtodo moveToBack() en nuestro cdigo tendr un nuevo aspecto del ejemplo :.
panelCapas.moveToBack(nuevaEtiqueta);
179
180
JButton
Esta clase hereda de la clase AbstractButton, al igual que lo hacen otras clases como pueden ser JCheckbox, JMenuItem o JToggleButton. Por lo tanto la clase JButton posee una serie de funcionalidades que son comunes a todas las clases que heredan de la clase AbstractButton. Un botn puede contener texto o imgenes o ambos elementos. El texto que contiene un botn se puede alinear con respecto a la imagen, tambin se pueden especificar teclas de teclado alternativas, que se indicarn mediante el subrayado de la letra del texto correspondiente. Cuando un botn se encuentra deshabilitado el Look and Feel correspondiente genera de forma automtica el aspecto del botn. Sin embargo, se puede indicar una imagen para que se muestre cuando el botn se encuentre deshabilitado. Para ver la clase JButton en accin vamos a utilizar un ejemplo muy sencillo que consiste en mostrar tres botones con imgenes y ayudas (tooltips), la pulsacin de dos de los tres botones activar o desactivar el botn central.
import java.awt.*; import java.awt.event.*; import javax.swing.*; public class Botones extends JFrame implements ActionListener{ private JButton botonIzq; private JButton botonDer; private JButton botonCentro; private ImageIcon iconoIzq=new ImageIcon("icono3.gif"); private ImageIcon iconoDer=new ImageIcon("icono1.gif"); private ImageIcon iconoCentro=new ImageIcon("icono2.gif"); public Botones (){ super("Ventana con botones"); this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); } public void creaBotones(){ //se instancia el botn indicando la imagen botonIzq=new JButton("Desactiva botn central",iconoIzq); //se da formato al texto botonIzq.setVerticalTextPosition(AbstractButton.CENTER); botonIzq.setHorizontalTextPosition(AbstractButton.LEFT); //se indica la tecla asociada al botn botonIzq.setMnemonic(KeyEvent.VK_D); //se indica el comando de accin que se utiliza cuando se pulsa el botn botonIzq.setActionCommand("desactiva"); //se asigna un tooltip botonIzq.setToolTipText("Desactivo el botn central"); botonIzq.setEnabled(false); botonDer=new JButton("Activa botn central",iconoDer); botonDer.setVerticalTextPosition(AbstractButton.CENTER);
181
botonDer.setHorizontalTextPosition(AbstractButton.RIGHT); botonDer.setMnemonic(KeyEvent.VK_A); botonDer.setActionCommand("activa"); botonDer.setToolTipText("Activo el botn central"); botonCentro=new JButton("Botn central",iconoCentro); botonCentro.setEnabled(false); botonCentro.setToolTipText("No hago nada"); //se registran los oyentes, que son la misma clase. botonIzq.addActionListener(this); botonDer.addActionListener(this); } public void aadeBotones(){ //creamos un panel para aadir los botones JPanel panelContenido=new JPanel(); panelContenido.add(botonIzq); panelContenido.add(botonCentro); panelContenido.add(botonDer); //establecemos este panel como panel de contenido de la ventana setContentPane(panelContenido); } public void actionPerformed(ActionEvent evento){ if (evento.getActionCommand().equals("desactiva")){ //se desactiva el botn central y se actualiza //el estado de los otros botones botonCentro.setEnabled(false); botonIzq.setEnabled(false); botonDer.setEnabled(true); }else{ //se activa el botn central botonCentro.setEnabled(true); botonIzq.setEnabled(true); botonDer.setEnabled(false); } } public static void main(String args[]) { Botones ventana = new Botones(); ventana.creaBotones(); ventana.aadeBotones(); ventana.pack(); ventana.setVisible(true); } }
Se puede comprobar si pulsamos las teclas Alt+D o Alt+A tienen el mismo efecto que pulsar el botn correspondiente, adems esta combinacin de teclas aparece descrita en el tooltip. Podemos indicar el botn activo por defecto de un contenedor de alto nivel mediante el mtodo setDefaultButton() de la clase JRootPane. El botn activo por defecto aparece destacado del resto y si el usuario pulsa la tecla Enter es equivalente a pulsar este botn. Podemos modificar el ejemplo anterior para que el botn activo por defecto sea el botn de la derecha.
getRootPane().setDefaultButton(botonDer);
182
Otra caracterstica que nos ofrece la clase JButton es la posibilidad de utilizar etiquetas HTML dentro del texto del botn, para ello se debe poner la etiqueta <html> al inicio del texto, y a continuacin se pueden utilizar las etiquetas HTML que se consideren necesarias para dar el formato conveniente al texto. As por ejemplo, si retomamos nuestro cdigo anterior y modificamos las lneas en las que se crean los botones, escribiendo:
botonIzq=new JButton("<html><b><i><u>D</u>esactiva botn central</i><b>",iconoIzq); .......... botonDer=new JButton("<html><font color='red' size='4'>"+ "<u>A</u>ctiva botn central</font>",iconoDer); ........... botonCentro=new JButton("<html><small><i>Botn central</i></small>",iconoCentro);
Como se puede apreciar aunque los botones se encuentran desactivas, ahora el texto no aparece en color gris atenuado. Adems hemos tenido que utilizar la etiqueta de subrayado de HTML (<u></u>) para indicar la tecla que se corresponde con cada botn.
JCheckbox
Esta clase representa a las casillas de verificacin, tambin se pueden utilizar estas casillas de verificacin dentro de elementos de men, mediante la clase JCheckBoxMenuItem. Debido a que la clase JCheckBox hereda de la clase AbstractButton, presenta una serie de caractersticas comunes a todos los tipos de botones, que ya hemos visto en la seccin anterior con la clase JButton. Estas caractersticas son la posibilidad de utilizar imgenes, tooltips, teclas alternativas, etc. Las casillas de verificacin se suelen agrupar y es posible seleccionar, una, algunas o ninguna de ellas. En el siguiente ejemplo se muestra la utilizacin de la clase JCheckBox para configurar el aspecto de un botn. Se dispone de tres casillas de verificacin, una para indicar que el botn va a tener un icono, otra para indicar que tiene un tooltip y otra para indicar que tiene texto.
import java.awt.*; import java.awt.event.*; import javax.swing.*; public class Casillas extends JFrame implements ItemListener{ //cada una de las casillas private JCheckBox chkIcono; private JCheckBox chkTexto; private JCheckBox chkToolTip; //el botn del que indicamos el aspecto private JButton boton; //panel de las casillas private JPanel panelCasillas; //panel del botn private JPanel panelBoton; private ImageIcon icono=new ImageIcon("icono2.gif");
183
public Casillas (){ super("Ventana con casillas de verificacin"); this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); getContentPane().setLayout(new GridLayout(1,2)); } public void creaCasillas(){ panelCasillas=new JPanel(); panelCasillas.setLayout(new GridLayout(3,1)); chkIcono=new JCheckBox("Icono"); chkIcono.setMnemonic(KeyEvent.VK_I); chkIcono.setSelected(true); chkIcono.addItemListener(this); panelCasillas.add(chkIcono); chkTexto=new JCheckBox("Texto"); chkTexto.setMnemonic(KeyEvent.VK_T); chkTexto.setSelected(true); chkTexto.addItemListener(this); panelCasillas.add(chkTexto); chkToolTip=new JCheckBox("Tooltip"); chkToolTip.setMnemonic(KeyEvent.VK_L); chkToolTip.setSelected(true); chkToolTip.addItemListener(this); panelCasillas.add(chkToolTip); getContentPane().add(panelCasillas); } public void creaBoton(){ panelBoton=new JPanel(); panelBoton.setLayout(new FlowLayout()); boton=new JButton("Soy un botn",icono); boton.setToolTipText("Soy un botn"); panelBoton.add(boton); getContentPane().add(panelBoton); } public void itemStateChanged(ItemEvent evento) { Object fuente=evento.getSource(); int estado=evento.getStateChange(); if (fuente==chkIcono){ if(estado==ItemEvent.DESELECTED) boton.setIcon(null); else boton.setIcon(icono); } if (fuente==chkTexto){ if(estado==ItemEvent.DESELECTED) boton.setText(""); else boton.setText("Soy un botn"); } if (fuente==chkToolTip){ if(estado==ItemEvent.DESELECTED) boton.setToolTipText(""); else boton.setToolTipText("Soy un botn"); } } public static void main(String args[]) { Casillas ventana = new Casillas(); ventana.creaCasillas(); ventana.creaBoton();
184
ventana.pack(); ventana.setVisible(true); } }
Como se puede observar la clase JButton lanza un evento del tipo ItemEvent cuando se modifica el estado de un objeto. En el mtodo itemStateChanged() debemos averiguar primero la fuente del evento, es decir, la casilla que ha visto modificado su estado, y a continuacin si la casilla en cuestin ha sido seleccionada o no.
JRadioButton
Los botones de opcin se suelen encontrar en grupos en los que, por convencin, nicamente uno de los botones puede encontrarse seleccionado a un mismo tiempo. Tambin podemos utilizar botones de opciones en elementos de men mediante la clase JRadioButtonMenuItem. La clase JRadioButton tambin tiene como superclase o clase padre a la clase AbstractButton, por lo tanto presentar el comportamiento comn a todas las clases que heredan de AbstractButton, por ejemplo podemos indicar que un objeto JRadioButton muestre una imagen o un tooltip. Para esta clase vamos a mostrar un ejemplo que ofrecer un dibujo de un animal distinto segn el botn de opcin que se encuentre seleccionado en cada momento. Para agrupar los botones se utiliza la clase ButtonGroup. Nuestro ejemplo va a contener tres botones de opcin agrupados para mostrar tres dibujos distintos.
import java.awt.*; import java.awt.event.*; import javax.swing.*; public class Opciones extends JFrame implements ActionListener{ //cada una de las Opciones private JRadioButton opGato; private JRadioButton opCerdo; private JRadioButton opConejo; //imagen private JLabel imagen; //panel de las Opciones private JPanel panelOpciones; //panel de la imagen private JPanel panelImagen; public Opciones (){ super("Ventana con opciones"); this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); }
185
}); getContentPane().setLayout(new GridLayout(1,2)); } public void creaOpciones(){ panelOpciones=new JPanel(); panelOpciones.setLayout(new GridLayout(3,1)); opGato=new JRadioButton("Gato"); opGato.setMnemonic(KeyEvent.VK_G); opGato.addActionListener(this); opGato.setActionCommand("gato.gif"); panelOpciones.add(opGato); opCerdo=new JRadioButton("Cerdo"); opCerdo.setMnemonic(KeyEvent.VK_C); opCerdo.setSelected(true); opCerdo.addActionListener(this); opCerdo.setActionCommand("cerdo.gif"); panelOpciones.add(opCerdo); opConejo=new JRadioButton("Conejo"); opConejo.setMnemonic(KeyEvent.VK_J); opConejo.addActionListener(this); opConejo.setActionCommand("conejo.gif"); panelOpciones.add(opConejo); //se agrupan las opciones ButtonGroup grupo=new ButtonGroup(); grupo.add(opGato); grupo.add(opCerdo); grupo.add(opConejo); getContentPane().add(panelOpciones); } public void creaImagen(){ panelImagen=new JPanel(); panelImagen.setLayout(new BorderLayout()); imagen = new JLabel(new ImageIcon("cerdo.gif")); imagen.setPreferredSize(new Dimension(177, 122)); panelImagen.add(imagen,BorderLayout.CENTER); getContentPane().add(panelImagen); } public void actionPerformed(ActionEvent evento){ String comando=evento.getActionCommand(); imagen.setIcon(new ImageIcon(comando)); } public static void main(String args[]) { Opciones ventana = new Opciones(); ventana.creaOpciones(); ventana.creaImagen(); ventana.pack(); ventana.setVisible(true); } }
La clase JRadioButton, en lo que a tratamiento de eventos se refiere, funciona igual que la clase JButton, el botn que se pulse (seleccione) va a lanzar un evento de la clase
186
ActionEvent, para diferenciar entre los distintos botones se utiliza el ActionCommand del evento ActionEvent. Para mostrar la imagen se utiliza una etiqueta (JLabel) con un icono (ImageIcon).
JComboBox
Esta clase representa una lista desplegable de opciones, que puede ser editable o no. Cuando el usuario pulsa la lista, el objeto JComboBox muestra un men de elementos para elegir. Una lista desplegable editable es similar a una caja de texto (JTextField) con un pequeo botn. El usuario puede escribir un valor en la caja de texto o elegir un valor del men.
import java.awt.*; import java.awt.event.*; import javax.swing.*; public class Combo extends JFrame implements ActionListener{ //lista desplegable private JComboBox listaOpciones; //opciones de la lista String[] opciones={"Gato","Cerdo","Conejo"}; //imagen private JLabel imagen; public Combo(){ super("Ventana con lista desplegable"); this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); } public void creaCombo(){ listaOpciones=new JComboBox(opciones); listaOpciones.setSelectedIndex(0); getContentPane().add(listaOpciones,BorderLayout.NORTH); listaOpciones.addActionListener(this); } public void creaImagen(){ imagen = new JLabel(new ImageIcon("gato.gif")); imagen.setPreferredSize(new Dimension(177, 122)); getContentPane().add(imagen,BorderLayout.CENTER); } public void actionPerformed(ActionEvent evento){ JComboBox fuente=(JComboBox)evento.getSource(); //se obtiene el elemento seleccionado String seleccion=(String)fuente.getSelectedItem(); //se cambia la imagen imagen.setIcon(new ImageIcon(seleccion+".gif")); } public static void main(String args[]) { Combo ventana = new Combo(); ventana.creaCombo(); ventana.creaImagen(); ventana.pack(); ventana.setVisible(true); } }
187
Al seleccionar un elemento del objeto JComBox se lanza un evento ActionEvent, es decir, el mismo evento que se lanza cuando se pulsa un botn. El constructor utilizado para instanciar un objeto de la clase JComboBox recibe como argumento un array de cadenas (String), que representa las opciones que muestra la lista desplegable. Para indicar la opcin seleccionada por defecto utilizamos el mtodo setSelectedIndex() sobre el objeto de la clase JComboBox, y para obtener la opcin seleccionada y as mostrar la imagen correspondiente se utiliza el mtodo getSelectedItem() de la clase JComboBox.
JMenu
Un men permite elegir al usuario entre mltiples opciones disponibles. Los mens parecen normalmente dentro de barras de men o como mens contextuales (men popup). Un elemento de men hereda tambin de la clase AbstractButton, como muchas de las clases que hemos visto hasta ahora, veamos en la Figura 92, la jerarqua que presentan los distintos componentes Swing relacionados con la creacin de mens.
A continuacin se comentan cada una de estas clases. JMenuBar: representa la barra de men que va a contener los distintos elementos de men, que sern objetos de la clase JMenu. JMenu: es una opcin de men determinada, que contiene varios elementos de men, que
188
sern objetos de la clase JMenuItem, y tambin puede contener submens, es decir, objetos de la clase JMenu. JMenuItem: es un elemento de men. JCheckBoxMenuItem: elemento de men que posee una casilla de verificacin. JRadioButtonMenuItem: elemento de men que posee un botn de opcin. JSeparator: elemento de men especial que ofrece una separacin entre elementos de men de una misma opcin de men. JPopupMenu: representa un men contextual o de aparicin sbita, contiene elementos de men (JMenuItem). En el Cdigo siguiente se muestra la utilizacin de todas estas clases, adems se va mostrando en un rea de texto (JTextArea) los distintos eventos que van generando los distintos componentes de men junto con la fuente que ha producido dichos eventos. Como ocurre en estos casos primero se muestra el cdigo fuente del ejemplo.
import java.awt.*; import java.awt.event.*; import javax.swing.*; public class Menus extends JFrame implements ActionListener, ItemListener{ JMenuBar barraMenu; JMenu menu, submenu; JMenuItem elementoMenu; JRadioButtonMenuItem rbElementoMenu; JCheckBoxMenuItem cbElementoMenu; JTextArea texto; JScrollPane panelScroll; public Menus(){ super("Ventana con mltiples mens"); this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); } public void creaMenu(){ //Se crea la barra de men barraMenu = new JMenuBar(); setJMenuBar(barraMenu); //se crea el primer men menu = new JMenu("Un Men"); menu.setMnemonic(KeyEvent.VK_E); barraMenu.add(menu); //unos cuantos elementos de men elementoMenu=new JMenuItem("Elemento de men de texto",KeyEvent.VK_E); elementoMenu.addActionListener(this); menu.add(elementoMenu); //se asigna tecla de acceso rpido elementoMenu.setAccelerator(KeyStroke.getKeyStroke( KeyEvent.VK_1, ActionEvent.ALT_MASK)); elementoMenu=new JMenuItem("Texto e icono",new ImageIcon("icono2.gif")); elementoMenu.setMnemonic(KeyEvent.VK_T); elementoMenu.addActionListener(this); menu.add(elementoMenu); elementoMenu=new JMenuItem(new ImageIcon("icono2.gif")); elementoMenu.setMnemonic(KeyEvent.VK_D); elementoMenu.addActionListener(this); menu.add(elementoMenu); //se aade un separador menu.addSeparator();
189
//un grupo de elementos de men de botones de opcin. ButtonGroup grupo=new ButtonGroup(); rbElementoMenu=new JRadioButtonMenuItem("Botn de opcin"); rbElementoMenu.setSelected(true); rbElementoMenu.setMnemonic(KeyEvent.VK_O); grupo.add(rbElementoMenu); rbElementoMenu.addActionListener(this); menu.add(rbElementoMenu); rbElementoMenu= new JRadioButtonMenuItem("Otro botn de opcin"); rbElementoMenu.setMnemonic(KeyEvent.VK_B); grupo.add(rbElementoMenu); rbElementoMenu.addActionListener(this); menu.add(rbElementoMenu); //Un grupo de casillas de verificacin menu.addSeparator(); cbElementoMenu=new JCheckBoxMenuItem("Casilla de verificacin"); cbElementoMenu.setMnemonic(KeyEvent.VK_C); cbElementoMenu.addItemListener(this); menu.add(cbElementoMenu); cbElementoMenu=new JCheckBoxMenuItem("Otro ms"); cbElementoMenu.setMnemonic(KeyEvent.VK_M); cbElementoMenu.addItemListener(this); menu.add(cbElementoMenu); //un submen menu.addSeparator(); submenu=new JMenu("Un submen"); submenu.setMnemonic(KeyEvent.VK_S); elementoMenu=new JMenuItem("Un elemento de men del submen"); elementoMenu.setAccelerator(KeyStroke.getKeyStroke( KeyEvent.VK_2, ActionEvent.ALT_MASK)); elementoMenu.addActionListener(this); submenu.add(elementoMenu); elementoMenu= new JMenuItem("Otro elemento de men"); elementoMenu.addActionListener(this); submenu.add(elementoMenu); menu.add(submenu); //Segndo men de la barra de men menu = new JMenu("Otro Men"); menu.setMnemonic(KeyEvent.VK_M); barraMenu.add(menu); } public void creaTexto(){ texto= new JTextArea(10, 50); texto.setEditable(false); panelScroll= new JScrollPane(texto); getContentPane().add(panelScroll, BorderLayout.CENTER); } public void actionPerformed(ActionEvent evento){ JMenuItem fuente=(JMenuItem)(evento.getSource()); String mensaje= "ActionEvent detectado.\n" + " Fuente del evento: " + fuente.getText()+"\n"; texto.append(mensaje); } public void itemStateChanged(ItemEvent evento) { String estado=""; JMenuItem fuente =(JMenuItem)(evento.getSource()); if (evento.getStateChange()==ItemEvent.SELECTED) estado="Seleccionado"; else estado="No seleccionado"; String mensaje="ItemEvent detectado.\n" + " Fuente del evento: " + fuente.getText()+"\n" + " Nuevo estado: "+estado+"\n";
190
texto.append(mensaje); } public static void main(String args[]) { Menus ventana = new Menus(); ventana.creaMenu(); ventana.creaTexto(); ventana.pack(); ventana.setVisible(true); } }
Los elementos de men lanzan eventos ActionEvent, y los elementos de men del tipo JCheckBoxMenuItem lanzan eventos de la clase ItemEvent. El rea de texto (JTextArea) encargada de ir mostrando los eventos que se producen se aade a un panel de scroll (ScrollPane). Como se puede ver se han aadido dos teclas rpidas mediante el mtodo setAccelerator(). Un contextual o de aparicin sbita (popup) se encuentra representado por la clase JPopupMenu y debe registrarse un oyente de ratn en cada componente que tenga asociado el men popup, el oyente debe detectar que el usuario a pulsado el botn derecho del ratn para mostrar el men contextual correspondiente. El oyente que se ocupa de mostrar el men contextual lanza el mtodo show() sobre la instancia correspondiente de la clase JPopupMenu. El mtodo show() recibe como parmetros el componente al que se asocia el men y las coordenadas de la pantalla en la que se quiere mostrar el men contextual. Vamos a modificar el ejemplo anterior en el que utilizbamos la barra de men y vamos a aadir un men contextual (JPopupMenu) que se asociar al rea de texto (JTextArea) en la que se muestran los eventos lanzados por los distintos elementos de men. Se va aadir un nuevo atributo a nuestra clase, llamado menuPopup que pertenece a la clase JPopupMenu. Tambin se va a aadir un nuevo mtodo llamado creaPopup().
public void creaPopup(){ menuPopup=new JPopupMenu(); //se crean y aadden los distinos elementos de men de la misma manera elementoMenu=new JMenuItem("Primer elemento del popup",KeyEvent.VK_P); elementoMenu.addActionListener(this); menuPopup.add(elementoMenu); elementoMenu=new JMenuItem("Segundo del popup",new ImageIcon("icono2.gif"));
191
elementoMenu.setMnemonic(KeyEvent.VK_S); elementoMenu.addActionListener(this); menuPopup.add(elementoMenu); menuPopup.addSeparator(); cbElementoMenu=new JCheckBoxMenuItem("Tercer elemento"); cbElementoMenu.setMnemonic(KeyEvent.VK_T); cbElementoMenu.addItemListener(this); menuPopup.add(cbElementoMenu); //se crea el oyente y se registra para el rea de texto PopupListener oyente=new PopupListener(); texto.addMouseListener(oyente); }
Este mtodo se puede lanzar una vez utilizado el mtodo creaTexto(). Como se puede observar se crea una instancia de un objeto de la clase PopupListener, esta clase es una clase interna que hereda de la clase MouseAdapter y que va a ser el oyente de nuestro men contextual y el que va a mostrarlo. El cdigo de esta clase adaptadora interna es:
class PopupListener extends MouseAdapter{ public void mousePressed(MouseEvent evento){ menuPopup.show(evento.getComponent(),evento.getX(),evento.getY()); } }
Como se puede comprobar a la vista de la figura anterior, seguimos recogiendo los eventos de los distintos elementos de men, incluso del men contextual. Pero si el lector prueba este ejemplo comprobar que el men contextual parecer tambin cuando se pulse el botn izquierdo del ratn, y no slo cuando se pulse el botn derecho, que sera lo deseable. La clase MouseEvent ofrece el mtodo isPopupTrigger() para averiguar si debemos mostrar el men popup o no. El mtodo isPopupTrigger() devolver verdadero si la pulsacin del ratn se corresponde con un evento que debe mostrar un men contextual, en el caso de Windows se corresponde con el botn derecho del ratn. As nuestro cdigo fuente del adaptador del ratn quedara como se muestra en el Cdigo :
class PopupListener extends MouseAdapter{ public void mousePressed(MouseEvent evento){ if (evento.isPopupTrigger()){ menuPopup.show(evento.getComponent(),evento.getX(),evento.getY());
192
} } }
La sorpresa es que ahora no se muestra nunca el men contextual, no con le botn derecho ni con el botn derecho del ratn, la verdad no he conseguido discernir porque no aparece.
JSlider
Este componente permite al usuario seleccionar un valor numrico entre un rango determinado, este componente se utiliza para restringir los valores que puede ofrecer el usuario y as evitar errores y el tratamiento de los mismos. Para mostrar el funcionamiento de este componente Swing se ha utilizado un ejemplo en el que segn se indique en el selector (JSlider) se dar un tamao determinado a un botn (JButton), el botn se redimensionar segn se indique en el selector.
import java.awt.*; import java.awt.event.*; import javax.swing.*; //eventos nuevos de Swing import javax.swing.event.*; public class Selector extends JFrame implements ChangeListener{ //selector de valores private JSlider selector; private JButton boton; public Selector(){ super("Ventana con selector (JSlider)"); this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); } public void creaSelector(){ selector=new JSlider(JSlider.HORIZONTAL,0,300,50); selector.addChangeListener(this); selector.setMajorTickSpacing(100); selector.setMinorTickSpacing(10); selector.setPaintTicks(true); selector.setPaintLabels(true); getContentPane().add(selector,BorderLayout.NORTH); } public void creaBoton(){ boton=new JButton("Soy un botn"); JPanel panel=new JPanel(); panel.setLayout(new FlowLayout()); panel.add(boton); getContentPane().add(panel,BorderLayout.CENTER); boton.setSize(50,50); } public void stateChanged(ChangeEvent evento){ JSlider fuente=(JSlider)evento.getSource(); if (!fuente.getValueIsAdjusting()){ boton.setSize((int)fuente.getValue(),(int)fuente.getValue()); } } public static void main(String args[]) { Selector ventana = new Selector(); ventana.creaSelector();
193
A la vista del cdigo se pueden hacer los siguientes comentarios y apreciaciones. El constructor utilizado para crear el objeto de la clase JSlider, posee un argumento para indicar la orientacin del selector correspondiente, se corresponde con las constantes HORIZONTAL y VERTICAL de la clase JSlider. Los siguientes argumentos, especifican respectivamente, el valor mnimo, el valor mximo y el valor seleccionado inicialmente del objeto JSlider. Si no indicamos estos valores, el valor mnimo del selector ser 0, el mximo 100 y el seleccionado inicialmente 50. Para configurar inicialmente el objeto de la clase JSlider, hemos utilizado una serie de mtodos: setMajorTickSpacing(int espaciado): este mtodo nos permite indicar las separaciones mayores entre el mximo y el mnimo del selector. setMinorTickSpacing(int espaciado); este mtodo indica las separaciones menores entre el rango indicado para el componente Swing JSlider. setPaintTicks(boolean): mediante este mtodo indicamos si deseamos o no que parezcan las separaciones del objeto JSlider. setPaintLabels(boolean): nos permite indicar si queremos que se pinten las diferentes etiquetas del rango. Para registrar el oyente del selector hemos utilizado el mtodo addChangeListener(), ya que la clase JSlider lanza eventos de la clase ChangeEvent. Este es un nuevo tipo de evento que se incluye en el paquete javax.swing.event y se lanza cuando se modifica el valor actual de un objeto de la clase JSlider. El mtodo que debe implementar un oyente de eventos ChangeEvent es el mtodo stateChanged(). En el ejemplo anterior hemos visto que se ha utilizado el mtodo getValueIsAdjusting() de la clase JSlider, este mtodo devuelve verdadero mientras se est
194
seleccionando el valor del objeto JSlider, de esta forma no se redimensionar el botn hasta que el usuario no haya finalizado de arrastrar el tirador del selector, es decir, cuando el usuario ha finalizado con el proceso de seleccin. Las etiquetas que aparecen en el selector las podemos personalizar con la ayuda del mtodo setLabelTable() de la clase JSlider y de un objeto de la clase Hashtable. Para utilizar la clase Hashtable debemos importar el paquete Primero crearemos un objeto que representa una tabla hash y mediante el mtodo put() vamos indicando la posicin que queremos que ocupe la etiqueta y el objeto JLabel que va a mostrar el texto correspondiente. Veamos, en el Cdigo siguiente, un sencillo ejemplo.
public void creaSelector(){ selector=new JSlider(JSlider.HORIZONTAL,0,300,50); selector.addChangeListener(this); selector.setMajorTickSpacing(100); selector.setPaintTicks(true); //tabla de etiquetas Hashtable etiquetas=new Hashtable(); etiquetas.put(new Integer(0),new JLabel("Minsculo")); etiquetas.put(new Integer(100),new JLabel("Pequeo")); etiquetas.put(new Integer(200),new JLabel("Mediano")); etiquetas.put(new Integer(300),new JLabel("Grande")); //asignamos las etiquetas al selector selector.setLabelTable(etiquetas); selector.setPaintLabels(true); getContentPane().add(selector,BorderLayout.NORTH); }
Con la clase JSlider se da por terminado el apartado dedicado al grupo de componentes Swing cuya funcin es la de recoger la entrada del usuario, a continuacin vamos a comentar algunos componentes atmicos cuya funcin es la de simplemente mostrar informacin al usuario, algunos de los representantes de este grupo que vamos a tratar a continuacin ya los conoceremos de ejemplos anteriores, como ocurre con el componente JLabel.
195
JLabel
Ya hemos utilizado numerosas veces este til y sencillo componente de Swing, como ya sabemos su labor es la de mostrar una informacin al usuario, ya sea textual o con imgenes o con ambos elementos. En el siguiente ejemplo se muestra la utilizacin de la clase JLabel creando cuatro objetos distintos de esta clase, se ha aadido un borde a cada etiqueta para que se distingan claramente unas de otras.
import java.awt.*; import java.awt.event.*; import javax.swing.*; public class Etiquetas extends JFrame{ private JLabel etiqueta1; private JLabel etiqueta2; private JLabel etiqueta3; private JLabel etiqueta4; private ImageIcon icono; public Etiquetas(){ super("Ventana con etiquetas"); this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); } public void creaEtiquetas(){ icono=new ImageIcon("icono2.gif"); etiqueta1=new JLabel("Imagen y texto",icono,JLabel.CENTER); etiqueta1.setVerticalTextPosition(JLabel.BOTTOM); etiqueta1.setHorizontalTextPosition(JLabel.CENTER); etiqueta1.setBorder(BorderFactory.createLineBorder(Color.red)); etiqueta2=new JLabel("Slo texto"); etiqueta2.setHorizontalAlignment(JLabel.RIGHT); etiqueta2.setVerticalAlignment(JLabel.TOP); etiqueta2.setBorder(BorderFactory.createLineBorder(Color.blue)); etiqueta3=new JLabel(icono); etiqueta3.setBorder(BorderFactory.createLineBorder(Color.green)); etiqueta4=new JLabel("Imagen y texto",icono,JLabel.CENTER); etiqueta4.setVerticalTextPosition(JLabel.TOP); etiqueta4.setHorizontalTextPosition(JLabel.CENTER); etiqueta4.setBorder(BorderFactory.createLineBorder(Color.pink)); } public void aadeEtiquetas(){ getContentPane().setLayout(new GridLayout(1,4,5,5)); getContentPane().add(etiqueta1); getContentPane().add(etiqueta2); getContentPane().add(etiqueta3); getContentPane().add(etiqueta4); } public static void main(String args[]) { Etiquetas ventana = new Etiquetas(); ventana.creaEtiquetas(); ventana.aadeEtiquetas(); ventana.pack(); ventana.setVisible(true); }}
196
Como se puede observar en el ejemplo anterior es posible especificar la alineacin del texto respecto a la imagen que contiene la etiqueta utilizando el mtodo setVerticalTextPosition() y setHorizontalTextPostion(), y mediante las constantes LEFT, CENTER, RIGHT, TOP y BOTTOM. El valor por defecto es centrado (CENTER). Tambin se puede indicar la alineacin de los componentes dentro de la etiqueta con los mtodos setHorizontalAlignment() y setVerticalAlignment(). Al igual que suceda con la clase JButton, con los componentes JLabel podemos utilizar cdigo HTML para indicar el formato del texto que se va a mostrar. Veamos un ejemplo:l
import java.awt.*; import java.awt.event.*; import javax.swing.*; public class Etiquetas extends JFrame{ private JLabel etiqueta1; private JLabel etiqueta2; private JLabel etiqueta3; private JLabel etiqueta4; private ImageIcon icono; public Etiquetas(){ super("Ventana con etiquetas"); this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); } public void creaEtiquetas(){ icono=new ImageIcon("icono2.gif"); etiqueta1=new JLabel("<html><font size='4' color='red'><b><i>"+ "Imagen y texto</b></i></font>",icono,JLabel.CENTER); etiqueta1.setVerticalTextPosition(JLabel.BOTTOM); etiqueta1.setHorizontalTextPosition(JLabel.CENTER); etiqueta1.setBorder(BorderFactory.createLineBorder(Color.red)); etiqueta2=new JLabel("<html><ul><li>Elemento 1<li>Elemento2</ul>"); etiqueta2.setBorder(BorderFactory.createLineBorder(Color.blue)); etiqueta3=new JLabel("<html><table border='1'><tr><td>Celda 1</td>"+ "<td>Celda2</td></tr><tr><td align='center' colspan='2'>Celda 3</td>"+" </tr></table>", JLabel.CENTER); etiqueta3.setBorder(BorderFactory.createLineBorder(Color.green)); etiqueta4=new JLabel("<html><h2><i>Imagen y texto</i></h2><hr>", icono,JLabel.CENTER); etiqueta4.setVerticalTextPosition(JLabel.TOP); etiqueta4.setHorizontalTextPosition(JLabel.CENTER); etiqueta4.setBorder(BorderFactory.createLineBorder(Color.pink)); } public void aadeEtiquetas(){ getContentPane().setLayout(new GridLayout(4,1,15,15)); getContentPane().add(etiqueta1); getContentPane().add(etiqueta2); getContentPane().add(etiqueta3); getContentPane().add(etiqueta4); } public static void main(String args[]) {
197
JToolTip
Dentro del grupo de componentes atmicos de Swing encargados exclusivamente de mostrar informacin, tambin podemos encontrar la clase JToolTip, esta clase representa las pequeas ayudas que se muestran en forma de pequeo texto explicativo (tooltip) cuando nos situamos sobre un componente determinado. Aunque realmente no vamos a utilizar nunca directamente la clase JToolTip, sino que utilizaremos los mtodos setToolTipText() y getTootTipText() de la clase JComponent, ya hemos visto la utilizacin de estos mtodos en ejemplos anteriores, con el primero asignamos un tooltip a un componente Swing y con el segundo obtenemos el tooltip que tiene asignando un componente Swing determinado.
JProgressBar
Este componente Swing muestra grficamente el progreso de una operacin determinada, la barra de progreso va mostrando el porcentaje de tarea que se ha realizado, nos va informando de la evolucin de un proceso. Al crear una instancia de la clase JProgressBar podemos indicar la orientacin que va a tener la barra de progreso, para ello se utilizan las constantes HORIZONTAL y
198
VERTICAL de la clase JProgressBar. Tambin podemos indicar el mximo y el mnimo de la barra de progreso. Los mximos y mnimos los podemos manipular con los mtodos getMinumum()/setMinimum() y getMaximum()/setMaximum(). Para incrementar el valor actual de la barra de progreso, que se corresponde con el proceso actual, disponemos del mtodo setValue(). A continuacin se ofrece un sencillo ejemplo que consiste en ir pulsando un botn que incrementar por cada pulsacin el valor actual de la barra de progreso. Al llegar al mximo se emitir un sonido y despus se volver la barra de progreso a su estado inicial.
import java.awt.*; import java.awt.event.*; import javax.swing.*; public class BarraProgreso extends JFrame implements ActionListener{ private JProgressBar barra; //valor que se va incrementando private int valor; private JButton boton; public BarraProgreso(){ super("Ventana con barra de progreso"); this.valor=0; this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); } public void creaBarra(){ //una barra de mnimo 0 y mximo 100 barra=new JProgressBar(JProgressBar.HORIZONTAL,0,100); //valor inicial barra.setValue(0); //indicamos que aparecen las etiquetas del progreso barra.setStringPainted(true); getContentPane().add(barra,BorderLayout.NORTH); } public void creaBoton(){ boton=new JButton("Incrementar"); JPanel panel=new JPanel(); panel.setLayout(new FlowLayout()); panel.add(boton); boton.addActionListener(this); getContentPane().add(panel,BorderLayout.CENTER); } public void actionPerformed(ActionEvent event){ //se incrementa el valor valor=valor+10; if (valor>barra.getMaximum()){ //hemos sobrepasado el mximo de la barra de progreso barra.setValue(barra.getMinimum()); valor=barra.getMinimum(); } else{ barra.setValue(valor); if(valor==barra.getMaximum()) //se emite un pitido al llegar al mximo Toolkit.getDefaultToolkit().beep(); } } public static void main(String args[]) {
199
Como se puede comprobar en el ejemplo, la barra de progreso muestra el tanto por ciento (%) de la operacin realizada, este tanto por ciento lo calcula teniendo en cuenta el valor actual y sus valores mximo y mnimo. Para que se muestre el etiqueta del tanto por ciento se ha utilizado el mtodo setStringPainted() pasndole como argumento el valor true. Podemos mostrar en la barra de progreso cualquier texto mediante el mtodo setString() de la clase JProgressBar y as variar el comportamiento por defecto de la barra de progreso. Para obtener el tanto por ciento de la tarea realizada (completada) representada por la barra de progreso disponemos del mtodo getPercentComplete(). Este es el ltimo de los componentes Swing pertenecientes al grupo de componentes encargados de mostrar informacin, los siguientes componentes pertenecen al grupo de los componentes atmicos que muestran tambin una informacin pero de una forma ms compleja y estructurada, adems en algunos casos permiten editar esta informacin.
JColorChooser
Este componente Swing adems de ofrecer una informacin estructurada, como pueden ser los distintos colores, nos permite seleccionar un elemento determinado de esa informacin. La clase JColorChooser representa un componente Swing atmico, pero bastante complejo y completo que permite seleccionar colores de distintas formas. Adems este componente permite mostrar de forma sencilla un dilogo que permite seleccionar tambin un color. Veremos dos ejemplos de las dos formas que podemos utilizar el componente JColorChooser: como si se tratara de otro componente ms o como un dilogo.
200
En el Look & Feel de Java (en el apartado correspondiente hablaremos de los distintos Look & Feel y como establecerlos) una instancia de la clase JColorChooser muestra el siguiente aspecto: se encuentra dividido en dos partes, un panel con pestaas (JTabbedPane) y un panel con la muestra de la seleccin (preview). El panel de seleccin con pestaas encuentra en la parte superior y presenta tres pestaas, cada una de ellas permite una forma distinta de seleccionar los colores. El panel de muestra se encuentra en la parte inferior y va mostrando el color seleccionado en cada momento del panel de seleccin. Para poder tratar la seleccin actual el componente JColorChooser hace uso de una clase llamada ColorSelectionModel que se encuentra en el subpaquete de Swing denominado javax.swing.colorchooser. El modelo de seleccin de color (instancia de la clase ColorSelectionModel), lanza un evento de la clase ChangeEvent (este evento vimos que tambin era lanzado por el componente JSlider) cada vez que se cambia la seleccin del color en el selector de color (JColorChooser). Si queremos detectar los cambios de seleccin de color del componente JColorChooser debemos registrar el oyente correspondiente al evento ChageEvent, pero no se debe hacer directamente sobre el componente JColorChooser, sino sobre su ColorSelectionModel. Para obtener la instancia de la clase ColorSelectionModel correspondiente a un objeto de la clase JColorChooser, debemos lanzar sobre el objeto JColorChooser el mtodo getSelectionModel(). Pasemos ahora a ver un cdigo de ejemplo que utiliza un objeto de la clase JColorChooser para configurar el color del texto de una etiqueta.
import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.swing.event.*; import javax.swing.colorchooser.*; public class SelectorColor extends JFrame implements ChangeListener{ private JLabel etiqueta; private JColorChooser selectColor; public SelectorColor() { super("Selector de color"); this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); } public void creaEtiqueta(){ //Configuramos la etiqueta sobre la que se aplica la seleccin de color etiqueta= new JLabel("La Plataforma Java 2",JLabel.CENTER); etiqueta.setForeground(Color.green); etiqueta.setBackground(Color.white); etiqueta.setOpaque(true); etiqueta.setFont(new Font("SansSerif", Font.BOLD, 24));
201
etiqueta.setPreferredSize(new Dimension(80,45)); //panel con borde que va a contener la etiqueta JPanel etiquetaPanel = new JPanel(new BorderLayout()); etiquetaPanel.add(etiqueta, BorderLayout.CENTER); etiquetaPanel.setBorder(BorderFactory.createTitledBorder("etiqueta")); getContentPane().add(etiquetaPanel, BorderLayout.CENTER); } public void creaSelectorColor(){ selectColor= new JColorChooser(etiqueta.getForeground()); //se obtiene el modelo de selecin de color para registrar el oyente //de los eventos ChangeEvent selectColor.getSelectionModel().addChangeListener(this); selectColor.setBorder(BorderFactory.createTitledBorder ("Selecciona el color del texto")); getContentPane().add(selectColor,BorderLayout.SOUTH); } public void stateChanged(ChangeEvent e) { etiqueta.setForeground(selectColor.getColor()); } public static void main(String[] args) { SelectorColor ventana = new SelectorColor(); ventana.creaEtiqueta(); ventana.creaSelectorColor(); ventana.pack(); ventana.setVisible(true); } }
202
Como se puede comprobar en el ejemplo el constructor utilizado de la clase JColorChooser recibe como parmetro un color, este es el color seleccionado inicialmente dentro del selector de color. En nuestro caso le hemos indicado el color de la letra de la etiqueta sobre la que se va a aplicar las distintas selecciones de color. El mtodo stateChanged(), que se lanzar cada vez que se cambie la seleccin de color del selector de color, recupera el color actual mediante el mtodo getColor() de la clase JColorChooser, y se lo aplica al texto de la etiqueta mediante el mtodo setForeground(). En la ejecucin del ejemplo se puede observar las tres formas distintas de seleccionar el color que permite el componente JColorChooser, en la Figura 102 se puede ver otra seleccin distinta, utilizando el formato de color RGB. En este caso el componente JColorChooser lo hemos tratado como otro componente atmico ms, aadindolo al panel de contenido de la ventana, pero tambin podemos mostrarlo como un dilogo, para ello podemos utilizar el mtodo esttico showDialog() de la clase JColorChooser. La sintaxis de este mtodo es: JColorChooser.showDialog(Component padre, String ttulo, Color color)
203
El primer argumento de este mtodo es el componente padre del dilogo (el componente del que depende), el segundo el ttulo del dilogo y el tercero el color seleccionado por defecto en el dilogo de seleccin de color. Ahora vamos a modificar el ejemplo anterior para realizar una funcin similar pero utilizando el selector de colores como un dilogo modal. Para mostrar el dilogo vamos a utilizar un botn (JButton) y el evento ActionEvent. En este nuevo supuesto no se utiliza la clase ColorSelectionModel, sino que el color seleccionado lo obtenemos directamente cuando el usuario pulse el botn de OK del dilogo. Esto se ve mucho ms claro en el cdigo fuente del ejemplo
import java.awt.*; import java.awt.event.*; import javax.swing.*; public class DialogoSelectorColor extends JFrame implements ActionListener{ private JLabel etiqueta; private JButton boton; public DialogoSelectorColor() { super("Selector de color con dilogo"); this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); } public void creaEtiqueta(){ //Configuramos la etiqueta sobre la que se aplica la seleccin de color etiqueta= new JLabel("La Plataforma Java 2",JLabel.CENTER); etiqueta.setForeground(Color.green); etiqueta.setBackground(Color.white); etiqueta.setOpaque(true); etiqueta.setFont(new Font("SansSerif", Font.BOLD, 24)); etiqueta.setPreferredSize(new Dimension(80,45)); //panel con borde que va a contener la etiqueta JPanel etiquetaPanel = new JPanel(new BorderLayout()); etiquetaPanel.add(etiqueta, BorderLayout.CENTER); etiquetaPanel.setBorder(BorderFactory.createTitledBorder("etiqueta")); getContentPane().add(etiquetaPanel, BorderLayout.CENTER); } public void creaBoton(){ JPanel panel=new JPanel(); panel.setLayout(new FlowLayout()); boton=new JButton("Seleccin de color"); panel.add(boton); boton.addActionListener(this); getContentPane().add(panel,BorderLayout.SOUTH); } public void actionPerformed(ActionEvent evento) { Color color=JColorChooser.showDialog(this,"Selecciona un color", etiqueta.getForeground()); if (color!=null) etiqueta.setForeground(color); } public static void main(String[] args) { DialogoSelectorColor ventana = new DialogoSelectorColor(); ventana.creaEtiqueta(); ventana.creaBoton(); ventana.setSize(400,180);
204
ventana.setVisible(true); } }
Si el usuario cancela la seleccin de color, ya sea cerrando el dilogo o pulsando el botn de cancelacin, no se devolver ningn color, sino que se devolver el valor null (nulo). Al pulsar el botn seleccin de color aparece:
JFileChooser
Este es el ltimo componente atmico de Swing que vamos a ver. El componente JFileChooser es similar al visto anteriormente, podemos crearlo como un componente ms y aadirlo a un panel o bien mostrarlo como un dilogo modal.
205
Este componente nos permite navegar por el sistema de ficheros y seleccionar un fichero o directorio. Normalmente se utilizan estos componentes como dilogos modales. Pero la clase JFileChooser nicamente muestra el interfaz que nos permite seleccionar los ficheros, pero el tratamiento de los mismos corre de nuestra cuenta, es decir, el tratamiento de los ficheros debemos implementarlo en nuestra aplicacin. La clase JFileChooser ofrece dos mtodos para mostrar los dos tipos de selectores de ficheros distintos, el que se utiliza para abrir ficheros y el que se utiliza para grabar ficheros, estos mtodos son showOpenDialog() y showSaveDialog(). Ambos mtodos reciben como argumento un objeto Component que representa el padre del dilogo de seleccin de ficheros. Estos mtodos no son estticos como ocurra con el componente ColorChooser, sino que se deben lanzar sobre una instancia de la clase JFileChooser. Tanto el mtodo showOpenDialog() como showSaveDialog() devuelven un valor entero que se corresponde con las constantes que indican el resultado de la seleccin del usuario, indican si el usuario a pulsado el botn de cancelacin (CANCEL_OPTION) o no (APPROVE_OPTION), ambas constantes de encuentran definidas en la clase JFileChooser. El aspecto que muestra el dilogo para la apertura de fichero es muy similar al del dilogo para guardar ficheros. A continuacin vamos a mostrar un sencillo ejemplo que posee dos botones y un rea de texto (JTextArea), segn el botn que se pulse se mostrar un dilogo de seleccin de ficheros distinto, para abrir ficheros y para guardar ficheros. En el rea de texto se irn mostrando las selecciones de ficheros o cancelaciones realizadas por el usuario.
import java.awt.*; import java.awt.event.*; import javax.swing.*; import java.io.*; public class DialogoSelectorFichero extends JFrame implements ActionListener{ private JTextArea resultado; private JButton botonAbrir; private JButton botonGuardar; private JFileChooser selectorFichero; public DialogoSelectorFichero() { super("Dilogo para la seleccin de ficheros"); this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); selectorFichero=new JFileChooser(); } public void creaBotones(){ ImageIcon iconoAbrir = new ImageIcon("abrir.gif"); botonAbrir=new JButton("Abrir un fichero...", iconoAbrir); ImageIcon iconoGuardar = new ImageIcon("guardar.gif"); botonGuardar=new JButton("Guardar un fichero...", iconoGuardar); botonAbrir.addActionListener(this); botonGuardar.addActionListener(this); JPanel panelBotones=new JPanel(); panelBotones.add(botonAbrir); panelBotones.add(botonGuardar); getContentPane().add(panelBotones,BorderLayout.NORTH); } public void creaResultado(){
206
resultado = new JTextArea(5,20); resultado.setMargin(new Insets(5,5,5,5)); resultado.setEditable(false); JScrollPane panelResultado = new JScrollPane(resultado); getContentPane().add(panelResultado,BorderLayout.CENTER); } public void actionPerformed(ActionEvent evento) { if (evento.getSource()==botonAbrir){ int valorDevuelto= selectorFichero.showOpenDialog(this); if (valorDevuelto== JFileChooser.APPROVE_OPTION) { File fichero = selectorFichero.getSelectedFile(); resultado.append("Abierto: " + fichero.getName()+"\n"); } else resultado.append("Apertura cancelada por el usuario\n"); }else{ int valorDevuelto= selectorFichero.showSaveDialog(this); if (valorDevuelto== JFileChooser.APPROVE_OPTION) { File fichero = selectorFichero.getSelectedFile(); resultado.append("Guardado: " + fichero.getName()+"\n"); } else resultado.append("Operacin de grabado cancelada"+ "por el usuario\n"); } } public static void main(String[] args) { DialogoSelectorFichero ventana = new DialogoSelectorFichero(); ventana.creaBotones(); ventana.creaResultado(); ventana.pack(); ventana.setVisible(true); } }
207
Como se puede comprobar en el ejemplo, para obtener el fichero que se ha seleccionado se utiliza el mtodo getSelectedFile(), que devuelve un objeto de la clase File. A travs de este objeto podemos manipular ficheros, en una aplicacin real se utilizara para guardar o abrir ficheros. El constructor de la clase JFileChooser puede recibir como argumento un objeto File o String para indicar el directorio inicial. El directorio inicial tambin se puede especificar mediante el mtodo setCurrentDirectory() al que se le pasa como argumento un objeto de la clase File. Cada vez que se muestra el dilogo para la seleccin de ficheros guarda el directorio actual de la seleccin anterior, es decir, va recordando las rutas de las selecciones anteriores. Adems permite la creacin de nuevos directorios. Este ha sido el componente Swing atmico que cierra este captulo, en el siguiente captulo trataremos el nuevo gestor de diseo de Swing y la caracterstica Pluggable Look & Feel.
208
En el ejemplo anterior podemos comprobar la forma de crear un gestor de diseo para distribuir los componentes de arriba a abajo. El constructor de la clase BoxLayout posee dos argumentos, el panel (JPanel) sobre el que se va a aplicar el gestor de diseo y la orientacin en la que se van a distribuir los componentes dentro de dicho panel. Este ltimo parmetro es un entero que se corresponde con las siguientes constantes definidas en la clase BoxLayout: X_AXIS: los componentes se distribuyen de izquierda a derecha. Y_AXIS: los componentes se distribuyen de arriba a abajo. Si retomamos el ejemplo anterior y cambiamos la constante BoxLayout.Y_AXIS por BoxLayout.X_AXIS, se obtiene el resultado que muestra continuacin:
209
Conjuntamente con la clase BoxLayout, se suele utilizar la clase Box, esta clase ofrece una serie de mtodos para controlar de una forma ms precisa la forma en la que se distribuyen los componentes dentro de un panel al que se le ha aplicado este gestor de diseo BoxLayout.
import javax.swing.*; import java.awt.event.*; import java.awt.*; public class DosBoxLayout{ public static void main(String args[]) { JFrame ventana = new JFrame("Ventana con BoxLayout"); ventana.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); JPanel panelSuperior=new JPanel(); panelSuperior.setBorder(BorderFactory.createLineBorder(Color.red)); panelSuperior.setLayout(new BoxLayout(panelSuperior,BoxLayout.Y_AXIS)); panelSuperior.add(new JLabel("Esto es una etiqueta")); panelSuperior.add(Box.createRigidArea(new Dimension(0,5))); panelSuperior.add(new JTextArea(5,30)); JPanel panelInferior=new JPanel(); panelInferior.setBorder(BorderFactory.createLineBorder(Color.green)); panelInferior.setLayout(new BoxLayout(panelInferior,BoxLayout.X_AXIS)); panelInferior.add(Box.createHorizontalGlue()); panelInferior.add(new JButton("Botn 1")); panelInferior.add(Box.createRigidArea(new Dimension(10,0))); panelInferior.add(new JButton("Botn 2")); ventana.getContentPane().add(panelSuperior,BorderLayout.NORTH); ventana.getContentPane().add(panelInferior,BorderLayout.CENTER); ventana.pack(); ventana.setVisible(true); } }
A la vista del cdigo se puede decir que este interfaz est compuesto por dos paneles los que se han aplicado un gestor de diseo BoxLayout distinto, uno de arriba a abajo y otro de izquierda a derecha.
210
Se ha aplicado un borde a cada uno de estos dos paneles para distinguirlos ms claramente. En el primer panel se ha utilizado el mtodo createRigidArea() de la clase Box para crear una separacin fija (rea rgida) invisible entre la etiqueta (JLabel) y el rea de texto (JTextArea). En este caso se ha indicado una separacin vertical de cinco pixels. En el segundo panel se utiliza otro mtodo de la clase Box, el mtodo createHorizontalGlue(), este mtodo provoca que los dos botones que se aadan a continuacin se desplacen hacia la derecha, es decir, este mtodo rellena el espacio sobrante y desplaza a los componentes hacia la derecha. El mtodo createHorizontalGlue() crea un rea invisible que crece horizontalmente para rellenar todo el espacio libre disponible dentro del contenedor. En este panel inferior tambin se utiliza el mtodo createRigidArea() para separar los dos botones diez pixels, en este caso se ha utilizado un rea slo con componente horizontal. Si la llamada al mtodo createHorizontalGlue() la hubiramos realizado en el lugar en la que se encuentra la llamada al mtodo createRigidArea(), el resultado hubiera sido la Figura siguiente:
A la vista de la imagen se puede decir que el rea se ha extendido entre los dos botones, enviando al segundo botn a la derecha del todo. La clase Box ofrece tambin el mtodo createVerticalGlue(), en este caso el rea invisible se extiende de forma vertical enviando los componentes dentro del panel hacia la zona inferior. El gestor de diseo BoxLayout respeta el tamao mximo de los componentes que contiene y tambin la alineacin establecida por los mismos, aunque para que no existan problemas de alineacin todos los componentes deben tener la misma. Si modificamos el primer ejemplo visto en este apartado tenemos:
import java.awt.event.*; import java.awt.*; public class BoxLayoutSencillo{ public static void main(String args[]) { JFrame ventana = new JFrame("Ventana con BoxLayout"); ventana.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); JPanel panelContenido=new JPanel(); JButton boton1=new JButton("Soy un botn"); boton1.setAlignmentX(Component.CENTER_ALIGNMENT); JButton boton2=new JButton("Soy un botn ms"); JButton boton3=new JButton("Otro botn"); boton3.setAlignmentX(Component.RIGHT_ALIGNMENT); JButton boton4=new JButton("Soy el ltimo");
211
boton4.setAlignmentX(Component.CENTER_ALIGNMENT); panelContenido.setLayout(new BoxLayout(panelContenido,BoxLayout.Y_AXIS)); panelContenido.add(boton1); panelContenido.add(boton2); panelContenido.add(boton3); panelContenido.add(boton4); ventana.setContentPane(panelContenido); ventana.pack(); ventana.setVisible(true); } }
212
El mtodo setLookAndFeel() de la clase UIManager lanza una excepcin de la clase UnsupportedLookAndFeelException, que se encuentra tambin dentro del paquete javax.swing, cuando el Look & Feel que queremos utilizar no es soportado por la plataforma actual, deberemos atrapar por lo tanto esta excepcin cuando invoquemos el mtodo setLookAndFeel(). De esta forma si queremos que nuestra aplicacin posea el Look & Feel de Motif deberemos incluir el siguiente cdigo dentro del mtodo main() de la aplicacin.
try { UIManager.setLookAndFeel("com.sun.java.swing.plaf.motif.MotifLookAndFeel"); } catch (Exception e) { } new AplicacionSwing();
Pero el Look & Feel, como ya hemos adelantado al comienzo de este apartado, se puede establecer una vez que la aplicacin ya se ha iniciado y el interfaz de usuario es visible, es decir, podemos establecer el Look & Feel de la aplicacin en tiempo de ejecucin. En este caso adems de utilizar el mtodo setLookAndFeel() de la clase UIManager, deberemos utilizar el mtodo esttico updateComponentTreeUI() de la clase SwingUtilities, presente tambin en el paquete javax.swing. El mtodo updateComponentTreeUI() recibe como argumento un objeto de la clase Component que representa el contenedor de alto nivel sobre el que se quiere actualizar su Look & Feel. El mtodo updateComponentTreeUI() de la clase SwingUtilities debe utilizarse para cada uno de los contenedores de alto nivel del interfaz de usuario de la aplicacin, para que de esta forma todos actualicen su Look & Feel en tiempo de ejecucin. Adems es recomendable lanzar el mtodo pack() sobre cada uno de los contenedores de alto nivel del interfaz de usuario, para que se redimensionen todos los componentes segn su nuevo Look & Feel. La utilizacin de este mtodo, conjuntamente con el mtodo setLookAndFee() se puede ver en el Cdigo siguiente.
try { UIManager.setLookAndFeel(lf); SwingUtilities.updateComponentTreeUI(contenedor); this.pack(contenedor); } catch (Exception excepcion) { System.out.println("No se pudo asignar el Look & Feel"); }
En este caso se supone que slo existe un contenedor de alto nivel. La clase UIManager, adems del mtodo setLookAndFeel(), nos ofrece otros dos mtodos que pueden resultar de utilidad, estos mtodos son getSystemLookAndFeelClassName() y getCrossPlatformLooAndFeelClassName(). Ambos mtodos devuelven una cadena, en el primer caso se corresponde con el nombre de la clase del Look & Feel de la plataforma sobre la que se est ejecutando la aplicacin, y el segundo caso se corresponde con el nombre de la clase que ofrece el Look & Feel de Java, que es el Look & Feel que se garantiza funcionar correctamente para todas las plataformas. Por lo tanto estos dos mtodos se pueden utilizar como argumento para el mtodo setLookAndFeel(), as por ejemplo si deseamos aplicar el Look & Feel de la plataforma sobre la que se ejecuta la aplicacin, utilizaramos el Cdigo:
try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch (Exception e) { }
214
Para mostrar la caracterstica Pluggable Look & Feel de Swing se va a utilizar un sencillo ejemplo que consiste una aplicacin con una ventana (JFrame) que permite especificar el Look & Feel en tiempo de ejecucin. LA ventana de la aplicacin del ejemplo, adems de contener diversos componentes Swing, contiene cinco botones de opcin (JOptionButton) agrupados, y que al seleccionar cada uno de ellos se aplicar sobre la aplicacin el Look & Feel que representa cada opcin. Es decir, existen cinco botones de opcin, cuatro de ellos para cada uno de los distintos Look & Feel (Java, Windows, Motif y Mac) y otro para seleccionar el Look & Feel de la plataforma sobre la que se ejecuta la aplicacin. Antes de seguir comentando ms detalles de esta aplicacin de ejemplo, vamos a mostrar el aspecto que tendra esta aplicacin. Al ejecutar la aplicacin el aspecto que muestra es el de la Figura, y se corresponde con el Look & Feel de Java.
Al iniciarse su ejecucin dentro del constructor de la aplicacin, se establece el Look & Feel mediante el nombre de la clase devuelto por el mtodo getCrossPlatformLookAndFeelClassName() de la clase UIManager, debido a esto al arrancar la aplicacin comienza a ejecutarse utilizando el Look & Feel de Java. El cdigo fuente perteneciente al constructor de nuestra clase se puede observar :
public LF(){ super("Pluggable Look & Feel"); this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); try { UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName()); } catch (Exception e) { } getContentPane().setLayout(new BoxLayout(getContentPane(),BoxLayout.Y_AXIS)); }
En el constructor adems de establecer el Look & Feel de Java se establece el gestor de diseo que va a utilizar el panel de contenido de la ventana. El gestor de diseo que se
215
utiliza es BoxLayout, descrito en el apartado anterior, en su variante de distribucin de componentes de arriba abajo (BoxLayout.Y_AXIS). De la figura anterior tambin se puede comprobar que la opcin seleccionada por defecto se corresponde lgicamente con el Look & Feel que presenta la aplicacin inicialmente. Podemos ir asignando a la aplicacin los distintos Look & Feel seleccionando la opcin correspondiente. A continuacin se muestran otras dos figuras, cada una con otro nuevo Look & Feel. La primera corresponde al Look & Feel de Windows y la segunda al Look & Feel de Motif.
En mi caso no he podido utilizar el Look & Feel de Mac, ya que he utilizado una plataforma Windows para ejecutar la aplicacin de ejemplo, por lo tanto ste Look & Feel no estar disponible, y adems en mi caso particular el Look & Feel del sistema (Look & Feel de la plataforma actual) ser por lo tanto el de Windows. No vamos hacer como en otros ejemplos de este curso, en los que se ha ofrecido el cdigo completo del ejemplo, slo vamos a mostrar y comentar aquellos fragmentos que resultan ms interesantes (sobre todo desde el punto de vista de la caracterstica Pluggable Look & Feel), ya que el cdigo fuente de la aplicacin de ejemplo es bastante sencillo y se utilizan componentes Swing vistos en los captulos anteriores.
216
Como se puede ver es una sencilla ventana que va a contener una serie de componentes Swing. No vamos a detenernos en la creacin de los distintos componentes del interfaz de usuario, pero si que es interesante mostrar como se crean las opciones que permiten seleccionar el Look & Feel de la aplicacin. Para crear las opciones que representan los distintos LooK & Feel se ha utilizado el mtodo creaOpciones(), cuyo cdigo veremos:
public void creaOpciones(){ panelOpciones=new JPanel(); panelOpciones.setLayout(new GridLayout(1,5,5,5)); opWindows=new JRadioButton("Windows"); opWindows.setMnemonic(KeyEvent.VK_W); opWindows.addActionListener(this); panelOpciones.add(opWindows); opJava=new JRadioButton("Java/Metal"); opJava.setMnemonic(KeyEvent.VK_J); opJava.addActionListener(this); panelOpciones.add(opJava); opJava.setSelected(true); opMotif=new JRadioButton("Motif"); opMotif.setMnemonic(KeyEvent.VK_M); opMotif.addActionListener(this); panelOpciones.add(opMotif); opMac=new JRadioButton("Mac"); opMac.setMnemonic(KeyEvent.VK_C); opMac.addActionListener(this); panelOpciones.add(opMac); opSistema=new JRadioButton("Sistema"); opSistema.setMnemonic(KeyEvent.VK_S); opSistema.addActionListener(this); panelOpciones.add(opSistema); panelOpciones.setBorder(BorderFactory.createLoweredBevelBorder()); //se agrupan las opciones ButtonGroup grupo=new ButtonGroup(); grupo.add(opWindows); grupo.add(opJava); grupo.add(opMotif); grupo.add(opMac); grupo.add(opSistema); getContentPane().add(panelOpciones); }
La pulsacin de cada opcin ser tratada dentro del mtodo actionPerformed() que realmente, conjuntamente con el constructor de la clase, es dnde utilizamos mtodos y clases relacionados directamente con el mecanismo Pluggable Look & Feel. A continuacin se ofrece el cdigo fuente perteneciente al mtodo actionPerformed(). Ser en este mtodo dnde se establezca el Look & Feel seleccionado a travs de los distintos botones de opcin.
public void actionPerformed(ActionEvent evento){ Object fuente=evento.getSource(); String lf=""; if (fuente==opWindows) lf="com.sun.java.swing.plaf.windows.WindowsLookAndFeel"; else if(fuente==opJava) lf="javax.swing.plaf.metal.MetalLookAndFeel"; else if(fuente==opMotif) lf="com.sun.java.swing.plaf.motif.MotifLookAndFeel";
217
else if(fuente==opMac) lf="javax.swing.plaf.mac.MacLookAndFeel"; else if(fuente==opSistema) lf=UIManager.getSystemLookAndFeelClassName(); try { UIManager.setLookAndFeel(lf); SwingUtilities.updateComponentTreeUI(this); this.pack(); } catch (UnsupportedLookAndFeelException excepcion) { texto.append("Look & Feel no soportado.\n"+excepcion+"\n"); } catch (ClassNotFoundException excepcion){ texto.append("Clase no encontrada.\n"+excepcion+"\n"); } catch (InstantiationException excepcion){ texto.append("Excepcin de instanciacin.\n"+excepcion+"\n"); } catch(IllegalAccessException e){ texto.append("Excepcin de acceso.\n"+e+"\n"); } }
Como se puede comprobar a la vista del cdigo anterior, se decidimos atrapar la excepcin UnsupportedLookAndFeelException, se nos obligar a atrapar tres excepciones ms. Por lo dems, si se tiene en cuenta lo explicado hasta el momento, el cdigo de este mtodo es bastante sencillo, lo nico que se hace es establecer el Look & Feel que se corresponde con la opcin pulsada. Si ocurre una excepcin se muestra en el rea de texto (JTextArea) de la aplicacin. Si en mi caso particular, utilizando una plataforma Windows, selecciono la opcin correspondiente al Look & Feel de Mac, obtendr una excepcin lanzada por el mtodo setLooAndFeel(), esto se puede observar en la Figura.
218
Ejemplos de Swing
Campos de Texto. import java.awt.*; import javax.swing.*; //import java.swing.text.*; public class CampoTexto extends JApplet { JPasswordField jtf; public void init() { Container cp = getContentPane(); cp.setLayout(new FlowLayout()); jtf = new JPasswordField(15); jtf.setEchoChar('*'); cp.add(jtf); } } JcomboBox (Combos) import java.awt.*; import java.awt.event.*; import javax.swing.*; public class ComboImagen extends JApplet implements ItemListener { JLabel jl; ImageIcon open, save, delete; public void init() { Container cp = getContentPane(); cp.setLayout(new FlowLayout()); JComboBox jc = new JComboBox(); jc.addItem("open"); jc.addItem("save"); jc.addItem("delete"); jc.addItemListener(this); cp.add(jc); jl = new JLabel(new ImageIcon("open.gif")); cp.add(jl); } public void itemStateChanged(ItemEvent ie) { String s = (String)ie.getItem(); jl.setIcon(new ImageIcon(s + ".gif")); } } Jbutttton (Botones) import java.awt.*; import java.awt.event.*; import javax.swing.*; public class BotoneImagen extends JApplet implements ActionListener { JTextField jtf; public void init() { Container cp = getContentPane();
219
cp.setLayout(new FlowLayout()); ImageIcon abrir = new ImageIcon("open.gif"); JButton jb = new JButton(abrir); jb.setActionCommand("abrir"); jb.addActionListener(this); cp.add(jb); ImageIcon salvar = new ImageIcon("save.gif"); jb = new JButton(salvar); jb.setActionCommand("salvar"); jb.addActionListener(this); cp.add(jb); ImageIcon borrar = new ImageIcon("delete.gif"); jb = new JButton(borrar); jb.setActionCommand("borrar"); jb.addActionListener(this); cp.add(jb); jtf = new JTextField(15); cp.add(jtf); } public void actionPerformed(ActionEvent ae) { jtf.setText(ae.getActionCommand()); } } Componentes ficha (Tabbed Pane) import javax.swing.*; public class JTabbedPaneEjemplo extends JApplet { public void init() { JTabbedPane jtp = new JTabbedPane(); jtp.addTab("Ciudad", new Ciudades()); jtp.addTab("Color", new Colores()); jtp.addTab("Savor", new Savores()); getContentPane().add(jtp); } } class Ciudades extends JPanel { public Ciudades() { JButton b1 = new JButton("San Vicente"); add(b1); JButton b2 = new JButton("San Salvador"); add(b2); JButton b3 = new JButton("Santa Ana"); add(b3); JButton b4 = new JButton("San Miguel"); add(b4); } } class Colores extends JPanel { public Colores() {
220
JCheckBox cb1 = new JCheckBox("Rojo"); add(cb1); JCheckBox cb2 = new JCheckBox("Verde"); add(cb2); JCheckBox cb3 = new JCheckBox("Azul"); add(cb3); } } class Savores extends JPanel { public Savores() { JComboBox jcb = new JComboBox(); jcb.addItem("Vainilla"); jcb.addItem("Chocolate"); jcb.addItem("Fresa"); add(jcb); } } Arboles (Trees) import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.swing.tree.*; /* <applet code="Arboles" width=400 height=200> </applet> */ public class Arboles extends JApplet { JTree arbol; JTextField jtf; public void init() { // Obtener el content pane Container contentPane = getContentPane(); // Configurando el layout contentPane.setLayout(new BorderLayout()); // Creando el nodo raiz DefaultMutableTreeNode tope = new DefaultMutableTreeNode("Opciones"); // Creando el subarbol "A" DefaultMutableTreeNode a = new DefaultMutableTreeNode("A"); tope.add(a); DefaultMutableTreeNode a1 = new DefaultMutableTreeNode("A1"); a.add(a1); DefaultMutableTreeNode a2 = new DefaultMutableTreeNode("A2"); a.add(a2); // Creando el subarbol "B" DefaultMutableTreeNode b = new DefaultMutableTreeNode("B"); tope.add(b);
221
DefaultMutableTreeNode b1 = new DefaultMutableTreeNode("B1"); b.add(b1); DefaultMutableTreeNode b2 = new DefaultMutableTreeNode("B2"); b.add(b2); DefaultMutableTreeNode b3 = new DefaultMutableTreeNode("B3"); b.add(b3); // Creando el arbol arbol = new JTree(tope); // agregando al scroll pane int v = ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED; int h = ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED; JScrollPane jsp = new JScrollPane(arbol, v, h); // agregando el scroll pane al content pane contentPane.add(jsp, BorderLayout.CENTER); // agregando text field to applet jtf = new JTextField("", 20); contentPane.add(jtf, BorderLayout.SOUTH); // implementar la clase interna para eventos de raton arbol.addMouseListener(new MouseAdapter() { public void mouseClicked(MouseEvent me) { doMouseClicked(me); } }); } void doMouseClicked(MouseEvent me) { TreePath tp = arbol.getPathForLocation(me.getX(), me.getY()); if(tp != null) jtf.setText(tp.toString()); else jtf.setText(""); } } Tablas(Tables) import java.awt.*; import javax.swing.*; public class Tablas extends JApplet { public void init() { Container contentPane = getContentPane(); // Manejador layout contentPane.setLayout(new BorderLayout()); // Inicializando las columnas final String[] cabecera = { "Nombre", "Edad", "Sexo" }; // Inicializando los datos final Object[][] datos = { { "Luis", "25", "M" }, { "Maria", "24", "F" }, { "Roberto", "28", "M" },
222
{ "Cesar", "24", "M" }, { "Laura", "20", "F" }, { "Elena", "19", "F" } }; // Crear la tabla JTable tabla = new JTable(datos, cabecera); // agregando scroll pane int v = ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED; int h = ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED; JScrollPane jsp = new JScrollPane(tabla, v, h); // agregando al content pane contentPane.add(jsp, BorderLayout.CENTER); } } Empezando a dibujar con Swing A continuacin vamos a ver qu tenemos que hacer para dibujar en Swing. Lo primero es hacerse con el objeto grfico del componente sobre el cual queremos dibujar. El objeto grfico es un objeto que posee toda la informacin que un componente necesita para dibujarse en pantalla. Para obtener el objeto grfico empleamos el mtodo getGraphics(), de JComponent. Graphics g = Componente.getGraphics() Una vez obtenido dicho objeto grfico procedemos a dibujar empleando los mtodos que dicho objeto nos proporciona. Estos mtodos son tan intuitivos como numerosos, por lo que no haremos aqu una revisin de ellos. Si deseamos dibujar algo lo que ms normal es acudir a la documentacin de las librera y buscar en ella los mtodos que necesitemos. A modo de ejemplo, si queremos dibujar una lnea y vamos a la documentacin de la clase Graphics encontraremos un mtodo drawLine(int x1, int x2, int x3 int x4) en el cual (x1, x2) es el punto de inicio de la lnea y (x3, x4) el fin de la lnea. Veamos un ejemplo de esto. Ejemplo 1. import java.awt.event.*; import javax.swing.*; import java.awt.*; public class Dibujar1 extends JFrame implements ActionListener{ public Dibujar1(){ setSize(400,400); setTitle("Dibujo"); Container contentpane = this.getContentPane(); contentpane.setLayout (new BorderLayout()); JPanel p =new JPanel(); contentpane.add(p, BorderLayout.SOUTH); JButton boton = new JButton("Dibujar"); boton.addActionListener(this); boton.setActionCommand("Dibujar"); p.add(boton); JButton boton2 = new JButton("Salir"); boton2.addActionListener(this); boton2.setActionCommand("Salir"); p.add(boton2); contentpane.add(panel, BorderLayout.CENTER); this.setVisible(true);
223
} public void actionPerformed(ActionEvent e){ String comando = e.getActionCommand(); if(comando.equals("Salir")){ System.exit(0); } Graphics g = panel.getGraphics(); g.setColor(Color.blue); g.drawLine(0,0, 100, 100); g.drawLine(150, 150,(int) (this.getSize()).getWidth(),(int)(this.getSize()).getHeight()); g.drawRect(100, 80, 200, 200); g.setColor(Color.red); g.fillRect(110, 90, 150, 150); } JPanel panel = new JPanel(); public static void main(String[] args){ new Dibujar1(); } } El mtodo paintComponent Cuando cualquier componente de las libreras Swing por cualquier razn necesita redibujarse en pantalla llama al mtodo paintComponent. En este mtodo se halla toda la informacin que se necesita para dibujar el componente en pantalla. Causas externas a un componente que fuerzan que este se redibuje son que la ventana en la que est se minimiza y luego se maximiza o que otra ventana se ponga encima de la ventana que lo contiene. Adems el componente se dibujar cuando se crea y cuando se invoque el mtodo repaint(). Cuando este mtodo es invocado lo nico que aparecer en el componente es lo que se dibuje desde el, todo lo dems es borrado. Este es el motivo por el cual en nuestro ejemplo anterior al minimizar y luego maximizar la pantalla dejamos de ver lo que habamos dibujado. Si queremos que siempre que el componente se redibuje apareciesen nuestros dibujos hemos de sobrescribir el mtodo paintComponent y escribir en el cdigo necesario para realizar estos dibujos. Veamos como haramos esto sobre el ejemplo Dibujar1: Ejemplo 2. import java.awt.event.*; import javax.swing.*; import java.awt.*; public class Dibujar2 extends JFrame implements ActionListener{ public Dibujar2(){ setSize(400,400); setTitle("Dibujo"); Container contentpane = this.getContentPane(); contentpane.setLayout (new BorderLayout()); JPanel p =new JPanel(); contentpane.add(p, BorderLayout.SOUTH); JButton boton = new JButton("Dibujar"); boton.addActionListener(this); boton.setActionCommand("Dibujar"); p.add(boton); JButton boton2 = new JButton("Salir");
224
boton2.addActionListener(this); boton2.setActionCommand("Salir"); p.add(boton2); contentpane.add(panel, BorderLayout.CENTER); this.setVisible(true); } public void actionPerformed(ActionEvent e){ String comando = e.getActionCommand(); if(comando.equals("Salir")){ System.exit(0); } Graphics g = panel.getGraphics(); g.setColor(Color.red); g.fillRect(110, 90, 150, 150); panel.paintComponent(g); } MyPanel panel = new MyPanel(); public static void main(String[] args){ new Dibujar2(); }} class MyPanel extends JPanel{ public void paintComponent(Graphics g){ g.setColor(Color.blue); g.drawLine(0,0, 100, 100); g.drawLine(150, 150,(int) (this.getSize()).getWidth(),(int)(this.getSize()).getHeight()); g.drawRect(100, 80, 200, 200); }}
225
226
1. INTRODUCCION JDBC
Java Database Connectivity (JDBC) es una interfase de acceso a bases de datos estndar SQL que proporciona un acceso uniforme a una gran variedad de bases de datos relacionales. JDBC tambin proporciona una base comn para la construccin de herramientas y utilidades de alto nivel.
Drivers JDBC Para usar JDBC con un sistema gestor de base de datos en particular, es necesario disponer del driver JDBC apropiado que haga de intermediario entre sta y JDBC. Dependiendo de varios factores, este driver puede estar escrito en Java puro, o ser una mezcla de Java y mtodos nativos JNI (Java Native Interface).
1.1 Qu es JDBC?
JDBC es el API para la ejecucin de sentencias SQL. (Como punto de inters JDBC es una marca registrada y no un acrnimo, no obstante a menudo es conocido como Java Database Connectivity). Consiste en un conjunto de clases e interfases escritas en el lenguaje de programacin Java. JDBC suministra un API estndar para los desarrolladores y hace posible escribir aplicaciones de base de datos usando un API puro Java. Usando JDBC es fcil enviar sentencias SQL virtualmente a cualquier sistema de base de datos. En otras palabras, con el API JDBC, no es necesario escribir un programa que acceda a una base de datos Sybase, otro para acceder a Oracle y otro para acceder a Informix. Un nico programa escrito usando el API JDBC y el programa ser capaz de enviar sentencias SQL a la base de datos apropiada. Y, con una aplicacin escrita en el lenguaje de programacin Java, tampoco es necesario escribir diferentes aplicaciones para ejecutar en diferentes plataformas. La combinacin de Java y JDBC permite al programador escribir una sola vez y ejecutarlo en cualquier entorno. Java, siendo robusto, seguro, fcil de usar, fcil de entender, y descargable automticamente desde la red, es un lenguaje base excelente para aplicaciones de base de datos.
227
JDBC expande las posibilidades de Java. Por ejemplo, con Java y JDBC API, es posible publicar una pgina web que contenga un applet que usa informacin obtenida de una base de datos remota. O una empresa puede usar JDBC para conectar a todos sus empleados (incluso si usan un conglomerado de mquinas Windows, Macintosh y UNIX) a una base de datos interna va intranet. Con cada vez ms y ms programadores desarrollando en lenguaje Java, la necesidad de acceso fcil a base de datos desde Java contina creciendo.
El siguiente fragmento de cdigo nos muestra un ejemplo bsico de estas tres cosas:
Connection con = DriverManager.getConnection ( "jdbc:odbc:wombat", "login", "password"); Statement stmt = con.createStatement(); ResultSet rs = stmt.executeQuery("SELECT a, b, c FROM Table1"); while (rs.next()) { int x = rs.getInt("a"); String s = rs.getString("b"); float f = rs.getFloat("c"); }
1.1.2 JDBC es un API de bajo nivel y una base para APIs de alto nivel.
JDBC es una interfase de bajo nivel, lo que quiere decir que se usa para invocar o llamar a comandos SQL directamente. En esta funcin trabaja muy bien y es ms fcil de usar que otros APIs de conexin a bases de datos, pero est diseado de forma que tambin sea la base sobre la cual construir interfaces y herramientas de alto nivel. Una interfase de alto nivel es amigable, usa un API mas entendible o ms conveniente que luego se traduce en la interfase de bajo nivel tal como JDBC.
228
229
1.1.4 Modelos en dos y tres pisos. El API JDBC soporta los modelos en dos y tres pisos de acceso a base de datos. En el modelo de dos-pisos, un applet Java o una aplicacin habla directamente con la base de datos. Esto requiere un driver JDBC que pueda comunicar con el gestor de base de datos particular al que se pretende acceder. Las sentencias SQL de usuario se envan a la base de datos, y el resultado de estas sentencias se envan al usuario. La base de datos puede estar localizada en otra mquina a la que el usuario se conecta mediante la red. Esta es una configuracin Cliente/Servidor en la que la mquina del usuario es el cliente y la mquina que hospeda a la base de datos es el servidor. La red puede ser una intranet, por ejemplo, que conecta a los empleados dentro de la corporacin, o puede ser Internet.
Aplicacion Java
Mquina cliente
DBMS
Servidor de BD.
En el modelo de tres-pisos, los comandos se envan a un piso intermedio de servicios, que enva las sentencias SQL a la base de datos. La base de datos procesa las sentencias SQL y devuelve los resultados a el piso intermedio, que a su vez lo enva al usuario. Los directores de IS encuentran este modelo muy atractivo por que el piso intermedio hace posible mantener el control sobre los datos y los tipos de actualizaciones que pueden hacerse en los datos corporativos. Otra ventaja es que el usuario puede emplear un API de alto nivel ms sencillo que es traducido por el piso intermedio en las llamadas de bajo nivel apropiadas. Finalmente en muchos casos la arquitectura de tres niveles puede proporcionar ventajas de rendimiento. Hasta ahora, este nivel intermedio ha sido escrito en lenguajes como C C++, que ofrecen un rendimiento ms rpido. De cualquier modo, con la introduccin de compiladores optimizadores que traducen el bytecode en cdigo mquina eficiente, se est convirtiendo en prctico desarrollar este nivel intermedio en Java.
230
Esta es una gran ventaja al hacer posible aprovechar las caractersticas de robustez, multiproceso y seguridad de Java.
Aplicacion Java
Mquina cliente
DBMS
Servidor de BD.
231
diseada para especificas DBMS (para consultas a imgenes o documentos por ejemplo).
Una segunda forma en que JDBC trata este problema es proveer clusulas de escape al estilo de ODBC , que se discutirn en el 4.1.5. Sintaxis de escape en Sentencias Objetos.
La sintaxis de escape provee una sintaxis JDBC estndar para varias de las reas ms comunes de divergencia SQL. Por ejemplo, ah escapes para literales de fecha o procedimientos almacenados. Para aplicaciones complejas, JDBC trata la conformidad SQL de una tercera manera. Y es proveer informacin descriptiva sobre el DBMS por medio de la interfase DatabaseMetaData por la que las aplicaciones pueden adaptarse a
los requerimientos y posibilidades de cada DBMS:
Como el API JDBC se usar como base para el desarrollo de herramientas de acceso y APIs de alto nivel , direccionar el problema de la conformidad a cualquiera de estas. La designacin JDBC COMPLIANT se cre para situar un nivel estndar de funcionalidad JDBC en la que los usuarios puedan confiar. En orden a usar esta designacin, un driver debe soportar al menos el nivel de entrada ANSI SQL-2 Entry Level. Los desarrolladores de drivers pueden cerciorarse que sus drivers cumplan estas especificaciones mediante la suite de test disponible en el API JDBC. La designacin JDBC COMPLIANT indica que la implementacin JDBC de un vendedor ha pasado los tests de conformidad suministrados por JavaSoft. Estas pruebas de conformidad chequean la existencia de todas las clases y mtodos definidos en el API JDBC, y chequean tanto como es posible que la funcionalidad SQL Entry Level est disponible. Tales tests no son exhaustivos, por supuesto, y JavaSoft no esta distribuyendo implementaciones de vendedores, pero esta definicin de compliance tiene algn grado de seguridad en una implementacin JDBC. Con la mayor aceptacin de JDBC por parte de vendedores de bases de datos, de servicios de Internet y desarrolladores, JDBC se est convirtiendo en el estndar de acceso a bases de datos.
232
El gestor de drivers es la espina dorsal de la arquitectura JDBC. Actualmente es bastante pequea y simple; su funcin principal es conectar las aplicaciones Java al driver JDBC correcto y despus soltarlo.
La suite de testeo JDBXC suministra seguridad y confianza en los drivers JDBC que se ejecutarn en el programa. Solo los drivers que pasan el test pueden ser designados JDBC COMPLIANT. El puente JDBC-ODBC permite a los drivers ODBC usarse como drivers JDBC. Fue implementado como una forma de llegar rpidamente al fondo de JDBC y para proveer de acceso a los DBMS menos populares si no existen drivers JDBC para ellos.
233
234
2. CONEXIN
2.1 Vista Preliminar
Un objeto Connection representa una conexin con una base de datos. Una sesin de conexin incluye las sentencias SQL que se ejecutan y los resultados que son devueltos despus de la conexin. Una nica aplicacin puede tener una o ms conexiones con una nica base de datos, o puede tener varias conexiones con varias bases de datos diferentes.
La primera parte de una URL especifica el protocolo usado para acceder a la informacin y va siempre seguida por dos puntos. Algunos protocolos comunes son ftp, que especifica file transfer protocol y http que especifica hypertext transfer protocol. Si el protocolo es file indica que el recurso est en un sistema de ficheros local mejor que en Internet : veamos unos ejemplos:
ftp://javasoft.com/docs/JDK-1_apidocs.zip http://java.sun.com/products/jdk/CurrentRelease file:/home/haroldw/docs/books/tutorial/summary.html
El resto de la URL, todo despus de los dos puntos, da informacin sobre donde se encuentra la fuente de los datos. Si el protocolo es file, el resto de la URL es el path al fichero. Para los protocolos ftp y http, el resto de la URL identifica el host y puede opcionalmente dar un path ms especfico al sitio. Por ejemplo, el siguiente es la URL para la home page de JavaSoft. Esta URL identifica solo al host:
http://java.sun.com
236
Esto permite a los administradores de sistemas evitar dar especificaciones de sus hosts como parte del nombre JDBC. Hay una variedad de servicios de nomenclatura de red diferentes (tales como DNS, NIS y DCE), y no hay restriccin acerca de cual usar. La sintaxis para las URLs JDBC que se muestra a continuacin tiene tres partes separadas por dos puntos:
jdbc:<subprotocol>:<subname>
Las tres partes se descomponen como sigue: 1 2 jdbc el protocolo. El protocolo en una URL JDBC es siempre jdbc - <subprotocol> - el nombre del driver o el nombre del mecanismo de conectividad con la base de datos, que puede estar soportado por uno o ms drivers. Un ejemplo sobresaliente de un subprotocolo es odbc, que ha sido reservado para URLs que especifican nombres de fuentes de datos estilo ODBC. Por ejemplo para acceder a la base de datos a travs del puente JDBC-ODBC, la URL a usar podra ser algo as como lo siguiente:
jdbc:odbc:fred
En este ejemplo, el subprotocolo es odbc y el subnombre fred es el nombre de la fuente de datos ODBC local. Si se quiere usar un servicio de nombre de la red (ya que el nombre de la base de datos en la URL JDBC no tiene por que ser su nombre actal), el servicio de nombre puede ser el subprotocolo. Por tanto, para el ejemplo, podra tener una URL como : jdbc:dcenaming:accounts-payable En este ejemplo, la URL especifica que el servicio local DCE resolver el nombre de la base de datos accounts-payable para poder conectar con la base de datos real. 3 <subname> - una forma de identificar la base de datos. El subnombre puede variar dependiendo del subprotocolo, y puede tener un subnombre con cualquier sintaxis interna que el fabricante del driver haya escogido. El punto de un subnombre es para dar informacin suficiente para localizar la base de datos. En el ejemplo anterior fred es suficiente porque ODBC suministra la informacin restante. Una base de datos en un servidor remoto requiere ms informacin. Si la base de datos va a ser accesible a travs de Internet, por ejemplo, la direccin de red debera incluirse en la URL JDBC como parte del
237
Suponiendo que dbnet es un protocolo para conectar a un host, la URL JDBC debera parecerse a algo como:
jdbc:dbnet://wombat:356/fred
Level para ser designado JDBC COMPLIANT. Esto significa que los usuarios pueden contar al menos con este nivel de funcionalidad. JDBC suministra tres clases para el envo de sentencias SQL y tres mtodos en la interfaz Connection para crear instancias de estas tres clases. Estas clases y mtodos son los siguientes: 1.- Statement creada por el mtodo createStatement. Un objeto Statement
se usa para enviar sentencias SQL simples 2.- PreparedStatement creada por el mtodo prepareStatement- Un objeto PreparedStatement se usa para sentencias SQL que toman uno o ms parmetros como argumentos de entrada (parmetros IN). PreparedStatement tiene un grupo de mtodos que fijan los valores de los parmetros IN, los cuales son enviados a la base de datos cuando se procesa la sentencia SQL. Instancias de PreparedStatement extienden Statement y por tanto heredan los mtodos de Statement. Un objeto PreparedStatement es potencialmente ms eficiente que un objeto Statement porque este ha sido precompilado y almacenado para su uso futuro.
3.- CallableStatement creado por el mtodo prepareCall. Los objetos CallableStatement se usan para ejecutar procedimientos almacenados SQL un grupo de sentencias SQL que son llamados mediante un nombre, algo parecido a una funcin - . Un objeto CallableStatement hereda mtodos para el manejo de los parmetros IN de PreparedStatement, y aade mtodos para el manejo de los parmetros OUT e INOUT. La lista siguiente da una forma rpida de determinar que mtodo Connection es el apropiado para crear los diferentes tipos de sentencias SQL. El mtodo createStatement se usa para: Sentencias SQL simples (sin parmetros).
El mtodo prepareStatement se usa para: Sentencias SQL con uno ms parmetros IN Sentencias SQL simples que se ejecutan frecuentemente
2.1.7 Transacciones
Una transaccin consiste en una o ms sentencias que han sido ejecutadas, completas y, o bien se ha hecho commit o bien roll-back. Cuando se llama al mtodo commit o rollback , la transaccin actal finaliza y comienza otra.
239
Una conexin nueva se abre por defecto en modo auto-commit, y esto significa que cuando se completa se llama automticamente al mtodo commit. En este caso, cada sentencia es commitada individualmente, por tanto una transaccin se compone de una nica sentencia. Si el modo auto-commit es desactivado, la transaccin no terminar hasta que se llame al mtodo commit o al mtodo rollback explcitamente, por lo tanto incluir todas las sentencias que han sido ejecutadas desde la ltima invocacin a uno de los mtodos commit o rollback. En este segundo caso, todas las sentencias de la transaccin son commitadas o deshechas en grupo. El mtodo commit hace permanente cualquier cambio que una sentencia SQL realiza en la base de datos, y libera cualquier bloqueo mantenido por la transaccin. El mtodo rollback descarta estos cambios. A veces un usuario no quiere que tenga efecto un determinado cambio a menos que se efectu otro. Esto puede hacerse desactivando el modo auto-commit y agrupando ambas actualizaciones en una transaccin. Si ambas actualizaciones tienen xito se llama al mtodo commit haciendo estos cambios permanentes, si uno o los dos fallan, se llama al mtodo rollback, restaurando los valores existentes l inicio de la transaccin. Muchos drivers JDBC soportan transacciones. De hecho, un driver JDBC-compliant debe soportar transacciones. DatabaseMetaData suministra informacin que describe el nivel de transaccin soportado por el DBMS.
se incrementan los bloqueos y se disminuye la concurrencia de los usuarios). El desarrollador debe balancear la necesidad de rendimiento con la necesidad de la consistencia de los datos al tomar la decisin del nivel de aislamiento a usar. Por supuesto, el nivel de aislamiento que pueda soportarse depende de las posibilidades de la base de datos subyacente. Cuando se crea un nuevo objeto Connection, su nivel de aislamiento depende del driver, pero normalmente por defecto es el de la DBMS subyacente. Un usuario puede llamar al mtodo setIsolationLevel para cambiar el nivel de aislamiento de la transaccin, y este nuevo nivel estar efectivo durante la sesin de conexin. Para cambiar el nivel de aislamiento solo para una transaccin, es necesario fijar este antes de la transaccin comience y volverlo a situar en su valor anterior una vez que la transaccin haya terminado. No se recomienda cambiar el nivel de aislamiento de transaccin en medio de una puesto que lanzar una llamada inmediata al mtodo commit, provocando que los cambios hasta ese punto se hagan permanentes en la base de datos.
3. LA CLASE DriverManager
3.1 Vista preliminar
La clase DriverManager implementa la capa de gestin de JDBC, y trabaja como intermediaria entre el usuario y los drivers. Guarda la lista de los drivers que estn disponibles y establece la conexin entre la base de datos y el driver apropiado. Adems la clase DriverManager se ocupa de cosas cmo gestionar los lmites de tiempo de login en el driver y de la salida de los mensajes de traza y log. Para aplicaciones simples, el nico mtodo en esta clase que necesita un programador general para su uso directamente es DriverManager.getConnection. Como su nombre indica, este mtodo establece una conexin con la base de datos. JDBC permite al usuario llamar a los mtodos de DriverManager getDriver, getDrivers y registerDriver as como al mtodo de Driver connect, pero en la mayora de los casos es preferible dejar que la clase DriverManager gestione los detalles al establecer la conexin.
automticamente por el driver cuando est se carga,. Una clase Driver se carga, y por tanto se registra, de dos formas diferentes: 1 Mediante una llamada al mtodo Class.forName. Este carga explcitamente la clase driver. Dado que no depende de ningn setup externo, esta forma de cargar drivers es la recomendada. El siguiente cdigo carga la clase acme.db.Driver:
Class.forName("acme.db.Driver");
Si acme.db.Driver se ha escrito para que al cargar produzca una instancia y llame al mtodo DriverManager.registerDriver con esta instancia como argumento (es lo que debera hacer), entonces este estar en la lista de drivers disponibles para efectuar la conexin.
2
Mediante la adicin del driver a la propiedad jdbc.drivers de java.lang.System- Esta es una lista de nombres de clases de drivers, separadas por dos puntos, que es la que carga la clase DriverManager. Cuando la clase DriverManager se inicializa, mira en la propiedad jdbc.drivers, y si el usuario ha introducido uno o ms drivers, la clase DriverManager intenta cargarlos. El siguiente cdigo ilutsra como un programador debera introducir estas tres clases en ~/.hotjava/properties
jdbc.drivers=foo.bah.Driver:wombat.sql.Driver:bad.test.ourDriver;
La primera llamada a el mtodo DriverManager har que estos drivers se carguen automticamente. Notese que en esta segunda manera de cargar los drivers es necesario una preparacin del entorno que es persistente. Si existe alguna duda sobre esto es preferible y ms seguro llamar al mtodo Class.forName para cargar explicitamente cada driver. Este es tambin el mtodo que se usa para traer un driver particular puesto que una vez que la clase DriverManager ha sido inicializada no chequear la lista de propiedades jdbc.drivers. En ambos casos, es responsabilidad de la clase Driver recin cargada registrarse a si misma mediante una llamada a DriverManager.registerDriver. Como se ha mencionado anteriormente, esto debera hacerse automticamente al cargar la clase. Por razones de seguridad, la capa de gestin de JDBC guardar traza de que clases de cargadores provee cada driver. Entonces cuando la clase DriverManager abre una conexin solo usar los drivers que vienen desde el sistema de ficheros local o desde las mismas clases cargadoras como el cdigo que solicita la conexin.
242
4. LA CLASE Statement
4.1 Vista Preliminar
Un objeto Statement se usa para enviar sentencias SQL a la base de datos. Actualmente hay tres tipos de objetos Statement, todos los cuales actan como contenedores para la ejecucin de sentencias en una conexin dada: Statement,
243
PreparedStatement que hereda de Statement y CallableStatement que hereda de PreparedStatement. Estas estn especializadas para enviar tipos particulares de sentencias SQL, Un objeto Statement se usa para ejecutar una sentencia SQL simple sin parmetros. Un objeto PreparedStatement se usa para ejecutar sentencias SQL precompiladas con o sin parmetros IN; y un objeto CallableStatement se usa para ejecutar un procedimieno de base de datos almacenado. La interfase Statement suminstra mtodos bsicos para ejecutar sentencias y devolver resultados. La interfase PreparedStatement aade mtodos para trabajat con los parmetros IN; y la interfase CallableStatement aade mtodos para trabajar con parameters OUT.
La sentencia SQL que ser enviada a la base de datos es alimentada como un argumento a uno de los mtodos de ejecucin del objeto Statement. Por ejemplo:
ResultSet rs = stmt.executeQuery("SELECT a, b, c FROM Table2");
El mtodo execute se usa para ejecutar sentencias que devuelven ms de un result set, ms que un update count o una combinacin de ambos. Como es esta una caracterstica avanzada que muchos programadores no necesitaran nunca se ver en su propia seccin. Todos los mtodos que ejecutan sentencias cierran los objetos Resultset abiertos como resultado de las llamadas a Statement. Esto quiere decir que es necesario completar el proceso con el actual objeto Resulset antes de reejecutar una sentencia Statement. Debe notarse que la interfase PreparedStatement, que hereda los mtodos de la interfase Statement, tiene sus propias versiones de los mtodos executeQuery, executeUpdate y execute. Los objetos Statement en si mismos no contienen una sentencia SQL, por tanto debe suministrarse como un argumento a los mtodos Statement.execute. Los objetos PreparedStatement no suministran una sentencia SQL como argumento a estos mtodos puesto que ya tienen la sentencia precompilada. Los objetos CallableStatement heredan las formas de estos mtodos de PreparedStatement. Usar un parametro de query con las versiones de los mtodos de PreparedStatement o CallableStatement produciri una SQLException,.
Utilizar el Mtodo next La variable rs, que es un ejemplar de ResultSet, contiene las filas de cafs y sus precios mostrados en el juego de resultados de la pgina anterior. Para acceder a los nombres y los precios, iremos a la fila y recuperaremos los valores de acuerdo con sus tipos. El mtodo next mueve algo llamado cursor a la siguiente fila y hace que esa fila (llamada fila actual) sea con la que podamos operar. Como el cursor inicialmente se posiciona justo encima de la primera fila de un objeto ResultSet, primero debemos llamar al mtodo next para mover el cursor a la primera fila y convertirla en la fila actual. Sucesivas invocaciones del mtodo next movern el cursor de lnea en lnea de arriba a abajo. Observa que con el JDBC 2.0, cubierto en la siguiente seccin, se puede mover el cursor hacia atrs, hacia posiciones especficas y a posiciones relativas a la fila actual adems de mover el cursor hacia adelante. Utilizar los mtodos getXXX Los mtodos getXXX del tipo apropiado se utilizan para recuperar el valor de cada columna. Por ejemplo, la primera columna de cada fila de rs es COF_NAME, que almacena un valor del tipo VARCHAR de SQL. El mtodo para recuperar un valor VARCHAR es getString. La segunda columna de cada fila almacena un valor del tipo FLOAT de SQL, y el mtodo para recuperar valores de ese tipo es getFloat. El siguiente
246
cdigo accede a los valores almacenados en la fila actual de rs e imprime una lnea con el nombre seguido por tres espacios y el precio. Cada vez que se llama al mtodo next, la siguiente fila se convierte en la actual, y el bucle contina hasta que no haya ms filas en rs.
String query = "SELECT COF_NAME, PRICE FROM COFFEES"; ResultSet rs = stmt.executeQuery(query); while (rs.next()) { String s = rs.getString("COF_NAME"); Float n = rs.getFloat("PRICE"); System.out.println(s + " " + n); }
Veamos cmo funcionan los mtodos getXXX examinando las dos sentencias getXXX de este cdigo. Primero examinaremos getString.
String s = rs.getString("COF_NAME");
El mtodo getString es invocado sobre el objeto ResultSet: rs, por eso getString recuperar (obtendr) el valor almacenado en la columna COF_NAME de la fila actual de rs. El valor recuperado por getString se ha convertido desde un VARCHAR de SQL a un String de Java y se ha asignado al objeto String s. Observa que utilizamos la variable s en la expresin println mostrada arriba, de esta forma: println(s + " " + n) La situacin es similar con el mtodo getFloat excepto en que recupera el valor almacenado en la columna PRICE, que es un FLOAT de SQL, y lo convierte a un float de Java antes de asignarlo a la variable n. JDBC ofrece dos formas para identificar la columna de la que un mtodo getXXX obtiene un valor. Una forma es dar el nombre de la columna, como se ha hecho arriba. La segunda forma es dar el ndice de la columna (el nmero de columna), con un 1 significando la primera columna, un 2 para la segunda, etc. Si utilizramos el nmero de columna en vez del nombre de columna el cdigo anterior se podra parecer a esto.
String s = rs.getString(1); float n = rs.getFloat(2);
La primera lnea de cdigo obtiene el valor de la primera columna de la fila actual de rs (columna COF_NAME), convirtindolo a un objeto String de Java y asignndolo a s. La segunda lnea de cdigo obtiene el valor de la segunda columna de la fila actual de rs, lo convierte a un float de Java y lo asigna a n. Recuerda que el nmero de columna se refiere al nmero de columna en la hoja de resultados no en la tabla original. En suma, JDBC permite utilizar tanto el nombre cmo el nmero de la columna como argumento a un mtodo getXXX. Utilizar el nmero de columna es un poco ms eficiente, y hay algunos casos donde es necesario utilizarlo.
247
JDBC permite muchas lateralidades para utilizar los mtodos getXXX para obtener diferentes tipos de datos SQL. Por ejemplo, el mtodo getInt puede ser utilizado para recuperar cualquier tipo numrico de caracteres. Los datos recuperados sern convertidos a un int; esto es, si el tipo SQL es VARCHAR, JDBC intentar convertirlo en un entero. Se recomienda utilizar el mtodo getInt slo para recuperar INTEGER de SQL, sin embargo, no puede utilizarse con los tipos BINARY, VARBINARY, LONGVARBINARY, DATE, TIME, o TIMESTAMP de SQL. Mtodos para Recuperar Tipos SQL muestra qu mtodos pueden utilizarse legalmente para recuperar tipos SQL, y ms importante, qu mtodos estn recomendados para recuperar los distintos tipos SQL. Observa que esta tabla utiliza el trmino "JDBC type" en lugar de "SQL type." Ambos trminos se refieren a los tipos genricos de SQL definidos en java.sql.Types, y ambos son intercambiables. Utilizar el mtodo getString Aunque el metodo getString est recomendado para recuperar tipos CHAR y VARCHAR de SQL, es posible recuperar cualquier tipo bsico SQL con l. (Sin embargo, no se pueden recuperar los nuevos tipos de datoas del SQL3. Explicaremos el SQL3 ms adelante). Obtener un valor con getString puede ser muy til, pero tiene sus limitaciones. Por ejemplo, si se est utilizando para recuperar un tipo numrico, getString lo convertir en un String de Java, y el valor tendr que ser convertido de nuevo a nmero antes de poder operar con l. Utilizar los mtodos de ResultSet.getXXX para Recuperar tipos JDBC Una "x" indica que el mtodo getXXX se puede utilizar legalmente para recuperar el tipo JDBC dado. Una "X" indica que el mtodo getXXX est recomendado para recuperar el tipo JDBC dado.
Actualizar Tablas
Supongamos que despus de una primera semana exitosa, el propietario de "The Coffee Break" quiere actualizar la columna SALES de la tabla COFFEES introduciendo el nmero de libras vendidas de cada tipo de caf. La sentencia SQL para actualizar una columna se podra parecer a esto. String updateString = "UPDATE COFFEES " + "SET SALES = 75 " + "WHERE COF_NAME LIKE 'Colombian'"; Uitlizando el objeto stmt, este cdigo JDBC ejecuta la sentencia SQL contenida en updateString. stmt.executeUpdate(updateString); La tablaCOFFEES ahora se parecer a esto.
SALES 75 0 0 0 0
TOTAL 0 0 0 0 0
Observa que todava no hemos actualizado la columna TOTAL, y por eso tiene valor 0. Ahora seleccionaremos la fila que hemos actualizado, recuperando los valores de las columnas COF_NAME y SALES, e imprimiendo esos valores.
String query = "SELECT COF_NAME, SALES FROM COFFEES " + "WHERE COF_NAME LIKE 'Colombian'"; ResultSet rs = stmt.executeQuery(query); while (rs.next()) { String s = rs.getString("COF_NAME"); int n = rs.getInt("SALES"); System.out.println(n + " pounds of " + s + " sold this week.") }
Cmo la clasula WHERE lmita la seleccin a una sla lnea, slo hay una lnea en la ResultSet: rs y una lnea en la salida. Por lo tanto, sera posible escribir el cdigo sin un bucle while.
rs.next(); String s = rs.getString(1); int n = rs.getInt(2); System.out.println(n + " pounds of " + s + " sold this week.")
Aunque hay una sla lnea en la hoja de resultados, necesitamos utilizar el mtodo next para acceder a ella. Un objeto ResultSet se crea con un cursor apuntando por encima de la primera fila. La primera llamada al mtodo next posiciona el cursor en la primera fila (y en este caso, la nica) de rs. En este cdigo, slo se llama una vez a next, si sucediera que existiera una lnea, nunca se accedera a ella. Ahora actualizaremos la columna TOTAL aadiendo la cantidad vendida durante la semana a la cantidad total existente, y luego imprimiremos el nmero de libras vendidas hasta la fecha.
String updateString = "UPDATE COFFEES " + "SET TOTAL = TOTAL + 75 " + "WHERE COF_NAME LIKE 'Colombian'"; stmt.executeUpdate(updateString); String query = "SELECT COF_NAME, TOTAL FROM COFFEES " + "WHERE COF_NAME LIKE 'Colombian'"; ResultSet rs = stmt.executeQuery(query); while (rs.next()) { String s = rs.getString(1); int n = rs.getInt(2); System.out.println(n + " pounds of " + s + " sold to date.") }
Observa que en este ejemplo, utilizamos el ndice de columna en vez del nombre de columna, suministrando el ndice 1 a getString (la primera columna de la hoja de resultados es COF_NAME), y el ndice 2 a getInt (la segunda columna de la hoja de resultados es TOTAL). Es importante distinguir entre un ndice de columna en la tabla de la base de datos como opuesto al ndice en la tabla de la hoja de resultados. Por ejemplo, TOTAL es la quinta columna en la tabla COFFEES pero es la segunda columna en la hoja de resultados generada por la peticin del ejemplo anterior.
249
La variable updateSales contiene la sentencia SQL, "UPDATE COFFEES SET SALES = ? WHERE COF_NAME LIKE ?", que tambin ha sido, en la mayora de los casos, enviada al controlador de la base de datos, y ha sido precompilado. Suministrar Valores para los Parmetros de un PreparedStatement Necesitamos suministrar los valores que se utilizarn en los luegares donde estn las marcas de interrogacin, si hay alguno, antes de ejecutar un objeto PreparedStatement. Podemos hacer esto llamado a uno de los mtodos setXXX definidos en la clase PreparedStatement. Si el valor que queremos sustituir por una marca de interrogacin es un int de Java, podemos llamar al mtodo setInt. Si el valor que queremos sustituir es un String de Java, podemos llamar al mtodo setString, etc. En general, hay un mtodo setXXX para cada tipo Java. Utilizando el objeto updateSales del ejemplo anterior, la siguiente lnea de cdigo selecciona la primera marca de interrogacin para un int de Java, con un valor de 75.
updateSales.setInt(1, 75);
250
Cmo podramos asumir a partir de este ejemplo, el primer argumento de un mtodo setXXX indica la marca de interrogacin que queremos seleccionar, y el segundo argumento el valor que queremos ponerle. El siguiente ejemplo selecciona la segunda marca de interrogacin con el string "Colombian".
updateSales.setString(2, "Colombian");
Despus de que estos valores hayan sido asignados para sus dos parmetros, la sentencia SQL de updateSales ser equivalente a la sentencia SQL que hay en string updateString que utilizando en el ejemplo anterior. Por lo tanto, los dos fragmentos de cdigo siguientes consiguen la misma cosa. Cdigo 1.
String updateString = "UPDATE COFFEES SET SALES = 75 " + "WHERE COF_NAME LIKE 'Colombian'"; stmt.executeUpdate(updateString);
Cdigo 2.
PreparedStatement updateSales = con.prepareStatement( "UPDATE COFFEES SET SALES = ? WHERE COF_NAME LIKE ? "); updateSales.setInt(1, 75); updateSales.setString(2, "Colombian"); updateSales.executeUpdate().
Utilizamos el mtodo executeUpdate para ejecutar ambas sentencias stmt updateSales. Observa, sin embargo, que no se suministran argumentos a executeUpdate cuando se utiliza para ejecutar updateSales. Esto es cierto porque updateSales ya contiene la sentencia SQL a ejecutar. Mirando esto ejemplos podramos preguntarnos por qu utilizar un objeto PreparedStatement con parmetros en vez de una simple sentencia, ya que la sentencia simple implica menos pasos. Si actualizramos la columna SALES slo una o dos veces, no sera necesario utilizar una sentencia SQL con parmetros. Si por otro lado, tuvieramos que actualizarla frecuentemente, podra ser ms fcil utilizar un objeto PreparedStatement, especialmente en situaciones cuando la utilizamos con un bucle while para seleccionar un parmetro a una sucesin de valores. Veremos este ejemplo ms adelante en esta seccin. Una vez que a un parmetro se ha asignado un valor, el valor permanece hasta que lo resetee otro valor o se llame al mtodo clearParameters. Utilizando el objeto PreparedStatement: updateSales, el siguiente fragmento de cdigo reutiliza una sentencia prepared despus de resetar el valor de uno de sus parmetros, dejando el otro igual.
updateSales.setInt(1, 100); updateSales.setString(2, "French_Roast"); updateSales.executeUpdate(); // changes SALES column of French Roast row to 100 updateSales.setString(2, "Espresso"); updateSales.executeUpdate(); // changes SALES column of Espresso row to 100 (the first // parameter stayed 100, and the second parameter was reset // to "Espresso")
251
Utilizar una Bucle para asignar Valores Normalmente se codifica ms sencillo utilizando un bucle for o while para asignar valores de los parmetros de entrada. El siguiente fragmento de cdigo demuestra la utilizacin de un bucle for para asignar los parmetros en un objeto PreparedStatement: updateSales. El array salesForWeek contiene las cantidades vendidas semanalmente. Estas cantidades corresponden con los nombres de los cafs listados en el array coffees, por eso la primera cantidad de salesForWeek (175) se aplica al primer nombre de caf de coffees ("Colombian"), la segunda cantidad de salesForWeek (150) se aplica al segundo nombre de caf en coffees ("French_Roast"), etc. Este fragmento de cdigo demuestra la actualizacin de la columna SALES para todos los cafs de la tabla COFFEES PreparedStatement updateSales; String updateString = "update COFFEES " + "set SALES = ? where COF_NAME like ?"; updateSales = con.prepareStatement(updateString);int [] salesForWeek = {175, 150, 60, 155, 90}; String [] coffees = {"Colombian", "French_Roast", "Espresso", "Colombian_Decaf", "French_Roast_Decaf"}; int len = coffees.length; for(int i = 0; i < len; i++) { updateSales.setInt(1, salesForWeek[i]); updateSales.setString(2, coffees[i]); updateSales.executeUpdate(); }
Cuando el propietario quiera actualizar las ventas de la semana siguiente, puede utilizar el mismo cdigo como una plantilla. Todo lo que tiene que haces es introducir las nuevas cantidades en el orden apropiado en el array salesForWeek. Los nombres de cafs del array coffees permanecen constantes, por eso no necesitan cambiarse. (En una aplicacin real, los valores probablemente seran introducidos por el usuario en vez de desde un array inicializado). Valores de retorno del mtodo executeUpdate Siempre que executeQuery devuelve un objeto ResultSet que contiene los resultados de una peticin al controlador de la base datos, el valor devuelto por executeUpdate es un int que indica cuntas lneas de la tabla fueron actualizadas. Por ejemplo, el siguiente cdigo muestra el valor de retorno de executeUpdate asignado a la variable n.
updateSales.setInt(1, 50); updateSales.setString(2, "Espresso"); int n = updateSales.executeUpdate(); // n = 1 because one row had a change in it
La tabla COFFEES se ha actualziado poniendo el valor 50 en la columna SALES de la fila correspondiente a Espresso. La actualizacin afecta slo a una lnea de la tabla, por eso n es igual a 1. Cuando el mtodo executeUpdate es utilizado para ejecutar una sentecia DDL, como la creacin de una tabla, devuelve el int: 0. Consecuentemente, en el siguiente fragmento de cdigo, que ejecuta la sentencia DDL utilizada pra crear la tabla COFFEES, n tendr el valor 0.
int n = executeUpdate(createTableCoffees); // n = 0
252
Observa que cuando el valor devuelto por executeUpdate sea 0, puede significar dos cosas: (1) la sentencia ejecutada no ha actualizado ninguna fila, o (2) la sentencia ejecutada fue una sentencia DDL.
Utilizar Uniones
Algunas veces necesitamos utilizar una o ms tablas para obtener los datos que queremos. Por ejemplo, supongamos que el propietario del "The Coffee Break" quiere una lista de los cafs que le compra a Acme, Inc. Esto implica informacin de la tabla COFFEES y tambin de la que vamos a crear SUPPLIERS. Este es el caso en que se necesitan los "joins" (unin). Una unin es una operacin de base de datos que relaciona dos o ms tablas por medio de los valores que comparten. En nuestro ejemplo, las tablas COFFEES y SUPPLIERS tienen la columna SUP_ID, que puede ser utilizada para unirlas. Antes de ir ms all, necesitamos crear la tabla SUPPLIERS y rellenarla con valores. El siguiente cdigo crea la tabla SUPPLIERS.
String createSUPPLIERS = "create table SUPPLIERS " + "(SUP_ID INTEGER, SUP_NAME VARCHAR(40), " + "STREET VARCHAR(40), CITY VARCHAR(20), " + "STATE CHAR(2), ZIP CHAR(5))"; stmt.executeUpdate(createSUPPLIERS);
Ahora que tenemos las tablas COFFEES y SUPPLIERS, podremos proceder con el escenario en que el propietario quera una lista de los cafs comprados a un suministrador particular. Los nombres de los suminstradores estn en la tabla SUPPLIERS, y los nombres de los cafs en la tabla COFFEES. Como ambas tablas tienen la columna SUP_ID, podemos utilizar esta columna en una unin. Lo siguiente que necesitamos es la forma de distinguir la columna SUP_ID a la que nos referimos. Esto se hace precediendo el nombre de la columna con el nombre de la tabla, "COFFEES.SUP_ID" para indicar que queremos referirnos a la columna SUP_ID de la tabla COFFEES. En el siguiente cdigo, donde stmt es un objeto Statement, seleccionamos los cafs comprados a Acme, Inc..
String query = " SELECT COFFEES.COF_NAME " + "FROM COFFEES, SUPPLIERS " + "WHERE SUPPLIERS.SUP_NAME LIKE 'Acme, Inc.'" + "and SUPPLIERS.SUP_ID = COFFEES.SUP_ID"; ResultSet rs = stmt.executeQuery(query); System.out.println("Coffees bought from Acme, Inc.: "); while (rs.next()) { String coffeeName = getString("COF_NAME"); System.out.println(" " + coffeeName); }
253
El asterisco (*) indica que todas las clases del paquete java.sql sern importadas. Importar una clase la hace visible y significa que no tendremos que escribir su nombre totalmente cualificado cuando utilicemos un mtodo o un campo de esa clase. Si no incluimos "import java.sql.*;" en nuestro cdigo, tendramos que escribir "java.sql." ms el nombre de la clase delante de todos los campos o mtodos JDBC que utilicemos cada vez que los utilicemos. Observa que tambin podemos importar clases individuales selectivamente en vez de importar un paquete completo. Java no requiere que importemos clases o paquetes, pero al hacerlo el cdigo se hace mucho ms conveniente. Cualquier lnea que importe clases aparece en la parte superior de los ejemplos de cdigo, que es donde deben estar para hacer visibles las clases importadas a la clase que est siendo definida. La definicin real de la clase sigue a cualquier lnea que importe clases. Utilizar el Mtodo main() Si una clase se va a ejecutar, debe contener un mtodo static public main. Este mtodo viene justo despus de la lnea que declara la clase y llama a los otros mtodos de la clase.
254
La palabra clave static indica que este mtodo opera a nivel de clase en vez sobre ejemplares individuales de la clase. La palabra clave public significa que los miembros de cualquier clase pueden acceder a este mtodo. Como no estamos definiendo clases slo para ser ejecutadas por otras clases sino que queremos ejecutarlas, las aplicaciones de ejemplo de este captulo incluyen un mtodo main. Utilizar bloques try y catch Algo que tambin incluyen todas las aplicaciones de ejemplo son los bloques try y catch. Este es un mecanismo del lenguaje Java para manejar excepciones. Java requiere que cuando un mtodo lanza un excepcin exista un mecanismo que la maneje. Generalmente un bloque catch capturar la excepcin y especificar lo que suceder (que podra ser no hacer nada). En el cdigo de ejemplo, utilizamos dos bloques try y dos bloques catch. El primer bloque try contiene el mtodo Class.forName, del paquete java.lang. Este mtodo lanza una ClassNotFoundException, por eso el bloque catch que le sigue maneja esa excepcin. El segundo bloque try contiene mtodos JDBC, todos ellos lanzan SQLException, por eso el bloque catch del final de la aplicacin puede manejar el resto de las excepciones que podran lanzarse ya que todas seran objetos SQLException. Recuperar Excepciones JDBC permite ver los avisos y excepciones generados por nuestro controlador de base de datos y por el compilador Java. Para ver las excepciones, podemos tener un bloque catch que las imprima. Por ejemplo, los dos bloques catch del siguiente cdigo de ejemplo imprimen un mensaje explicando la excepcin.
try { // Aqu va el cdigo que podra generar la excepcin. // Si se genera una excepcin, el bloque catch imprimir // informacin sobre ella. } catch(SQLException ex) { System.err.println("SQLException: " + ex.getMessage()); } try { Class.forName("myDriverClassName"); } catch(java.lang.ClassNotFoundException e) { System.err.print("ClassNotFoundException: "); System.err.println(e.getMessage()); }
Este ejemplo ilustra la impresin del componente mensaje de un objeto SQLException, lo que es suficiente para la mayora de las situaciones. Sin embargo, realmente existen tres componentes, y para ser completos, podemos imprimirlos todos. El siguiente fragmento de cdigo muestra un bloque catch que se ha completado de dos formas. Primero, imprime las tres partes de un objeto SQLException: el
255
mensaje (un string que describe el error), el SQLState (un string que identifica el error de acuerdo a los convenciones X/Open de SQLState), y un cdigo de error del vendedor (un nmero que es el cdigo de error del vendedor del driver). El objeto SQLException, ex es capturado y se accede a sus tres componentes con los mtodos getMessage, getSQLState, y getErrorCode. La segunda forma del siguiente bloque catch completo obtiene todas las exepciones que podran haber sido lanzada. Si hay una segunda excepcin, sera encadenada a ex, por eso se llama a ex.getNextException para ver si hay ms excepciones. Si las hay, el bucle while contina e imprime el mensaje de la siguiente excecpcin, el SQLState, y el cdigo de error del vendedor. Esto contina hasta que no haya ms excepciones.
try { // Aqu va el cdigo que podra generar la excepcin. // Si se genera una excepcin, el bloque catch imprimir // informacin sobre ella. } catch(SQLException ex) { System.out.println("\n--- SQLException caught ---\n"); while (ex != null) { System.out.println("Message: " + ex.getMessage ()); System.out.println("SQLState: " + ex.getSQLState ()); System.out.println("ErrorCode: " + ex.getErrorCode ()); ex = ex.getNextException(); System.out.println(""); } }
Si hubieramos sustituido el bloque catch anterior en el Cdigo de ejemplo 1 (CreateCoffees) y lo hubieramos ejecutado despus de que la tabla COFFEES ya se hubiera creado, obtendramos la siguiente informacin.
--- SQLException caught --Message: There is already an object named 'COFFEES' in the database. Severity 16, State 1, Line 1 SQLState: 42501 ErrorCode: 2714
SQLState es un cdigo definido en X/Open y ANSI-92 que identifica la excepcin. Aqu podemos ver dos ejemplos de cdigos SQLState.
08001 -- No suitable driver HY011 -- Operation invalid at this time
El cdigo de error del vendedor es especfico de cada driver, por lo que debemos revisar la documentacin del driver buscando una lista con el significado de estos cdigos de error. Recuperar Avisos Los objetos SQLWarning son una subclase de SQLException que trata los avisos de accesos a bases de datos. Los Avisos no detienen la ejecucin de una aplicacin, como las excepciones; simplemente alertan al usuario de que algo no ha salido como se esperaba. Por ejemplo, un aviso podra hacernos saber que un privilegio que queriamos revocar no ha fue revocado. O un aviso podra decirnos que ha ocurrido algn error durante una peticin de desconexin. Un aviso puede reportarse sobre un objeto Connection, un objeto Statement (incluyendo objetios PreparedStatement y CallableStatement), o un objeto ResultSet. Cada una de
256
esas clases tiene un mtodo getWarnings, al que debemos llamar para ver el primer aviso reportado en la llamada al objeto. Si getWarnings devuelve un aviso, podemos llamar al mtodo getNextWarning de SQLWarning para obtener avisos adicionales. Al ejecutar una sentencia se borran automticamente los avisos de la sentencia anterior, por eso no se apilan. Sin embargo, esto significa que si queremos recuperar los avisos reportados por una sentencia, debemos hacerlo antes de ejecutar otra sentencia. El siguiente fragmento de cdigo ilustra como obtener informacin completa sobre los avisos reportados por el objeto Statement, stmt y tambin por el objeto ResultSet, rs.
Statement stmt = con.createStatement(); ResultSet rs = stmt.executeQuery("select COF_NAME from COFFEES"); while (rs.next()) { String coffeeName = rs.getString("COF_NAME"); System.out.println("Coffees available at the Coffee Break: "); System.out.println(" " + coffeeName); SQLWarning warning = stmt.getWarnings(); if (warning != null) { System.out.println("\n---Warning---\n"); while (warning != null) { System.out.println("Message: " + warning.getMessage()); System.out.println("SQLState: " + warning.getSQLState()); System.out.print("Vendor error code: "); System.out.println(warning.getErrorCode()); System.out.println(""); warning = warning.getNextWarning(); } } SQLWarning warn = rs.getWarnings(); if (warn != null) { System.out.println("\n---Warning---\n"); while (warn != null) { System.out.println("Message: " + warn.getMessage()); System.out.println("SQLState: " + warn.getSQLState()); System.out.print("Vendor error code: "); System.out.println(warn.getErrorCode()); System.out.println(""); warn = warn.getNextWarning(); } } }
Los avisos no son muy comunes, De aquellos que son reportados, el aviso ms comn es un DataTruncation, una subclase de SQLWarning. Todos los objetos DataTruncation tienen un SQLState 01004, indicando que ha habido un problema al leer o escribir datos. Los mtodos de DataTruncation permiten encontrar en que columna o parmetro se truncaron los datos, si la ruptura se produjo en una operacin de lectura o de escritura, cuntos bytes deberan haber sido transmitidos, y cuntos bytes se transmitieron realmente.
Con el API JDBC 2.0, podremos hacer las siguientes cosas: Ir hacia adelante o hacia atrs en una hoja de resultados o movernos a un fila especfica. Hacer actualizaciones de las tablas de la base datos utilizando mtodos Java en lugar de utilizar comandos SQL. Enviar mltiples secuencias SQL a la base de datos como una unidad, o batch. Uitlizar los nuevos tipos de datos SQL3 como valores de columnas.
Este cdigo es similar al utilizado anteriormente, excepto en que aade dos argumentos al mtodo createStatement. El primer argumento es una de las tres constantes aadidas al API ResultSet para indicar el tipo de un objeto ResultSet: TYPE_FORWARD_ONLY, TYPE_SCROLL_INSENSITIVE, y TYPE_SCROLL_SENSITIVE. El segundo argumento es una de las dos constantes de ResultSet para especificar si la hoja de resultados es de slo lectura o actualizable:CONCUR_READ_ONLY y CONCUR_UPDATABLE. Lo que debemos recordar aqu es que si especificamos un tipo, tambin debemos especificar si es de slo lectura o actualizable. Tambin, debemos especificar primero el tipo, y como ambos parmetros son int, el compilador no comprobar si los hemos intercambiado.
258
Especificando la constante TYPE_FORWARD_ONLY se crea una hoja de resultados no desplazable, es decir, una hoja en la que el cursor slo se mueve hacia adelante. Si no se especifican constantes para el tipo y actualizacin de un objeto ResultSet, obtendremos automticamente una TYPE_FORWARD_ONLY y CONCUR_READ_ONLY (exactamente igual que en el API del JDBC 1.0). Obtendremos un objeto ResultSet desplazable si utilizamos una de estas constantes:TYPE_SCROLL_INSENSITIVE o TYPE_SCROLL_SENSITIVE. La diferencia entre estas dos es si la hoja de resultados refleja los cambios que se han hecho mientras estaba abierta y si se puede llamar a ciertos mtodos para detectar estos cambios. Generalmente hablando, una hoja de resultados TYPE_SCROLL_INSENSITIVE no refleja los cambios hechos mientras estaba abierta y en una hoja TYPE_SCROLL_SENSITIVE si se reflejan. Los tres tipos de hojas de resultados harn visibles los resultados si se cierran y se vuelve a abrir. En este momento, no necesitamos preocuparnos de los puntos delicados de las capacidades de un objeto ResultSet, entraremos en ms detalle ms adelante. Aunque deberamos tener en mente el hecho de que no importa el tipo de hoja de resultados que especifiquemos, siempre estaremos limitados por nuestro controlador de base de datos y el driver utilizados. Una vez que tengamos un objeto ResultSet desplazable, srs en el ejemplo anterior, podemos utilizarlo para mover el cursor sobre la hoja de resultados. Recuerda que cuando creabamos un objeto ResultSet anteriormente, tena el cursor posicionado antes de la primera fila. Incluso aunque una hoja de resultados se seleccione desplazable, el cursor tambin se posiciona inicialmente delante de la primera fila. En el API JDBC 1.0, la nica forma de mover el cursor era llamar al mtodo next. Este mtodo todava es apropiado si queremos acceder a las filas una a una, yendo de la primera fila a la ltima, pero ahora tenemos muchas ms formas para mover el cursor. La contrapartida del mtodo next, que mueve el cursor una fila hacia delante (hacia el final de la hoja de resultados), es el nuevo mtodo previous, que mueve el cursor una fila hacia atrs (hacia el inicio de la hoja de resultados). Ambos mtodos devuelven false cuando el cursor se sale de la hoja de resultados (posicin antes de la primera o despus de la ltima fila), lo que hace posible utilizarlos en un bucle while. Ye hemos utilizado un mtodo next en un bucle while, pero para refrescar la memoria, aqu tenemos un ejemplo que mueve el cursor a la primera fila y luego a la siguiente cada vez que pasa por el bucle while. El bucle termina cuando alcanza la ltima fila, haciendo que el mtodo next devuelva false. El siguiente fragmento de cdigo imprime los valores de cada fila de srs, con cinco espacios en blanco entre el nombre y el precio.
Statement stmt = con.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY); ResultSet srs = stmt.executeQuery("SELECT COF_NAME, PRICE FROM COFFEES"); while (srs.next()) { String name = srs.getString("COF_NAME"); float price = srs.getFloat("PRICE"); System.out.println(name + " " + price); }
Al igual que en el fragmento anterior, podemos procesar todas las filas de srs hacia atrs, pero para hacer esto, el cursor debe estar detrs de la ltima fila. Se puede mover el cursor explcitamente a esa posicn con el mtodo afterLast. Luego el mtodo previous mueve el cursor desde la posicn detrs de la ltima fila a la ltima fila, y luego a la fila anterior en cada iteraccin del bucle while. El bucle termina cuando el cursor alcanza la posicin anterior a la primera fila, cuando el mtodo previous devuelve false.
Statement stmt = con.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY); ResultSet srs = stmt.executeQuery("SELECT COF_NAME, PRICE FROM COFFEES"); srs.afterLast(); while (srs.previous()) { String name = srs.getString("COF_NAME"); float price = srs.getFloat("PRICE"); System.out.println(name + " " + price); }
Como se puede ver, las dos salidas tienen los mismos valores, pero las filas estn en orden inverso. Se puede mover el cursor a una fila particular en un objeto ResultSet. Los mtodos first, last, beforeFirst, y afterLast mueven el cursor a la fila indicada en sus nombres. El mtodo absolute mover el cursor al nmero de fila indicado en su argumento. Si el nmero es positivo, el cursor se mueve al nmero dado desde el principio, por eso llamar a absolute(1) pone el cursor en la primera fila. Si el nmero es negativo, mueve el cursor al nmero dado desde el final, por eso llamar a absolute(-1) pone el cursor en la ltima fila. La siguiente lnea de cdigo mueve el cursor a la cuarta fila de srs.
srs.absolute(4);
Si srs tuviera 500 filas, la siguiente lnea de cdigo movera el cursor a la fila 497.
srs.absolute(-4);
Tres mtodos mueven el cursor a una posicin relativa a su posicin actual. Como hemos podido ver, el mtodo next mueve el cursor a la fila siguiente, y el mtodo previous lo mueve a la fila anterior. Con el mtodo relative, se puede especificar cuntas filas se mover desde la fila actual y tambin la direccin en la que se mover. Un nmero positivo mueve el cursor hacia adelante el nmero de filas dado; un nmero negativo mueve el cursor hacia atrs el nmero de filas dado. Por ejemplo, en el siguiente fragmente de cdigo, el cursor se mueve a la cuarta fila, luego a la primera y por ltimo a la tercera.
srs.absolute(4); // cursor est en la cuarta fila . . . srs.relative(-3); // cursor est en la primera fila . . . srs.relative(2); // cursor est en la tercera fila
260
El mtodo getRow permite comprobar el nmero de fila donde est el cursor. Por ejemplo, se puede utilizar getRow para verificar la posicin actual del cursor en el ejemplo anterior.
srs.absolute(4); int rowNum = srs.getRow(); // rowNum debera ser 4 srs.relative(-3); int rowNum = srs.getRow(); // rowNum debera ser 1 srs.relative(2); int rowNum = srs.getRow(); // rowNum debera ser 3
Existen cuatro mtodos adicionales que permiten verificar si el cursor se encuentra en una posicin particular. La posicin se indica en sus nombres:isFirst, isLast, isBeforeFirst, isAfterLast. Todos estos mtodos devuelven un boolean y por lo tanto pueden ser utilizados en una sentencia condicional. Por ejemplo, el siguiente fragmento de cdigo comprueba si el cursor est despus de la ltima fila antes de llamar al mtodo previous en un bucle while. Si el mtodo isAfterLast devuelve false, el cursor no estar despus de la ltima fila, por eso se llama al mtodo afterLast. Esto garantiza que el cursor estra despus de la ltima fila antes de utilizar el mtodo previous en el bucle while para cubrir todas las filas de srs.
if (srs.isAfterLast() == false) { srs.afterLast(); } while (srs.previous()) { String name = srs.getString("COF_NAME"); float price = srs.getFloat("PRICE"); System.out.println(name + " " + price); }
En la siguiente pgina, veremos cmo utilizar otros dos mtodos de ResultSet para mover el cursor, moveToInsertRow y moveToCurrentRow. Tambin veremos ejemplos que ilustran por qu podramos querer mover el cursor a ciertas posiciones.
Podemos utilizar los nuevos mtodos del JDBC 2.0 en el interface ResultSet para insertar una nueva fila en uprs, borrar una fila de uprs, o modificar un valor de una columna de uprs.
El siguiente fragmento de cdigo muesta otra forma de realizar la actualizacin, esta vez utilizando el API JDBC 2.0.
uprs.last(); uprs.updateFloat("PRICE", 10.99);
Las operaciones de actualizacin en el API JDBC 2.0 afectan a los valores de columna de la fila en la que se encuentra el cursor, por eso en le primera lnea se llama al mtodo last para mover el cursor a la ltima fila (la fila donde la columna COF_NAME tiene el valor FRENCH_ROAST_DECAF). Una vez situado el cursor, todos los mtodos de actualizacin que llamemos operarn sobre esa fila hasta que movamos el cursor a otra fila. La segunda lnea de cdigo cambia el valor de la columna PRICE a 10.99 llamando al mtodo updateFloat. Se utiliza este mtodo porque el valor de la columna que queremos actualizar es un float Java. Los mtodos updateXXX de ResultSet toman dos parmetros: la columna a actualizar y el nuevo valor a colocar en ella. Al igual que en los mtodos getXXX de ResultSet., el parmetro que designa la columna podra ser el nombre de la columna o el nmero de la columna. Existe un mtodo updateXXX diferente para cada tipo (updateString, updateBigDecimal, updateInt, etc.) En este punto, el precio en uprs para "French Roast Decaf" ser 10.99, pero el precio en la tabla COFFEES de la base de datos ser todava 9.99. Para que la actualizacin tenga efecto en la base de datos y no slo en la hoja de resultados, debemos llamar al mtodo updateRow de ResultSet. Aqu est el cdigo para actualizar tanto uprs como COFFEES.
uprs.last(); uprs.updateFloat("PRICE", 10.99); uprs.updateRow();
262
Si hubiramos movido el cursor a una fila diferente antes de llamar al mtodo updateRow, la actualizacin se habra perdido. Si, por el contrario, nos damos cuenta de que el precio debera haber sido 10.79 en vez de 10.99 podramos haber cancelado la actualizacin llamando al mtodo cancelRowUpdates. Tenemos que llamar al mtodo cancelRowUpdates antes de llamar al mtodo updateRow; una vez que se llama a updateRow, llamar a cancelRowUpdates no har nada. Observa que cancelRowUpdates cancela todas las actualizaciones en una fila, por eso, si haba muchas llamadas a mtodo updateXXX en la misma fila, no podemos cancelar slo una de ellas. El siguiente fragmento de cdigo primero cancela el precio 10.99 y luego lo actualiza a 10.79.
uprs.last(); uprs.updateFloat("PRICE", 10.99); uprs.cancelRowUpdates(); uprs.updateFloat("PRICE", 10.79); uprs.updateRow();
En este ejemplo, slo se haba actualizado una columna, pero podemos llamar a un mtodo updateXXX apropiado para cada una de las columnas de la fila. El concepto a recordar es que las actualizaciones y las operaciones relacionadas se aplican sobre la fila en la que se encuentra el cursor. Incluso si hay muchas llamadas a mtodos updateXXX, slo se hace una llamada al mtodo updateRow para actualizar la base de datos con todos los cambios realizados en la fila actual. Si tambin queremos actualizar el precio de COLOMBIAN_DECAF, tenemos que mover el cursor a la fila que contiene ese caf. Cmo la fila de COLOMBIAN_DECAF precede inmediatamente a la fila de FRENCH_ROAST_DECAF, podemos llamar al mtodo previous para posicionar el cursor en la fila de COLOMBIAN_DECAF. El siguiente fragmento de cdigo cambia el precio de esa fila a 9.79 tanto en la hoja de resultados como en la base de datos.
uprs.previous(); uprs.updateFloat("PRICE", 9.79); uprs.updateRow();
Todos los movimientos de cursor se refieren a filas del objeto ResultSet, no a filas de la
263
tabla de la base de datos. Si una peticin selecciona cinco filas de la tabla de la base de datos, habr cinco filas en la hoja de resultados, con la primera fila siendo la fila 1, la sengunda siendo la fila 2, etc. La fila 1 puede ser identificada como la primera, y, en una hoja de resultados con cinco filas, la fila 5 ser la ltima. El orden de las filas en la hoja de resultados no tiene nada que ver con el orden de las filas en la tablas de la base de datos. De hecho, el orden de las filas en la tabla de la base de datos es indeterminado. El controlador de la base de datos sigue la pista de las filas seleccionadas, y hace las actualizaciones en la fila apropiada, pero podran estar localizadas en cualquier lugar de la tabla. Cuando se inserta una fila, por ejemplo, no hay forma de saber donde ser insertada dentro de la tabla.
Se puede hacer esto mismo sin comandos SQL utilizando los mtodos de ResultSet del API JDBC 2.0. Bsicamente, despus de tener un objeto ResultSet con los resultados de la tabla COFFEES, podemos constuir una nueva fila insertndola tanto en la hoja de resultados como en la tabla COFFEES en un slo paso. Se construye una nueva fila en una llamada "fila de insercin", una fila especial asociada con cada objeto ResultSet. Esta fila realmente no forma parte de la hoja de resultados; podemos pensar en ella como un buffer separado en el que componer una nueva fila. El primer paso ser mover el cursor a la fila de insercin, lo que podemos hacer llamando al mtodo moveToInsertRow. El siguiente paso ser seleccionar un valor para cada columna de la fila. Hacemos esto llamando a los mtodos updateXXX apropiados para cada valor. Observa que estos son los mismos mtodos updateXXX utilizados en la pgina anterior para cambiar el valor de una columna. Finalmente, podemos llamar al mtodo insertRow para insertar la fila que hemos rellenado en la hoja de resultados. Este nico mtodo inserta la fila simultneamente tanto en el objeto ResultSet como en la tabla de la base de datos de la que la hoja de datos fue seleccionada. El siguiente fragmento de cdigo crea un objeto ResultSet actualizable y desplazable, uprs, que contiene todas las filas y columnas de la tabla COFFEES.
Connection con = DriverManager.getConnection("jdbc:mySubprotocol:mySubName"); Statement stmt = con.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE); ResultSet uprs = stmt.executeQuery("SELECT * FROM COFFEES");
El siguiente fragmento de cdigo utiliza el objeto ResultSet, uprs para insertar la fila para "kona", mostrada en el ejemplo SQL. Mueve el cursor a la fila de insercin, selecciona los cinco valores de columna e inserta la fila dentro de uprs y COFFEES.
uprs.moveToInsertRow(); uprs.updateString("COF_NAME", "Kona"); uprs.updateInt("SUP_ID", 150); uprs.updateFloat("PRICE", 10.99); uprs.updateInt("SALES", 0); uprs.updateInt("TOTAL", 0); uprs.insertRow();
Como podemos utilizar el nombre o el nmero de la columna para indicar la columna seleccionada, nuestro cdigo para seleccionar los valores de columna se podra parecer a esto.
uprs.updateString(1, "Kona"); uprs.updateInt(2, 150); uprs.updateFloat(3, 10.99); uprs.updateInt(4, 0); uprs.updateInt(5, 0);
Podramos habernos preguntado por qu los mtodos updateXXX parecen tener un comportamiento distinto a como lo hacan en los ejemplos de actualizacin. En aquellos ejemplos, el valor seleccionado con un mtodo updateXXX reemplazaba inmediatamente el valor de la columna en la hoja de resultados. Esto era porque el cursor estaba sobre una fila de la hoja de resultados. Cuando el cursor est sobre la fila de insercin, el valor seleccionado con un mtodo updateXXX tambin es automticamente seleccionado, pero lo es en la fila de insercin y no en la propia hoja de resultados. Tanto en actualizaciones como en inserciones, llamar a los mtodos updateXXX no afectan a la tabla de la base de datos. Se debe llamar al mtodo updateRow para hacer que las actualizaciones ocurran en la base de datos. Para el inserciones, el mtodo insertRow inserta la nueva fila en la hoja de resultados y en la base de datos al mismo tiempo. Podramos preguntarnos que sucedera si insertramos una fila pero sin suministrar los valores para cada columna. Si no suministramos valores para una columna que estaba definida para aceptar valores NULL de SQL, el valor asignado a esa columna es NULL. Si la columna no acepta valores null, obtendremos una SQLException. Esto tambin es cierto si falta una columna de la tabla en nuestro objeto ResultSet. En el ejemplo anterior, la peticin era SELECT * FROM COFFEES, lo que produca una hoja de resultados con todas las columnas y todas las filas. Cuando queremos insertar una o ms filas, nuestra peticin no tiene porque seleccionar todas las filas, pero s todas las columnas. Especialmente si nuestra tabla tiene cientos o miles de filas, querremos utilizar una clasula WHERE para lmitar el nmero de filas devueltas por la sentencia SELECT.
265
Despus de haber llamado al mtodo insertRow, podemos construir otra fila, o podemos mover el cursor de nuevo a la hoja de resultados. Por ejemplo, podemos, llamar a cualquier mtodo que ponga el cursor en una fila especfica, como first, last, beforeFirst, afterLast, y absolute. Tambin podemos utilizar los mtodos previous, relative, y moveToCurrentRow. Observa que slo podemos llamar a moveToCurrentRow cuando el cursor est en la fila de insercin. Cuando llamamos al mtodo moveToInsertRow, la hoja de resultados graba la fila en la que se encontraba el cursor, que por definicin es la fila actual. Como consecuencia, el mtodo moveToCurrentRow puede mover el cursor desde la fila de insercin a la fila en la que se encontraba anteriormente. Esto tambin explica porque podemos utilizar los mtodos previous y relative, que requieren movimientos relativos a la fila actual.
uprs.insertRow(); uprs.updateString("COF_NAME", "Kona_Decaf"); uprs.updateInt("SUP_ID", 150); uprs.updateFloat("PRICE", 11.99f); uprs.updateInt("SALES", 0); uprs.updateInt("TOTAL", 0); uprs.insertRow(); uprs.beforeFirst(); System.out.println("Table COFFEES after insertion:"); while (uprs.next()) { String name = uprs.getString("COF_NAME"); int id = uprs.getInt("SUP_ID"); float price = uprs.getFloat("PRICE"); int sales = uprs.getInt("SALES"); int total = uprs.getInt("TOTAL"); System.out.print(name + " " + id + " " + price); System.out.println(" " + sales + " " + total); } uprs.close(); stmt.close(); con.close(); } catch(SQLException ex) { System.err.println("SQLException: " + ex.getMessage()); } }
La cuarta fila ha sido eliminada de uprs y de la base de datos. El nico problema con las eliminaciones es lo que ResultSet realmente hace cuando se borra una fila. Con algunos driver JDBC, una lnea borrada es eliminada y ya no es visible en una hoja de resultados. Algunos drives JDBC utilizan una fila en blanco en su lugar pone (un "hole") donde la fila borrada fuera utilizada. Si existe una fila en blanco en lugar de la fila borrada, se puede utilizar el mtodo absolute con la posicin original de la fila para mover el cursor, porque el nmero de filas en la hoja de resultados no ha cambiado. En cualquier caso, deberamos recordar que los drivers JDBC manejan las eliminaciones de forma diferente. Por ejemplo, si escribimos una aplicacin para ejecutarse con diferentes bases de datos, no deberamos escribir cdigo que dependiera de si hay una fila vaca en la hoja de resultados. Ver los cambios en una Hoja de Resultados Si modificamos los datos en un objeto ResultSet, los cambios se harn visibles si lo cerramos y lo abrimos de nuevo. En otras palabras, si re-ejecutamos la misma peticin,
267
producir una nueva hoja de resultados, basada en los datos actuales de la tabla. Esta hoja de resultados reflejara naturalmente los cambios que hayamos hecho anteriormente. La cuestin es si podemos ver los cambios que hayamos realizado mientras el objeto ResultSet est todava abierto. (Generalmente, estaremos ms interesados en los cambios hechos por otros). La respuesta depende del controlador de la base de datos, del driver, y del tipo del objeto ResultSet utilizado. Con un objeto ResultSet que sea TYPE_SCROLL_SENSITIVE, siempre podremos ver las actualizaciones que alguien haga en los valores de las columnas. Normalmente veremos inserciones y eliminaciones, pero la nica forma de estar seguros es utilizar los mtodos DatabaseMetaData que devuelven esta informacin. Podemos regular la extensin de que los cambios sean visibles aumentando o bajando el nivel de aislamiento de la transacin con la base de datos. Por ejemplo, la siguiente lnea de cdigo, donde con es un objeto Connection activo, selecciona el nivel de aislamiento de la conexin a TRANSACTION_READ_COMMITTED.
con.setTransactionIsolation(TRANSACTION_READ_COMMITTED);
Con este nivel de aislamiento, nuestro objeto ResultSet no mostrar ningn cambio antes de ser enviado, pero puede mostrar los cambios que podran tener problemas de consistencia. Para permitir menores niveles de inconsistencia, podramos subir el nive de aislamiento a TRANSACTION_REPEATABLE_READ. El problema es que a niveles ms altos de aislamiento, el rendimiento se empobrece. Y siempre estamos limitados por lo que proporcionan las bases de datos y los drivers. En un objeto ResultSet que sea TYPE_SCROLL_INSENSITIVE, generalmente no podremos ver los cambios hechos mientras est abierta. Algunos programadores utilizan slo este tipo de objeto ResultSet porque quieren una vista consistente de los datos y no quieren ver los cambios hechos por otros. Se puede utilizar el mtodo refreshRow para obtener los ltimos valores de una fila en la base de datos. Este mtodo puede utilizar muchos recursos, especialmente si el controlador de la base de datos devuelve mltiples filas cada vez que se llama a refreshRow. De todas formas, puede utilizarse cuando es crtico tener los ltimos datos. Incluso aunque una hoja de resultados sea sensible y los cambios sean visibles, una aplicacin no podra ver siempre los ltimos cambios si el driver recupera varias filas a la vez y las almacena. Por eso, utilizar el mtodo refreshRow es el nico mtodo para asegurarnos que estamos viendo los ltimos datos. El siguiente cdigo ilustra cmo una aplicacin podra utilizar el mtodo refreshRow cuando es absolutamente crtico ver los ltimos valores. Observa que la hoja se resultados debera ser sensible; si queremos utilizar el mtodo refreshRow con un objeto ResultSet que sea TYPE_SCROLL_INSENSITIVE, no har nada. (La urgencia de obtener los ltimos datos es bastante improvable en la tabla COFFEES, pero la fortuna de un inversor, depende de conocer los ltimos precios en la amplia fluctuacin del mercado del caf. O,
268
por ejemplo, querriamos asegurarnos de que el nuestro asiento reservado en el avin de regreso todava est disponible).
Statement stmt = con.createStatement( ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE); ResultSet uprs = stmt.executeQuery(SELECT COF_NAME, PRICE FROM COFFEES); uprs.absolute(4); Float price1 = uprs.getFloat("PRICE"); // do something. . . uprs.absolute(4); uprs.refreshRow(); Float price2 = uprs.getFloat("PRICE"); if (price2 > price1) { // do something. . . }
269