How to wrap lines in a jtable cell?
I'm trying to implement a custom TableRenderer as described in this tutorial. I'd like to have the renderer line-wrap each text that is to long for the given cell. The idea is, to use a TextArea as renderer, as it supports line wrapping. However, the following code does not behave as expected:
public class LineWrapCellRenderer extends JTextArea implements TableCellRenderer {
@Override
public Component getTableCellRendererComponent(
JTable table,
Object value,
boolean isSelected,
boolean hasFocus,
int row,
int column) {
this.setText((String)value);
this.setWrapStyleWord(true);
this.setLineWrap(true);
return this;
}
}
I set this renderer with
table.setDefaultRenderer(String.class, new LineWrapCellRenderer());
But the cell entries stay unwrapped.
If I add this.setBackground(Color.YELLOW)
to the getTableCellRendererComponent()
method,
all cells are yellow as expected, but not wrapped.
Any ideas?
UPDATE: As Michael Borgwardt stated in the comments, the problem is not the line wrap, but the row height: JTables rows are fixed size, so if a cell is getting higher (cause the text is now multi-lined), we have to increase the row height. But how much? I will check if this is worth another SO-question. If not, I will add this solution here.
Update2: The following code will determine the row height (if placed in getTableCellRendererComponent()
):
int fontHeight = this.getFontMetrics(this.getFont()).getHeight();
int textLength = this.getText().length();
int lines = textLength / this.getColumns() +1;//+1, cause we need at least 1 row.
int height = fontHeight * lines;
table.setRowHeight(row, height);
Solution 1:
The problem is that the height of rows in JTable is fixed, so it's not just a matter of having a renderer that wraps; I'm not sure why it doesn't, but if it did, the wrapped text would be cropped - or maybe that's exactly what you're seeing. To adjust row heights, you need to set them individually.
Solution 2:
Hi I had your same problem but the solution I implemented is inspired by the sample available from the Java Tutorial for drawing multiline text and draws the text on the cell using the text APIs.
http://java.sun.com/docs/books/tutorial/2d/text/drawmulstring.html
import java.awt.Component;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.font.FontRenderContext;
import java.awt.font.LineBreakMeasurer;
import java.awt.font.TextLayout;
import java.text.AttributedCharacterIterator;
import java.text.AttributedString;
import java.text.BreakIterator;
import javax.swing.JTable;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableCellRenderer;
public class MultilineTableCell
implements TableCellRenderer {
class CellArea extends DefaultTableCellRenderer {
/**
*
*/
private static final long serialVersionUID = 1L;
private String text;
protected int rowIndex;
protected int columnIndex;
protected JTable table;
protected Font font;
private int paragraphStart,paragraphEnd;
private LineBreakMeasurer lineMeasurer;
public CellArea(String s, JTable tab, int row, int column,boolean isSelected) {
text = s;
rowIndex = row;
columnIndex = column;
table = tab;
font = table.getFont();
if (isSelected) {
setForeground(table.getSelectionForeground());
setBackground(table.getSelectionBackground());
}
}
public void paintComponent(Graphics gr) {
super.paintComponent(gr);
if ( text != null && !text.isEmpty() ) {
Graphics2D g = (Graphics2D) gr;
if (lineMeasurer == null) {
AttributedCharacterIterator paragraph = new AttributedString(text).getIterator();
paragraphStart = paragraph.getBeginIndex();
paragraphEnd = paragraph.getEndIndex();
FontRenderContext frc = g.getFontRenderContext();
lineMeasurer = new LineBreakMeasurer(paragraph,BreakIterator.getWordInstance(), frc);
}
float breakWidth = (float)table.getColumnModel().getColumn(columnIndex).getWidth();
float drawPosY = 0;
// Set position to the index of the first character in the paragraph.
lineMeasurer.setPosition(paragraphStart);
// Get lines until the entire paragraph has been displayed.
while (lineMeasurer.getPosition() < paragraphEnd) {
// Retrieve next layout. A cleverer program would also cache
// these layouts until the component is re-sized.
TextLayout layout = lineMeasurer.nextLayout(breakWidth);
// Compute pen x position. If the paragraph is right-to-left we
// will align the TextLayouts to the right edge of the panel.
// Note: this won't occur for the English text in this sample.
// Note: drawPosX is always where the LEFT of the text is placed.
float drawPosX = layout.isLeftToRight()
? 0 : breakWidth - layout.getAdvance();
// Move y-coordinate by the ascent of the layout.
drawPosY += layout.getAscent();
// Draw the TextLayout at (drawPosX, drawPosY).
layout.draw(g, drawPosX, drawPosY);
// Move y-coordinate in preparation for next layout.
drawPosY += layout.getDescent() + layout.getLeading();
}
table.setRowHeight(rowIndex,(int) drawPosY);
}
}
}
public Component getTableCellRendererComponent(
JTable table, Object value,boolean isSelected, boolean hasFocus, int row,int column
)
{
CellArea area = new CellArea(value.toString(),table,row,column,isSelected);
return area;
}
}
It resizes row heigth too but it does it well only when this renderer is used for a single column.
And this is the way I used to invoke it for render my table.
final int wordWrapColumnIndex = ...;
myTable = new JTable() {
public TableCellRenderer getCellRenderer(int row, int column) {
if (column == wordWrapColumnIndex ) {
return wordWrapRenderer;
}
else {
return super.getCellRenderer(row, column);
}
}
};
Solution 3:
In addition to this question I'd like to share with you solution for multiline cell editor. It's a bit hacky (stores reference to edited row), but does the job.
import javax.swing.*;
import javax.swing.table.TableCellEditor;
import java.awt.*;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
class MultilineTableCellEditor extends AbstractCellEditor implements TableCellEditor {
JComponent component = new JTextArea();
JTable table;
int lastRowIndex;
public MultilineTableCellEditor() {
JTextArea textArea = ((JTextArea) component);
textArea.setLineWrap(true);
textArea.setWrapStyleWord(true);
textArea.addComponentListener(new ComponentAdapter() {
@Override
public void componentResized(ComponentEvent e) {
super.componentResized(e);
table.setRowHeight(lastRowIndex, (int) (textArea.getPreferredSize().getHeight()));
}
});
textArea.addKeyListener(new KeyAdapter() {
@Override
public void keyTyped(KeyEvent e) {
super.keyTyped(e);
table.setRowHeight(lastRowIndex, (int) (textArea.getPreferredSize().getHeight()));
}
});
}
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected,
int rowIndex, int vColIndex) {
this.table = table;
lastRowIndex = rowIndex;
((JTextArea) component).setText((String) value);
component.setFont(table.getFont());
return component;
}
public Object getCellEditorValue() {
return ((JTextArea) component).getText();
}
}
Used as so:
JTable table = new JTable(tableModel) {
// Cell renderer by Alessandro Rossi (posted as solution to this question)
MultilineTableCell renderer = new MultilineTableCell();
MultilineTableCellEditor editor = new MultilineTableCellEditor();
@Override
public TableCellRenderer getCellRenderer(int row, int column) {
if (column == multilineColumn) {
return renderer;
}
return super.getCellRenderer(row, column);
}
@Override
public TableCellEditor getCellEditor(int row, int column) {
if ( column == multilineColumn ) {
return editor;
}
return super.getCellEditor(row, column);
}
};
Solution 4:
You could use a JLabel as a renderer and insert the text into a HTML tag and just add <br>
where appropriate
How to use HTML in Swing Components