Taskguide Viewer 1.3.2: creacción simple de Wizard

En este artículo nos centraremos en conocer qué es y cómo trabaja la clase Graphics. Dicha clase, que pertenece al paquete java.awt es el sistema básico para todas las operaciones relacionadas con el uso de gráficos en Java y soporta un gran surtido de métodos posibilitándole al programador dibujar o colocar imágenes dentro de un componente. Veremos también las técnicas y métodos que se emplean para "pintar" y en el ejemplo mostraré algunas de las formas básicas que esta clase nos proporciona.
La clase Graphics es el sistema básico de todas las operaciones gráficas por lo que parece fundamental que se entienda bien su concepto antes de ponernos a tratar gráficos o imágenes. Esta clase abstracta tiene la misión de conformar el contexto gráfico y la de proporcionar los métodos necesarios para poder dibujar en la pantalla.
Consiste en encapsular la información necesaria para las operaciones de renderizado básicas, es decir, toda la información que afecte a las operaciones de dibujado conforma el contexto gráfico y la salida a la ventana en la que se va a dibujar tiene que pasar por este contexto. Incluye las siguientes propiedades más importantes:
Pero para dibujar, nuestro programa necesita un contexto gráfico válido que vendrá dado a través de una instancia o ejemplar de la clase Graphics. El problema es que al ser una clase abstracta, ésta no se puede instanciar directamente por lo que lo que habrá que hacer es pasarle el contexto al programa a través de los métodos paint() o update() o bien, que el programa obtenga el contexto mediante el método getGraphics() de la clase Component().
En este artículo nosotros obtendremos una instancia de la clase Graphics a través de los métodos paint() o update().
Graphics nos debe proporcionar todos los métodos necesarios para poder dibujar formas básicas, texto con todas las fuentes que tiene el sistema así como permitirnos cargar imágenes. Todo lo que portemos a la ventana de renderizado se hará a través de estas funciones.
Estos 3 métodos son los encargados de mostrar los gráficos. Java nos proporciona una versión por defecto en la clase Component por lo que tendremos que sobrecargar update() y paint() para que nuestro programa pinte lo que deseemos y como queramos.
Cuando el usuario llama al método repaint() de un componente, el AWT (recondando que Swing es "algo asi" como una extensión de AWT) llama al método update() de ese componente, que por defecto llama al método paint().
Su sintáxis es:
public void paint(Graphics g)
Es el responsable de dibujar las imágenes. Normalmente es el sistema operativo el que lo llama al dibujar la ventana por primera vez, volviéndolo a llamar cada vez que entiende que la ventana o una parte de ella debe ser redibujada (por ejemplo, por haber estado tapada por una ventana y quedar de nuevo a la vista). Esta llamada la hace a través del método update().
Su sintáxis es:
public void update(Graphics g)
Este método se llama en respuesta a una solicitud por parte del método repaint(), por el AWT o por el programador cuando ha realizado un cambio en la ventana de renderización y necesita que se dibuje de nuevo.
La implementación por defecto trabaja "borrando" la escena actual mediante un repintado del mismo color que el de background y luego llama al método paint() para pintar la nueva escena. Esta técnica provoca el molesto efecto llamado parpadeo (flickering) el cual se puede solucionar redefiniendo el método update() de manera que solo actualice la zona de pintado donde se han hecho modificaciones, o bien, como veremos en un artículo posterior, empleando la técnica del doble buffer.
Su sintáxis es:
public void repaint()
public void repaint(long tm)
public void repaint(int x, int y, int w, int h)
public void repaint(long tm, int x, int y, int w, int h)
Este método solicita que un componente sea repintado llamando lo más pronto posible al método update() del componente. De las posibles sintáxis que disponemos se extrae que el repintado puede ser inmediato, transcurrido un tiempo (mseg) o repintar solo la zona rectangular especificada en los parámetros. Esta última técnica es de gran utilidad cuando el repintado de la escena consume mucho tiempo, así solo repintamos la franja que se haya modificado.
Para reducir el tiempo que se tarda en repintar, el AWT toma dos técnicas:
Todo lo dicho anteriormente necesita ser ligeramente corregido en caso de trabajar con componentes Swing, es decir, si vamos a pintar sobre un JFrame o un JApplet. Esto se debe a que estas dos clases definen su propio método update(Graphics g) pero éste lo que hace es tan solo llamar al método paint(), es decir, no limpia la pantalla cada vez que se necesita repintar la escena como ocurría con AWT. Se hace por tanto necesario sobrecargar este método como se puede ver en el ejemplo.
En el siguiente ejemplo se muestran algunas de las primitivas gráficas que la clase Graphics incorpora. No las comento en el artículo porque su sintáxis es sencilla. La explicación de todas y cada una de ellas lo puedes encontrar en las especificaciones HTML del API de Java.
Así mismo, seguiremos el movimiento del ratón por la ventana de pintado para ver cómo actua el método repaint() y cómo implementar un método update() que reduzca lo más posible el parpadeo (flickering) (aunque cada problema requerirá su solución particular).
//Fichero FormasBasicas.java
/*
<applet code = "FormasBasicas" width = 600 height = 300>
</applet>
*/
import java.awt.Graphics;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import javax.swing.JApplet;
public class FormasBasicas extends JApplet implements MouseMotionListener {
Dimension d;
int coordX[] = {250,350,330,350,250,270,250};
int coordY[] = {100,100,125,150,150,125,100};
int puntos = 7;
//variables para almacenar la posición actual del mouse
int posX = 0, posY=0;
//variables para recordar la posición anterior del mouse
int memX = 0, memY=0;
public void init() {
d = this.getSize();
addMouseMotionListener(this);
}
public void mouseDragged(MouseEvent me) {}
public void mouseMoved(MouseEvent me) {
this.showStatus("Coordenadas en : " +me.getX() + ", " + me.getY());
posX = me.getX();
posY = me.getY();
this.repaint();
}
/*
En esta implementación de update() trato de "disimular" el flickering, para
ello voy borrando el rastro que va dejando el mouse repintándolo de blanco.
*/
public void update(Graphics g) {
g.setColor(Color.black);
g.drawLine(0,posY,d.width-1,posY);
g.drawLine(posX,0,posX,d.height-1);
g.setColor(Color.white);
if (memY != posY) {
g.drawLine(0,memY,d.width-1,memY);
memY = posY;
}
if (memX != posX) {
g.drawLine(memX,0,memX,d.height-1);
memX = posX;
}
g.setColor(Color.black);
this.paint(g);
}
/*
Este es el update() básico, como trabaja en el AWT, solo que lo he
implementado porque estoy trabajando con Swing. Juega con ambos para
que observes la diferencia y lo molesto que puede llegar a ser el parpadeo.
*/
/*public void update(Graphics g) {
g.setColor(Color.white);
g.fillRect(0,0,d.width-1,d.height-1);
g.setColor(Color.black);
g.drawLine(0,posY,d.width-1,posY);
g.drawLine(posX,0,posX,d.height-1);
this.paint(g);
}*/
/*
Aquí pintamos todas las figuras. Observa que al pasar por las figuras
sólidas las líneas que nos señalan la posición del ratón parpadean y/o
desaparecen. Podríamos haber puesto todo este código dentro del método
update() y haber reducido aun más el efecto del parpadeo.
*/
public void paint(Graphics g) {
g.drawRect(0,0,d.width-1,d.height-1);
g.drawLine(10,10,110,60);
g.setColor(Color.green);
g.fillRect(130,10,100,50);
g.setColor(Color.orange);
g.drawRoundRect(250,10,150,50,20,20);
g.setColor(Color.blue);
g.fillArc(10,100,100,50,90,270);
g.setColor(Color.magenta);
g.drawOval(130,100,100,50);
g.setColor(Color.red);
g.fillPolygon(coordX,coordY,puntos);
g.setColor(Color.gray);
g.drawString("Linea",10,80);
g.drawString("Rectangulo sólido",130,80);
g.drawString("Rectángulo redondeado",250,80);
g.drawString("Arco sólido",10,170);
g.drawString("Elipse",130,170);
g.drawString("Un poligono propio",250,170);
g.drawString("Este texto queda cortado", d.width-60,170);
}
}
A partir del próximo artículo nos meteremos a estudiar el API Java 2D empezando por la clase Graphics2D. Si bien este artículo es introductorio al tratamiento de gráficos, la clase Graphics2D extiende de la clase Graphics por lo que rápidamente se deduce que es más potente. Es por ello que a partir de ahora trabajaremos con esta última.
|
Fecha de creación: 15.05.2001
Revisión 1.0 (15.05.2001)
Leo Suarez
leo AT javahispano DOT org
|
¿Ya te has dado un atracón de POO?, ¿Has recopilado toda la documentación necesaria?[1].Pues si es así vamos a ponernos manos a la masa que es lo que nos interesa a todos... sumergirnos en el apasionante mundo de Java ;-). Eso sí, a partir de ahora debemos tener abiertas en el navegador que utilices las especificaciones HTML de la API de Java, o tener uno de esos entornos de desarrollo modernos que te sugiere los métodos de una clase según los escribes.
En este apartado siempre interesa empezar por los típicos programas del estilo Hola Mundo aunque cuando tengas más soltura lo que te recomiendo es que te plantees solucionar algún problema, es decir, programarlo y en los tutoriales (un mazo de ejemplos en el directorio .../java/demo/ y en los tutoriales on-line de Sun) vayas buscando los ejemplos que se asemejen a las clases que quieres realizar. Eso te va a servir de referencia ya que tienes una base de la que partir, te familiarizará con las clases que incorpora la API de Java, sus métodos, etc...
En este artículo nos vamos a centrar en cómo empezar a desarrollar una clase, adquirir un estilo de programación y saber qué son y para qué son las palabras reservadas que casi siempre aparecen en cualquier clase hecha en Java.
Una cosa que siempre cuesta al principio es programar orientado a objetos, esto es, emplear los beneficios de la POO ya que lo que normalmente hacemos es usar los métodos y clases que Java nos ofrece pero con la tendencia de programación estructurada. La principal ventaja de la POO es que cada objeto (clase) hace su cometido y si otra clase necesita algo de ella pues que se lo pida.
Para adoptar este hábito lo más rápido posible, aconsejo utilizar la sentencia package que lo que hace es agrupar las clases bajo un mismo paquete. Esto también nos ayudará a hacer un diseño de la aplicación bastante limpio, pues agruparemos las clases en paquetes según la finalidad que tengan, quedando nuestra aplicación mucho más definida y estructurada si posteriormente queremos hacer modificaciones.
IMPORTANTE: Las clases que pertenecen a un paquete HAY que ponerlas en un directorio del mismo nombre. Por ejemplo, si tenemos las clases A, B, C, D que pertenecen al paquete "misejemplos", entonces, deberán estar alojadas en el directorio "misejemplos" para que el núcleo de Java no nos de un error al compilar la aplicación.
Una última cosa a tener en cuenta es que, aunque no es obligatorio, debemos poner cada clase que creemos en ficheros diferentes y en la primera línea de cada uno poner el paquete al que pertenecen, empaquetar no significa poner todas las clases de una aplicación en un disquete, ni meter todas las clases de un paquete en un fichero, empaquetar es referenciar cada uno de los ficheros (con una única clase) al paquete al que pertenecen por la palabra reservada package.
Con esta sentencia importamos las clases de los paquetes que necesitamos para nuestro código, las de la API de Java y las de creación propia.
Pero, ¿es obligatorio importar las clases? Pues no, pero para que el compilador no te diera el error de que no ha encontrado la clase tal tendrías que poner todo el path de los paquetes de las clases que emplees , por ejemplo, para crear un objeto Frame habría que hacer:
java.awt.Frame frame = new java.awt.Frame();
No te parece un peñazo? ;-), así que usemos import y nos bastará con:
import java.awt.*;Puede parecer que con una clase solo no merece la pena, pero creeme que tu emplearás más de una clase del JDK y que entonces merecerá mucho la pena.
. . .
Frame frame = new Frame();
Algo que es muy habitual encontrar en los fuentes de java que consultamos es que importan el paquete entero (todas sus clases) mediante el comodín *, por ejemplo, import javax.swing.* pero si bien es lo más cómodo te recomiendo que cargues solo las clases que necesitas, por experiencia propia te aseguro que las asimilas antes pues te ves obligado a importarlas.
Esta es una de las propiedades de la POO y hace referencia a la visibilidad que le concedemos a una clase, un método o una variable, es decir, define su accesibilidad. Si te has mirado cualquier tutorial sobre la POO habrás visto que existen diferentes tipos de restricciones pero para que no te hagas un lío a la hora de definir las restricciones de una clase y de sus métodos y atributos ten en cuenta la siguiente distinción:
Lista 1: Restricciones de visibilidad |
Su propio nombre indica que lo que viene a continuación de ella es la clase que vamos a crear, no tiene más historia así que pasamos a otra cosa.
Aquí aparece otra propiedad de la POO, el concepto de herencia. Mediante esta palabra reservada heredamos los métodos y atributos de la clase madre de la que heredamos. Así si heredamos de la clase Frame heredamos todos sus métodos pero además los de la jerarquía de clases que están por encima de Frame que han ido heredando unas de otras.
Para que entiendas esto, consulta la clase Frame de las especificaciones y verás al principio de la página el árbol de herencias de dicha clase. Detrás de la relación de métodos que tiene esta clase verás todos los métodos a los que puede acceder una clase que herede de Frame.
Por definición, la herencia no es múltiple, es decir, solo se puede extender a única clase pero esta limitación la podemos saltar implementando las interfaces que deseemos de las que proporciona el JDK como se verá en capítulos posteriores.
Por último, decir que las clases heredadas las podemos redefinir a nuestro gusto para que se ajusten a nuestras necesidades o ampliarlas.
Esta palabra está íntimamente relacionada con los conceptos de herencia y de encapsulación. Aunque una subclase incluye todos los miembros de su super-clase, ésta no puede acceder a aquellos miembros de la super-clase que se hayan declarado como private.
Cuando la superclase de la que heredamos guarda sus datos miembro como privados, la clase heredera no los podrá inicializar, es por ello que cuando una subclase necesita referirse a su inmediata superclase utilizamos la palabra reservada super. Su uso puede tener dos objetivos:
Lista 2: Usos de super |
Esta palabra se emplea dentro de cualquier método para referirse al objeto sobre el que estamos trabajando, es decir, this es siempre una referencia al objeto sobre el cual el método se invocó. Un caso en el que se aprecia bastante bien la utilidad de esta palabra es cuando resolvemos un conflicto de nombres idénticos, pero la realidad es que la mayoría de las veces, su uso es redundante pues nos lo podríamos ahorrar. No obstante, su empleo clarifica aun más el código y mi recomendación es que siempre se utilice.
En el siguiente ejemplo se intenta recoger todo lo aprendido en este artículo, es un ejemplo muy sencillo que calcula volúmenes.
/*
fichero Volumen.java en directorio cap2. Obviamente, lo tenemos que tener
recogido en el classpath pues si no el núcleo de java nos
cantaría el error "java.lang.NoClassDefFound cap2/Volumen".
*/
package cap2;
public class Volumen {
private double x;
private double y;
private double z;
public Volumen(double x, double y, double z) {
//al usar this resolvemos el conflicto de nombres
this.x = x;
this.y = y;
this.z = z;
}
public double calculaVol() {
return this.x * this.y * this.z;
/*
en cambio, su uso aquí es redundante, podríamos haber
hecho simplemente x*y*z
*/
}
}
//fichero Demo.java
import cap2.Volumen;
class Demo extends Volumen {
Demo(double x, double y, double z) {
super(x,y,z); //llamamos al constructor de la clase Volumen
/*
inicializamos las variables de esta clase.Observa, que al ser declaradas
como private las variables en la clase Volumen, ésta
es la única manera de acceder a ellas.
*/
}
private void printVol() {
System.out.println("El volumen total es : " + this.calculaVol());
}
public static void main(String args[]) {
Demo volumen = new Demo(20,30,20);
volumen.printVol();
}
}
[1] Parte 1 del diario de un principiante.,
/articles.article.action?id=6
Leo Suarez
Leo Suarez está actualmente realizando el proyecto de fin de carrera para obtener el título de Ingeniero Superior de Telecomunicaciones por la Universidad de Las Palmas de Gran Canaria.
Cuando no está proyectando o escribiendo para javaHispano aprovecha para disfrutar con la novia y los amigos del estupendo clima de las islas afortunadas.