| Printing Swing Components in Java 1.2 |
|---|
java.awt.print package. This differs from Java 1.1
printing in that you can obtain high-resolution results (not
just blown up screen shots) and that you have much better control
over the print jobs. This section of the
Quick Swing Tutorial for AWT Programmers describes how to use
this printing package to print Swing components. In particular, it
illustrates two important concepts not sufficiently described in the API
or in any of the other articles, book chapters, or on-line tutorials on
Java 1.2 printing that I've seen:
printComponent
routine to which you can pass any component you want printed.
The component to be printed need not have a print method,
implement any particular interface, or in fact do anything special at all.
In fact, if you don't really care to understand how printing works behind
the scenes, you can skip reading this tutorial and simply download
PrintUtilities.java
and call PrintUtilities.printComponent(componentToBePrinted)
whenever you want to print the contents of a window.
PrinterJob
object, pass a Printable to its setPrintable
method, call printDialog to pop up an OS-specific print
dialog, and finally, assuming the user hasn't cancelled the printing
from the dialog (you check this by testing the return value of
printDialog), call print on the
PrinterJob.
Note that the role of the Printable is to
define a print method showing how the actual drawing to the printer
will be done. That's described in the next section.
Here's an example:
PrinterJob printJob = PrinterJob.getPrinterJob();
printJob.setPrintable(this);
if (printJob.printDialog())
try {
printJob.print();
} catch(PrinterException pe) {
System.out.println("Error printing: " + pe);
}
Here's a representative sample of what you see when you call
printDialog:
Printable that is passed to setPrintable must have a
print method that describes how to send drawing to the printer.
In general, the first step is to decide what to do for different
pages of your print job, since Java repeatedly calls print
with higher and higher page indexes until print returns
NO_SUCH_PAGE. In the specific case of printing Swing components,
however, you only have a single page. So you return PAGE_EXISTS
for index 0, NO_SUCH_PAGE otherwise.
The second step is to start drawing. In general, you can
do any sort of drawing you want. In the specific case of
printing Swing components, however, your drawing should just
be a high-resolution version of what the component looks like on
the screen. So you cast the Graphics object to Graphics2D,
scale the resolution to the printer, and call the component's
paint method with this scaled Graphics2D. Note that it is this
reliance on the Graphics2D's coordinate transformations that prevents
this capability from being available in the Java 1.1 version of Swing.
For more details on Graphics2D, please see my separate
Java2D tutorial.
Here's an example:
public int print(Graphics g, PageFormat pageFormat, int pageIndex) {
if (pageIndex > 0) {
return(NO_SUCH_PAGE);
} else {
Graphics2D g2d = (Graphics2D)g;
g2d.translate(pageFormat.getImageableX(), pageFormat.getImageableY());
// Turn off double buffering
componentToBePrinted.paint(g2d);
// Turn double buffering back on
return(PAGE_EXISTS);
}
}
paintComponent method. However, in the specific case of printing,
it can be a huge problem. First, since printing components relies on
scaling the coordinate system and then simply calling the component's paint
method, if double buffering is enabled printing amounts to little more than
scaling up the buffer (off-screen image). This results in ugly low-resolution
printing like you already had available. Secondly, sending these huge buffers
to the printer results in huge print spooler files which take a very
long time to print.
Consequently, you need to make sure double buffering is turned off before
you print. If you have only a single JPanel or other
JComponent, you can call setDoubleBuffered(false)
on it before calling the paint method, and
setDoubleBuffered(true) afterwards. However, this suffers
from the flaw that if you later nest another container inside, you're
right back where you started from. A much better solution is to globally
turn off double buffering via
RepaintManager currentManager = RepaintManager.currentManager(c); currentManager.setDoubleBufferingEnabled(false);and then to re-enable it after calling paint via the
setDoubleBufferingEnabled(true). (Thanks to
Bob Evans
for suggesting RepaintManager instead of the much uglier
recursive descent method I was using to globally
disable and re-enable double buffering.) Although this
will completely fix the problem with low-resolution printouts,
if the components have large, complex filled backgrounds you can still
get big spool files and slow printing.
print
method is nothing more than to scale the Graphics, turn
off double buffering, and call paint, there is no particular
reason to put that print method in the component being printed.
And requiring printable components to directly implement the Printable
interface and define a print method makes for cumbersome code and
prevents you from printing components that didn't plan for it.
A better approach is to put the print method in an arbitrary object
and tell that object whose paint method to call back to.
This lets you build a generic printComponent method to which
you simply pass the component you want printed.
import java.awt.*;
import javax.swing.*;
import java.awt.print.*;
public class PrintUtilities implements Printable {
private Component componentToBePrinted;
public static void printComponent(Component c) {
new PrintUtilities(c).print();
}
public PrintUtilities(Component componentToBePrinted) {
this.componentToBePrinted = componentToBePrinted;
}
public void print() {
PrinterJob printJob = PrinterJob.getPrinterJob();
printJob.setPrintable(this);
if (printJob.printDialog())
try {
printJob.print();
} catch(PrinterException pe) {
System.out.println("Error printing: " + pe);
}
}
public int print(Graphics g, PageFormat pageFormat, int pageIndex) {
if (pageIndex > 0) {
return(NO_SUCH_PAGE);
} else {
Graphics2D g2d = (Graphics2D)g;
g2d.translate(pageFormat.getImageableX(), pageFormat.getImageableY());
disableDoubleBuffering(componentToBePrinted);
componentToBePrinted.paint(g2d);
enableDoubleBuffering(componentToBePrinted);
return(PAGE_EXISTS);
}
}
public static void disableDoubleBuffering(Component c) {
RepaintManager currentManager = RepaintManager.currentManager(c);
currentManager.setDoubleBufferingEnabled(false);
}
public static void enableDoubleBuffering(Component c) {
RepaintManager currentManager = RepaintManager.currentManager(c);
currentManager.setDoubleBufferingEnabled(true);
}
}
JPanels, one of which has a custom
paintComponent method, and another of which contains a JButton. A simple
call to PrintUtilities.printComponent prints it.
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
import java.awt.print.*;
public class PrintExample extends JFrame
implements ActionListener {
public static void main(String[] args) {
new PrintExample();
}
public PrintExample() {
super("Printing Swing Components");
WindowUtilities.setNativeLookAndFeel();
addWindowListener(new ExitListener());
Container content = getContentPane();
JButton printButton = new JButton("Print");
printButton.addActionListener(this);
JPanel buttonPanel = new JPanel();
buttonPanel.setBackground(Color.white);
buttonPanel.add(printButton);
content.add(buttonPanel, BorderLayout.SOUTH);
DrawingPanel drawingPanel = new DrawingPanel();
content.add(drawingPanel, BorderLayout.CENTER);
pack();
setVisible(true);
}
public void actionPerformed(ActionEvent event) {
PrintUtilities.printComponent(this);
}
}
Note: also requires
WindowUtilities.java
and
ExitListener.java,
shown earlier, plus DrawingPanel.java, shown below.
import java.awt.*;
import javax.swing.*;
import java.awt.geom.*;
public class DrawingPanel extends JPanel {
private int fontSize = 90;
private String message = "Java 2D";
private int messageWidth;
public DrawingPanel() {
setBackground(Color.white);
Font font = new Font("Serif", Font.PLAIN, fontSize);
setFont(font);
FontMetrics metrics = getFontMetrics(font);
messageWidth = metrics.stringWidth(message);
int width = messageWidth*5/3;
int height = fontSize*3;
setPreferredSize(new Dimension(width, height));
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D)g;
int x = messageWidth/10;
int y = fontSize*5/2;
g2d.translate(x, y);
g2d.setPaint(Color.lightGray);
AffineTransform origTransform = g2d.getTransform();
g2d.shear(-0.95, 0);
g2d.scale(1, 3);
g2d.drawString(message, 0, 0);
g2d.setTransform(origTransform);
g2d.setPaint(Color.black);
g2d.drawString(message, 0, 0);
}
}