Print JTable with Multiple pages and rows
I took the printing example at http://java.sun.com/developer/onlineTraining/Programming/JDCBook/advprint.html#pe and modified it a bit to include the following:
1) To Print Multiple pages
2) To wrap lines that is too long for the column
3) To print with a more proffesional style, so that it doesn't look like a screen capture is printed
4) To align the numbers to the right and center column headings
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;
import javax.print.*;
import javax.print.attribute.*;
import javax.print.attribute.standard.*;
import java.text.*;
publicclass Reportimplements Printable
{
privatefinalint LEFT_ALIGN = -1;
privatefinalint CENTER_ALIGN = 0;
privatefinalint RIGHT_ALIGN = 1;
private JFrame frame;
private JTable tableView;
private String lastPrintDate;
private Font defaultFont;
private Font headerFont;
private Font footerFont;
privateint headerHeight;
privateint footerHeight;
privateint cellBuffer = 5;
privateboolean first_pass;
private ArrayList pages;
public Report()
{
frame =new JFrame("Sales Report");
frame.addWindowListener(new WindowAdapter()
{
publicvoid windowClosing(WindowEvent e)
{
System.exit(0);
}
});
final String[] headers =
{
"ID",
"Description",
"open price",
"latest price",
"End Date",
"Quantity"
};
int count = 0;
final Object[][] data =
{
{new Integer(count++),"Box of BirosBox of BirosBox of BirosBox of BirosBox of BirosBox of BirosBox of BirosBox of BirosBox of BirosBox of BirosBox of BirosBox of BirosBox of BirosBox of Biros ppppppppppppppp","1.00","4.99",new Date(),new Integer(200000)},
{new Integer(count++),"Blue Biro","0.10","0.14",new Date(),new Integer(1)},
{new Integer(count++),"legal pad","1.00","2.49",new Date(),new Integer(1)},
{new Integer(count++),"tape","1.00","1.49",new Date(),new Integer(1)},
{new Integer(count++),"stapler","4.00","4.49",new Date(),new Integer(1)},
{new Integer(count++),"Box of Biros","1.00","4.99",new Date(),new Integer(2)},
{new Integer(count++),"Blue Biro","0.10","0.14",new Date(),new Integer(1)},
{new Integer(count++),"legal pad","1.00","2.49",new Date(),new Integer(1)},
{new Integer(count++),"tape","1.00","1.49",new Date(),new Integer(1)},
{new Integer(count++),"stapler","4.00","4.49",new Date(),new Integer(1)},
{new Integer(count++),"Box of Biros","1.00","4.99",new Date(),new Integer(2)},
{new Integer(count++),"Blue Biro","0.10","0.14",new Date(),new Integer(1)},
{new Integer(count++),"legal pad","1.00","2.49",new Date(),new Integer(1)},
{new Integer(count++),"tape","1.00","1.49",new Date(),new Integer(1)},
{new Integer(count++),"stapler","4.00","4.49",new Date(),new Integer(1)},
{new Integer(count++),"Box of Biros","1.00","4.99",new Date(),new Integer(2)},
{new Integer(count++),"Blue Biro","0.10","0.14",new Date(),new Integer(1)},
{new Integer(count++),"legal pad","1.00","2.49",new Date(),new Integer(1)},
{new Integer(count++),"tape","1.00","1.49",new Date(),new Integer(1)},
{new Integer(count++),"stapler","4.00","4.49",new Date(),new Integer(1)},
{new Integer(count++),"Box of Biros","1.00","4.99",new Date(),new Integer(2)},
{new Integer(count++),"Blue Biro","0.10","0.14",new Date(),new Integer(1)},
{new Integer(count++),"legal pad","1.00","2.49",new Date(),new Integer(1)},
{new Integer(count++),"tape","1.00","1.49",new Date(),new Integer(1)},
{new Integer(count++),"stapler","4.00","4.49",new Date(),new Integer(1)},
{new Integer(count++),"Box of Biros","1.00","4.99",new Date(),new Integer(2)},
{new Integer(count++),"Blue Biro","0.10","0.14",new Date(),new Integer(1)},
{new Integer(count++),"legal pad","1.00","2.49",new Date(),new Integer(1)},
{new Integer(count++),"tape","1.00","1.49",new Date(),new Integer(1)},
{new Integer(count++),"stapler","4.00","4.49",new Date(),new Integer(1)},
{new Integer(count++),"Box of Biros","1.00","4.99",new Date(),new Integer(2)}
};
TableModel dataModel =new AbstractTableModel()
{
publicint getColumnCount(){return headers.length;}
publicint 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();
}
publicboolean isCellEditable(int row,int col)
{
return (col==1);
}
publicvoid 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()
{
publicvoid actionPerformed(ActionEvent evt)
{
doPrint();
}
});
frame.setVisible(true);
}
/**
* Reset variables before printing
*/
privatevoid prepareForPrint()
{
pages =new ArrayList();
first_pass =true;
}
/**
* Display a print dialog with some hardcoded defaults
* The print fonts are also hardcoded
*/
publicvoid doPrint()
{
try
{
String jobName ="Java Report";
defaultFont =new Font("Arial", Font.PLAIN, 8);
footerFont =new Font("Arial", Font.PLAIN, 6);
headerFont =new Font("Arial", Font.BOLD, 10);
PrinterJob prnJob = PrinterJob.getPrinterJob();
prnJob.setPrintable(this);
PrintRequestAttributeSet prnSet =new HashPrintRequestAttributeSet();
prnSet.add(new Copies(1));
prnSet.add(new JobName(jobName,null));
prnSet.add(MediaSizeName.ISO_A4);
PageFormat pf = prnJob.defaultPage();
pf.setOrientation(java.awt.print.PageFormat.PORTRAIT);
prnJob.setJobName(jobName);
PrintService[] services = PrinterJob.lookupPrintServices();
if (services.length > 0)
{
if (prnJob.printDialog(prnSet))
{
/*
* Get print date
*/
String dateFormat ="dd/MM/yyyy HH:mm:ss";
DateFormat m_DateFormat =new SimpleDateFormat(dateFormat);
lastPrintDate = m_DateFormat.format(new Date()).toString();
prepareForPrint();
prnJob.print(prnSet);
}
}
else
{
JOptionPane.showMessageDialog(frame,"No Printer was found!!","Printer Error", JOptionPane.ERROR_MESSAGE);
return;
}
}
catch (PrinterException e)
{
e.printStackTrace();
}
}
publicint print(Graphics g, PageFormat pageFormat,int pageIndex)throws PrinterException
{
/**
* Check if this is the first time the print method is called for this print action.
* It is not guaranteed that the print will be called with synchronous pageIndex'es,
* so we need to calculate the number of pages and which rows appear on which pages.
* Then the correct page will be printed regardless of which pageIndex is sent through.
*/
if (first_pass)
{
calcPages(g, pageFormat);
}
first_pass =false;
// Stop printing if the pageIndex is out of range
if (pageIndex >= pages.size())
{
return NO_SUCH_PAGE;
}
Graphics2Dg2 = (Graphics2D) g;
g2.setColor(Color.black);
// The footer will be one line at the bottom of the page, cater for this.
g2.setFont(footerFont);
footerHeight = g2.getFontMetrics().getHeight() + g2.getFontMetrics().getDescent();
g2.setFont(defaultFont);
FontMetrics fontMetrics = g2.getFontMetrics();
int fontHeight = fontMetrics.getHeight();
int fontDescent = fontMetrics.getDescent();
double pageHeight = pageFormat.getImageableHeight() + pageFormat.getImageableY();
double pageWidth = pageFormat.getImageableWidth();
double tableWidth = (double) tableView.getColumnModel().getTotalColumnWidth();
// Shrink or expand the table to fit the page width
double scale = pageWidth / (tableWidth+ (cellBuffer * tableView.getColumnCount()));
// Calculate the width in pixels for each column
double[] columnWidths =newdouble[tableView.getColumnCount()];
for(int i = 0; i < tableView.getColumnCount(); i++)
{
columnWidths[i] = (double)tableView.getColumnModel().getColumn(i).getWidth() * scale;
}
// Reset the view to the start of the page
g2.translate(0, 0);
/*
// Draw a rectangle to see the printable area
g2.draw3DRect((int)pageFormat.getImageableX(),
(int)pageFormat.getImageableY(),
(int)pageFormat.getImageableWidth(),
(int)pageFormat.getImageableHeight(),
false);
*/
// Calculate the header height
g2.setFont(headerFont);
fontMetrics = g2.getFontMetrics();
// Print the headers and retreive the starting position for the data
int next_row = printLine(g2, pageFormat, fontMetrics, -1, (int)pageFormat.getImageableY() + fontHeight, columnWidths);
g2.setFont(defaultFont);
fontMetrics = g2.getFontMetrics();
// Start printing the detail
ArrayList page = (ArrayList)pages.get(pageIndex);
int start = ((Integer)page.get(0)).intValue();
int end = ((Integer)page.get(1)).intValue();
for (int i = start; i <= end; i++)
{
next_row = printLine(g2, pageFormat, fontMetrics, i, next_row, columnWidths);
}
// Print the footer
g2.setFont(footerFont);
String pageFooter ="Page " + (pageIndex + 1) +" - " + lastPrintDate;
g2.drawString(pageFooter,
(int)pageFormat.getWidth() / 2 - (fontMetrics.stringWidth(pageFooter) / 2),
(int)(pageHeight - fontDescent));
return PAGE_EXISTS;
}
/**
* We can't guarantee that the same amount of rows will be displayed on each page,
* the row heights are dynamic and may wrap onto 2 or more lines.
* Thus we need to calculate the height of each row and then test how may rows
* fit on a specific page. eg. Page 1 contains rows 1 to 10, Page 2 contains rows 11 to 15 etc.
*/
publicvoid calcPages(Graphics g, PageFormat pageFormat)throws PrinterException
{
Graphics2Dg2 = (Graphics2D) g;
g2.setColor(Color.black);
// The footer will be one line at the bottom of the page, cater for this.
g2.setFont(footerFont);
footerHeight = g2.getFontMetrics().getHeight() + g2.getFontMetrics().getDescent();
g2.setFont(defaultFont);
FontMetrics fontMetrics = g2.getFontMetrics();
int fontHeight = fontMetrics.getHeight();
int fontDescent = fontMetrics.getDescent();
double pageHeight = pageFormat.getImageableHeight() - fontHeight;
double pageWidth = pageFormat.getImageableWidth();
double tableWidth = (double) tableView.getColumnModel().getTotalColumnWidth();
// Shrink or expand the table to fit the page width
double scale = pageWidth / (tableWidth+ (cellBuffer * tableView.getColumnCount()));
// Calculate the width in pixels for each column
double[] columnWidths =newdouble[tableView.getColumnCount()];
for(int i = 0; i < tableView.getColumnCount(); i++)
{
columnWidths[i] = (double)tableView.getColumnModel().getColumn(i).getWidth() * scale;
}
// Calculate the header height
int maxHeight = 0;
g2.setFont(headerFont);
fontMetrics = g2.getFontMetrics();
for (int j = 0; j < tableView.getColumnCount(); j++)
{
String value = tableView.getColumnName(j).toString();
int numLines = (int)Math.ceil(fontMetrics.stringWidth(value) / columnWidths[j]);
if (numLines > maxHeight)
{
maxHeight = numLines;
}
}
headerHeight = g2.getFontMetrics().getHeight() * maxHeight;
g2.setFont(defaultFont);
fontMetrics = g2.getFontMetrics();
int pageNum = 0;
int bottom_of_page = (int)(pageFormat.getImageableHeight() + pageFormat.getImageableY()) - footerHeight;
int prev_row = 0;
int next_row = (int)pageFormat.getImageableY() + fontHeight + headerHeight;
int i = 0;
ArrayList page =new ArrayList();
page.add(new Integer(0));
for (i = 0; i < tableView.getRowCount(); i++)
{
maxHeight = 0;
for (int j = 0; j < tableView.getColumnCount(); j++)
{
String value = tableView.getValueAt(i, j).toString();
int numLines = (int)Math.ceil(fontMetrics.stringWidth(value) / columnWidths[j]);
if (numLines > maxHeight)
{
maxHeight = numLines;
}
}
prev_row = next_row;
next_row += (fontHeight * maxHeight);
// If we've reached the bottom of the page then set the current page's end row
if (next_row > bottom_of_page)
{
page.add(new Integer(i - 1));
pages.add(page);
page =new ArrayList();
page.add(new Integer(i));
pageNum++;
next_row = (int)pageFormat.getImageableY()
+ fontHeight
+ ((int)pageFormat.getHeight() * pageNum)
+ headerHeight;
bottom_of_page = (int)(pageFormat.getImageableHeight()
+ pageFormat.getImageableY())
+ ((int)pageFormat.getHeight() * pageNum)
- footerHeight;
//Include the current row on the next page, because there is no space on this page
i--;
}
}
page.add(new Integer(i - 1));
pages.add(page);
}
/**
* Print the headers or a row from the table to the graphics context
* Return the position of the row following this one
*/
publicint printLine(Graphics2D g2,
PageFormat pageFormat,
FontMetrics fontMetrics,
int rowNum,
int next_row,
double[] columnWidths)
throws PrinterException
{
int lead = 0;
int maxHeight = 0;
for (int j = 0; j < tableView.getColumnCount(); j++)
{
String value =null;
int align = LEFT_ALIGN;
if (rowNum > -1)
{
Object obj = tableView.getValueAt(rowNum, j);
if (objinstanceof Number)
{
align = RIGHT_ALIGN;
}
value = obj.toString();
}
else
{
align = CENTER_ALIGN;
value = tableView.getColumnName(j);
}
int numLines = (int)Math.ceil(fontMetrics.stringWidth(value) / columnWidths[j]);
if (numLines > maxHeight)
{
maxHeight = numLines;
}
if (fontMetrics.stringWidth(value) < columnWidths[j])
{
// Single line
int offset = 0;
// Work out the offset from the start of the column to display alignment correctly
switch (align)
{
case RIGHT_ALIGN: offset = (int)(columnWidths[j] - fontMetrics.stringWidth(value));break;
case CENTER_ALIGN: offset = (int)(columnWidths[j] - fontMetrics.stringWidth(value)) / 2;break;
default: offset = 0;break;
}
g2.drawString(value,
lead + (int)(pageFormat.getImageableX() + offset),
next_row);
}
else
{
for(int a = 0; a < numLines; a++)
{
//Multi-Line
int x = 0;
int width = 0;
for(x = 0; x < value.length(); x++)
{
width += fontMetrics.charWidth(value.charAt(x));
if (width > columnWidths[j])
{
break;
}
}
int offset = 0;
// Work out the offset from the start of the column to display alignment correctly
switch (align)
{
case RIGHT_ALIGN: offset = (int)(columnWidths[j] - fontMetrics.stringWidth(value));break;
case CENTER_ALIGN: offset = (int)(columnWidths[j] - fontMetrics.stringWidth(value)) / 2;break;
default: offset = 0;break;
}
g2.drawString(value.substring(0, x),
lead + (int)(pageFormat.getImageableX() + offset),
next_row + (fontMetrics.getHeight() * a));
value = value.substring(x);
}
}
lead += columnWidths[j] + cellBuffer;
}
// Draw a solid line below the row
g2.draw(new Line2D.Double(pageFormat.getImageableX(),
next_row + (fontMetrics.getHeight() * (maxHeight - 1)) + fontMetrics.getDescent(),
pageFormat.getImageableY() + pageFormat.getImageableWidth(),
next_row + (fontMetrics.getHeight() * (maxHeight - 1)) + fontMetrics.getDescent()));
// Return the position of the row following this one
return next_row + (fontMetrics.getHeight() * maxHeight);
}
publicstaticvoid main(String[] args)
{
new Report();
}
}

