JTable multiline cell renderer

by botunge October 09, 2009 18:57

I've searched the web a while in order to find a proper nicely written multiline cell renderer for a JTable, but all the solutions which I have found so far somehow does not meet my requirements, or they are just clumsy written.

The first example I found was this http://www.java2s.com/Code/Java/Swing-Components/MultiLineCellExample.htm, but somehow it does not meet the requirements which i have.
The renderer should be able to render individual row with different heights. And they should be able to resize etc. when manipulating the table.

By combining the above example with my own code, i came to the solution below, which is nice and clean and does not rely on anything outside the renderer.


  /**
   * Multiline Table Cell Renderer.
   */
  public class MultiLineTableCellRenderer extends JTextArea 
    implements TableCellRenderer {
    private List<List<Integer>> rowColHeight = new ArrayList<List<Integer>>();
   
    public MultiLineTableCellRenderer() {
      setLineWrap(true);
      setWrapStyleWord(true);
      setOpaque(true);
    }
   
    public Component getTableCellRendererComponent(
        JTable table, Object value, boolean isSelected, boolean hasFocus,
        int row, int column) {
      if (isSelected) {
        setForeground(table.getSelectionForeground());
        setBackground(table.getSelectionBackground());
      } else {
        setForeground(table.getForeground());
        setBackground(table.getBackground());
      }
      setFont(table.getFont());
      if (hasFocus) {
        setBorder(UIManager.getBorder("Table.focusCellHighlightBorder"));
        if (table.isCellEditable(row, column)) {
          setForeground(UIManager.getColor("Table.focusCellForeground"));
          setBackground(UIManager.getColor("Table.focusCellBackground"));
        }
      } else {
        setBorder(new EmptyBorder(1, 2, 1, 2));
      }
      if (value != null) {
        setText(value.toString());
      } else {
        setText("");
      }
      adjustRowHeight(table, row, column);
      return this;
    }
   
    /**
     * Calculate the new preferred height for a given row, and sets the height on the table.
     */
    private void adjustRowHeight(JTable table, int row, int column) {
      //The trick to get this to work properly is to set the width of the column to the
      //textarea. The reason for this is that getPreferredSize(), without a width tries
      //to place all the text in one line. By setting the size with the with of the column,
      //getPreferredSize() returnes the proper height which the row should have in
      //order to make room for the text.
      int cWidth = table.getTableHeader().getColumnModel().getColumn(column).getWidth();
      setSize(new Dimension(cWidth, 1000));
      int prefH = getPreferredSize().height;
      while (rowColHeight.size() <= row) {
        rowColHeight.add(new ArrayList<Integer>(column));
      }
      List<Integer> colHeights = rowColHeight.get(row);
      while (colHeights.size() <= column) {
        colHeights.add(0);
      }
      colHeights.set(column, prefH);
      int maxH = prefH;
      for (Integer colHeight : colHeights) {
        if (colHeight > maxH) {
          maxH = colHeight;
        }
      }
      if (table.getRowHeight(row) != maxH) {
        table.setRowHeight(row, maxH);
      }
    }
  }

The thing to not about the code above, it the setSize() call to the textarea. The height is not important, by the width is. getPreferredSize() is used to calculate the proper height of the cell, but getPreferredSize() only works if the width of the textarea has been set. If the width has not been set, getPreferredSize() thinks that it can lay out the text in the area in one line. By setting the width with setSize() we force getPreferredSize() to calculate a height for the text where the text cannot be wider then the width we've set using setSize().

As the renderer used an double arraylist to contain the preferred height of each cell, the same instance of the renderer must be used on all columns. The example belov just uses String in all column, but one might have domain specific object which are to be renderer. A trick to overcome this could be to make a domain specific renderer (MyDomainRenderer), which extends the MultiLineRenderer and encodes domain specific information into a String which is then passed to the MultiLineRenderer.


  public class MyDomainMultiLineCellRenderer extends MultiLineTableCellRenderer {
    public Component getTableCellRendererComponent(
        JTable table, Object value, boolean isSelected, boolean hasFocus,
        int row, int column) {
      String toRender = "";
      if (value instanceof SomeClass) {
        toRender = "SomeClass" + ((SomeClass)value).getSpecificMethod();
      } else {
        //etc.
      }
      super.getTableCellRendererComponent(table, toRender, isSelected, hasFocus, row, column);

    }
  }

To test the stuff above, you could write a test program like this:


import java.awt.Component;
import java.awt.Dimension;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.ArrayList;
import java.util.List;

import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextArea;
import javax.swing.UIManager;
import javax.swing.border.EmptyBorder;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableModel;
import javax.swing.table.TableRowSorter;

public class TestJTableMultiline extends JFrame {
  public TestJTableMultiline() {
    super("Multi-Line Cell Example");
    setDefaultCloseOperation(DISPOSE_ON_CLOSE);
    DefaultTableModel dm = new DefaultTableModel() {
      public Class<String> getColumnClass(int columnIndex) {
        return String.class;
      }
      public boolean isCellEditable(int row, int column) {
        return false;
      }
    };
    dm.setDataVector(
        new Object[][]{
            {"A0, Line1\nA0, Line2\nA0, Line3",
             "B0, Line1\nB0, Line2",
             "C0, Line1"},
            {"A1, Line1",
             "B1, Line1\nB1, Line2",
             "C1, Line1"},
            {"A2, Line1",
             "B2, Line1",
             "C2, Line1"}
            },
            new Object[] {"A", "B", "C"});

    JTable table = new JTable(dm);
    table.setDefaultRenderer(String.class, new MultiLineTableCellRenderer());
    TableRowSorter<? extends TableModel> sort = new TableRowSorter<DefaultTableModel>(dm);
    table.setRowSorter(sort);
    JScrollPane scroll = new JScrollPane(table);
    getContentPane().add(scroll);
    setLocationByPlatform(true);
    setSize(400, 430);
    setVisible(true);
  }

  public static void main(String[] args) {
    TestJTableMultiline frame = new TestJTableMultiline();
    frame.addWindowListener(new WindowAdapter() {
      public void windowClosing(WindowEvent e) {
        System.exit(0);
      }
    });
  }
}

And that kind of that... Now you have a proper multi line cell renderer, which can be reused over and over again since it has not been poluted with domain specific code.

Tags: , ,

Java

Comments are closed

Powered by BlogEngine.NET 1.6.1.0
Theme by Mads Kristensen | Modified by Mooglegiant

Welcome

Welcome to my blog.

My primary interest lies in Java Development, which is why this blog contains java code examples...

Calendar

<<  April 2014  >>
MoTuWeThFrSaSu
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011

View posts in large calendar