JTable header text wrapping for multiline header (custom TableCellRenderer)

喜夏-厌秋 提交于 2019-12-01 06:59:19

This here also uses JTextArea and also resizes the header height when the table is resized. The key to the correct calculation of the table header height is setSize(width, getPreferredSize().height);

class MultiLineTableHeaderRenderer extends JTextArea implements TableCellRenderer
{
  public MultiLineTableHeaderRenderer() {
    setEditable(false);
    setLineWrap(true);
    setOpaque(false);
    setFocusable(false);
    setWrapStyleWord(true);
    LookAndFeel.installBorder(this, "TableHeader.cellBorder");
  }

  @Override
  public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
    int width = table.getColumnModel().getColumn(column).getWidth();
    setText((String)value);
    setSize(width, getPreferredSize().height);
    return this;
  }
}

you need a Conponent that is able to wordwrap its content like JTextArea. I changed the cell renderer from your SSCCE so that is works initially, but it has a nasty resize behavior.

 class MultiLineHeaderRenderer extends JTextArea implements TableCellRenderer {
    public MultiLineHeaderRenderer()
    {
        setAlignmentY(JLabel.CENTER);
        setLineWrap(true);
        setWrapStyleWord(true);
        setBorder(BorderFactory.createCompoundBorder(
                BorderFactory.createLineBorder(Color.BLACK),
                BorderFactory.createEmptyBorder(3,3,3,3)
                ));

    }

    @Override
    public Component getTableCellRendererComponent(JTable table,
            Object value,
            boolean isSelected,
            boolean hasFocus,
            int row,
            int column) {
        setFont(table.getFont());
        String str = (value == null) ? "" : value.toString();
        setText(str);
        int columnWidth= getColumnWidth();
        setRows(str.length()/columnWidth);
        return this;
    }
}

Here is another approach. This solution has the following advantages:

  1. You need not manually break the column names.
  2. The columns dynamically word-wrap as you resize the columns and/or window.
  3. The header appearance will automatically be consistent with the installed look-and-feel.
  4. Unlike other solutions I have seen, this works even if the first column doesn't wrap (as in the example below).

It has the following disadvantage, however: It creates an unused JTableHeader object for each column, so it's a bit inelegant and probably not suitable if you have many columns.

The basic idea is that you wrap the column names in an <html> tags, and, crucially, every TableColumn gets its own TableCellRenderer object.

I came to this solution after debugging deep into the guts of the Swing table header layout plumbing. Without getting too much into the weeds, the problem is that if the TableColumns don't have a headerRenderer defined, the same default renderer is used for every column header cell. The layout code used for JTableHeader only bothers to ask the renderer of the first column header for its preferred size (see feature 4. above), and because the renderer is re-used, the call to its setText() method triggers the creation of a new View for the label, which, for reasons I'm too tired to even think about explaining, causes the header renderer to always report its preferred unwrapped height.

Here is a quick-and-dirty proof-of-concept:

package scratch;

import java.util.*;
import javax.swing.*;
import javax.swing.table.*;

@SuppressWarnings("serial")
public class WordWrappingTableHeaderDemo extends JFrame {

    class DemoTableModel extends AbstractTableModel {

        private ArrayList<String> wrappedColumnNames = new ArrayList<String>(); 
        private int numRows;

        DemoTableModel(List<String> columnNames, int numRows) {
            for (String name: columnNames)
                wrappedColumnNames.add("<html>" + name + "</html>");
            this.numRows = numRows;
        }

        public int getRowCount() {
            return numRows;
        }

        public int getColumnCount() {
            return wrappedColumnNames.size();
        }

        public Object getValueAt(int rowIndex, int columnIndex) {
            return Integer.valueOf(10000 + (rowIndex + 1)*(columnIndex + 1));
        }

        public String getColumnName(int column) {
            return wrappedColumnNames.get(column);
        }

        public Class<?> getColumnClass(int columnIndex) {
            return Integer.class;
        }
    }

    public WordWrappingTableHeaderDemo() {

        DefaultTableColumnModel tableColumnModel = new DefaultTableColumnModel() {
            public void addColumn(TableColumn column) {
                // This works, but is a bit kludgey as it creates an unused JTableHeader object for each column:
                column.setHeaderRenderer(new JTableHeader().getDefaultRenderer());
                super.addColumn(column);
            }
        };

        JTable table = new JTable();
        table.setFillsViewportHeight(true);;
        table.setColumnModel(tableColumnModel);
        table.setModel(
                new DemoTableModel(Arrays.asList("Name", "The Second Column Name is Very Long", "Column Three"), 20));
        getContentPane().add(new JScrollPane(table));
    }

    public static void createAndShowGUI() {
        WordWrappingTableHeaderDemo app = new WordWrappingTableHeaderDemo();
        app.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        app.setLocationByPlatform(true);
        app.pack();
        app.setVisible(true);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {createAndShowGUI();});
    }
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!