La
classe PrinterJob è la classe principale del package in quanto è
quella demandata al controllo della stampa. Questa classe non ha un costruttore,
ma un sua istanza può essere ottenuta invocando il metodo statico
PrinterJob.getPrinterJob(). Un’altra classe fondamentale nel package è
la classe Book che non è altro che una rappresentazione del documento
da stampare che ovviamente può essere costituito da più pagine
che possono anche essere di formato differente.Una volta ottenuto un Book
vuoto mediante il costruttore Book() è infatti possibile aggiungere
ad esso una o più pagine alla volta utilizzando i metodi:
book.append(Printable
painter, PageFormat pageFormat)
book.append(Printable
painter, PageFormat pageFormat, int numPages)
Come
si vede dalla firma di questi metodi al Book vengono aggiunti degli oggetti
che implementano l’interfaccia Printable. Quest’interfaccia è costituita
da un unico metodo la cui firma è:
int
print(Graphics graphics, PageFormat pageFormat, int pageIndex)
in
cui graphics è il contesto in cui la pagina da stampare verrà
disegnata, mentre l’oggetto pageFormat contiene informazioni quali le dimensioni
e l’orientamento della pagina. L’int ritornato da questo metodo deve essere
uno dei due membri statici dell’interfaccia Printable, ovvero:
-
Printable.PAGE_EXISTS
che indica che la pagina è stata disegnata con successo
-
Printable.NO_SUCH_PAGE
che
indica che il parametro pageIndex si riferisce ad una pagina non esistente
Infine,
una volta definito il Book, tutto quello che resta da fare è passarlo
al PrinterJob tramite il metodo printerJob.setPageable(book), subito prima
di far eseguire il PrinterJob stesso usando il metodo printerJob.print().
Praticamente
tutto quello che c’è da sapere per effettuare una stampa in Java
è tutto qui. Nel seguito ho scritto una classe che offre alcune
facilities per effettuare la stampa di testo semplice e tabelle, ma soprattutto
che chiarisce alcune aspetti dell’utilizzo del package java.awt.print.
L’unico costruttore di questa classe (che ho chiamato semplicemente Printer)
è il seguente:
public
class Printer {
PrinterJob pj;
PageFormat pf;
Font font;
FontMetrics fm;
Graphics graphics;
Vector itemsToPrint = new Vector();
public Printer() {
pj = PrinterJob.getPrinterJob();
pf = pj.defaultPage();
Book dummyBook = new Book();
dummyBook.append(new DummyPage(), new PageFormat());
pj.setPageable(dummyBook);
try {
pj.print();
} catch (PrinterException pe) {
pe.printStackTrace();
}
}
}
Come
si può notare questo costruttore oltre a creare un nuovo PrinterJob
a ad ottenere il suo PageFormat di defualt effettua anche la stampa di
un Book. L’oggetto DummyPage (che implementa l’interfaccia Printable come
richiesto) aggiunto al Book e poi mandato in stampa è il seguente:
class
DummyPage implements Printable {
public DummyPage() { }
public int print(Graphics g, PageFormat pf, int pageIndex)
throws PrinterException {
graphics = g;
return Printable.NO_SUCH_PAGE;
}
}
In
realtà quindi non viene mandato in stampa assolutamente niente in
quanto il metodo print() ritorna Printable.NO_SUCH_PAGE a prescindere dal
valore di pageIndex. L’unico motivo per cui viene effettuata questa stampa
fittizia è quello di salvare in una variabile di classe l’oggetto
Graphics passato al metodo print(). Quest’oggetto verrà utilizzato
in seguito come tavolozza su cui disegnare gli elementi (testo e linee)
che costituiranno la pagina da stampare.Tramite esso inoltre sarà
possibile ottenere il FontMetrics del Font correntemente utilizzato,
che in seguito sarà indispensabile per formattare correttamente
all’interno della pagina il testo da stampare. Per aggiungere dal testo
da stampare all’oggetto Printer è possibile utilizzare i metodi:
public
void setText(String text) {
itemsToPrint = new Vector();
itemsToPrint.add(new TextToPrint(text, font));
}
public
void appendText(String text) {
itemsToPrint.add(new TextToPrint(text, font));
}
Entrambi
questi metodi aggiungono un oggetto appartenente alla classe TextToPrint
al Vector itemsToPrint (definito a livello di classe) con l’unica differenza
che il primo svuota il Vector prima di usarlo. La classe TextToPrint
è definita semplicemente come:
class
TextToPrint {
public String text;
public Font font;
public TextToPrint(String text, Font font) {
this.text = text;
this.font = font;
}
}
ed
ha come unico scopo quello di immagazzinare il testo da stampare associandolo
al Font che dovrà essere utilizzato per farlo. A tal proposito per
settare il Font da usare nella stampa dei blocchi di testo che seguiranno
occorre invocare il metodo:
public
void setFont(Font font) {
this.font = font;
}
Allo
stesso modo per aggiungere una tabella alla lista degli elementi da stampare
è possibile usare uno dei metodi seguenti:
public
void appendTable(String[] header, Vector data) {
itemsToPrint.add(new TableToPrint(font, header, data, null, null));
}
public
void appendTable(String[] header, Vector data,
int[] columnWidth) {
itemsToPrint.add(
new TableToPrint(font, header, data, columnWidth, null));
}
public
void appendTable(String[] header, Vector data, boolean[]
rightAlligned) {
itemsToPrint.add(
new TableToPrint(font, header, data, null, rightAlligned));
}
public
void appendTable(String[] header, Vector data,
int[] columnWidth,boolean[] rightAlligned){
itemsToPrint.add(new
TableToPrint(font, header,
data,columnWidth,
rightAlligned));
}
Tutti
questi metodi necessitano come parametro in input di un array di String
ciascuna contenente il nome di una delle colonne della tabella e di un
Vector in cui ciascun elemento deve essere a sua volta un array di String
(della stessa lunghezza del primo) e rappresenta una riga della tabella.
Opzionalmente è possibile passare un array di int ciascuno dei quali
indica la larghezza relativa della corrispondente colonna della tabella
ed un array di boolean ciascuno dei quali (se settato a true) indica che
la corrispondente colonna della tabella deve essere allineata a destra.
Se si sceglie di non passare questi parametri le colonne vengono disegnate
tutte della stessa larghezza e vengono tutte allineate a sinistra. Anche
in tal caso ognuno di questi metodi aggiunge un elemento al Vector itemsToPrint,
e precisamente un oggetto appartenente alla classe TableToPrint che come
la classe TextToPrint ha lo scopo di immagazzinare informazioni riguardanti
la tabella da stampare:
class
TableToPrint {
public int[] columnWidth;
public boolean[] rightAlligned;
public String[] header;
public Vector data;
public int columnNr;
public Font font;
public TableToPrint(Font font, String[] header,
Vector data, int[]
columnWidth,
boolean[] rightAlligned){
this.header = header;
this.data = data;
this.columnWidth = columnWidth;
this.columnNr = header.length;
this.font = font;
// set the column width
if (columnWidth == null)
setDefaultColumnWidth();
else {
int totalWidth = 0;
for (int i = 0; i < columnNr; i++)
totalWidth += this.columnWidth[i];
for (int i = 0; i < columnNr; i++)
this.columnWidth[i] = this.columnWidth[i]
* 1000 / totalWidth;
}
if (rightAlligned == null)
setDefaultRightAlligned();
else
this.rightAlligned = rightAlligned;
}
private void setDefaultColumnWidth() {
int cw = 1000 / columnNr;
this.columnWidth = new int[columnNr];
for (int i = 0; i < columnNr; i++)
this.columnWidth[i] = cw;
}
private void setDefaultRightAlligned() {
this.rightAlligned = new boolean[columnNr];
for (int i = 0; i < columnNr; i++)
this.rightAlligned[i] = false;
}
}
L’interfaccia
della classe è ovviamente completato dal metodo che manda in stampa
i blocchi di testo e le tabelle precedentemente immagazzinati nel Vector
itemsToPrint:
public
void print() {
try {
pj.setPageable(getPages());
if (pj.printDialog()) pj.print();
}
catch (PrinterException pe) {
pe.printStackTrace();
}
}
Si
noti che l’effettivo invio in stampa del PrinterJob tramite il comando
printerJob.print() è condizionato dal valore ritornato dal metodo
printerJob.printDialog(). Invocando questo metodo viene mostrata all’utente
una dialog che gli consente di definire l’intervallo di pagine del documento
da stampare ed il numero di copie da fare. Questo metodo ritorna false
se l’utente decide di annullare la stampa, per cui in tal caso il metodo
printerJob.print() non viene invocato. Come già è stato anticipato,
per settare il lavoro da mandare in stampa, occorre passare al PrinterJob
il Book che si vuole stampare tramite il metodo setPageable(). In tal caso
il Book da stampare, viene costruito utilizzando il seguente metodo:
private
Book getPages() {
Book book = new Book();
Vector currentPage = new Vector();
int yLimit = (int)pf.getImageableHeight();
int xLimit = (int)pf.getImageableWidth();
int currX = 0;
int currY = 0;
for (int i = 0; i < itemsToPrint.size(); i++) {
if (itemsToPrint.elementAt(i) instanceof TableToPrint) {
TableToPrint table=(TableToPrint)itemsToPrint.elementAt(i);
currY=printTable(table, book, currentPage,
currY,
xLimit,
yLimit);
}
else{
TextToPrint text=(TextToPrint)itemsToPrint.elementAt(i);
currY = printText(text, book, currentPage,
currY,
xLimit,
yLimit);
}
}
if (currentPage.size() > 0)
book.append(new Page(currentPage), pf);
return book;
}
Come
si può notare questo metodo non fa altro che scorrere tutti gli
elementi del Vector itemsToPrint e aggiungerli uno ad uno al Book da mandare
in stampa utilizzando i metodi printTable() o printText() a seconda che
l’elemento da stampare sia una tabella o un blocco di testo piano. Questi
metodi non fanno altro che formattare gli elementi da stampare (il testo
e le linee che costituiscono i bordi delle celle delle tabelle) all’interno
della pagina, utilizzando l’oggetto Graphics proprio come se si stesse
disegnando su una porzione dello schermo. E’ a questo punto che si rivela
indispensabile l’aver effettuato la stampa fittizia di cui si parla all’inizio
dell’articolo. Questo piccolo trucco è infatti, come si è
detto, l’unico modo possibile per ottenere il FontMetrics del Font correntemente
impiegato e quindi suddividere il testo da stampare in righe della giusta
lunghezza. I particolari implementativi di questi metodi non sono a mio
avviso fondamentali ai fini della trattazione, ma sono comunque facilmente
desumibili dal codice sorgente della classe Printer, allegato a questo
documento, essendo peraltro commentati in maniera abbastanza esauriente.
L’ultima
cosa che resta da evidenziare è il fatto che gli elementi formattati
all’interno della pagina vengono di volta in volta aggiunti ad un secondo
Vector. Quando si è raggiunta la fine della pagina questo Vector
viene passato al costruttore della classe Page prima di essere svuotato
per la costruzione della pagina successiva:
class
Page implements Printable {
// A Vector of the items that will be printed in this page
private Vector text;
/**
* The contructor
*
* @param text The Vector of the items that will be printed in this page
*/
public Page(Vector text) {
this.text = text;
}
public
int print(Graphics g, PageFormat pf,
int pageIndex) throws PrinterException {
g.setColor(Color.black);
StringToPrint currentString;
for (int i = 0; i < text.size(); i++) {
if (text.elementAt(i) instanceof StringToPrint) {
currentString=(StringToPrint)text.elementAt(i);
g.setFont(currentString.font);
g.drawString(currentString.theString,
currentString.xPos,
currentString.yPos);
}
else {
LineToPrint currentLine=(LineToPrint)text.elementAt(i);
g.drawLine(currentLine.x1,
currentLine.y1,
currentLine.x2,
currentLine.y2);
}
}
return Printable.PAGE_EXISTS;
}
}
La
classe Page implementa l’interfaccia Printable e quindi definisce tramite
il metodo print() le modalità con cui la pagina deve essere stampata
e può essere aggiunta al Book che verrà mandato in stampa
usando uno dei suoi due metodi append().
Il
codice sorgente dell'esempio può essere scaricato cliccando qui |