Kapitel 1 Drucken von Swing-Komponenten. Wir wollen eine einfache Methode zum Drucken einer Swing-Komponente vorstellen und daran den Mechanismus der Druckereinbindung in JAVA studieren. Anschließen wird eine generische Klasse entwickelt, mit der man beliebige Komponenten drucken kann. Wir verwenden dazu Klassen aus der Bibliothek java.awt.print, die man so einbindet. import java.awt.print.*; Unser Beispiel Programm soll einen Frame mit einem Menü anzeigen. Das Menü hat nur einen einzigen Unterpunkt, nämlich Drucken“. In den Frame ” kleben wir ein Panel, in das ein Rechteck, ein ausgefülltes Oval und ein Text geschrieben werden. Um sehen zu können, ob bei der Druckerausgabe auch das ganze Panel druckt wird, zeichnen wir noch ein Rechteck ganz am Rand des Panels. Beim Anklicken des Menüpunktes Drucken“ soll nur das Panel, ” nicht der ganze Rahmen gedruckt werden. Die Bibliothek java.awt.print stellt sehr viele Klassen zur individuellen Eirichtung von Druckeranwendungen beschreit. Wir beschreiben hier eine einfache, aber oft ausreichende Möglichkeit zum Drucken von Dokumenten. Spezielleres findet man in der Dokumentation des JDK-Paketes oder in ´den Tutorials auf den JAVA-Seiten der Firma SUN. 1 2 1.1 KAPITEL 1. DRUCKEN VON SWING-KOMPONENTEN. Prinzipielles Vorgehen Um eine Swing-Komponente (hier ein Panel) druckbar zu machen, muss man für sie das Interface Printable implementieren. Dazu ist die Implementierung der Methode print ausreichend. Anschließend muss man einen so genannten PrinterJob definieren, ihm die Komponente übergeben und ihn starten. Man kann zusätzlich einen Druckerauswahldialog anzeigen lassen, in dem man den Drucker, die zu druckenden Seiten und gegebenenfalls noch weiter Optionen festlegen kann. Um die Aufbereitung der Daten und den Transfer zum Drucker muss sich der Benutzer nicht kümmern. Eingebettete Unter-Komponenten einer druckbaren Komponenten werden mit gedruckt. 1.2 Das Interface Printable Das Interface Printable verlangt die Implementation einer einzigen Methode public int print(Graphics graphics, throws PrinterException PageFormat pageFormat, int pageIndex) Diese Methode wird vom Laufzeitsystem aufgerufen, wenn gedruckt werden soll. Das Laufzeitsystem erzeugt auch die notwendigen Parameter graphics, pageFormat und pageIndex. Die Bedeutung dieser Parameter ist die folgende: graphics Dieses Objekt stellt die Verbindung zu den betriebssystemabhängigen Druckerroutinen (Druckertreibern) her. Es hat die gleiche Aufgabe beim Drucken wie das Graphics-Objekt, das der paintComponentMethode übergeben bei Zeichen übergeben wird und das die Verbindung zu den Zeichenroutienen herstellt. pageFormat Dieses Objekt vom Typ FageFormat wird vom Laufzeitsystem erstellt. Es enthält Informationen über das von gewählten Drucker verwendete Seitenformat. Man kann ihn die Papiergröße, den bedruckbaren Bereich des Papiers und weitere Parameter mit geeigneten getMethoden entnehmen. pageIndex Das Laufzeitsystem ruft die Methode print immer wieder mit neuen Werten von pageIndex auf. Diese Werte sind 0, 1, 2, 3, . . .. Liefert die Methode print die Konstante PAGE EXISTS (definiert im Interface Printable) zurück, so wird tatsächlich gedruckt und anschließend 1.2. DAS INTERFACE PRINTABLE 3 print mit dem nächsten Wert für pageIndex aufgerufen. Liefert die Methode print die Konstante NO SUCH PAGE zurück, so wird nicht gedruckt und print auch nicht erneut aufgerufen. Mit diesem Mechanismus ist es möglich, mehrseitige Dokumente zu drucken. Kommen wir nun zu einer einfachen, aber für unsere Zwecke ausreichenden Implementierung der des Interfaces Printabel, das heißt der Methode print. Zunächst fragen schauen wir, ob der übergebenen Wert von pageIndex gleich Null ist. Unser Panel ist ein einseitiges Dokument“, daher geben wir den ” Wert NO SUCH PAGE zurück, wenn pageIndex größer oder gleich 1 ist. Nur bei ersten Aufruf von print (dann ist pageIndex gleich Null) wird gedruckt. Das Drucken selbst geschieht durch einen Aufruf der paint-Methode des Panels. Man übergibt paint dazu den Graphics-Parameter, den die Methode print erhalten hat. Dadurch zeichnet“ paint nicht auf den Bildschirm son” dern auf den Drucker. Das Grobgerüst der Implementation von print sieht dann so aus. public int print(Graphics g, PageFormat pageFormat, int pageIndex) { if (pageIndex > 0) { return(NO_SUCH_PAGE); } else { this.paint(g); return(PAGE_EXISTS); } } Es gibt allerdings einige technisch bedingte Vorkehrung, die man für einen problemlosen Ausdruck noch treffen sollte. Zunächst sollte man vor dem Drucken die so genannte Doppelpufferung der Grafik ausschalten und anschließend wieder einschalten. Diese sorgt für eine schöne Bildschirm Darstellung beim Neuzeichnen einer Komponente, ist beim Drucken aber störend. Die folgenden Zeilen zeigen, wie man den paint Befehl einrahmt, um die Doppelpufferung entsprechend zu steuern. RepaintManager currentManager = RepaintManager.currentManager(this); currentManager.setDoubleBufferingEnabled(false); paint(g); 4 KAPITEL 1. DRUCKEN VON SWING-KOMPONENTEN. currentManager.setDoubleBufferingEnabled(true); Ein weiterer Punkt betrifft die Positionierung und Skalierung des Druckbildes. Der Nullpunkt des Druck-Koordinatensystems ist meistens die linke obere Ecke des Papiers. Da der Drucker aber einen Rand des Papiers aus technischen Gründen nicht bedrucken kann hat der liegt die linke obere Ecke des Druckbereichs tiefer und weiter rechts. Das Druck-Koordinatensystems steht wie das von Swing auf dem Kopf. Die das Graphics-Objekt der print-Methode identifiziert die beiden Nullpunkte von Grafik und Papier, was dazu führt das je ein Streifen links und oben in der Grafik in den Rand des Papiers fällt und nicht gedruckt wird. Abhilfe verschafft eine Verschiebung (Translation) des Druck-Koordinatensystems. Der Befehl g.translate(int xNeu, int yNeu) des verschiebt den Nullpunkt des Koordinatensystems des Graphics-Objektes g auf den Punkt (xneu , yneu ) (in den Koordinaten vor der Verschiebung). Dies hat denselben Effekt als würde man zu allen x- beziehungsweise yKoordinaten der Grafik den Wert xneu beziehungsweise yneu addieren. Gilt xneu > 0 und yneu > 0, so wird die Grafik nach rechts unten verschoben. Da wir diese Verschiebung auf das Graphics-Objektes des Druckes anwenden, ändert sich an der Bildschirmdarstellung nichts. Es bleibt noch zu klären, um wie viele wir Verschieben müssen. Hierzu entnehmen wir dem PageFormatObjekt die Daten über den druckbaren Bereich. Die Befehle double pageFormat.getImageableX() double pageFormat.getImageableY() liefern die x- beziehungsweise y-Koordinaten der linken oberen Ecke des druckbaren Bereichs. Zur Sicherheit sollte man die Werte aufrunden bevor man das Koordinatensystem verschiebt. Insgesamt ergibt sich so die folgende Implementierung des Interfaces Printable public int print(Graphics g, PageFormat pageFormat, int pageIndex) { if (pageIndex > 0) { return(NO_SUCH_PAGE); } else 1.3. DIE KLASSE PRINTERJOB 5 { int x = (int)pageFormat.getImageableX() + 1; int y = (int)pageFormat.getImageableY() + 1; g.translate(x,y); RepaintManager currentManager = RepaintManager.currentManager(this); currentManager.setDoubleBufferingEnabled(false); this.paint(g); currentManager.setDoubleBufferingEnabled(true); return(PAGE_EXISTS); } } 1.3 Die Klasse PrinterJob Die Klasse PrinterJob realisiert in JAVA einen Druckauftrag. Diese Klasse hat zwar einen Konstruktor,dieser wird aber nicht verwendet. Stattdessen benutzt man die Methode getPrinterJob. Einen Druckauftrag erzeugt man also so: PrinterJob printJob = PrinterJob.getPrinterJob(); Um das Drucken auszulösen ruft man die Methode print für das PrinterJobObjekt auf. printJob.print(); Diese Methode wirft eine PrinterException und gehört daher in einen trycatch-Block. Man muss dem Druckauftrag mitteilen, welche Komponente zu gedruckt werden soll, genauer: Man muss ihm mitteilen welches druckbare Objekt (Printable) zu drucken ist. Dies geschieht mit der Methode setPrintable wie folgt setPrintable(Printable pintableObject) Erwähnenswert ist noch die Methode boolean printDialog(); ! 6 KAPITEL 1. DRUCKEN VON SWING-KOMPONENTEN. Sie zeigt einen Auswahldialog an, der etwa wie in Abbildung 1.1 aussieht. In ihm kann man für den Druckauftrag einige Optionen auswählen. Dazu gehören: der Drucker, die zu druckenden Seiten und die Anzahl der Exemplare. Die Methode liefert beim Schließen einen Booleschen Wert zurück. Ist dieser true, so wurde im Dialog der Druckauftrag bestätigt, ist er false so wurde der Druckauftrag abgebrochen. Das folgende Programmfragment zeigt die Anwendung der Klasse PrinterJob. PrinterJob printJob = PrinterJob.getPrinterJob(); printJob.setPrintable(pp); if (printJob.printDialog()) try { printJob.print(); } catch(PrinterException pex) { pex.printStackTrace(); } Abbildung 1.1: Ein Druckerdialog. 1.4. EINE BEISPIELANWENDUNG 1.4 7 Eine Beispielanwendung Im folgenden sind drei JAVA-Dateien angegeben, die eine Druckanwendung darstellen. Die Klasse PrintPanel ist von JPanel abgeleitet. In ein solches Panel werden ein Rechteck, eine gefülltes Oval und ein Text gezeichnet. Zusätzlich wird ein Rechteck am Rand des Panels gezeichnet, um die Umrisse desselben im Ausdruck sichtbar zu machen. Ein PrintPanel ist druckbar, das heißt es implementiert das Interface Printable wie in Abschnitt 1.2 beschrieben durch Realisierung der Methode print; Datei: PrintPanel.java package eis.Printing; import java.awt.*; import javax.swing.*; import java.awt.print.*; public class PrintPanel extends JPanel implements Printable { public PrintPanel() { setBackground(Color.white); setPreferredSize(new Dimension(300, 200)); } public void paintComponent(Graphics g) { super.paintComponent(g); g.setColor(Color.black); g.drawRect(20,20,100,50); g.fillOval(80,80,60,30); g.drawString("Drucken aus Swing ist einfach",100,150); g.setColor(Color.red); g.drawRect(0,0,299,199); } public int print(Graphics g, PageFormat pageFormat, int pageIndex) { if (pageIndex > 0) { return(NO_SUCH_PAGE); } else { int x = (int)pageFormat.getImageableX() + 1; int y = (int)pageFormat.getImageableY() + 1; g.translate(x,y); RepaintManager currentManager = RepaintManager.currentManager(this); 8 KAPITEL 1. DRUCKEN VON SWING-KOMPONENTEN. currentManager.setDoubleBufferingEnabled(false); this.paint(g); currentManager.setDoubleBufferingEnabled(true); return(PAGE_EXISTS); } } } Die Klasse PrintFrame erzeugt einen Rahmen in dessen Content-Pane zentral eine PrintPanel eingebettet wird. Der Rahmen hat ein Menü, dessen einziger Menüpunkt Drucken“ den Druckvorgang auslöst. Dazu wird ein Druck” auftrag (PrinterJob) erzeugt, dem man das Panel übergibt. Dann wird ein Druckdialog angezeigt und bei Bestätigung des Druckauftrags im Dialog wird der Druckvorgang gestartet. Datei: PrintFrame.java package eis.Printing; import import import import import java.awt.*; javax.swing.*; java.awt.event.*; java.awt.print.*; eis.SimpleFrame.SimpleFrame; public class PrintFrame extends SimpleFrame implements ActionListener{ private PrintPanel pp; public PrintFrame() { this.setTitle("Drucken aus Swing"); pp = new PrintPanel(); this.getContentPane().add(pp,"Center"); JMenuBar menuBar = new JMenuBar(); JMenu menu = new JMenu("Datei"); JMenuItem druckItem = new JMenuItem("Drucken"); menuBar.add(menu); menu.add(druckItem); druckItem.addActionListener(this); this.setJMenuBar(menuBar); 1.5. EINE GENERISCHE KLASSE ZUM DRUCKEN 9 pack(); } public void actionPerformed(ActionEvent evt){ String command = evt.getActionCommand(); if(command.equals("Drucken")) { PrinterJob printJob = PrinterJob.getPrinterJob(); printJob.setPrintable(pp); if (printJob.printDialog()) try { printJob.print(); } catch(PrinterException pe) { System.out.println("Error printing: " + pe); } } } } Die Klasse PrintTest ist die Startklasse. Datei: PrintTest.java package eis.Printing; public class PrintTest { public static void main(String[] args) { PrintFrame prfr = new PrintFrame(); prfr.showIt(); } } 1.5 Eine generische Klasse zum Drucken Oft möchte man viele verschiedene Komponenten einer umfangreichen Anwendung druckbar machen. Dazu muss man für jede von Ihnen das Interface 10 KAPITEL 1. DRUCKEN VON SWING-KOMPONENTEN. Printable implementieren. Will man zum Beispiel eine JTextArea drucken, so muss man zunächst eine Klasse von JTextArea ableiten, die Printable implementiert, aber keine neuen Funktionen zu JTextArea hinzufügt. Um diesen Aufwand zu vermeiden definieren die Klasse PrintSuit. Sie stellt die Methode public static void printComponent(Component comp) Ihr übergibt man eine (nicht notwendigerweise druckbare) grafische Komponente comp und diese wird dann gedruckt. Der unten angegeben Code vereinigt die Implementation des Interfaces Printable und die Erzeugung der PrinterJob-Objekts. Datei: PrintSuit.java package eis.Printing; import import import import java.awt.*; javax.swing.*; java.awt.print.*; java.awt.Graphics2D; public class PrintSuit implements Printable { private Component compToPrint; public static void printComponent(Component comp) { new PrintSuit(comp).print(); } private PrintSuit(Component comp) { this.compToPrint = comp; } public void print() { PrinterJob printJob = PrinterJob.getPrinterJob(); printJob.setPrintable(this); if (printJob.printDialog()) try { printJob.print(); } catch(PrinterException pex) { pex.printStackTrace(); } 1.5. EINE GENERISCHE KLASSE ZUM DRUCKEN 11 } public int print(Graphics g, PageFormat pageFormat, int pageIndex) { if (pageIndex > 0) { return(NO_SUCH_PAGE); } else { int x = (int)pageFormat.getImageableX() + 1; int y = (int)pageFormat.getImageableY() + 1; g.translate(x,y); RepaintManager currentManager = RepaintManager.currentManager(compToPrint); currentManager.setDoubleBufferingEnabled(false); compToPrint.paint(g); currentManager.setDoubleBufferingEnabled(true); return(PAGE_EXISTS); } } } Das Programm PrintSuitTestFrame demonstriert die Verwendung von PrintSuit. Wir drucken den Code hier nicht ab, das Programm lässt sich aber von der Web-Seite herunterladen. Gedruckt wird nur das eingebettete Knopf, nicht der ganze Frame.