Stampare in Java

Date le pressanti richieste, ho deciso di scrivere un piccolo tutorial su come stampare in Java. La stampa è, da sempre, il fine ultimo delle applicazioni WYSIWYG per sistemi operativi visuali. Java però ha incluso i metodi per supportarla solo dalla release 1.1 del JDK, formalizzandola nella 1.2.

Sommario

La via di Java per la stampa

Immagine 1 - Interfaccia Printable

Come si vede dal grafico a sinistra la stampa per Java funziona col meccanismo della delega. In altri termini l'ambiente Java "delega" alla classe che implementa java.awt.print.Printable la preparazione dell'immagine. Questo meccanismo è page-based: cioè il metodo print verrà chiamato per ogni pagina che compone il documento da stampare.
Per questo scopo sono definite delle costanti da restituire per dire all'ambiente che le pagine da stampare sono finite (vedere Metodo print). La comodità di questo approccio è che è sufficiente aggiungere il metodo print() per far si che la classe che si sta sviluppando sia stampabile.

Indice

Metodo print(...)

Il metodo print, definito nell'interfaccia java.awt.print.Printable ha questa firma:

public int print(Graphics graphics,
                 PageFormat pageFormat,
                 int pageIndex)
          		throws PrinterException

Questo è un metodo, come detto, callback, nel senso che verrà chiamato dal sistema. Ma come fare a dire quante pagine bisogna stampare? Tramite il valore di ritorno della nostra funzione. Finchè ritorneremo PAGE_EXISTS il sistema stamperà la pagina e, successivamente, richiamerà la nostra funzione incrementando il contatore int pageIndex. Quando avremo finito restiuiremo NO_SUCH_PAGE.

Diamo un occhiata ai parametri che, ricordiamolo, ci vengono passati dall'ambiente (nella fattispecie da java.awt.print.PrinterJob):
Graphics graphics Il contesto grafico su cui disegnare.
PageFormat pageFormat Il formato della pagina (in che modo è messa la carta? quanto è grande? etc...)
int pageIndex Il numero di pagina che l'ambiente chiede sia preparata.

Il significato di questi parametri dovrebbe essere ovvio, eccetto per PageFormat che vedremo brevemente in seguito.
Ultima cosa da notare è che siamo in grado di generare l'eccezione java.awt.print.PrinterException in caso si verifichi un errore. L'eccezione, come sempre, sarà catturata dal codice che chiama java.awt.print.PrinterJob.print().
Perfetto! Ora sappiamo che il sistema chiamerà il nostro metodo print in continuazione fino a quando noi diremo basta (restituendo NO_SUCH_PAGE). Resta da capire come utilizzare il parametro PageFormat che sembra un pò intimidante...

Indice

PageFormat

Se credevate di poter lavorare nello stesso modo che utilizzate per lo schermo rimarrete delusi. Per iniziare a odiare i progettisti pensate che:

  • Non sapete a priori dove inizi la parte stampabile (dimenticate lo 0,0 degli assi cartesiani tanto caro all'AWT!)
  • Non sapete a priori dove finisca la parte stampabile.

Tuttavia Java vi fornisce tutti gli elementi necessari per essere a conoscenza del formato della pagina. I metodi più importanti sono:
double getImageableHeight() Restituisce l'altezza della pagina. In 1/72 di pollice.
double getImageableWidth() Restituisce la larghezza della pagina. In 1/72 di pollice.
double getImageableX() Restituisce la coordinata X iniziale della pagina. In 1/72 di pollice.
double getImageableY() Restituisce la coordinata Y iniziale della pagina. In 1/72 di pollice.
int (orientamento) getOrientation() Restituisce l'orientamento della pagina. E' una costante fra java.awt.print.PageFormat.LANDSCAPE, java.awt.print.PageFormat.PORTRAIT e java.awt.print.PageFormat.REVERSE_LANDSCAPE.

Grazie a questi metodi è possibile conoscere i limiti in cui disegnare. Da notare è che Java (o meglio la classe java.awt.Graphics) esegue il clipping. Ciò vuol dire che se disegnate all'esterno del rettangolo specificato da java.awt.print.PageFormat i pixel verranno automaticamente eliminati.

Indice

PrinterJob

Ora che sappiamo come creare le pagine da stampare ci manca un modo per iniziare la stampa. Java mette a disposizione la classe java.awt.print.PrinterJob per questo. Notiamo subito che il costruttore è pubblico però ci viene consigliato dall'help di usare il metodo statico java.awt.print.PrinterJob.getPrinterJob() che restituisce il PrinterJob di sistema. Questa prassi è necessaria per serializzare le formattazioni di pagina. Dopo questo notiamo che ci sono due categorie di metodi: quelli prestampa e quelli atti a controllare una stampa in corso. Vediamo brevemente i più comuni dei due:

Metodi prestampa

String getUserName() Restituisce il nome dell'utente che inizia la stampa (cioè chi ha fatto il logon di sistema).
void setJobName(String jobName) Imposta il nome del processo di stampa (in Windows è quello che appare in gestione stampanti).
String getJobName() Rileva il nome del processo di stampa (in Windows è quello che appare in gestione stampanti).
void setPrintable(Printable painter) Imposta la classe (che implementa l'interfaccia java.awt.print.Printable) responsabile della creazione delle pagine di stampa.
PageFormat validatePage(PageFormat page) Questo metodo, utilissimo, riceve un PageFormat e ne ritorna uno compatibile con la stampante selezionata il più possibile simile.
PageFormat pageDialog(PageFormat page) Questo metodo invoca la finestra di dialogo di selezione del formato della pagina. In caso di ritorno di null l'utente ha annullato la selezione.
void setCopies(int copies) Imposta il numero di copie da stampare.

Stampa in corso

void cancel() Questo metodo interrompe il processo di stampa in corso.
Nota: è un metodo serializzato. Ciò vuol dire che ritornerà immediatamente anche se non è detto che la stampa venga interrotta subito. Assicura comunque che la stampa venga interrotta alla prima occasione.
boolean isCancelled() Ritorna true se e solo se il processo di stampa sarà interrotto alla prima occasione. In tutti gli altri casi ritorna false.

Indice

Un esempio funzionante

Se siete arrivati fino a qui vuol dire che tutto vi è chiaro (e che il mio italiano non è poi così orribile... :) . Adesso vedremo come mettere tutto insieme per un programmino che stampa. Nulla di più e nulla di meno.
Prima però ripetete con me i passi per arrivare allo scopo:

  1. Implementare java.awt.print.Printable e nello specifico il metodo java.awt.print.Printable.print(...).
  2. Ottenere un oggetto java.awt.print.PrinterJob.
  3. Passare al PrinterJob la nostra classe con java.awt.print.Printable.Print().
  4. Chiamare java.awt.print.PrinterJob.print() e aspettare.

Nota bene: Per risparmiarvi lo sbattimento di copiare il codice (ahhh! Io lo facevo ai gloriosi tempi dello Speccy! :-) scaricatevi il codice completo.

Passo 1: Implementare Printable

Creiamo una nuova classe e procediamo (il codice di riferimento è qui):

import java.awt.print.*;
import java.awt.*;

public class MindPrint implements Printable
{

public int print(Graphics grap,
 PageFormat pageFormat, int pageIndex) throws PrinterException
{

Vogliamo stampare una sola pagina: quindi controlliamo pageIndex e restituiamo NO_SUCH_PAGE se è maggiore di zero.

 if(pageIndex > 0)
 	return NO_SUCH_PAGE;

Scriviamo qualcosa. Notate come non possiamo assumere l'origine degli assi a (0,0) ma dobbiamo avvalerci dei metodi della classe PageFormat.

grap.drawString("Ready to roll out!", (int)pageFormat.getImageableX(), 
   (int)pageFormat.getImageableY()+5);
 
grap.setColor(Color.blue);
 
grap.drawLine((int)pageFormat.getImageableX(), (int)pageFormat.getImageableY(), 
   (int)pageFormat.getImageableWidth(), (int)pageFormat.getImageableHeight());

Ora che abbiamo creato questa bellissima pagina diciamo al sistema che è pronta per essere stampata.

 return PAGE_EXISTS;

}

Passi 2,3 e 4: Stampiamo!

Creiamo una classe consolle (anche se non è ovviamente necessario). Se volete il codice di riferimento cliccate qui.

import java.awt.print.*;

public class TestPrint
{

public static void main(String ag[]) throws Exception
{

Passo 2!

  PrinterJob pj = PrinterJob.getPrinterJob();

Passi opzionali...

  pj.setJobName("MindFlavor's Java test.");
  System.out.println("Job name == " + pj.getJobName());
  System.out.println("User name == " + pj.getUserName());
  pj.printDialog();

Passo 3!

  pj.setPrintable(new MindPrint());

Passo 4 e incrociamo le dita! ;-)

  pj.print();
  
  System.exit(-1);
}
}

Indice

Ultimo aggiornamento della pagina: 8 Settembre 2003

@2002 Francesco Cogno.