Skip to main content

skip to main content

developerWorks  >  Java technology  >

Magic with Merlin: Printing in JDK 1.4, Part 2

How to listen for printing-related events and directly print graphics

developerWorks
Document options

Document options requiring JavaScript are not displayed

Discuss


Rate this page

Help us improve this content


Level: Introductory

John Zukowski (jaz@zukowski.net), President, JZ Ventures, Inc.

01 Apr 2002

In this second installment of his two-part introduction to the new Java Print Service API, John Zukowski shows how to set up event listeners for your print operations, print the contents of a screen or component, and remove the sleep() call from your printing operations.

In the first installment of this two-part introduction to the new Java Print Service API, you learned how to print directly from the file system and prompt for printer selection with the printer dialog. This week, we'll build on that discussion, adding two more printing capabilities to your repertoire. I'll show you how to introduce event listeners to your print operations, and how to print directly from a screen or component. As we did last time, we'll close with a working example that lets you explore the new print API for yourself. If you are unfamiliar with the new Print Service API, you should read the first installment of this series before you read this one.

Listening for printing events

Recall from Part 1 the last line from the example program:

Thread.sleep(10000);

This call puts the main program to sleep for 10 seconds while another thread transfers data from program to printer. Assuming a reasonable transfer rate, most image files should finish transferring in the designated time. More than likely, the transfer will be complete within fewer than 10 seconds. Because the transfer is usually quicker, you have the option to gradually tweak the sleep time for optimum performance, but there are many factors involved, so you can't simply calculate the best time.

For those who don't want to rely on Thread.sleep() and guesstimate when the transfer will complete, the Java Print Service API provides an alternative, in the form of PrintJobListener. This interface, found in the javax.print.event package, is shown in Listing 1:


Listing 1. The PrintJobListener interface

   public interface PrintJobListener {
     public void printDataTransferCompleted(PrintJobEvent e);
     public void printJobCompleted(PrintJobEvent e);
     public void printJobFailed(PrintJobEvent e);
     public void printJobCanceled(PrintJobEvent e);
     public void printJobNoMoreEvents(PrintJobEvent e);
     public void printJobRequiresAttention(PrintJobEvent e);
   }

As shown above, the PrintJobListener interface comes with six methods. The PrintJobListener works like the other delegation-based event listeners: once you have created a listener and registered it for certain events, it will be notified when those events occur. So, for example, by registering a PrintJobListener with the DocPrintJob created from the PrintService you could receive notification whenever data was transferred to the PrintService.

While you have the option to implement the methods of the interface yourself, the Print Service API comes with an adapter class that will do it all for you. All you have to do is subclass PrintJobAdapter and implement the specific methods for which you're interested in receiving notification.

For instance, to have your program stop as soon as the transfer has completed, just add a call to System.exit() in the printDataTransferCompleted() call, as shown in Listing 2:


Listing 2. Registering a PrintJobListener

   PrintService printService =
     PrintServiceLookup.lookupDefaultPrintService();
   DocPrintJob job = printService.createPrintJob();
   PrintJobListener listener = new PrintJobAdapter() {
     public void printDataTransferCompleted(PrintJobEvent e) {
       System.out.println("Good-bye");
       System.exit(0);
     }
   };
   job.addPrintJobListener(listener);

While the PrintJobListener works fine, the system must still wait while the transfer happens. If you don't show a printer selection dialog or some other GUI-related task, the program will immediately exit. The data transfer to the printer happens in a daemon thread, so the Java runtime can shut down while the transfer is ongoing. Doing some GUI-related task creates a non-daemon AWT thread that will keep the system alive while the data transfer happens.



Back to top


Drawing graphics

In the previous installment, you learned how to print files from disk. Printing an in-memory image or the contents of a component is a little different. This is where the DocFlavor.SERVICE_FORMATTED comes into play.

The DocFlavor.SERVICE_FORMATTED works with three interfaces:

  • java.awt.print.Pageable

  • java.awt.print.Printable

  • java.awt.image.renderable.RenderableImage

In the last installment, when we defined the Doc to print by creating a SimpleDoc, we passed in the InputStream for the disk file, as shown in Listing 3.


Listing 3. Setting the content

   DocFlavor flavor = DocFlavor.INPUT_STREAM.PNG;
   String filename = ...;
   FileInputStream fis = new FileInputSteam(filename);
   DocAttributeSet das = new HashDocAttributeSet();
   Doc doc = new SimpleDoc(fis, flavor, das);

When working with the DocFlavor.SERVICE_FORMATTED, if we want to print something graphical from memory we must pass in the implementer of one of the interfaces.

To demonstrate, let's define a component that implements the java.awt.print.Printable interface, as shown in Listing 4.


Listing 4. The Printable interface

   public interface Printable {
     public static final int PAGE_EXISTS;
     public static final int NO_SUCH_PAGE;
     public int print(Graphics g, PageFormat pf, int page)
       throws PrinterException;
   }

By creating a component that essentially just calls paint() from the print() method of Printable, we've implemented a Printable interface. A generic implementation is shown in Listing 5.


Listing 5. The Printable implementation

   public int print(Graphics g, PageFormat pageFormat, int pageIndex) {
     int x = (int)pageFormat.getImageableX();
     int y = (int)pageFormat.getImageableY();
     g.translate(x, y);
     if (pageIndex == 0) {
       paint(g);
       return Printable.PAGE_EXISTS;
     } else {
       return Printable.NO_SUCH_PAGE;
     }
   }

We can use this implementation for all components, assuming they are only one page long. Essentially, the first part of the method says, "move the drawing area into the area used by the printer." The second part says, "for the first page, call paint(), and for other pages, don't." This results in a single call to paint() when you want to print.



Back to top


A working example

As in the previous installment, we'll wrap up with a complete example that lets you try out the new print capabilities. Listing 6 combines all the code we've worked with thus far into one runnable example. The custom component below draws "Hello, Printer" in the center of your screen, with a box around it. When you press the Print button, the output is sent to the default printer and then the program shuts down. For a further exercise, try adding in a call to display the printer dialog, just like we did last month.


Listing 6. A printing example

import javax.print.*;
import javax.print.event.*;
import javax.print.attribute.*;
import java.awt.print.*;
import java.awt.*;
import java.awt.geom.*;
import java.awt.event.*;
import javax.swing.*;
import java.io.*;

public class PrintPrintable {

   static class MyComponent extends JPanel
       implements Printable {

     Font theFont = new Font("Serif", Font.ITALIC, 48);

     public void paint(Graphics g) {
       super.paint(g);
       String msg = "Hello, Printer";

       g.setFont(theFont);
       FontMetrics fm = g.getFontMetrics();

       // Center line
       int width = getWidth();
       int stringWidth = fm.stringWidth(msg);
       int x = (width - stringWidth)/2;

       int height = getHeight();
       int stringHeight = fm.getHeight();
       int ascent  = fm.getAscent();
       int y = (height - stringHeight)/2 + ascent;

       g.drawString(msg, x, y);
       g.drawRect(x, y-ascent, stringWidth, stringHeight);

     }

     public int print(Graphics g, PageFormat pageFormat, int pageIndex) {
       int x = (int)pageFormat.getImageableX();
       int y = (int)pageFormat.getImageableY();
       g.translate(x, y);
       if (pageIndex == 0) {
         paint(g);
         return Printable.PAGE_EXISTS;
       } else {
         return Printable.NO_SUCH_PAGE;
       }
     }
   }

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

     final JFrame frame = new JFrame("Printing Graphics");
     frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

     Container contentPane = frame.getContentPane();

     final Component printIt = new MyComponent();
     contentPane.add(printIt, BorderLayout.CENTER);

     JButton button = new JButton("Print");
     contentPane.add(button, BorderLayout.SOUTH);

     ActionListener listener = new ActionListener() {
       public void actionPerformed(ActionEvent e) {
         DocFlavor flavor = DocFlavor.SERVICE_FORMATTED.PRINTABLE;
         PrintService printService =
           PrintServiceLookup.lookupDefaultPrintService();
         DocPrintJob job = printService.createPrintJob();
         PrintJobListener pjlistener = new PrintJobAdapter() {
           public void printDataTransferCompleted(PrintJobEvent e) {
             System.out.println("Good-bye");
             System.exit(0);
           }
         };
         job.addPrintJobListener(pjlistener);

         PrintRequestAttributeSet pras =
           new HashPrintRequestAttributeSet();

         DocAttributeSet das = new HashDocAttributeSet();
         Doc doc = new SimpleDoc(printIt, flavor, das);
         try {
           job.print(doc, pras);
         } catch (PrintException pe) {
           pe.printStackTrace();
         }
       }
     };
     button.addActionListener(listener);

     frame.setSize(350, 250);
     frame.show();
   }
}

In the next installment of Magic with Merlin we'll take a look at Swing's new JFormattedTextField component, and you'll learn about prompting the user for formatted text input such as social security numbers, telephone numbers, or just plain integers.



Resources



About the author

Author photo

John Zukowski conducts strategic Java consulting with JZ Ventures, Inc. and serves as the resident guru for a number of jGuru's community-driven Java FAQs. His latest books are Mastering Java 2, J2SE 1.4 and Learn Java with JBuilder 6. Reach John at jaz@zukowski.net.




Rate this page


Please take a moment to complete this form to help us better serve you.



YesNoDon't know
 


 


12345
Not
useful
Extremely
useful
 


Back to top