I am trying to find a way to programmatically set page breaks for printing out a JTable.
E.g., I have a table with approx 150 rows like this:
Line Number Data1 Data2 Data3 …etc
1 a b c d
1 a b c d
1 a b c d
2 a b c d
2 a b c d
3 a b c d
3 a b c d
3 a b c d
3 a b c d
4 a b c d
5 a b c d
5 a b c d
5 a b c d
…etc …etc …etc …etc …etc
I need to find a way to start printing a new page when the line number changes.
I found a method for printing selected rows, and so far have modified it to loop through my table adding rows to a temporary print model and then calling print() method to print before resetting the temporary variables. However, this means that I am calling print() maybe 10 times, once for each line number, which is an unacceptable solution. The code that accomplishes current flawed solution is as follows:
btnPrint.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
try {
int wrappingLineNumber = (Integer) table.getValueAt(0, 0);
WrappingSheetsTableModel printModel = new WrappingSheetsTableModel();
for (int i = 0; i < table.getRowCount(); i++) {
if ((Integer)table.getValueAt(i, 0) == wrappingLineNumber) {
System.out.println(table.getValueAt(i, -1));
printModel.addRow((WrappingSheets) table.getValueAt(i, -1));
} else {
wrappingLineNumber = (Integer) table.getValueAt(i, 0);
// if not the same, i.e., value changed
JTable toPrint = new JTable(printModel);
toPrint.setSize(toPrint.getPreferredSize());
JTableHeader tableHeader = toPrint.getTableHeader();
tableHeader.setSize(tableHeader.getPreferredSize());
toPrint.print(JTable.PrintMode.FIT_WIDTH);
printModel.removeAll();
printModel.addRow((WrappingSheets) table.getValueAt(i, -1));
}
}
System.out.println("success printing");
} catch (PrinterException pe) {
System.out.println("printing failed");
pe.printStackTrace();
}
};
});
Can anyone provide a better solution that will start printing on a new page whenever the value in the line number column changes?
Many thanks in advance for any help provided!
After further research and thought I realized that it is possible to create sub-tables/temporary tables holding only the information relevant to each wrapping line. The question then is how to print multiple JTables as one print job. Variations of this question have been asked and answered here and particularly here. Following the information provided in the second of those links, the solution I came up with is as follows:
Solution utilizes unaltered PrintableWrapper provided by Durandal at link 2 above, included here for information purposes "to spare us the hassle of iterating over each page":
public class PrintableWrapper implements Printable {
private Printable delegate;
private int offset;
public PrintableWrapper(Printable delegate, int offset) {
this.offset = offset;
this.delegate = delegate;
}
#Override
public int print(Graphics graphics, PageFormat pageFormat, int pageIndex) throws PrinterException {
return delegate.print(graphics, pageFormat, pageIndex-offset);
}}
Solution also utilizes unaltered kleopatra / Durandal's code from same link for getting number of pages required for each table:
public int getNumberOfPages(Printable delegate, PageFormat pageFormat) throws PrinterException {
Graphics g = new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB).createGraphics();
int numPages = 0;
while (true) {
int result = delegate.print(g, pageFormat, numPages);
if (result == Printable.PAGE_EXISTS) {
++numPages;
} else {
break;
}
}
return numPages;
}
From this, the solution was to create a PrinterJob, Book and Temporary JTables. Then add the temporary tables to the book, using the above class and methods. And lastly to print the book. Current code for this solution can be found below, with explanatory comments included in the code:
btnPrint.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
try {
//Code revised from https://stackoverflow.com/questions/14775753/printing-multiple-jtables-as-one-job-book-object-only-prints-1st-table?rq=1
// \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/
PrinterJob printerJob = PrinterJob.getPrinterJob();
//Set 1/4 " margins and orientation
PageFormat pf = printerJob.defaultPage();
pf.setOrientation(PageFormat.LANDSCAPE);
Paper paper = new Paper();
//double margin = 36; // half inch
double margin = 18; // quarter inch
paper.setImageableArea(margin, margin, paper.getWidth() - margin * 2, paper.getHeight() - margin * 2);
pf.setPaper(paper);
Book bookToPrint = new Book();
// /\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
//Code revised from https://stackoverflow.com/questions/14775753/printing-multiple-jtables-as-one-job-book-object-only-prints-1st-table?rq=1
// List to store temporary tables to print
List<JTable> tablesToPrint = new ArrayList<JTable>();
// create temporary print tables by wrapping line number
int wrappingLineNumber = (Integer) table.getValueAt(0, 0);
WrappingSheetsTableModel printModel = new WrappingSheetsTableModel();
for (int i = 0; i < table.getRowCount(); i++) {
if ((Integer)table.getValueAt(i, 0) == wrappingLineNumber) {
// add wrapping sheet row to print model
printModel.addRow((WrappingSheets) table.getValueAt(i, -1));
} else {
// if not the same, i.e., value changed
// create a new table
JTable toPrint = new JTable(printModel);
// set size of table as if size isn't set the table contents aren't printed
toPrint.setSize(toPrint.getPreferredSize());
// reduce row height for printing
toPrint.setRowHeight(16);
// add table to print to list of tables to be printed
tablesToPrint.add(toPrint);
// create a new print model for next table to be printed
printModel = new WrappingSheetsTableModel();
// add first new wrapping sheet row to print model
printModel.addRow((WrappingSheets) table.getValueAt(i, -1));
// increment the wrapping line number
wrappingLineNumber = (Integer) table.getValueAt(i, 0);
}
}
// add in last table to print
JTable toPrint = new JTable(printModel);
// set size of table as if size isn't set the table contents aren't printed
toPrint.setSize(toPrint.getPreferredSize());
// reduce row height for printing
toPrint.setRowHeight(16);
// add last table to print to list of tables to be printed
tablesToPrint.add(toPrint);
// add each table to be printed to book for printing
int totalPages = 0;
for (JTable table : tablesToPrint) {
// get the table header information from table
JTableHeader tableHeader = table.getTableHeader();
// set size so that table header will be visible
tableHeader.setSize(tableHeader.getPreferredSize());
// get number of pages in printable table
int pages = getNumberOfPages(table.getPrintable(PrintMode.FIT_WIDTH, null, null), pf);
// create temporary printable to add to book
Printable p = table.getPrintable(PrintMode.FIT_WIDTH, null, null);
// add printable to book using printable wrapper
// p - printable, totalPages - total pages, pf - page format, pages - num pages to add
bookToPrint.append(new PrintableWrapper(p, totalPages), pf, pages);
// increase total pages count
totalPages += pages;
}
//Checks for page count:
//System.out.println(totalPages);
//System.out.println(bookToPrint.getNumberOfPages());
// Queries document for the number of pages
// and the PageFormat
// and Printable
// for each page held in the Pageable instance, document.
printerJob.setPageable(bookToPrint);
// show print dialog
// - printer selection and pages to print work
// - changing from e.g. landscape to portrait does not work currently
if(printerJob.printDialog()) printerJob.print();
System.out.println("success printing X");
} catch (PrinterException pe) {
System.out.println("printing failed");
pe.printStackTrace();
}
};
});
Each page now contains only those rows with the same wrapping line number and a new page is started when the wrapping line number changes. (More research is still needed in relation to the PageFormat and PrintDialog, as changes to page orientation in the print dialog do not currently affect the final print layout).
Related
We have fairly complicated model in a JTable. In new development I noticed that GUI does not refresh when I call fireTableChanged(...) for individual cells.
So, my question is:
What do I put into TableModelEvent - model row id or view row id?
Looking into JTable code (I have jdk1.8.0_202):
public class JTable extends JComponent implements TableModelListener, Scrollable,
...
public void tableChanged(TableModelEvent e) {
...
int modelColumn = e.getColumn();
int start = e.getFirstRow();
int end = e.getLastRow();
Rectangle dirtyRegion;
if (modelColumn == TableModelEvent.ALL_COLUMNS) {
// 1 or more rows changed
dirtyRegion = new Rectangle(0, start * getRowHeight(),
getColumnModel().getTotalColumnWidth(), 0);
}
else {
// A cell or column of cells has changed.
// Unlike the rest of the methods in the JTable, the TableModelEvent
// uses the coordinate system of the model instead of the view.
// This is the only place in the JTable where this "reverse mapping"
// is used.
int column = convertColumnIndexToView(modelColumn);
dirtyRegion = getCellRect(start, column, false);
}
I see that in order to calculate dirty region, it converts column index, but does not do the same for row.
What does "reverse mapping" comment mean?
Looks like a bug in Swing to me.
What do you think?
UPDATE
My code is simple:
model.fireTableChanged(new TableModelEvent(model, rowNumber, rowNumber, columnNumber));
GUI does NOT refresh the cell.
UPDATE2
The issue is in my model which is too complicated to post it here. :(
I cannot blame JTable. It is designed this way. The only possible addition to it is RowSorter, and in there it does correct conversion:
private void repaintSortedRows(ModelChange change) {
...
int modelIndex = change.startModelIndex;
while (modelIndex <= change.endModelIndex) {
int viewIndex = convertRowIndexToView(modelIndex++);
if (viewIndex != -1) {
Rectangle dirty = getCellRect(viewIndex, columnViewIndex,
false);
int x = dirty.x;
int w = dirty.width;
if (eventColumn == TableModelEvent.ALL_COLUMNS) {
x = 0;
w = getWidth();
}
repaint(x, dirty.y, w, dirty.height);
}
}
}
Thanks everybody. Sorry for disturbance.
My code is simple:
model.fireTableChanged(new TableModelEvent(model, rowNumber, rowNumber, columnNumber));
That is not how you change data in a JTable. You should NOT be invoking that method directly. It is the responsibility of the TableModel to invoke that method when data is changed.
The point of using a TableModelListener is to listen for changes in the TableModel. You only need to implement the listener if you want to do special processing AFTER the data has changed as I demonstrated in the link I provided in my comment.
If you have data in an existing cell and you want to change its value then can do something like:
model.setValueAt("new value", 0, 0);
If you want to add a new row of data you use:
model.addRow(...);
The point is all changes should be done via the TableModel.
Note the JTable also has a convenience setValueAt(...) method which will invoke the model for you.
am trying to fill a JTable form from an existing Object[][][] Array the problem that i all the data or containing the [Ljava.lang.Object;# instead of my (integer) data even though i mad a System.out.println("") to print the data before putting them into the JTable but i always get the same problem ; here is the code next with a small shot screen and thanx for the help.
import java.awt.Component;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
public class gass extends JFrame {
String title[] ={"Box", "Weight", "Priority"};
public gass() {
int nb=interface1.BNumber;
Object[][][] data = new Object[nb][nb][nb];
int E1=0, E2=0;
for (int i=0;i<nb;i++)
{ data[i][0][0] = i+1;
E1 = (int) (Math.random() * 100);
data[0][i][0] = E1;
E2 = (int) (Math.random() * 10);
data[0][0][i] = E2;
}
for (int j=0;j<nb;j++)
{
System.out.println("*"+data[j][0][0]+"*"+data[0][j][0]+"*"+data[0][0][j]+"*");
}
JTable table = new JTable(data, title);
Component add = this.getContentPane().add(new JScrollPane(table));
this.setVisible(true);
table.setPreferredScrollableViewportSize(table.getPreferredSize());
this.setSize(800,400);
}
}
Another problem that i get wrong data always in the first cases of the Object Array ***data[0][0][0] = wrong information !!***
next, a Link for a description of the output of my small application and thanks a lot for the help
Click in this link here to get Description Image
The JTable constructor takes an Object[][] as argument.
This array is an array of rows. So data[i] is a row, which is an array of columns.
And each row in the array is itself an array of columns. Each column (data[i][j]) should contain some data displayed in one cell of the JTable.
In your case, this data is itself an array. Since there is no specific renderer associated to object arrays, the toString() method of your array is used to display the array in the cell. And an array's toString() method returns something like [Ljava.lang.Object;#.
You should tell us what you would like to display in each cell, to get a better answer, explaining what you should do.
EDIT:
given what you want to display, you just need a two-dimensional array:
Object[][] data = new Object[nb][3]; // nb rows, 3 columns
for (int row = 0; row < nb; row++) {
data[row][0] = row + 1; // first column: row number
data[row][1] = Math.random(100); // second column: weight
data[row][2] = Math.random(10): // third column: priority
}
I have create a table with bars which shows frequency of the words in a text.I show the number of special word which user click on them or frequency of whole words in the text. I fetch my list of list and send it to the fill table function. All thing is OK but when I select a special word and then click to show whole words I get indexoutofbounds exception. I guess it is because I change my datasource. It is really strange but simple. However, I could not solve it.
public void fill_count_table(List<RootWordSet> source){
final List<RootWordSet> mysource=source;
if(source!=null){
for(int i=0;i<source.size();i++){
TableItem ti=new TableItem(count_table, SWT.NONE);
ti.setText(source.get(i).getRoot());
}
count_table.addListener(SWT.PaintItem, new Listener() {
public void handleEvent(Event event) {
if (event.index == 1) {
try{
GC gc = event.gc;
TableItem item = (TableItem)event.item;
int index = count_table.indexOf(item);
System.out.println(mysource.size());
int percent = mysource.get(index).getWordNumber();
org.eclipse.swt.graphics.Color foreground = gc.getForeground();
org.eclipse.swt.graphics.Color background = gc.getBackground();
gc.setForeground(Display_1.getSystemColor(SWT.COLOR_BLUE));
gc.setBackground(Display_1.getSystemColor(SWT.COLOR_DARK_BLUE));
int width = (tc2.getWidth() - 1) * percent / 100;
gc.fillGradientRectangle(event.x, event.y, width, event.height, true);
Rectangle rect2 = new Rectangle(event.x, event.y, width-1, event.height-1);
gc.drawRectangle(rect2);
gc.setForeground(Display_1.getSystemColor(SWT.COLOR_RED));
String text = Integer.toString(percent) ;
Point size = event.gc.textExtent(text);
int offset = Math.max(0, (event.height - size.y) / 2);
gc.drawText(text, event.x+2, event.y+offset, true);
gc.setForeground(background);
gc.setBackground(foreground);
}
catch(Exception e){
System.out.println(e.getMessage());
e.printStackTrace();
}
}
}
});
}
else{
count_table.removeAll();
count_table.redraw();
}
}
this is the line that make error: int percent = mysource.get(index).getWordNumber();
I really do not know what happens when I change datasources. when I shift to each other it stuck. Even I put a println to check the size of datasource but it was quite strange it had two size. One belong to former datasource and one belong to newwr. Anyway If I remove this graphic part table fill correctly. What do you think?
change this line:
int percent = mysource.get(index).getWordNumber();
to
int percent = mysource.get(index-1).getWordNumber();
I am able to print a full JTable, but actually I would like more to print just a specific part of a JTable, for example from Row 10 to Row 50 and Column 70 to Column 150.
How to do it ?
I've faced this problem too. Solved by hiding columns before printing and restoring columns after printing:
// get column num from settings
int num = gridSettings.getColumnsOnPage();// first <num> columns of the table will be printed
final TableColumnModel model = table.getColumnModel();
// list of removed columns. After printing we add them back
final List<TableColumn> removed = new ArrayList<TableColumn>();
int columnCount = model.getColumnCount();
// hiding columns which are not used for printing
for(int i = num; i < columnCount; ++i){
TableColumn col = model.getColumn(num);
removed.add(col);
model.removeColumn(col);
}
// printing after GUI will be updated
SwingUtilities.invokeLater(new Runnable() {
public void run() {
// table printing
try {
table.print(PrintMode.FIT_WIDTH, null, null, true, hpset, true); // here can be your printing
} catch (PrinterException e) {
e.printStackTrace();
}
// columns restoring
for(TableColumn col : removed){
model.addColumn(col);
}
}
});
For printing your specific part of a JTable, just change a little bit this code.
Get cell bounds for the selected fragment and calculate desired region (Rectangle), define clip region to paint only desired region, in the printable use Graphics's translate() method to shift the rendering.
I have an application, and I would like to print a JTable, but since it has many columns, I would like user to select/limit which columns to print so it can fit in regular printer paper.
I'm using JTable.print() function to print. (http://java.sun.com/docs/books/tutorial/uiswing/misc/printtable.html)
Right now, my solution, is to create another JTable with the columns that user selected, then repopulate the table with the data, then send it to printer.
Is there a better way to do it?
As I've answered here, I solved it by hiding columns before printing and restoring columns after printing:
// get column num from settings
int num = gridSettings.getColumnsOnPage();// first <num> columns of the table will be printed
final TableColumnModel model = table.getColumnModel();
// list of removed columns. After printing we add them back
final List<TableColumn> removed = new ArrayList<TableColumn>();
int columnCount = model.getColumnCount();
// hiding columns which are not used for printing
for(int i = num; i < columnCount; ++i){
TableColumn col = model.getColumn(num);
removed.add(col);
model.removeColumn(col);
}
// printing after GUI will be updated
SwingUtilities.invokeLater(new Runnable() {
public void run() {
// table printing
try {
table.print(PrintMode.FIT_WIDTH, null, null, true, hpset, true); // here can be your printing
} catch (PrinterException e) {
e.printStackTrace();
}
// columns restoring
for(TableColumn col : removed){
model.addColumn(col);
}
}
});
For printing specific part of a JTable, just change a little bit this code.
The best way: just set column width to zero then it will be hidden:)
TableColumn column = table.getColumnModel().getColumn(i);
//backup first
int minWidth=column.getMinWidth();
int width=column.getPreferredWidth();
//to hide:
column.setMinWidth(0);
column.setPreferredWidth(0);
//to show it back:
column.setMinWidth(minWidth);
column.setPreferredWidth(width);