Tutorial de Java

Imprimir con AWT

Anterior | Siguiente

La clase Toolkit es una clase que proporciona un interfaz independiente de plataforma para servicios específicos de esas plataformas, como pueden ser: fuentes de caracteres, imágenes, impresión y parámetros de pantalla. El constructor de la clase es abstracto y, por lo tanto, no se puede instanciar ningún objeto de la clase Toolkit; sin embargo, sí que se puede obtener un objeto Toolkit mediante la invocación del método getDefaultToolkit(), que devolverá un objeto de este tipo, adecuado a la plataforma en que se esté ejecutando.

De entre los muchos métodos de la clase Toolkit, el que representa el máximo interés en este momento es el método getPrintJob(), que devuelve un objeto de tipo PrintJob para usarlo en la impresión desde Java.

En Java hay, al menos, dos formas de poder imprimir. Una es coger un objeto de tipo Graphics, que haga las veces del papel en la impresora y dibujar, o pintar, sobre ese objeto. La otra, consiste en preguntar a un Componente, o a todos, si tienen algo que imprimir, y hacerlo a través del método printAll().

HolaMundo

Como el camino siempre se muestra andando, como decía el poeta, a continuación se muestra una de estas formas de imprimir para, como no, hacer aparecer el saludo ya conocido del "Hola Mundo!" en la impresora. El ejemplo HolaMundoPrn.java, cuyo código reproducen las siguiente líneas, consigue esto.

import java.awt.*;

class HolaMundoPrn extends Frame {
  static public void main( String args[] ) {
    // Creamos un Frame para obtener un objeto PrintJob sobre él
    Frame f = new Frame( "prueba" );
    f.pack();
    
    // Se obtiene el objeto PrintJob
    PrintJob pjob = f.getToolkit().getPrintJob( f,
      "Impresion del Saludo",null );
    // Se obtiene el objeto graphics sobre el que pintar
    Graphics pg = pjob.getGraphics();     
    // Se fija el font de caracteres con que se escribe
    pg.setFont( new Font( "SansSerif",Font.PLAIN,12 ) );
    // Se escribe el mensaje de saludo
    pg.drawString( "Hola Mundo!",100,100 );
    // Se finaliza la página
    pg.dispose();
    // Se hace que la impresora termine el trabajo y escupa la página
    pjob.end();          
    // Se acabó
    System.exit( 0 );    
  }
}

Aunque sencillo, en el código se pueden observar las acciones y precauciones que hay que tomar a la hora de mandar algo a imprimir, y que se resumen en la siguiente lista:

  • El objeto PrintJob se debe crear sobre un Frame, con lo cual se pordrían asociar siempre a las aplicaciones visuales.
  • Cuando se crea una clase PrintJob, el sistema presenta el cuadro de diálogo de control de la impresora, en donde se puede seleccionar el tipo de impresora, el tamaño del papel o el número de copias que se desean obtener.
  • Aunque el objeto PrintJob se ha de crear sobre un Frame, no es necesario que éste sea visibleo tenga un tamaño distinto de cero.
  • Antes de escribir nada en la impresora, es necesario seleccionar la fuente de caracteres con que se desea hacerlo, el sistema no proporciona ningún font de caracteres por defecto.
  • La impresión se consigue pintando sobre el objeto Graphics de la impresora.
  • La impresión se realiza página a página, de tal modo que cada una de ellas tiene su propio objeto Graphics. El método dispose() se utiliza para completar cada una de las páginas y que la impresora la lance.

Imprimir Componentes

El propósito del ejemplo java1330.java es mostrar la capacidad para imprimir de los Componentes del AWT que se encuentran en un Contenedor, bien sea éste el Contenedor raiz o se encuentre incluido en otro Contenedor. La imagen siguiente reproduce la ventana que se presenta en pantalla al ejecutar el ejemplo.

El programa coloca uno de dos objetos Panel seleccionables y cuatro objetos Button, sobre un objeto Frame. Uno de los botones tiene un receptor que hace que el Panel que se encuentre seleccionado y todos los Componentes que se encuentren en ese Panel sean impresos. Otro de los botones tiene un receptor que hace que el Frame raiz y todos los Componentes sean enviados a la impresora. En realidad, el Frame no se puede imprimir a sí mismo, sino que hace que todos sus Componentes sean impresos, lo cual contradice un poco lo que aparece en la documentación de JavaSoft, que dice, literalmente: "Imprime este componente y todos sus subcomponentes". Lo mismo, probablemente, le pase a Panel, pero este objeto Panel no tiene ninguna característica que permita saber si está siendo o no impreso.

Los dos botones anteriores comparten el mismo objeto receptor, pero la acción que realizan es la descrita. Los otros dos botones se utilizan para seleccionar los dos paneles. Es decir, el usuario puede seleccionar entre los dos paneles diferentes y hace que el que se esté visualizando en el Frame se envíe a la impresora.

Cuando el Panel seleccionado se está imprimiendo, los otros Componentes del Frame son ignorados. Sin embargo, cuando el Frame se está imprimiendo, todos los Componentes del Frame, incluido el Panel seleccionado, serán enviados a imprimirse.

El contenido de los paneles es solamente como muestra, por ello uno contiene una etiqueta, un campo de texto y un botón no activo, y el otro contiene una etiqueta, un campo de texto y dos botones inactivos.

A continuación se repasan los fragmentos de código que pueden resultar interesantes del ejemplo. El primer fragmento de código interesante es el que muesta la clase de control, con el método main() que instancia un objeto de la clase IHM, que es el que en realidad aparece en la pantalla.

public class java1330 {
    public static void main( String args[] ) {
        // Se instancia un objeto de la clase Interfaz Gráfico
        IHM ihm = new IHM();
        }
    }

En el código siguiente se muestra el comienzo de la clase IHM, incluyendo la declaración de diferentes variables de referencia.

class IHM {
    // El contenedor miFrame y todos los componenete que contiene, serán
    // impresos o enviados a un fichero de impresora cuando se pulse el
    // botón con el rótulo "Imprimir Frame"
    Frame miFrame = new Frame( "Tutorial de Java, AWT" );

    // El contenedor panelAImprimir y todos los componentes que contiene,
    // serán impresos o enviados a un fichero de impresora cuando se
    // pulse el botón con el rótulo "Imprimir Panel"
    Panel panelAImprimir = null;

    // Referencias a los dos paneles seleccionables
    Panel panel0;
    Panel panel1;

El contenedor miFrame y todos los Componentes que contiene serán enviados a la impresora, o a un fichero de impresión, cuando se pulse el botón rotulado "Imprimir Frame". El contenedor panelAImprimir y todos sus Componentes, serán impresos o enviados a un fichero de impresión al pulsar el botón "Imprimir Panel". Las variables de referencia de los objetos Panel, son referencias a los dos paneles seleccionables.

Tanto el código correspondiente al constructor de la clase IHM, como el utilizado para construir los paneles es muy semejante al que se ha visto en secciones anteriores, por lo que no merece la pena revisarlo, aunque no deja de tener un cierto interés.

Las líneas de código que aparecen a continuación sí que ya resultan interesantes para el objetivo de esta sección. Muestran la definición de una clase anidada de la clase IHM, que es utilizada por el objeto Panel, referenciado por panelAImprimir, o el objeto Frame, referenciado por miFrame, sean impresos.

class PrintActionListener implements ActionListener {
    public void actionPerformed( ActionEvent evt ) {
        // Coge un objeto PrintJob. Esto hace que aparezca el
        // diálogo estándar de impresión, que si se cierra sin
        // imprimir devolverá un nulo
        PrintJob miPrintJob = miFrame.getToolkit().
            getPrintJob( miFrame,"Tutorial de Java, AWT",null );

Esta clase es un receptor de eventos de tipo Action, y el código del método actionPerformed() es el que hace que tenga lugar la impresión. Las líneas de código anteriores, correspondientes al comienzo de la clase anidada, son utilizados para conseguir un objeto de tipo PrintJob. Esto hace que el diálogo estándar de selección de impresora aparezca en la pantalla. Si el usuario cierra este diálogo mediande el botón "Cancelar", es decir, sin activar la impresión, el método getPrintJob() devolverá null.

El siguiente código, de la misma clase anidada, comprueba que efectivamente, el usuario quiere imprimir para proceder con esa impresión.

if( miPrintJob != null ) {
    // Coge el objeto gráfico que va a imprimir
    Graphics graficoImpresion = miPrintJob.getGraphics();

    if( graficoImpresion != null ) {
        // Invoca la método printAll() del objeto Panel, o del
        // objeto Frame para hacer que los componentes del
        // que sea se dibujen sobre el objeto gráfico y se
        // pinten sobbre el papel de la impresora
        if( evt.getActionCommand().equals( "Imprimir Panel" ) )
            panelAImprimir.printAll( graficoImpresion );
        else 
            miFrame.printAll( graficoImpresion );

El primer paso, en caso afirmativo, es utilizar el método getGraphics() para obtener un objeto de tipo Graphics, que representará el papel de la impresora. Este objetos erá el que sea requerido por el método printAll(), que se invocará seguidamente.

Una vez comprobada la validez del objeto Graphics, hay que determinar los objetos que se van a aimprimir. El contenedor externo es un objeto Frame, y el contenedor interno es un objeto Panel. Esto se hace invocando el método getActionCommand() sobre el objeto que ha sido origen del evento ActionEvent. Luego, depende del origen del evento que el método printAll() sea invocado sobre el Panel o el Frame, pasando el objeto Graphics como parámetro.

Como lo que se están imprimiendo son gráficos, la calidad de la impresión dependerá de la impresora, en la Laserjet-5MP PostScript que utiliza el autor, la calidad en blanco y negro es realmente buena.

Una vez concluida la impresión, es necesario hacer que el papel salga y, también, liberar todos los recursos que hayan sido cogidos por el objeto Graphics. Esto se consigue invocando al método dispose() sobre el objeto Graphics, tal como se muestra en el código que se reproduce a continuación.

           // Hacemos que se libere el papel de la impresora y los
            // recursos del sistema que estaba utilizando el
            // objeto gráfico
            graficoImpresion.dispose();
            }
        else 
            System.out.println( "No se puede imprimir el objeto" );
        // Se concluye la impresión y se realiza la limpieza
        // necesaria
        miPrintJob.end();
        }
    else 
        System.out.println( "Impresion cancelada" );
    }
}

En el código anterior también se llama al método end() sobre el objeto PrintJob, porque hay que ser obedientes y hacer las cosas tal como indica JavaSoft para "finalizar la impresión y realizar cualquier limpieza necesaria" (sic).

Las líneas de código anteriores también contienen la parte del else correspondiente al if que comprueba la validez del objeto Graphics. Si no es válido, el proceso de impresión se corta y aparecen algunos mensajes en pantalla, aunque probablemente estaría más elegante el lanzar una excepción, pero como ejemplo es suficiente.

Las dos definiciones de las clases ActionListener que siguen en el código del ejemplo son los utilizados para el control del proceso de selección del panel que se va a visualizar. El lector debería echarles un vistazo, aunque aquí no se reproduzcan por no ser el tema concreto de la sección.

El siguiente ejemplo, java1331.java, muestra la capacidad para imprimir selectivamente Componentes del AWT correspondientes a un Contenedor embebido en otro Contenedor.

La palabra selectivamente se usa para diferenciar el método de impresión del visto en el ejemplo anterior, porque en el programa se utilizaba el método printAll() para imprimir todos los Componentes del Contenedor y, en este nuevo programa, se ha incorporado la capacidad de seleccionar los Componentes que van a ser impresos y también, la posibilidad de seleccionar información de estos Componentes para enviarla a imprimir.

De forma semejante al ejemplo anterior java1330.java, el programa coloca uno de los dos objetos Panel seleccionables y tres objetos Button sobre un objeto Frame. El objeto Panel sabe cómo imprimir sus Componentes a través del método paint() que está sobrescrito.

Uno de los botones tiene un receptor que hace que el Panel seleccionado se imprima, lo cual requiere que cada Panel tenga un método paint() sobrescrito y para ello, cada Panel creado debe extender la clase Panel. El método paint() define la forma en que se va a imprimir el Panel.

Los otros dos botones se utilizan para seleccionar entre los dos paneles a la hora de presentarlos en pantalla e imprimirlos. En otras palabras, el usuario puede seleccionar entre los dos paneles y hacer que el que esté presente en el Frame se imprima. Cuando este Panel seleccionado se está imprimiendo, los otros Componentes del Frame son ignorados.

import java.awt.*;
import java.awt.event.*;

public class java1331 {
    public static void main( String args[] ) {
        // Se instancia un objeto de la clase Interfaz Gráfico
        IHM ihm = new IHM();
        }
    }

class IHM {
    Frame miFrame = new Frame( "Tutorial de Java, AWT" );

    // El contenedor panelAImprimir y todos los componentes que contiene,
    // serán impresos o enviados a un fichero de impresora
    Panel areaAImprimir = null;

    // Se colocan dos paneles sobre el Frame de forma que se pueda
    // seleccionar cualquiera de ellos. Son paneles propios, es decir,
    // que son creados exprofeso, ya que su apariencia es diferente y
    // la forma de imprimirse es distinta
    MiPanel0 panel0;
    MiPanel1 panel1;

    public IHM() {
        // Este botón hace que el contenedor que esté actualmente referenciado
        // por areaAImprimir se imprima a sí mismo
        Button botonImprimir = new Button( "Imprimir" );
        botonImprimir.addActionListener( new PrintActionListener() );
        miFrame.add( botonImprimir,"North" );

        // Los siguientes botones son los que se utilizan para seleccionar
        // cual de los dos paneles se presentará en pantalla y será el que
        // se imprima
        Button botonPanel0 =  new Button( "Selecciona Panel 0" );
        botonPanel0.addActionListener( new Panel0Listener() );
        miFrame.add( botonPanel0,"West" );

        Button botonPanel1 =  new Button( "Selecciona Panel 1" );
        botonPanel1.addActionListener( new Panel1Listener() );
        miFrame.add( botonPanel1,"East" );

        // Aquí se construyen los paneles que luego se asignarán a la
        // referencia areaAImprimir al hacer una selección. La rutina
        // de impresión hará que el contenedor referenciado por 
        // areaAImprimir y todos sus componentes sean impresos
        panel0 = new MiPanel0();
        panel1 = new MiPanel1();

        // Es necesaria una referencia válida en areaAImprimir para
        // evitar la presencia de una excepción por puntero nulo
        // al realizar la selección e intentar eliminar la referencia
        // anterior
        areaAImprimir = panel0;

        miFrame.setSize( 340,200 );
        miFrame.setVisible( true );

        // Esta es una clase anidada anónima que se utiliza para
        // concluir la ejecución del programa cuando el usuario
        // decide cerrar el Frame
        miFrame.addWindowListener( new WindowAdapter() {
            public void windowClosing( WindowEvent evt ) {
                System.exit( 0 );
                }
            } );
        }

    // Esta es una clase anidada utilizada para imprimir el 
    // contenedor referenciado por areaAImprimir. Esto se consigue
    // aceptando un contexto de impresora y pasándoselo al
    // método paint() del contenedor referenciado por areaAImprimir.
    class PrintActionListener implements ActionListener {

      public void actionPerformed( ActionEvent evt ) {
          // Coge un objeto PrintJob. Esto hace que aparezca el 
          // diálogo estándar de impresión, que si se cierra sin
          // imprimir devolverá un nulo
          PrintJob miPrintJob = miFrame.getToolkit().
              getPrintJob( miFrame,"Tutorial de Java, AWT",null );
          if( miPrintJob != null ) {
              // Coge el objeto gráfico que va a imprimir
              Graphics graficoImpresion = miPrintJob.getGraphics();
              if( graficoImpresion != null ) {
                  // Invoca la método paint() del objeto Panel que se
                  // ha creado para hacer que los componentes del
                  // que sea se dibujen sobre el objeto gráfico y se
                  // pinten sobbre el papel de la impresora
                  areaAImprimir.paint( graficoImpresion );
                  // Hacemos que se libere el papel de la impresora y los
                  // recursos del sistema que estaba utilizando el
                  // objeto gráfico
                  graficoImpresion.dispose();
                  }
              else 
                  System.out.println( "No se puede imprimir el objeto" );

              // Se concluye la impresión y se realiza la limpieza
              // necesaria
              miPrintJob.end();
              }
          else 
              System.out.println( "Impresion cancelada" );
          }
      }

    // Esta es una de las clases propias que extienden a la clase
    // Panel, para conseguir el panel que se desea. Los objetos de
    // esta clase saben cómo imprimirse a sí mismos cuando se 
    // invoca a su método paint() pasándole como parámetro un
    // objeto de tipo PrintGraphics. En el método sobrescrito
    // paint() es donde se indica la forma en que se imprime.
    class MiPanel0 extends Panel {
        // Estos son los componentes que contiens los datos que se
        // van a imprimir
        Label labPanel0;
        TextField textoPanel0;
        Button botonPanel0;

        // Este es el constructor para los objetos de la clase
        // Panel que se ha creado  
        MiPanel0() {
            labPanel0 = new Label( "Panel 0" );
            this.add( labPanel0 );
            textoPanel0 = new TextField( "Texto" );
            this.add( textoPanel0 );
            botonPanel0 = new Button( "Boton Panel 0" );
            this.add( botonPanel0 );
            this.setBackground( Color.yellow );
            }

        // Este es el método sobrescrito paint(), que ni tan
        // siquiera hace el intento de manipular el tamaño de
        // la fuente de caracteres, porque con la versión del
        // JDK que estoy utilizando, hay un crash bestial  
        public void paint( Graphics g ) {
            // Hay que separar el pintado sobre la pantalla de la
            // impresión sobre papel, de tal forma que esto último
            // solamente se ejecute en caso de que se pase como
            // parámetro un objeto de tipo PrintGraphics. En 
            // cualquier otro caso, la información y componentes
            // aparecerá sobre la pantalla
            if( g instanceof PrintGraphics ) {
                // Esta versión de paint() se limita a imprimir una 
                // línea de cabecera y a extraer datos de los componentes
                // del Panel, imprimiéndolos en líneas sucesivas
                int margenIzqdo = 10; // Posición X de cada línea
                int margenSup = 20;   // Posición Y de la primera línea
                int pasoLinea = 13;   // Incremento o salto entre líneas

                // El cotnexto de impresión no tiene una fuente de 
                // caracteres por defecto, así que hay que proporcionársela
                g.setFont( new Font( "Serif",Font.BOLD,18 ) );         
                g.drawString( "Hola desde el Panel 0 del TUTORIAL",
                    margenIzqdo,margenSup += pasoLinea );
                g.setFont( new Font( "Serif",Font.PLAIN,10 ) );
                g.drawString( "Texto de la Etiqueta: "+labPanel0.getText(),
                    margenIzqdo,margenSup += pasoLinea );
                g.drawString( "Texto del Campo: "+textoPanel0.getText(),
                    margenIzqdo,margenSup += pasoLinea );
                g.drawString( "Rotulo del Boton: "+botonPanel0.getLabel(),
                    margenIzqdo,margenSup += pasoLinea );
                }
            // En el caso de que g no sea un objeto de tipo
            // PrintGraphics
            else
                // Se invoca el método paint() de la clase Panel
                super.paint( g );
            }
        }

    // Esta es la otra de las clases propias que extienden a la clase
    // Panel, para conseguir el panel que se desea. Los objetos de
    // esta clase saben cómo imprimirse a sí mismos cuando se 
    // invoca a su método paint() pasándole como parámetro un
    // objeto de tipo PrintGraphics. En el método sobrescrito
    // paint() es donde se indica la forma en que se imprime.
    class MiPanel1 extends Panel{
        // Estos son los componentes que contiens los datos que se
        // van a imprimir
        Label labPanel1;
        TextField textoPanel1;
        Button botonPanel_0;
        Button botonPanel_1;

        MiPanel1(){
            labPanel1 = new Label("Panel 1");
            this.add(labPanel1);
            textoPanel1 = new TextField("Texto");
            this.add(textoPanel1);
            botonPanel_0 = new Button("Un Boton");
            this.add(botonPanel_0);
            botonPanel_1 = new Button("Otro Boton");
            this.add(botonPanel_1);
            this.setBackground(Color.red);
            }

        // Este es el método sobrescrito paint(), que ni tan
        // siquiera hace el intento de manipular el tamaño de
        // la fuente de caracteres, porque con la versión del
        // JDK que estoy utilizando, hay un crash bestial  
        public void paint( Graphics g ) {
            // Hay que separar el pintado sobre la pantalla de la
            // impresión sobre papel, de tal forma que esto último
            // solamente se ejecute en caso de que se pase como
            // parámetro un objeto de tipo PrintGraphics. En 
            // cualquier otro caso, la información y componentes
            // aparecerá sobre la pantalla
            if( g instanceof PrintGraphics ) {
                // Esta versión de paint() se limita a imprimir una 
                // línea de cabecera y a extraer datos de los componentes
                // del Panel, imprimiéndolos en líneas sucesivas
                int margenIzqdo = 10; // Posición X de cada línea
                int margenSup = 20;   // Posición Y de la primera línea
                int pasoLinea = 13;   // Incremento o salto entre líneas

                // El contexto de impresión no tiene una fuente de 
                // caracteres por defecto, así que hay que proporcionársela
                g.setFont(new Font("Serif", Font.BOLD, 18));
                g.drawString( "Hola desde el Panel 1 del TUTORIAL",
                    margenIzqdo,margenSup += pasoLinea );
                g.setFont( new Font( "Serif",Font.PLAIN,10 ) );
                g.drawString( "Texto de la Etiqueta: "+labPanel1.getText(),
                    margenIzqdo,margenSup += pasoLinea );
                g.drawString( "Texto del Campo: "+textoPanel1.getText(),
                    margenIzqdo,margenSup += pasoLinea );
                g.drawString( "Rotulo del Boton: "+botonPanel_0.getLabel(),
                    margenIzqdo,margenSup += pasoLinea );
                g.drawString( "Rotulo del Boton: "+botonPanel_1.getLabel(),
                    margenIzqdo,margenSup += pasoLinea );
                }
            // Esto en el caso de que g no se un objeto de tipo
            // PrintGraphics
            else
                super.paint( g );
            }
        }   

    // Esta es una clase anidada que permite seleccionar e
    // imprimir el panel0. Evidentemente, esta clase y la quue
    // sigue, se pueden combinar en una sola que utilizaría
    // el origen del evento para determinar el panel que debe
    // enviar a la impresora
    class Panel0Listener implements ActionListener {
        public void actionPerformed( ActionEvent evt ) {
            miFrame.remove( areaAImprimir );
            areaAImprimir = panel0;
            miFrame.add( areaAImprimir,"Center" );
            miFrame.invalidate();   // Fuerza el repintado
            miFrame.setVisible( true );
            }
        }

    // Esta es una clase anidada que permite seleccionar e
    // imprimir el panel1
    class Panel1Listener implements ActionListener{
        public void actionPerformed( ActionEvent evt ) {
            miFrame.remove( areaAImprimir );
            areaAImprimir = panel1;
            miFrame.add( areaAImprimir,"Center" );
            miFrame.invalidate();   // Fuerza el repintado
            miFrame.setVisible( true );
            }
        }
    }

En este caso, el formato de impresión definido en el método paint() hace que el texto situado en el objeto Label y en el objeto TextField se impriman y también los títulos de los objetos Button. Esto es solamente como ejemplo, porque utilizando la misma técnica, el programador tiene completa libertad a la hora de asociar la información que será impresa con cada uno de los Componentes del programa.

El código del ejemplo, como el lector habrá podido observar, es casi idéntico o muy similar al del ejemplo anterior, por lo que no se repite su explicación. La diferencia más significativa reside en el hecho de la declaración de los objetos Panel propios, porque no pueden ser objetos de tipo Panel como en el ejemplo anterior, ya que es necesario que tengan sobrescrito el método paint(). Además, deben ser de diferentes tipos porque tanto su apariencia como la forma en que se imprime su contenido son diferentes.

Si se vuelven a repasar los fragmentos de código más interesantes del ejemplo, se pueden reproducir en primer lugar las líneas que crean la clase IHM, que muestran las referencias a los dos paneles seleccionables que se van a instalar sobre el objeto Frame.

class IHM {
    Frame miFrame = new Frame( "Tutorial de Java, AWT" );
  
    // El contenedor panelAImprimir y todos los componentes que contiene,
    // serán impresos o enviados a un fichero de impresora
    Panel areaAImprimir = null;

    // Se colocan dos paneles sobre el Frame de forma que se pueda
    // seleccionar cualquiera de ellos. Son paneles propios, es decir,
    // que son creados exprofeso, ya que su apariencia es diferente y
    // la forma de imprimirse es distinta
    MiPanel0 panel0;
    MiPanel1 panel1;

La diferencia con el programa anterior estriba fundamentalmente en el tipo del Panel, MiPanelX. En este caso no pueden ser de tipo Panel, como en el programa anterior, porque va a ser necesario sobreescribir su método paint(), con lo cual no queda más remedio que extender la clase Panel. Además, han de ser de tipos diferentes porque su apariencia y comportamiento a la hora de la impresión van a ser diferentes. Por ello, el constructor difiere del ejemplo anterior en lo indicado, en el resto es muy semejante, tal como se muestra en el código que sigue.

public IHM() {
... 
    // Aquí se construyen los paneles que luego se asignarán a la
    // referencia areaAImprimir al hacer una selección. La rutina
    // de impresión hará que el contenedor referenciado por 
    // areaAImprimir y todos sus componentes sean impresos
    panel0 = new MiPanel0();
    panel1 = new MiPanel1();
...
    }

Ahora le toca el turno a la clase anidada de la clase IHM que se utiliza para que el objeto Panel referenciado por la variable de referencia areaAImprimir, se imprima. Esto se consigue solicitando un contexto para imprimir y pasándoselo al método sobrescrito paint() del panel referenciado por la variable anterior. La mayor parte del código es similar al ejemplo anterior, excepto por la línea que se ha dejado huérfana en el siguiente código.

public void actionPerformed( ActionEvent evt ) {
...
            areaAImprimir.paint( graficoImpresion );
...
    }

El fragmento de código se encuentra dentro del método actionPerformed() de la clase PrintActionListener, donde se invoca al método paint() propio, lo cual hace que el el objeto se imprima. Todo esto viene desde la clase en la cual se instanciaron los paneles, ya que los objetos de esta clase saben cómo imprimirse a través del método paint() sobreescrito. EN este método paint() es donde hay que definir el formato de impresión que se desea.

Una cosa a resaltar es que la clase extiende a la clase Panel, para poder sobreescribir su método paint(). Dentro del método es necesario separar el pintado en pantalla del pintado en impresora, porque de no hacer pueden aparecer cosas en pantalla que en realidad están destinadas a la impresora.

Se hace una comprobación inicial para ejecutar el código del método paint() solamente si el objeto Graphics es de tipo PrintGraphics; sino, se invoca al método paint() de la superclase para seguir preservando la posibilidad de pintar en pantalla. El método paint() sobrescrito imprime una línea de cabecera y extrae datos de los componentes del panel, imprimiéndolos en sucesivas líneas. Aquí es donde se puede colocar el código que formatee la salida impresa al gusto.

Hay que tener en cuenta que la impresión directa no tiene una fuente de caracteres por defecto, así que hay que proporcionarle una, porque sino, el sistema puede quejarse. El siguiente código reproduce el método paint().

public void paint( Graphics g ) {
    // Hay que separar el pintado sobre la pantalla de la
    // impresión sobre papel, de tal forma que esto último
    // solamente se ejecute en caso de que se pase como
    // parámetro un objeto de tipo PrintGraphics. En 
    // cualquier otro caso, la información y componentes
    // aparecerá sobre la pantalla
    if( g instanceof PrintGraphics ) {
        // Esta versión de paint() se limita a imprimir una 
        // línea de cabecera y a extraer datos de los componentes
        // del Panel, imprimiéndolos en líneas sucesivas
        int margenIzqdo = 10; // Posición X de cada línea
        int margenSup = 20;   // Posición Y de la primera línea
        int pasoLinea = 13;   // Incremento o salto entre líneas

        // El contexto de impresión no tiene una fuente de 
        // caracteres por defecto, así que hay que proporcionársela
        g.setFont( new Font( "Serif",Font.BOLD,18 ) );         
        g.drawString( "Hola desde el Panel 0 del TUTORIAL",
            margenIzqdo,margenSup += pasoLinea );
        g.setFont( new Font( "Serif",Font.PLAIN,10 ) );
        g.drawString( "Texto de la Etiqueta: "+labPanel0.getText(),
            margenIzqdo,margenSup += pasoLinea );
        g.drawString( "Texto del Campo: "+textoPanel0.getText(),
            margenIzqdo,margenSup += pasoLinea );
        g.drawString( "Rotulo del Boton: "+botonPanel0.getLabel(),
            margenIzqdo,margenSup += pasoLinea );
        }
    // En el caso de que g no sea un objeto de tipo
    // PrintGraphics
    else
        // Se invoca el método paint() de la clase Panel
        super.paint( g );
    }

Como se puede comprobar, la impresión consiste simplemente en invocar al método drawString() sobre el objeto PrintGraphics del mismo modo que se hace en cualquier otro programa o applet, incluso en el básico HolaMundo. Lo interesante, si el lector observa, es que se está pintando sobre el papel; es decir, que no hay que resignarse a imprimir solamente texto, sino que la imaginación es la que impone el límite.

La parte final del código anterior, correspondiente a la parte del else, es una invocación al método paint() de la superclase, Panel en este caso, cuando el método paint() sobrescrito es invocado, pero no se le pasa como parámetro un objeto de tipo PrintGraphics. Esto es necesario porque hay que seguir preservando la posibilidad de pintar sobre la pantalla.

La clase anterior está seguida por otra semejante para el otro panel, así que no se va a insistir sobre ello.

Clase Impresora

Quizá estas clases parezcan un acercamiento un tanto primitivo a la impresión, ya que hay que manejar directamente cada salto de línea, cada fuente de caracteres y controlar cada página por separado. Así que se puede intentar hacer una clase un poco más general como ejemplo de que la complejidad se detiene en donde uno se lo proponga. El ejemplo java1334.java crea una clase Impresora que oculta alguna de la complejidad inherente a la impresión en Java.

import java.awt.*;
import java.awt.event.*;
import java.util.*;

public class java1334 extends Frame implements ActionListener, WindowListener {
  Button botonImp;
  Button botonVer;
  Impresora impresora;

  public java1334() {
     super( "Tutorial de Java, Impresora" );         
     addWindowListener( this );

     // Panel para contener los botones
     Panel panel = new Panel();      
     add( "South",panel );
     // Se añaden los botones que permiten imrpimir o previsualizar el
     // texto que se va a imprimir
     botonImp = new Button( "Imprimir" );     
     panel.add( "South",botonImp );
     botonImp.addActionListener( this );
     botonVer = new Button( "Visualizar" );
     panel.add( botonVer );
     botonVer.addActionListener( this );
   
     // Se crea un objeto de la clase que se ha creado para controlar la
     // impresión
     impresora = new Impresora( this );
     
     setBounds( 100,100,250,100 );
     setVisible( true );
     // Se crea el texto que se va a imprimir, como ejemplo
     imprimirTexto();
   }

  // Este método es el que crea las líneas de texto que se van a 
  // imprimir, utilizando los métodos de formateo que se han creado
  // para la clase Impresora, colocando ese texto en el objeto de
  // tipo Impresora que se ha creado al arrancar
  private void imprimirTexto() {
    int i = 1;

    impresora.setFont( new Font( "SanSerif",Font.PLAIN,12 ) );   
    impresora.print( i++ + "." ); 
    impresora.tab( 10 );
    impresora.println( "Ejemplo para la impresora" );
    
    impresora.print( i++ + "." );  
    impresora.tab( 10 );
    impresora.setFont( new Font( "Helvetica",Font.BOLD,12 ) );
    impresora.println( "Texto con otro tipo de fuente, negrilla" );
    
    impresora.print( i++ + "." );  
    impresora.tab( 10 );
    impresora.setFont( new Font( "Helvetica",Font.ITALIC,18 ) );
    impresora.println( "Lo mismo pero cayendo a derechas" );
  }

  // Aquí es donde se controla la acción de los botones, para
  // imprimir o visualizar, según lo que se haya seleccionado
  public void actionPerformed( ActionEvent evt ) {
    Object obj = evt.getSource();
    
    // Una vez que se sabe el botón que es, se realiza la acción 
    if( obj == botonImp ) {
      // Se genera el salto de página y se finaliza el Job, de forma
      // que la impresora sacará a fuera la página
      impresora.saltoPagina();
      impresora.finImpresion();
    }
    
    if( obj == botonVer ) {
      // Se manda lo mismo a la pantalla
      preVisualizacion(); 
    }
  }

   private void preVisualizacion() {
      Dimension dim = impresora.tamPagina();
      setBounds( 0,0,dim.width,dim.height );
      impresora.setBounds( 0,0,dim.width,dim.height );
      impresora.setVisible( true );   
   }

  static public void main( String argv[] ) {
      new java1334();
   }

   public void windowClosing( WindowEvent evt ) {
     // Cuando se pica el botón de salir se corta la ejecución
    System.exit( 0 );
   }
  // Los demás métodos de control de la ventana hay que 
  // sobreescribirlos porque se está utilizando el interfaz
  // y hay que implementar todos los métodos, aunque no se
  // haga nada en ellos
    public void windowClosed( WindowEvent evt ){}
    public void windowOpened( WindowEvent evt ){}
    public void windowIconified( WindowEvent evt ){}
    public void windowDeiconified( WindowEvent evt ){}
    public void windowActivated( WindowEvent evt ){}
    public void windowDeactivated( WindowEvent evt ){}   
}


// Esta es la clase Impresora objeto real del ejemplo
class Impresora extends Canvas {
   Frame f;          // Frame padre
   PrintJob pjob;    // Objeto de impresión
   Graphics pg;      // Objeto donde pintar lo que se imprimirá
   Vector objetos;   // Array de instrucciones a la impresora
   Point pt;         // Posición actual de la impresión
   Font fnt;         // Font actual
   Font tabFont;     // Font para calcular los tabulados

public Impresora( Frame frm ) {
  // Se crea el Frame y se añade el objeto
  f = frm;          
  f.add( this );      
  // Pero no se visualiza
  setVisible( false );
  // Todavía no hay nada que imprimir
  pjob = null;
  
  // Posición inicial de impresión
  pt = new Point( 0,0 );  
  // Inicialización del array de instrucciones
   objetos = new Vector(); 
   // Fuentes a utilizar por defecto
   // Para los tabuladores utilizamos una fuente "no proporcional" para
   // que los espacios tengan el mismo tamaño que las letras, de forma
   // que una "m" ocupe exactamente el mispo espacio que " "
   tabFont = new Font( "MonoSpaced",Font.PLAIN,12 );
   fnt = new Font( "SansSerif",Font.PLAIN,12 );
   }

  // Método para fijar la fuente de caracteres a utilizar en la
  // impresión hasta que no se indique otro
  public void setFont( Font f ) {
    objetos.addElement( new printFont( f ) );
  }

  // Este método imprime una cadena, pero no realiza el salto de
  // línea, de tal modo que lo que se imprima luego, se hará
  // a continuación de lo que se envíe a la impresora en este
  // método
  public void print( String s ) {
    objetos.addElement( new printString( s ) );
  }

  // Este método imprime una cadena y salta a la línea siguiente
  public void println( String s ) {
    print( s );
    objetos.addElement( new saltoLinea() );
  }

  // Este método realiza el salto de página. Como la impresión
  // se realiza página a página, hay que concluir el trabajo y
  // posicionarse de nuevo en la posición incial para pintar la
  // siguiente página
  public void saltoPagina() {
    if( pjob == null ) {
      pjob = getToolkit().getPrintJob( f,"Impresora",null );
    }
    pg = pjob.getGraphics();
    print( pg );
    pg.dispose();
    pt = new Point( 0,0 );  // posición inicial en la página
    objetos = new Vector(); // objetos a imprimir
  }

  // Este método es el que concluye totalmente la impresión
  // comprobando que ya no quedan objetos que enviar a la
  // impresora
  public void finalize() {
    if( objetos.size() > 0 ) 
      saltoPagina();
    finImpresion();
  }

  // Concluye el trabajo de impresión
  public void finImpresion() {
    pjob.end();
  }

  // Incluye un tabulador, colocando tantos espacios como
  // saltos de tabulador
  public void tab( int tabstop ) {
    objetos.addElement( new printTab( tabFont,tabstop ) );
  }

  // Este método controla el tamaño de la página que se va a 
  // imprimir, asegurándose de que hay un tamaño por defecto
  // para poder visualizar el contenido
  public Dimension tamPagina() {
    if( pjob == null ) {
      return( new Dimension( 620,790 ) ); 
    }
    else {
      pjob = getToolkit().getPrintJob( f,"Impresora",null );
      return( pjob.getPageDimension() );
    }
  }

  // Sobrescritura del método paint() para que se pueda realizar
  // la previsualización de lo que se va a imprimir
  public void paint( Graphics g ) {
    pt = new Point( 0,0 );
    print( g );     
  }


  // Este es el método que realmente realiza la impresión sobre
  // la impresora, recorriendo la lista de objetos que se ha
  // construido y enviando todos sus elementos al objeto Impresora
  // que se ha creado, pintándolos igual que si lo estuviese 
  // naciendo en la pantalla
  public void print( Graphics g ) {
    objImpresora imp;
    
    // Siempre hay que comenzar con alguna fuente
    f.setFont( fnt );   
    for( int i=0; i < objetos.size(); i++ ) {
      imp = (objImpresora)objetos.elementAt( i );
      imp.draw( g,pt );
    } 
  }
} 


// Clase abstracta que define el método draw() para que
// las clases que implementan los distintos objetos que
// se van a controlar para facilitar el trabajo de imprimir
// con la impresora, lo sobreescriban pintando lo que les
// corresponda
abstract class objImpresora {
   abstract void draw( Graphics g,Point p );
}

// Salto de linea. Vuelve a la x=0 e incrementa la y en
// lo que ocupa la fuente de caracteres con que se está
// pintando
class saltoLinea extends objImpresora {
  public void draw( Graphics g,Point p ) {
    p.x = 0;
    p.y += g.getFontMetrics( g.getFont() ).getHeight();
  }
}

// Cadena. Pinta una cadena en la posición en que se encuentre
// posicionado el puntero de impresión y desplaza a este en la
// anchura de la cadena
class printString extends objImpresora {
  String cadena;
  
  public printString( String s ) {
    cadena = s;
  }

  public void draw( Graphics g,Point p ) {
    g.drawString( cadena,p.x,p.y);
    p.x += g.getFontMetrics( g.getFont() ).stringWidth( cadena );
  }
}

// Fuente de caracteres. Fija la fuente de caracteres que se va a 
// utilizar para seguir imprimiendo
class printFont extends objImpresora {
  Font fuente;
  
  public printFont( Font f ) {
    fuente = f;
  }
  
  public void draw( Graphics g,Point p ) {
    g.setFont( fuente );
    if( p.y <= 0 ) {
      p.y = g.getFontMetrics( fuente ).getHeight();
    }
  }
}

// Tabulador. Desplaza el puntero que recorre el eje de la X en
// tantas posiciones como se indique. Para saber el desplazamiento
// que corresponde a cada uno de los puntos de tabulación, se 
// utiliza el ancho de la letra M, que suele ser la más ancha,
// en previsión de que la fuente por defecto que se utilice para
// la tabulación sea una proporcional
class printTab extends objImpresora {
  static int tabulador = 0;
  int tab_dist;
  Font tabFnt;
  
  public printTab( Font tbFont,int tabdist ) {
    tabFnt = tbFont;
    tab_dist = tabdist;
  }
  
  public void draw( Graphics g,Point p ) {
    if( tabulador == 0 ) {
      tabulador = g.getFontMetrics( tabFnt ).stringWidth( "M" );
    }
    if( p.x < ( tab_dist * tabulador ) ) {
      p.x = tab_dist * tabulador;
    }
  }
}

Si se ejecuta el programa, en pantalla aparecerá una ventana con dos botones que permiten la impresión y la previsualización del contenido que se puede imprimir. La figura siguiente muestra precisamente la previsualización, en donde se observan las líneas de texto escritas con distintos fonts de caracteres. Si se activa el botón de impresión, aparecerá el diálogo del sistema que permite la selección de impresora y características de la impresión.

A continuación se repasan los trozos de código más interesantes del ejemplo. En primer lugar, hay que tener en cuenta que se debe levantar todo sobre un Frame, pero se puede observar que la clase Impresora se construye sobre un Canvas; es decir, se puede utilizar cualquier Contenedor que se pueda incorporar a un Frame, sin tener que usar un Frame en sí mismo. Bajo condiciones normales, el objeto Canvas permanecerá invisible y se ajustará a su tamaño mínimo: 1x1 pixels. Pero como se está realizando el dibujo dentro del lienzo, se puede aprovechar la circunstancia para crear un método de previsualización de lo que se va a imprimir, simplemente presentando el objeto Canvas y llamando a su método paint().

class Impresora extends Canvas {
   Frame f;          // Frame padre

Como capacidades que se quieren incoporar a la clase están la de porder imprimir cadenas, imprimir líneas completas, cambiar la fuente de caracteres, imprimir en columnas en base a topes de tabulador y, la ya comentada posibilidad de previsualización del contenido antes de realizar la impresión. Para que esto último sea posible, es necesario que la página esté completa antes de que se imprima. Para que todas estas características estén presentes, se ha dotado a la clase Impresora de los métodos adecuados, y para que la previsualización pueda implementarse, los distintos comandos que se envíen a la impresora son almacenados dentro del objeto Impresora y luego reproducidos, bien sobre la pantalla en el caso de la previsualización o sobre la impresora en el caso de querer imprimir. El lector puede incorporar sus propios métodos para que la clase sea capaz de imprimir imágenes o gráficos, por ejemplo.

// Inicialización del array de instrucciones
objetos = new Vector(); 

La línea de código anterior es la que, en el constructor de la clase Impresora, crea un objeto Vector, donde se van a ir guardando todas las intrucciones o comandos que se quieran enviar a la impresora. La impresión se realizará recorriendo el contenido del Vector y llamando al método draw() del comando correspondiente. Esto es vital, ya que no se puede saber a priori, cuál es el comando que sigue en la lista, así que hay que dotarlos a todos de la posibilidad de que se impriman sin necesitar de nada más.

abstract class objImpresora {
  abstract void draw( Graphics g,Point p );
}

Por ello, la clase abstracta define el método draw(), al que hay que indicar el objeto Graphics sobre el que se va a imprimir y la posición en la que se va a iniciar esa impresión, ya sea en la pantalla o directamente en la impresora. Luego, los comandos derivarán de esta clase y proporcionarán una implementación adecuada del método draw(), de acuerdo a las características de la información que van a imprimir. La ventaja adicional que proporciona esta aproximación, aparte de que es un buen diseño orientado a objetos, es que no hay que comprobar el tipo de objeto que se quiere imprimir a la hora de hacerlo, sino que cada uno de los objetos correspondientes a los comandos sabe cómo tiene que imprimirse.

    for( int i=0; i < objetos.size(); i++ ) {
      imp = (objImpresora)objetos.elementAt( i );
      imp.draw( g,pt );
    } 

Las líneas de código anteriores son las que se encargan de la impresión de todos los objetos de los comandos de impresión. Obsérverse que hay que hacer un moldeo de los elementos del Vector al tipo objImpresora, porque no se sabe el tipo del elemento que se está imprimiendo, él tiene su propia forma de imprimirse, con su implementación particular del método draw(), como se ha indicado.

Otra parte interesante del código del ejemplo, corresponde a las clases que implementan cada uno de los comandos de impresión que va a soportar el objeto Impresora que se ha creado. Cada una de ellas extiende la clase abstracta objImpresora y disponen de un constructor particular en el cual se indica la información que corresponde al comando, ya que el constructor no se puede colocar en la clase abstracta. En el código se indica la acción que realiza cada una de las clases.

class printTab extends objImpresora {
  static int tabulador = 0;
  int tab_dist;
  Font tabFnt;
  
  public printTab( Font tbFont,int tabdist ) {
    tabFnt = tbFont;
    tab_dist = tabdist;
  }
  
  public void draw( Graphics g,Point p ) {
    if( tabulador == 0 ) {
      tabulador = g.getFontMetrics( tabFnt ).stringWidth( "M" );
    }
    if( p.x < ( tab_dist * tabulador ) ) {
      p.x = tab_dist * tabulador;
    }
  }

De las clases, quizá la que merezca un comentario sea la clase que implementa la tabulación, cuyo código reproducen las líneas anteriores. Como se puede observar, esta clase avanza la posición x una cantidad igual al númeroi del ancho del carácter que se le haya indicado. La pregunta que surge es, ¿cuál debe ser el carácter y la fuente de caracteres a utilizar? La respuesta, por supuesto, es que no hay forma de determinar la anchura de un carácter tabulador, así que hay que inventárselas. Para asegurar que siempre se utilice la misma anchura, la variable que indica esta cantidad se hace estática, de forma que todas las copias de la clase tomen como referencia un valor igual. Por defecto, si no se indica ninguno, se toma como tamaño del tabulador la anchura del carácter M en la fuente de caracteres que se indique, que en principio es la no proporcional. Una vez fijado este valor, el código no volverá a ejecutarse.

Imprimir un tabulador de n posiciones, consistirá simplemente en desplazarse en el eje X ese número de anchuras del carácter tabulador.

Además de los métodos que implementan los comandos, son necesarios un método para el salto de página y otro para el fin del trabajo, que son los métodos saltoPagina() y finImpresion(), respectivamente. Este último, si por negligencia no es llamado, se llama en el método finalize().

  public void paint( Graphics g ) {
    pt = new Point( 0,0 );
    print( g );     
  }

Y ya, por fin, las líneas anteriores son las que quedan como más interesantes, ya que permiten la previsualización del contenido de los objetos y ver cómo aparecerán (más o menos) en la impresión sobre papel. Como se puede ver, el método es muy simple, consiste en llamar al mismo método print(), utilizando la pantalla en vez de la impresora como objeto. Y, ¿dónde está ese objeto pantalla? Pues se coge del método paint() estándar, que es llamado por el método print().

Navegador

Home | Anterior | Siguiente | Indice | Correo