logo
Published on developer.* Blogs (http://www.developerdotstar.com/community)

Printing In Java

By Rob MacGrogan
Created 2005-02-16 19:02

Let me begin by saying, before I digress into stream-of-consciousness blogging mode, that I've got a solution to some tricky Java printing problems. The bottom of this post contains code that you can re-use, for free, to very easily print the multi-page contents of Java Swing components.

In many ways, Java makes an excellent language for developing desktop GUI applications. The Swing toolkit is rich and versital and makes performing certain kinds of tasks relatively easy. (For example, mouse scroll button support is "automagic" in all JScrollPanes, and the default implementation--whatever you mouse over you scroll--is a feature I wish other platforms would embrace.)

But, of course, its limitation are legion, and it is certainly not your first choice when your goal is to develop a desktop GUI app rapidly.

In a previous post I mocked WebObjects for it's amaturish Java GUI. But anyone who has ever used Eclipse knows that Java can build rich, clean, and rock-solid visual applications. (Yes, I know, Eclipse does not use Swing, but that's another story.) And dare I toot my own horn (Nilges does it all the time, so why not?) and mention that the aspect of SourceJammer (the version control application I developed) that users typically say they like most is the clean and easy-to-use GUI. So we know it's possible to GUI development right with Java.

But out of the box, Swing has some serious limitation that make the learning curve steep and development slow and treacherous. One glaring problem is the lack of a standard combo box component with typeahead support. If you want typehead in your combobox in a Swing app, you have to either a) figure out how to implement this yourself, b) pay money for some 3rd-party toolkit or c) trust in one of the very few open source solutions (PSwing at http://pswing.sourceforge.net [1], which I also developed, being one).

Another glaring shortcoming of the Swing api is very poor support for printing.

Well, let me rephrase that. The support is excellent and highly configurable. It's just incredibly difficult to use.

For the past several days I was tasked with figuring out how to perform batch printing from a Swing application. More specifically, the application needed to hit a series of web pages and print each page--not the raw HTML, of course, but the generated page. This was far more difficult than it should have been.

The Swing JEditorPane provides reasonable rendering of web pages. Don't expect it to work if you've got frames or javascript, though. Low tech pages only, please. Regardless, that part was easy enough to get up and running.

It was the printing tha really gave me fits.

Explanations of how to print using Swing are few and far between on the internet. One of the better ones was located here [2].

In order to print the contents of a Swing component, you first need to create an Object that implements the Printable interface. There is no default implementation of Printable out there in Javaland that I could find. Nope. You've got to build your own, and this means implementing the print() method which needs to deal with the underlying Graphics object (arrggg!) and also figuring out which portion of the component to print based on the page number.

I must be kidding or simply mis-informed, right? Surely it can't be that complex? Graphics math? Apple II-e, anyone? Sadly, Sun's assumption seems to be that, yes, developers should implement the print() method themselves. See their tutorial on the subject here [3].

Luckily, I discovered a some code on a forum at java.sun.com that made this process a little bit easier. I combined the code posted on that forum with the code from the tutorial above (not the Sun tutorial, the one from www.ap1.jhu.edu [4]). In the public interest, I post the combination here in the hope that this will be helpful to someone some day.

The following code can be compiled into a class that can easily print the multi-page contents of a Swing component. If fact, if you just use the static print() method, you're user will see the print dialog and everything else will happen without you having to get your fingers dirty.

/*
* Copied from this tutorial:
*
* http://www.apl.jhu.edu/~hall/java/Swing-Tutorial/Swing-Tutorial-Printing... [5]
*
* And also from a post on the forums at java.swing.com. My apologies that do not have
* a link to that post, by my hat goes off to the poster because he/she figured out the
* sticky problem of paging properly when printing a Swing component.
*/

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 {
System.out.println("Calling PrintJob.print()");
printJob.print();
System.out.println("End PrintJob.print()");
}
catch (PrinterException pe) {
System.out.println("Error printing: " + pe);
}
}

public int print(Graphics g, PageFormat pf, int pageIndex) {
int response = NO_SUCH_PAGE;

Graphics2D g2 = (Graphics2D) g;

// for faster printing, turn off double buffering
disableDoubleBuffering(componentToBePrinted);

Dimension d = componentToBePrinted.getSize(); //get size of document
double panelWidth = d.width; //width in pixels
double panelHeight = d.height; //height in pixels

double pageHeight = pf.getImageableHeight(); //height of printer page
double pageWidth = pf.getImageableWidth(); //width of printer page

double scale = pageWidth / panelWidth;
int totalNumPages = (int) Math.ceil(scale * panelHeight / pageHeight);

// make sure not print empty pages
if (pageIndex >= totalNumPages) {
response = NO_SUCH_PAGE;
}
else {

// shift Graphic to line up with beginning of print-imageable region
g2.translate(pf.getImageableX(), pf.getImageableY());

// shift Graphic to line up with beginning of next page to print
g2.translate(0f, -pageIndex * pageHeight);

// scale the page so the width fits...
g2.scale(scale, scale);

componentToBePrinted.paint(g2); //repaint the page for printing

enableDoubleBuffering(componentToBePrinted);
response = Printable.PAGE_EXISTS;
}
return response;
}

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);
}
}

If you encounter trouble using this class, let me know. And if it helps you, let me know as well.


Source URL:
http://www.developerdotstar.com/community/community/node/124