Java Technology Home Page
A-Z Index

Java Developer Connection(SM)
Online Training

Downloads, APIs, Documentation
Java Developer Connection
Tutorials, Tech Articles, Training
Online Support
Community Discussion
News & Events from Everywhere
Products from Everywhere
How Java Technology is Used Worldwide
 
Training Index

Writing Advanced Applications
Chapter 6 Continued: Advanced Printing

[<<BACK] [CONTENTS] [NEXT>>]

The previous section explained how to print simple components and covered techniques that can be used to print screen captures. However, if you want to print more than one component per page, or if your component is larger than one page size, you need to do some additional work inside your print method. This section explains what you need to do and concludes with an example of how to print the contents of a JTable component.


Multiple Components Per Page

There are times when printing one component on a page does not meet your printing needs. For example, you might want to include a header on each page or print a footer with the page number--something that isn't necessarily displayed on the screen.

Unfortunately, printing multiple customized components on a page is not as easy as adding additional paint calls because each paint call overwrites the output of the previous call.

The key to printing more than one component on a page, is to use the translate(double, double) and setClip methods in the Graphics2D class.

The translate method moves an imaginary pen to the next position of the print output where the component can be painted and then printed. There are two translate methods in the Graphics2D class. To print multiple components you need the one t hat takes double arguments because this translate method allows relative positioning. Be sure to cast any integer values to double or float. Relative positioning in this context means that previous calls to translate are taken into account when calculating the new translated point.

The setClip method is used to restrict the component to only be painted, and therefore printed, in the area specified. This lets you print multiple components on a page by moving the imaginary pen to different points on the page and then painting each component in the clip area.

Example

You can replace the print method in the Abstract Window Toolkit (AWT) and Swing printbutton.java examples with the following code to add the footer message Company Confidential to the page.

public int print(Graphics g, PageFormat pf, int pi) 
		throws PrinterException {

    if (pi >= 1) {
       return Printable.NO_SUCH_PAGE;
    }

    Graphics2D g2 = (Graphics2D) g;
    Font f= Font.getFont("Courier");
    double height=pf.getImageableHeight();
    double width=pf.getImageableWidth();
         
    g2.translate(pf.getImageableX(), 
                 pf.getImageableY());
    g2.setColor(Color.black);
    g2.drawString("Company Confidential", (int)width/2, 
	(int)height-g2.getFontMetrics().getHeight());
    g2.translate(0f,0f);
    g2.setClip(0,0,(int)width, 
	(int)(height-g2.getFontMetrics().getHeight()*2));
    paint (g2);
    return Printable.PAGE_EXISTS;
}   

In the new print method, the Graphics2D context is clipped before calling the parent JButton paint method. This prevents the JButton paint method from overwriting the bottom of the page. The translate method is used to point the JButton paint method to start the paint at offset 0,0 from the visible part of the page. The visible area was already calculated by the previous translate call:
  g2.translate(pf.getImageableX(), pf.getImageableY());
For some components, you might also need to set the foreground color to see your results. In this example the text color was printed in black.

Useful Methods To Call In The print Method

The following methods are useful for calculating the number of pages required and for shrinking components to fit on a page:

PageFormat methods:

getImageableHeight()
returns the page height you can user for printing your output.

getImageableWidth()
returns the page width you can use for printing your output.

Graphics2D method:

scale(xratio, yratio)
scales the 2D graphics context by this size. A ratio of one maintains the size, less than one will shrink the graphics context.

Components Larger Than One Page

The JavaTM 2 Printing API has a Book API that provides the concept of pages. However, the Book API only adds printable objects to a collection of printable objects. It does not calculate page breaks or split components over multiple pages.

When printing a simple component on a page, you only have to check for the index value being greater or equal to one and return NO_SUCH_PAGE when this value is reached.

To print multiple pages, you have to calculate the number of pages needed to contain the component. You can calculate the total number of pages needed by subtracting the space taken by the component from the value returned by getImageableHeight. Once the total number of pages is calculated, you can run the following check inside the print method:

  if (pageIndex >=TotalPages) {
	return NO_SUCH_PAGE;
  }
The Printing framework calls the print method multiple times until pageIndex is less than or equal to TotalPages. All you need to do is create a new page from the same component on each print loop. This is done by treating the printed page like a sliding window over the component. The part of the component that is to be printed is selected by a translate call to mark the top of the page and a setClip call to mark the bottom of the page. The following diagram illustrates this process.

The left side of the diagram represents the page sent to the printer. The right side contains the long component being printed in the print method. The first page can be represented as follows:

The printed page window then slides along the component to print the second page, page index one.

This process continues until the last page from the total number of pages is reached:

Printing A JTable Component

The Report.java class uses many of the advanced techniques covered in this section to print out the data and header of a JTable component that can span many pages. The printed output also includes a footer at the bottom with the page number.

This diagram shows how the report looks when it prints:


import javax.swing.*;
import javax.swing.table.*;
import java.awt.print.*;
import java.util.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.awt.Dimension;

public class Report implements Printable{
  JFrame frame;
  JTable tableView;

  public Report() {
    frame = new JFrame("Sales Report");
    frame.addWindowListener(new WindowAdapter() {
    public void windowClosing(WindowEvent e) {
      System.exit(0);}});

    final String[] headers = {"Description", "open price", 
	"latest price", "End Date", "Quantity"};
    final Object[][] data = {
        {"Box of Biros", "1.00", "4.99", new Date(), 
          new Integer(2)},
        {"Blue Biro", "0.10", "0.14", new Date(), 
          new Integer(1)},
        {"legal pad", "1.00", "2.49", new Date(), 
          new Integer(1)},
        {"tape", "1.00", "1.49", new Date(), 
          new Integer(1)},
        {"stapler", "4.00", "4.49", new Date(), 
          new Integer(1)},
        {"legal pad", "1.00", "2.29", new Date(), 
          new Integer(5)}
    };

    TableModel dataModel = new AbstractTableModel() {
        public int getColumnCount() { 
          return headers.length; }
        public int getRowCount() { return data.length;}
        public Object getValueAt(int row, int col) {
        	return data[row][col];}
        public String getColumnName(int column) {
         	return headers[column];}
        public Class getColumnClass(int col) {
                return getValueAt(0,col).getClass();}
        public boolean isCellEditable(int row, int col) {
                return (col==1);}
        public void setValueAt(Object aValue, int row, 
                      int column) {
                data[row][column] = aValue;
       }
     };

     tableView = new JTable(dataModel);
     JScrollPane scrollpane = new JScrollPane(tableView);

     scrollpane.setPreferredSize(new Dimension(500, 80));
     frame.getContentPane().setLayout(
               new BorderLayout());
     frame.getContentPane().add(
               BorderLayout.CENTER,scrollpane);
     frame.pack();
     JButton printButton= new JButton();

     printButton.setText("print me!");

     frame.getContentPane().add(
               BorderLayout.SOUTH,printButton);

     // for faster printing turn double buffering off

     RepaintManager.currentManager(
     	frame).setDoubleBufferingEnabled(false);

     printButton.addActionListener( new ActionListener(){
        public void actionPerformed(ActionEvent evt) {
          PrinterJob pj=PrinterJob.getPrinterJob();
          pj.setPrintable(Report.this);
          pj.printDialog();
          try{ 
            pj.print();
          }catch (Exception PrintException) {}
          }
        });

        frame.setVisible(true);
     }

     public int print(Graphics g, PageFormat pageFormat, 
        int pageIndex) throws PrinterException {
     	Graphics2D  g2 = (Graphics2D) g;
     	g2.setColor(Color.black);
     	int fontHeight=g2.getFontMetrics().getHeight();
     	int fontDesent=g2.getFontMetrics().getDescent();

     	//leave room for page number
     	double pageHeight = 
     	  pageFormat.getImageableHeight()-fontHeight;
     	double pageWidth = 
     	  pageFormat.getImageableWidth();
     	double tableWidth = (double) 
          tableView.getColumnModel(
          ).getTotalColumnWidth();
     	double scale = 1; 
     	if (tableWidth >= pageWidth) {
		scale =  pageWidth / tableWidth;
	}

     	double headerHeightOnPage=
                 tableView.getTableHeader(
                 ).getHeight()*scale;
     	double tableWidthOnPage=tableWidth*scale;

     	double oneRowHeight=(tableView.getRowHeight()+
                      tableView.getRowMargin())*scale;
     	int numRowsOnAPage=
              (int)((pageHeight-headerHeightOnPage)/
                                  oneRowHeight);
     	double pageHeightForTable=oneRowHeight*
     	                            numRowsOnAPage;
     	int totalNumPages= 
     	      (int)Math.ceil((
                (double)tableView.getRowCount())/
                                    numRowsOnAPage);
     	if(pageIndex>=totalNumPages) {
                      return NO_SUCH_PAGE;
     	}

     	g2.translate(pageFormat.getImageableX(), 
                       pageFormat.getImageableY());
//bottom center
     	g2.drawString("Page: "+(pageIndex+1),
     	    (int)pageWidth/2-35, (int)(pageHeight
     	    +fontHeight-fontDesent));

     	g2.translate(0f,headerHeightOnPage);
     	g2.translate(0f,-pageIndex*pageHeightForTable);

     	//If this piece of the table is smaller 
     	//than the size available,
     	//clip to the appropriate bounds.
     	if (pageIndex + 1 == totalNumPages) {
           int lastRowPrinted = 
                 numRowsOnAPage * pageIndex;
           int numRowsLeft = 
                 tableView.getRowCount() 
                 - lastRowPrinted;
           g2.setClip(0, 
             (int)(pageHeightForTable * pageIndex),
             (int) Math.ceil(tableWidthOnPage),
             (int) Math.ceil(oneRowHeight * 
                               numRowsLeft));
     	}
     	//else clip to the entire area available.
     	else{    
             g2.setClip(0, 
             (int)(pageHeightForTable*pageIndex), 
             (int) Math.ceil(tableWidthOnPage),
             (int) Math.ceil(pageHeightForTable));        
     	}

     	g2.scale(scale,scale);
     	tableView.paint(g2);
     	g2.scale(1/scale,1/scale);
     	g2.translate(0f,pageIndex*pageHeightForTable);
     	g2.translate(0f, -headerHeightOnPage);
     	g2.setClip(0, 0,
     	  (int) Math.ceil(tableWidthOnPage), 
          (int)Math.ceil(headerHeightOnPage));
     	g2.scale(scale,scale);
     	tableView.getTableHeader().paint(g2);
     	//paint header at top

     	return Printable.PAGE_EXISTS;
   }

   public static void main(String[] args) {
	new Report();
   }
}

Print a Sales Report

The SalesReport.java Applet class prints a sales report with the rows split over multiple pages with numbers at the bottom of each page. Here is how the application looks when launched:

You need this policy file to launch the applet:

grant {
  permission java.lang.RuntimePermission 
                         "queuePrintJob";
};
To launch the applet assuming a policy file named printpol and an HTML file named SalesReport.html, you would type:
  appletviewer -J-Djava.security.policy=
                 printpol SalesReport.html
This diagram shows how the report prints:

[TOP]


[ This page was updated: 13-Oct-99 ]

Products & APIs | Developer Connection | Docs & Training | Online Support
Community Discussion | Industry News | Solutions Marketplace | Case Studies
Glossary - Applets - Tutorial - Employment - Business & Licensing - Java Store - Java in the Real World
FAQ | Feedback | Map | A-Z Index
For more information on Java technology
and other software from Sun Microsystems, call:
(800) 786-7638
Outside the U.S. and Canada, dial your country's AT&T Direct Access Number first.
Sun Microsystems, Inc.
Copyright © 1995-99 Sun Microsystems, Inc.
All Rights Reserved. Legal Terms. Privacy Policy.