I am trying to get the header on a table to have word wrap. I have managed to do this but the first data row is expanding. The code for the table is:
public class GenerateTable extends JTable {
private JCheckBox boxSelect = new JCheckBox();
private JTableHeader hdGen;
public class LineWrapCellRenderer extends JTextArea implements TableCellRenderer {
private static final long serialVersionUID = 1L;
int rowHeight = 0; // current max row height for this scan
#Override
public Component getTableCellRendererComponent(
JTable table,
Object value,
boolean isSelected,
boolean hasFocus,
int row,
int column)
{
/*
* row < 0 means header
*/
if(row >= 0) {
setWrapStyleWord(false);
return this;
}
setText((String) value);
setWrapStyleWord(true);
setLineWrap(true);
// current table column width in pixels
int colWidth = table.getColumnModel().getColumn(column).getWidth();
// set the text area width (height doesn't matter here)
setSize(new Dimension(colWidth, 1));
// get the text area preferred height and add the row margin
int height = getPreferredSize().height + table.getRowMargin();
// ensure the row height fits the cell with most lines, row = -1 for header
if (column == 2 || height > rowHeight) {
table.setRowHeight(row, height);
rowHeight = height;
}
return this;
}
}
LineWrapCellRenderer lwHeader = new LineWrapCellRenderer();
public GenerateTable(GenerateTableModel model) {
super(model);
this.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
/*
* Select
*/
TableColumn colSelect = this.getColumnModel().getColumn(0);
colSelect.setCellEditor(new DefaultCellEditor(boxSelect));
colSelect.setPreferredWidth(60);
/*
* category
*/
this.getColumnModel().getColumn(1).setResizable(false);
this.getColumnModel().getColumn(1).setPreferredWidth(200);
/*
* Amount values
*/
for (int i=2;i<model.getColumnCount();i++) {
colSelect = this.getColumnModel().getColumn(i);
colSelect.setPreferredWidth(100);
colSelect.setResizable(false);
colSelect.setHeaderRenderer(lwHeader);
}
}
}
The output is:
I have followed the code through in debug and LineWrapCellRenderer is not being called for the data lines. If I take the code out I get a normal table but no wrap on the header. Is this a recognised problem or am I missing something?
Any help appreciated
You can achieve multi-line headers much easier.
As with many Swing components you can use HTML code. In HTML specify <br> elements to indicate where line breaks / new lines should occur.
For example if you use the following header values (column names):
String[] columnNames = {
"<html>First<br>column</html>",
"<html>Second<br>column</html>",
"<html>Third<br>column</html>"
};
Then the headers will be properly rendered in 2 lines. You don't even need to create/use a custom header renderer, the default header renderer properly handles HTML code.
Note: The header height will be determined by the height of the first column. So you have to use a 2-line HTML value for the first column too. If you only have 1 word for the first column, you may additionally add an empty second line like this: "<html>Select<br> </html>"
Related
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 8 years ago.
Improve this question
I have a JTable with cells that have variable heights because of there Content. I accomplished this by using a JTextArea in my TableCellRenderer. I want to Color parts of the String in different colors. JTextPane supports HTML tags and text area doesn't but with text pane it is not possible to change the height of the cell.
Any idea how I can use variable cell heights and coloring of the string with JTable?
public class LineWrapCellRenderer extends JTextPane implements TableCellRenderer {
int rowHeight = 0; // current max row height for this scan
final int paddingRight = 4;
final int paddingLeft = 4;
Border padding = BorderFactory.createEmptyBorder(5, paddingLeft, 5, paddingRight);
#Override
public Component getTableCellRendererComponent(
JTable table,
Object value,
boolean isSelected,
boolean hasFocus,
int row,
int column){
setContentType("text/html");
setText(setHTML((String) value));
setSelectionColor(Color.BLUE);
this.setBorder(padding);//Abstände der Zeilen
//markieren fals selektiert
if (isSelected){
setBackground(table.getSelectionBackground());
// setForeground(table.getSelectionForeground());
}
else
{
setBackground(table.getBackground());
// setForeground(table.getForeground());
}
// current table column width in pixels
int colWidth = table.getColumnModel().getColumn(column).getWidth() + table.getIntercellSpacing().width;
// set the text area width (height doesn't matter here)
Dimension dim = new Dimension(colWidth, 1);
setSize(dim);
// get the text area preferred height and add the row margin
int height = getPreferredSize().height + table.getRowMargin();
// ensure the row height fits the cell with most lines
if (height != table.getRowHeight(row)) {
table.setRowHeight(row, height);
}
return this;
}
using this code with JTextPane has no effect on the cell height. using the same code with JTextArea the height is adjusted.
You can set/change the height of each individual row with the following method:
JTable.setRowHeight(int row, int rowHeight);
As for coloring parts of the text displayed in a cell, you can simply use HTML code, e.g.
String value = "<html>Following word is <font color=\"red\">RED</font>.</html>";
The default table cell renderer (DefaultTableCellRenderer) uses/extends JLabel which properly handles/accepts HTML code.
Manual height
See this example:
JFrame f = new JFrame("Test");
JTable t = new JTable();
((DefaultTableModel)t.getModel()).setDataVector(new Object[][]{
{"<html>Next word is <font color=\"red\">RED</font>.</html>", "50px"},
{"<html>Following is <font color=\"blue\">BLUE</font>.<br><br>"
+ "Long lines are automatically wrapped"
+ " as this long line demonstrates it.</html>", "150px"},
}, new Object[]{"Formatted text","Height"});
t.setRowHeight(0, 50);
t.setRowHeight(1, 150);
f.add(new JScrollPane(t));
f.pack();
f.setVisible(true);
Result:
Packing / Authoheight
If you want to "pack" all your rows to have the minimum height the value requires, you can do it like this:
JFrame f = new JFrame("Test");
JTable t = new JTable();
((DefaultTableModel) t.getModel()).setDataVector(new Object[][] {
{"<html>Next word is <font color='red'>RED</font>.</html>", "50px" },
{"<html><body style='width:200px'>Following is"
+ " <font color='blue'>BLUE</font>.<br><br>"
+ "Long lines are automatically wrapped "
+ "as this long line demonstrates it.</body></html>", "150px" }, },
new Object[] {"Formatted text", "Height"});
for (int i = 0; i < t.getRowCount(); i++)
t.setRowHeight(i, t.getCellRenderer(i, 0)
.getTableCellRendererComponent(t, t.getValueAt(i, 0), false, false, i, 0)
.getPreferredSize().height);
f.add(new JScrollPane(t));
f.pack();
f.setVisible(true);
Basically iterate over the rows, and ask the preferred height from the renderer, and set that as the row height.
Note: This required to set width style in HTML values that are auto-wrapped to multiple lines. If you don't do this, the preferred height of an HTML value will be the preferred height without auto-wrapping long lines (manual wrapping like <br> tags will still be considered).
This will result in this:
I am looking for a method to split wide tables so that they span across multiple pages. The goal is to make tables with large number of columns readable. I found one discussion thread where this topic is covered; however, the example referenced in there is not available. Manning's "iText in Action" (2006) doesn't cover this topic.
Can this be done in version 1.4.8, if not, to which version of iText should I upgrade to?
Please take a look at the examples of chapter 4 of my book, more specifically at the Zhang example. In this example, I have a table with four columns: (1) year, (2) movie title in English, (3) movie title in Chinese, and (4) run length. If you look at the resulting PDF, you will see that this table is split vertically.
Achieving this requires more work then simply adding a table and allowing iText to decide how to split it in between rows. When you want to split in between columns, you need to organize the layout in your code. This is done using the writeSelectedRows()) method.
In my simple book example, I use these lines:
// draw the first two columns on one page
table.writeSelectedRows(0, 2, 0, -1, 236, 806, canvas);
document.newPage();
// draw the remaining two columns on the next page
table.writeSelectedRows(2, -1, 0, -1, 36, 806, canvas);
First I draw the columns from index 0 to index 2. The column with index 0 is the first column, the column with index 2 is the first column that isn't included, namely the third column. I draw the rows from index 0 (first row) until -1. Minus one means: draw all the remaining rows.
You also see minus one on the next page, where I draw the column with index 2 (the third column) until the column with index -1 (meaning: the rest of the columns).
The values (236, 806) and (36, 806) are coordinates: that's where you want the table to start. You can't define "end coordinates". If the table doesn't fit on the page, iText will just continue drawing the table, even if that means that some content exceeds the visible area of the page. This means that you'll have to be very careful when using this method: you'll need to calculate widths and heights of rows and columns before adding the table, otherwise you may end up with parts of the table that aren't visible.
This is the source code of a class that splits your table over multiple pages when your columns don't fit in a single page
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import com.lowagie.text.Document;
import com.lowagie.text.DocumentException;
import com.lowagie.text.pdf.PdfContentByte;
import com.lowagie.text.pdf.PdfPTable;
import com.lowagie.text.pdf.PdfWriter;
/**
* Class that writes a <code>PdfPTable</code>, and spans it across multiple
* pages if the columns won't fit on one page
*/
public class PdfPTableWriter {
// Instance variables
private PdfPTable table;
private PdfWriter writer;
private Document document;
// List of how many columns per horizontal page
private List numberOfColumnsPerPage;
// List of how many rows per vertical page
private List numberOfRowsPerPage;
// Offsets if given
private float widthOffset = 20;
private float heightOffset = 70;
/**
* Class Constructor
*/
public PdfPTableWriter(Document document, PdfWriter writer, PdfPTable table) {
this.document = document;
this.writer = writer;
this.table = table;
calculateColumns();
calculateRows();
}
/**
* Writes the table to the document
*/
public void writeTable() throws DocumentException {
// Begin at row 1 (row after the header)
int rowBegin = 1;
int rowEnd = 0;
// Note the size of numberOfRowsPerPage is how many vertical
// pages there are.
Iterator rowsIter = numberOfRowsPerPage.iterator();
while (rowsIter.hasNext()) {
rowEnd = ((Integer) rowsIter.next()).intValue();
writeSelectedRows(rowBegin, rowEnd);
rowBegin = rowEnd;
}
}
/**
* Prints the Report's columns (splitting horizontally if necessary) and
* subsequent rows
*
* #param rowBegin
* #param rowEnd
* #throws DocumentException
*/
private void writeSelectedRows(int rowBegin, int rowEnd) throws DocumentException {
int colBegin = 0;
int colEnd = 0;
float pageHeight = document.getPageSize().getHeight() - heightOffset;
PdfContentByte contentByte = writer.getDirectContent();
Iterator columnsIter = numberOfColumnsPerPage.iterator();
while (columnsIter.hasNext()) {
colEnd = colBegin + ((Integer) columnsIter.next()).intValue();
// Writer table header
writeSelectedRows(colBegin, colEnd, 0, 1, widthOffset, pageHeight);
// Writes selected rows to the document
writeSelectedRows(colBegin, colEnd, rowBegin, rowEnd, widthOffset, pageHeight - table.getRowHeight(0) /*table.getHeaderHeight()*/);
// Add a new page
document.newPage();
colBegin = colEnd;
}
}
public int getTotalPages() {
return numberOfColumnsPerPage.size() * numberOfRowsPerPage.size();
}
public void setHeightOffset(float heightOffset) {
this.heightOffset = heightOffset;
}
public void setWidthOffset(float widthOffset) {
this.widthOffset = widthOffset;
}
private void writeSelectedRows(int colBegin, int colEnd, int rowBegin, int rowEnd, float x, float y) {
PdfContentByte cb = writer.getDirectContent();
table.writeSelectedRows(colBegin, colEnd, rowBegin, rowEnd, x, y, cb);
}
private void calculateColumns() {
numberOfColumnsPerPage = new ArrayList();
float pageWidth = document.getPageSize().getWidth() - widthOffset;
float[] widths = table.getAbsoluteWidths();
if (table.getTotalWidth() > pageWidth) {
// tmp variable for amount of total width thus far
float tmp = 0f;
// How many columns for this page
int columnCount = 0;
// Current page we're on
int currentPage = 0;
// Iterate through the column widths
for (int i = 0; i < widths.length; i++) {
// Add to the temporary total
tmp += widths[i];
// If this column will not fit on the page
if (tmp > pageWidth) {
// Add the current column count to this page
numberOfColumnsPerPage.add(new Integer(columnCount));
// Since this column won't fit, the tmp variable should
// start off the next iteration
// as this column's width
tmp = widths[i];
// Set column count to 1, since we have moved this column to
// the next page
columnCount = 1;
}
// If this is will fit on the page
else {
// Increase the column count
columnCount++;
}
}
// Save the remaining columns
numberOfColumnsPerPage.add(new Integer(columnCount));
}
// All the columns will fit on one horizontal page
// Note: -1 means all the columns
else {
numberOfColumnsPerPage.add(new Integer(-1));
}
}
private void calculateRows() {
numberOfRowsPerPage = new ArrayList();
float pageHeight = document.getPageSize().getHeight() - heightOffset - table.getRowHeight(0) /*table.getHeaderHeight()*/;
// If the table won't fit on the first page
if (table.getTotalHeight() > pageHeight /*table.getHeaderHeight()*/) {
// Temp variables
float tmp = 0f;
// Determine the start and end rows for each page
for (int i = 1; i < table.size(); i++) {
// Add this row's height to the tmp total
tmp += table.getRowHeight(i);
if (tmp > pageHeight - (heightOffset/2)) {
// This row won't fit so end at previous row
numberOfRowsPerPage.add(new Integer(i - 1));
// Since this row won't fit, the tmp variable should start
// off the next iteration
// as this row's height
tmp = table.getRowHeight(i);
}
}
// Last page always ends on totalRows
numberOfRowsPerPage.add(new Integer(table.size()));
}
// All the rows will fit on one vertical page
// Note: -1 means all the rows
else {
numberOfRowsPerPage.add(new Integer(-1));
}
}
}
Well, I'll try to give you some kind of response. First at all, as #mkl says, 1.4.8 is ancient version. Look at http://sourceforge.net/projects/itext/files/iText/ to get something better, the last version is 5.4.5. And as I know, there is no way to split wide table in two pages. If the document is "a bit" wider than the page - rotate the page, but if you have many columns that don't fit - you have to do it your way and this could be painful. There is no automatic function that can check if your columns have too much text and don't fit the page.
Hope this helps you.
I want a table that has a combo as one of its columnheaders.
I already found out that it is impossible with Table from this question:
Controls (Combo, Radio, Text) in column header SWT
Is there a way around that? I tried TableViewer but didn't find a way to do it with it either.
Is there any way this can be achieved?
You could create your own column headers in a Composite above the table using normal controls.
You will then need to adjust the size of these controls to match the table column sizes. One way to do this is to use a table layout class extending the jface TableColumnLayout and overriding the setColumnWidths method which is called each time the column sizes change, so you can adjust your header control widths.
Note: TableColumnLayout needs to be on a Composite containing just the Table rather than directly on the Table.
So something like this for the layout:
/**
* Table column layout extended to manage a separate table header.
*/
public class TableColumnLayoutWithSeparateHeader extends TableColumnLayout
{
/** Header composite */
private final Composite _headerComposite;
/** Right margin adjust */
private final int _rightMargin;
/**
* Constructor.
*
* #param headerComposite Header composite
* #param rightMargin Right margin value
*/
public TableColumnLayoutWithSeparateHeader(final Composite headerComposite, final int rightMargin)
{
super();
_headerComposite = headerComposite;
_rightMargin = rightMargin;
}
/**
* {#inheritDoc}
* #see org.eclipse.jface.layout.TableColumnLayout#setColumnWidths(org.eclipse.swt.widgets.Scrollable, int[])
*/
#Override
protected void setColumnWidths(final Scrollable tableTree, final int [] widths)
{
super.setColumnWidths(tableTree, widths);
// Update the header composite
final Control [] children = _headerComposite.getChildren();
final int size = Math.min(widths.length, children.length);
for (int index = 0; index < size; ++index) {
final GridData data = (GridData)children[index].getLayoutData();
int width = widths[index];
if (index == (size - 1))
width -= _rightMargin;
data.widthHint = width;
}
_headerComposite.layout();
}
}
I'm trying to color particular rows according to the first column values in JTable, but the code below colors the rows according to the row's index. My table has simply four columns. The first column has ID numbers. I need to color the rows according to these ID numbers. For example, if the first ID is 0 and the second is also 0, both of them should be "lightGray". Any idea, please?
table_1 = new JTable(){
public Component prepareRenderer(TableCellRenderer renderer,int Index_row, int Index_col) {
Component comp = super.prepareRenderer(renderer,Index_row, Index_col);
//even index, selected or not selected
if (Index_row % 2==0 && !isCellSelected(Index_row, Index_col)) {
comp.setBackground(Color.lightGray);
} else {
comp.setBackground(Color.white);
}
return comp;
}
};
Here is how it looks now:
Your renderer is choosing the color based on the row parameter passed to prepareRenderer(). The predicate row % 2 == 0 selects alternating rows for shading, as shown in your picture. Your question implies that you actually want to base shading on the value of column zero, ID. For this you need to examine the result of the getValueAt(row, 0).
The exact formulation depends on your model. Using this example, the following renderer shades rows starting with the letter "T".
private JTable table = new JTable(dataModel) {
#Override
public Component prepareRenderer(TableCellRenderer renderer, int row, int col) {
Component comp = super.prepareRenderer(renderer, row, col);
int modelRow = convertRowIndexToModel(row);
if (((String) dataModel.getValueAt(modelRow, 0)).startsWith("T")
&& !isCellSelected(row, col)) {
comp.setBackground(Color.lightGray);
} else {
comp.setBackground(Color.white);
}
return comp;
}
};
Addendum: #mKorbel helpfully comments on the need to convert between model and view coordinates when sorting is enabled, as discussed here.
I have a JTable with 3 columns:
- No. #
- Name
- PhoneNumber
I want to make specific width for each column as follows:
and I want the JTable able to update the widths of its columns dynamically if needed (for example, inserting large number in the column #) and keeping same style of the JTable
I solved the first issue, using this code:
myTable.getColumnModel().getColumn(columnNumber).setPreferredWidth(columnWidth);
but I didn't success to make myTable to update the widths dynamically ONLY if the current width of the column doesn't fit its contents. Can you help me solving this issue?
Here I found my answer: http://tips4java.wordpress.com/2008/11/10/table-column-adjuster/
The idea is to check some rows' content length to adjust the column width.
In the article, the author provided a full code in a downloadable java file.
JTable table = new JTable( ... );
table.setAutoResizeMode( JTable.AUTO_RESIZE_OFF );
for (int column = 0; column < table.getColumnCount(); column++)
{
TableColumn tableColumn = table.getColumnModel().getColumn(column);
int preferredWidth = tableColumn.getMinWidth();
int maxWidth = tableColumn.getMaxWidth();
for (int row = 0; row < table.getRowCount(); row++)
{
TableCellRenderer cellRenderer = table.getCellRenderer(row, column);
Component c = table.prepareRenderer(cellRenderer, row, column);
int width = c.getPreferredSize().width + table.getIntercellSpacing().width;
preferredWidth = Math.max(preferredWidth, width);
// We've exceeded the maximum width, no need to check other rows
if (preferredWidth >= maxWidth)
{
preferredWidth = maxWidth;
break;
}
}
tableColumn.setPreferredWidth( preferredWidth );
}
Use the addRow(...) method of the DefaultTableModel to add data to the table dynamically.
Update:
To adjust the width of a visible column I think you need to use:
tableColumn.setWidth(...);
I actually run into this problem too. I've found one useful link that solved my issue.
Pretty much get the specific column and set its setMinWidth and setMaxWidth to be the same(as fixed.)
private void fixWidth(final JTable table, final int columnIndex, final int width) {
TableColumn column = table.getColumnModel().getColumn(columnIndex);
column.setMinWidth(width);
column.setMaxWidth(width);
column.setPreferredWidth(width);
}
Ref: https://forums.oracle.com/thread/1353172