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