Vamos con un pequeño ejemplo de un gráfico simple en java, es decir, pintar una línea. En java hay varios componentes para la construcción de interfaces gráficas de usuario (ventanitas) del estilo de botones, etiquetas, tablas, etc. Podemos dibujar en cualquiera de ellas pero hay un pequeño problema. Un botón, por ejemplo, dibuja un botón. Si dibujamos sobre el botón, se verá nuestro dibujo encima del botón. Puede que algún momento nos interese dibujar sobre un botón, pero lo habitual es que queramos hacer nuestro propio dibujo en una zona reservada para nuestro dibujo, no sobre un botón.. Para dibujar, el componente que java ha pensado para ello, es java.awt.Canvas (Canvas es lienzo en inglés). Por ello este es el que utllizaremos, aunque podría ser cualquier otro.
Para dibujar necesitamos una instancia de la clase Graphics. Esta clase debe estar asociada al componente sobre el que queremos dibujar para que dibuje sobre él. Para obtener una instancia de esta clase asociada a un componentel, basta con pedirle al componente en el que queremos dibujar un Graphics. Por ejemplo
Canvas lienzo = new
Canvas();
Graphics g = lienzo.getGraphics();
g.drawLine (...);
La clase Graphics tiene metodos de dibujo, estilo drawLine() , drawRectangle(), etc. Puesto que ese Graphics lo hemos obtenido del Canvas, lo que dibujemos con ese Graphics se dibujará sobre el Canvas. El problema es que esto no funcionará bien. Cuando el componente (el Canvas), necesite repintarse, porque le pase otra ventana por encima, porque lo minimicemos y luego lo maximicemos o por la razón que sea, no se redibujará nuestro dibujo.
Para dibujar correctamente, debemos hacer nuestro propio Canvas. Para ello hacemos una clase (Lienzo en nuestro ejemplo) que herede de Canvas. Cuando el Canvas necesita ser repintado por cualquiera de los motivos anteriores, java llamará al método paint() del Canvas. Lo que tenemos que hacer en nuestra clase, es redefinir ese método, para que pinte lo que nosotros queramos. De esta forma, cuando el Canvas necesite repintado, pintará nuestro dibujo. Al método paint() java le pasa un parámetro Graphics. Este parámetro es el que necesitamos para dibujar sobre el Canvas. Basta con ir llamando a los distintos métodos drawLine() y similares que deseemos.
class Lienzo
extends Canvas
{
public void paint (Graphics g)
{
g.drawLine (...);
}
...
}
El resultado es el esperado. Cuando nuestro Canvas necesite redibujarse por cualquier motivo, se repintará tambien el dibujo que hemos realizado.
Cambiar el dibujo del Canvas
Es bastante habitual también que queramos ir cambiando el dibujo según nuestro programa va leyendo datos de algún sitio o los va recibiendo o según actua el usuario sobre él. Necesitamos que nuestro paint() dibuje cosas distintas y necesitamos decirle a java que nuestro Canvas necesita un repintado.
La primera parte es fácil (o no) y cada uno puede implementarla como quiera (o pueda). Para hacer que el Canvas dibuje cosas distintas podemos, por ejemplo, ponerle a nuestra clase Lienzo un método tomaDibujo() o lo que sea. En nuestro ejemplo, como vamos a dibujar una línea, pondremos un método tomaLinea() al que se le pasa una línea como parámetro. Este método guardará la línea en un atributo de la clase. El método paint() dibujará la línea almacenada en dicho atributo. De esta manera, llamando a tomaLinea(), cuando el Canvas se redibuje, pintará la línea que le hemos pasado.
¿Cómo le decimos a java que nuestro Canvas necesita un repintado?. Para eso está el método repaint() del Canvas (y de cualquier componente). Este método no hace el repintado del componente, únicamente avisa a java de que el componente necesita repintado. Cuando java lo considere oportuno dentro de sus complejos threads (hilos de ejecución), redibujará el Canvas llamando a su método paint(), que es el que nosotros hemos redefinido. No hay que preocuparse, java suele decidir repintarnos bastante rápido.
En el ejemplo hemos hecho símplemente lo indicado. Una clase Lienzo que hereda de Canvas, que redefine el método paint() y que tiene un método tomaLinea() para pasarle al lienzo la línea que debe dibujar. Aquí tienes el ejemplo como un Applet y los fuentes.
Lienzo.java es la clase que hereda de Canvas y redefine el paint().
PanelPrincipal.java es un panel con el Lienzo y un botón. El botón permite cambiar la línea que dibuja el Lienzo.
AppletPrincipal.java es el Applet que presenta el PanelPrincipal.
Principal.java es lo mismo que AppletPrincipal, pero como aplicación independiente. Puedes descargar los fuentes, quitarles la extensión .txt y jugar con ellos a tu gusto.
Quiero aprovechar la ocasión para comentar un problemilla que nos puede suceder si nuestro programa realiza mucho cálculo.
Cuando hacemos un programa en java con interfaces gráficas de usuario (ventanitas), hay al menos tres hilos de ejecución (threads) que crea java sin que nos enteremos. Por un lado el hilo correspondiente al main() de nuestro programa, por otro lado el hilo del recolector de basura y finalmente lo que se conoce como hilo de AWT.
El hilo de AWT es el encargado de atender todos los eventos de ratón y repintados de ventana. Si damos mucho trabajo a este hilo (y es habitual hacerlo sin querer), nuestras ventanas pueden repintarse mal, lentamente o incluso quedar colgadas. Cargar de trabajo a este hilo es muy fácil, basta con hacer que al pulsar un botón el programa se ponga a echar cuentas, es decir, hacer las cuentas directamente en un ActionListener. Lo mismo vale para cualquier tipo de evento provocado por el ratón.
Por ello, cuando hay cuentas pesadas que hacer como resultado de la pulsación de un botón (o cualquier otro evento de ratón) o cuando hay que realizar operaciones de entrada/salida susceptibles de quedarse "en espera" (un socket o similar), es mejor hacer que estas cuentas u operaciones se hagan en un hilo separado creado especificamente para ello. De esta forma se deja libre el hilo de AWT para que siga con el repintado de las ventanas o tratando eventos de ratón. Puedes ver este problema con un poco más de detalle en "el hilo de awt".
En el caso de nuestro gráfico, podemos cargar dicho hilo si nuestro método paint() realiza muchas cuentas. Es mejor en general dejar las cuentas preparadas en métodos del estilo tomaLinea() para que luego paint() únicamente haga el dibujo.