viernes, 27 de julio de 2007

Applet de una bola rebotando

Para aprender a programar applets y a controlar los loops, me han mandado programar una bola que rebota. Analizo el programa:

La clase Applet implementa varios métodos a los que llama de manera predeterminada y que podemos o tenemos que hacerles override. Los dos más importantes son init y paint(Graphics g). Pretenden que en init se carguen los datos iniciales y se instancien las declaraciones de objetos. En el caso de que también se trabaje con animaciones, que implican una sucesión de operaciones intercaladas por interrupciones de una longitud que podemos determinar, necesitamos recurrir a la clase Thread. Los Thread representan hilos de proceso. Paint, que recibe como parámetro una instancia de la clase abstracta Graphics, puede hacer dos cosas: o bien pinta la bola, con lo que recibiría los datos de anchura, altura, color, etc. de la bola, o bien invocamos un método pinta de la bola.

public void init() {

height=this.getBounds().height;
width=this.getBounds().width;

bola=new Bola(200,201);
ThreadBola t=new ThreadBola();
t.start();

}


public void paint(Graphics g) {

bola.pinta(g);

}

Guardo en los atributos estáticos height y width la altura y la anchura del applet respectivamente. Llamo al constructor de la clase Bola pasándole las coordenadas en las que quiero que la pelota aparezca. Como se ve, basta con llamar al método start del objeto ThreadBola para que él mismo se organice.

Necesito una clase que represente a la bola en sí. La clase Bola consiste en un constructor que la coloca en unas coordenadas preestablecidas de la pantalla y que inicializa la dirección en la que se moverá por ambos ejes. Además, la clase ThreadBola necesitará que se mueva a razón de una vez por "fotograma", así que también será necesario implementar un método mueve. Quiero que la bola se pinte a sí misma, por así decirlo, así que tendré que implementar otro método pinta al que se le pase el objeto de tipo Graphics.

public Bola() {

//Empieza.

xpos=100;
ypos=100;
dirx=xpos % 10 +1;
diry=ypos % 10 +1;


}


public void mueve() {

if (xpos<0||xpos>=appletBola.width) {
dirx=-dirx;}

if(ypos<0||ypos>appletBola.height) {
diry=-diry;}

xpos+=dirx;
ypos+=diry;

}


public void pinta(Graphics g) {
g.setColor(color);
g.fillOval(xpos, ypos, 20, 20);
}


El método mueve tiene que controlar que no haya llegado a los límites de la pantalla. Si lo ha hecho, ya sea en el eje y o en el eje x, alteraré la dirección del movimiento en ese eje. Obliga a la bola a tomar la dirección opuesta a la que llevaba en el eje en el que se cumpla la condición. Además, sumo la dirección en x a la posición que tenía anteriormente y lo mismo con la dirección en y.

La clase Thread funciona de la siguiente manera. Después de que se llame al método start, el objeto llama la función run, que engloba la operativa principal de la clase. Primero comprueba si el hilo no se ha interrumpido por algún motivo, caso en el que no ejecutaría las operaciones. Dentro de la dinámica de cada "fotograma", le decimos a la pelota que se mueva mediante el método mueve. Llamamos entonces al método repaint del applet, que a su vez hemos organizado de manera que llame al método pinta de la bola. Por último, indicamos las unidades de tiempo durante las que el thread "dormirá". Cuando menor sea el número, más rápido se moverá la bola.


public void run() {

while(true)
{


bola.mueve();
repaint();

try { Thread.sleep(15);
} catch (InterruptedException exc) { }

}

}


Subiría el resultado, pero soy pobre y vago. Imagináoslo.

jueves, 26 de julio de 2007

Cargar una imagen en un JPanel

Aunque me empiezo a acostumbrar a trabajar con el entorno de desarrollo limitado con el que programo en java, limitado al menos en comparación con el de .NET, sigue haciéndome caer en trampas que se considerarían minucias en otros entornos de desarrollo. Por ejemplo, NetBeans carece de un control de imagen propiamente dicho, por lo que, de buenas a primeras, desconocía dónde meter un archivo .jpg. Después de buscar en internet durante un par de horas, descubrí que debía utilizar un JPanel. Mostraré la dinámica de uso, los problemas que pueden surgir y el código final.

1) Hace falta tener en cuenta cómo se organiza un JPanel. El control almacena componentes. Es posible que tenga varios; de hecho, una configuración común es contar con una imagen y con un botón. En mi caso trabajaré con un único componente a la vez, así que necesito eliminar los que contenga, por si se ha llenado en algún otro método.

panelPortada.removeAll();

2) Prefiero evitar llenar la base de datos de archivos .jpg, así que me limito a guardar el nombre del archivo en un nvarchar. Por lo tanto, necesito cargar el nombre de la imagen. Sin embargo, la mayoría de los métodos requieren que les pase la ruta relativa, que puede variar en función de qué distribución se trate. Guardo las imágenes en un directorio Ruta/gestionvideoclub/. Puedo salvar el problema y recurrir al método getResource.

java.net.URL imgURL = Peliculas.class.getResource(txtPortada.getText());

Es importante notar que hace referencia al JFrame en el que trabajamos, que en este caso se llama Peliculas.

3) Puede que no encuentre la imagen, en cuyo caso imgURL contendría null. Si no la encuentra, lanzo un mensaje de aviso, como ya copiaré después.

4) Java obliga a trabajar con objetos ImageIcon, a cuyo constructor se le pasa la ruta válida de la imagen.

ImageIcon icon=new ImageIcon(imgURL);

5) Suele ser necesario escalar la imagen en función del contenedor. Para ello, hay que reparar en que el objeto ImageIcon actúa de contenedor de un objeto Image. Para escalar la imagen, hay que recuperar la imagen primero con el método getImage. La imagen contiene el método getScaledInstance, que hace el resto del trabajo.

icon.getImage().getScaledInstance(panelPortada.getWidth(), panelPortada.getHeight(), java.awt.Image.SCALE_SMOOTH);

6) El JPanel utiliza como componentes principales los del tipo JLabel, en el que introduciremos el ImageIcon instanciado anterior. No estoy seguro de que se trate de un paso obligatorio, pero, por si acaso, establezco las dimensiones del label mediante el método setBounds.

JLabel label=new JLabel();
label.setIcon(icon);
label.setBounds(0,0,icon.getIconWidth(),icon.getIconHeight());

7) Por último, añado el label al panel que hemos puesto en el formulario mediante el método add. Necesito pintarlo de nuevo con el método repaint.

panelPortada.add(label);
panelPortada.repaint();

8) Uno de los motivos por los cuales la imagen podría no mostrarse se debe a que el JPanel está establecido a opaco. Por lo tanto, lo vuelvo transparente mediante el método setOpaque.

panelPortada.setOpaque(false);

Desde la pantalla de diseño, necesitamos indicar qué borde utilizará. Muestra las opciones de la propiedad border.

Presentación

Desde que me di cuenta de que revolvía entre clases antiguas y razonamientos teóricos perdidos en archivos de word o en folios arrugados, he intentado unificar mis métodos de trabajo en archivos localizables y manejables. No caí en la cuenta, sin embargo, de que podría documentar mis líneas de razonamiento y mis conclusiones en un blog sin tener que menear los archivos de terminal en terminal. Por lo tanto, dedicaré la página a meditar sobre las técnicas de programación con las que pelee en cada momento y sobre los proyectos que me interese desarrollar. Intentaré mantener mis preocupaciones personales en mi otro blog, puesto que bastante me desquito allá.

Si encuentras este blog por casualidad, espero que te sirva para sacarte de apuros en caso de que te hayas atascado en algún método.