首页 > 解决方案 > JTextArea(换行)作为 TableCellRender 到多个列

问题描述

标题说明了一切。我有一个表格,在每个单元格中都有可能有长文本。我TableCellRenderer应该使用什么来显示数据 - 每个单元格的文本以及正确的换行?我已经尝试了很多与渲染器大小混淆的变体,setRowHeight但似乎没有什么是最佳的。我尝试的每一件事都有某种不稳定。

我尝试的第一件事是使用 aJTextArea作为 myTableCellRenderer以利用setLineWrap. 这个答案准确地描述了我所做的。它完全按照我的意愿工作,但存在问题。此渲染器只能拥有一列。如果将渲染器添加到第二列,则具有较大“id”(表中的列索引)的列将“占主导地位”(并为表的行提供高度),而忽略列中文本较小的情况“ id" 需要显示更多行,即更大的高度。

检查这个 gif。这正是我想要实现的行为,具有“最高”文本的列在行高中占主导地位。它之所以有效,是因为该列具有最大的索引。(最后渲染)

好的

你看?列的宽度减小了,所以文本需要更多的行才能完整地表示,并且第 1 列中的文本是可以的,因为它的所有文本都是可见的。

现在让我们看看第 1 列中的文本比最后一列需要更多行的情况。

坏的

很明显,我们丢失了第一列中的文本。最后一列(最后呈现)具有适当的高度,因此第 1 列不会“占主导地位”并用空间填充最后一列。

产生此行为的 SSCCE:

public class TableTest extends JTable {
    private static final long serialVersionUID = 7180027425789244942L;
    private static final String[] COLUMNS = { "SomeColumn", "OtherColumn", "OtherOtherColumn" };

    public TableTest() {
        super();
        Object[][] data = new Object[5][3];
        for (int i = 0; i < data.length; i++) {
            data[i][0] = "Row: " + i + " - " + loremIpsum();
            data[i][1] = "Row: " + i + " Maybe something small?";
            data[i][2] = "Row: " + i + "___" + new StringBuilder(loremIpsum()).reverse().toString();
        }
        setModel(new DefaultTableModel(data, COLUMNS) {
            @Override
            public Class<?> getColumnClass(int columnIndex) {
                return String.class;
            }
        });
        setDefaultRenderer(String.class, new WordWrapCellRenderer());
        getTableHeader().setReorderingAllowed(false);
    }

    public static class WordWrapCellRenderer extends JTextArea implements TableCellRenderer {
        private WordWrapCellRenderer() {
            setLineWrap(true);
            setWrapStyleWord(true);
        }

        @Override
        public WordWrapCellRenderer getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
            setText(value.toString());
            setSize(table.getColumnModel().getColumn(column).getWidth(), getPreferredSize().height);
            if (table.getRowHeight(row) != getPreferredSize().height) {
                table.setRowHeight(row, getPreferredSize().height);
            }
            return this;
        }
    }

    private String loremIpsum() {
        return "Lorem Ipsum is simply dummy text of the printing and typesetting industry."
                + " Lorem Ipsum has been the industry's standard dummy text ever since the 1500s,"
                + " when an unknown printer took a galley of type and scrambled it to make a type specimen book."
                + " It has survived not only five centuries, but also the leap into electronic typesetting, "
                + "remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset"
                + " sheets containing Lorem Ipsum passages, and more recently with desktop publishing software"
                + " like Aldus PageMaker including versions of Lorem Ipsum";
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            JFrame frame = new JFrame("Test");
            frame.setLayout(new BorderLayout());

            TableTest table = new TableTest();

            JScrollPane sp = new JScrollPane(table);
            frame.add(sp);
            frame.setSize(500, 500);
            frame.setLocationRelativeTo(null);
            frame.setVisible(true);
        });
    }
}

解决此问题的第一个尝试是更改table.getRowHeight(row) != getPreferredSize().heighttable.getRowHeight(row) <= getPreferredSize().height,以便仅在必须呈现“更高”组件时才更改行的高度。这也不起作用,因为在“高”渲染行高之后将不会恢复(包装)。相关gif图片:

在此处输入图像描述

看到这个之后,我尝试创建某种侦听器(ComponentListener#componentResized-MouseListener#mouseReleased到标题?),恢复每一行的高度,如下所示:

private void restoreRowHeight() {
    if (getModel() == null) //causing NPE
        return;

    for (int row = 0; row < getRowCount(); row++) {
        int heightOfTheTallestComponent = -1;
        for (int column = 0; column < getColumnCount(); column++) {
            Component c = prepareRenderer(getDefaultRenderer(String.class), row, column);
            if (c.getPreferredSize().height > heightOfTheTallestComponent)
                heightOfTheTallestComponent = c.getPreferredSize().height;
        }
        setRowHeight(row, heightOfTheTallestComponent);
    }
}

我能想到的听众似乎都不合适。然而,即使我找到了合适的监听器来调用这个方法,也会发生一个小但非常烦人的小故障。(欢迎任何阻止它的替代方案)。


最后我抱了一些希望(我在 10 分钟后后悔了),也许 JTable 可以用文本正确呈现JLabels (当它有 HTML 文本时换行)并使用以下(扩展)<html>JLabelDefaultTableCellRenderer

int width = table.getColumnModel().getColumn(column).getWidth();
setText("<html><p style='width: " + width + "px'>" + String.valueOf(value));

但当然,没有机会。

我尝试的另一种方法是再次使用此答案中描述的 JLabel,但同样,如果需要恢复,则没有高度恢复。

是否有任何解决方案可以正确包装和显示所有列的文本并且不会导致故障?

标签: javaswingjtablejtextarea

解决方案


也许我在问题中提到的关于使用侦听器恢复额外行空间的解决方案一点也不差。即使它不是这个问题的 100% 最佳解决方案(这就是我不接受我的答案的原因),它是稳定的并且不会导致任何性能问题。另外,它很容易理解(这是一个优点,对吧?)。

问题是恢复额外的行高和包装单元格必须在正确的时间进行,也就是使用正确的侦听器。额外的空间可能由 2 个事件引起。

  1. 当用户手动更改列的宽度时。
  2. 当由于某种原因调整表格大小时。

为了涵盖第一个,我发现最好的侦听器是使用MouseListener表的标题,更具体地说是捕获mouseReleased事件,因为当鼠标单击标题时,列的大小调整结束。

关于第二个 aComponentListener#componentResized足以覆盖它。请注意,即使表的数据发生更改,也会调用此侦听器,这就是dataChanged不需要“”类型的侦听器的原因(可能覆盖模型的fireTableDataChanged方法?)

这不是最好的,但它是一些东西。

预习:

预习

代码:

public class TableTest extends JTable {
    private static final String[] COLUMNS = { "SomeColumn", "OtherColumn", "OtherOtherColumn" };

    public TableTest() {
        super();
        Object[][] data = new Object[5][3];
        for (int i = 0; i < data.length; i++) {
            data[i][0] = "Row: " + i + " - " + loremIpsum();
            data[i][1] = "Row: " + i + " Maybe something small?";
            data[i][2] = "Row: " + i + "___" + new StringBuilder(loremIpsum()).reverse().toString();
        }
        setModel(new DefaultTableModel(data, COLUMNS) {
            @Override
            public Class<?> getColumnClass(int columnIndex) {
                return String.class;
            }
        });
        setDefaultRenderer(String.class, new WordWrapCellRenderer());
        getTableHeader().setReorderingAllowed(false);
        getTableHeader().setReorderingAllowed(true);
        getTableHeader().addMouseListener(new MouseAdapter() {
            @Override
            public void mouseReleased(MouseEvent e) {
                restoreRowHeight();
            }

        });
        addComponentListener(new ComponentAdapter() {
            @Override
            public void componentResized(ComponentEvent e) {
                restoreRowHeight();
            }
        });
    }

    private void restoreRowHeight() {
        if (getModel() == null) // causing NPE
            return;

        for (int row = 0; row < getRowCount(); row++) {
            int heightOfTheTallestComponent = -1;
            for (int column = 0; column < getColumnCount(); column++) {
                Component c = prepareRenderer(getDefaultRenderer(String.class), row, column);
                if (c.getPreferredSize().height > heightOfTheTallestComponent)
                    heightOfTheTallestComponent = c.getPreferredSize().height;
            }
            setRowHeight(row, heightOfTheTallestComponent);
        }
    }

    public static class WordWrapCellRenderer extends JTextArea implements TableCellRenderer {
        private WordWrapCellRenderer() {
            setLineWrap(true);
            setWrapStyleWord(true);
        }

        @Override
        public WordWrapCellRenderer getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
            setText(value.toString());
            setSize(table.getColumnModel().getColumn(column).getWidth(), getPreferredSize().height);
            if (table.getRowHeight(row) < getPreferredSize().height) {
                table.setRowHeight(row, getPreferredSize().height);
            }
            return this;
        }
    }

    private String loremIpsum() {
        return "Lorem Ipsum is simply dummy text of the printing and typesetting industry."
                + " Lorem Ipsum has been the industry's standard dummy text ever since the 1500s,"
                + " when an unknown printer took a galley of type and scrambled it to make a type specimen book."
                + " It has survived not only five centuries, but also the leap into electronic typesetting, "
                + "remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset"
                + " sheets containing Lorem Ipsum passages, and more recently with desktop publishing software"
                + " like Aldus PageMaker including versions of Lorem Ipsum";
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            JFrame frame = new JFrame("Test");
            frame.setLayout(new BorderLayout());

            TableTest table = new TableTest();

            JScrollPane sp = new JScrollPane(table);
            frame.add(sp);
            frame.setSize(500, 500);
            frame.setLocationRelativeTo(null);
            frame.setVisible(true);
        });
    }
}

推荐阅读