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]
|