JEditorPane.getPreferredSize在Java 9中不总是起作用?

10
这个问题涉及到Java 8和Java 9中JEditorPane的不同行为。我想知道其他人是否遇到了相同的问题,是否可能是Java 9中的一个bug,并且如果可能的话,请您提供在Java 9中如何处理它的建议。
背景:在我们(古老的)代码库中,我们使用了JTable的子类,并且为了在其中一列中呈现多行HTML,我们使用了JEditorPane的子类。我们使用JEditorPane.getPreferredSize()来确定内容的高度,并将其用于设置表格行的高度。多年来一直运行良好。但在Java 9中无法正常工作;行只显示10像素高。在Windows和Mac上都有这个问题。
我想向您展示两个代码示例。如果第一个更简短的示例对您来说足够了解问题,请随意跳过第二个更长的示例。
MCVE:
JEditorPane pane = new JEditorPane("text/html", "");

pane.setText("<html>One line</html>");
System.out.println(pane.getPreferredSize());
pane.setText("<html>Line one<br />Line 2<br />Third line<br />Line four</html>");
System.out.println(pane.getPreferredSize());

Java 8 (1.8.0_131) 的输出结果为:
java.awt.Dimension[width=48,height=15]
java.awt.Dimension[width=57,height=60]

在Java 9(jdk-9.0.4)上:

java.awt.Dimension[width=49,height=15]
java.awt.Dimension[width=58,height=0]

在Java 9中,第一次设置文本时,首选高度会反映出来。之后的每一次都不会。
我已经搜索过是否能找到任何有关可能导致此问题的错误信息,但没有找到相关的内容。
问题:这是故意的(行为变化)吗?是一个错误吗?
public class TestJEditorPaneAsRenderer extends JFrame {

    public TestJEditorPaneAsRenderer() {
        super("Test JEditorPane");
        
        MyRenderer renderer = new MyRenderer();
        
        String html2 = "<html>one/2<br />two/2</html>";
        String html4 = "<html>one of four<br />two of four<br />"
                + "three of four<br />four of four</html>";
        JTable table = new JTable(new String[][] { { html2 }, { html4 } },
                                  new String[] { "Dummy col title" }) {
            @Override
            public TableCellRenderer getDefaultRenderer(Class<?> colType) {
                return renderer;
            }
        };
        
        add(table);
        setSize(100, 150);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }
    
    public static void main(String[] args) {
        System.out.println(System.getProperty("java.version"));
        new TestJEditorPaneAsRenderer().setVisible(true);
    }

}

class MyRenderer extends JEditorPane implements TableCellRenderer {

    public MyRenderer() {
        super("text/html", "");
    }
    
    @Override
    public Component getTableCellRendererComponent(
            JTable table, Object value, boolean selected, boolean hasFocus, int row, int col) {
        setText(value.toString());
        Dimension preferredSize = getPreferredSize();
        System.out.format("Row %d preferred size %s%n",row, preferredSize);
        if (preferredSize.height > 0 && table.getRowHeight(row) != preferredSize.height) {
            table.setRowHeight(row, preferredSize.height);
        }
        return this;
    }
    
}

Java 8的结果如预期所示:

Java 8 screen shot

Java 8的输出结果:
1.8.0_131
Row 0 preferred size java.awt.Dimension[width=32,height=30]
Row 1 preferred size java.awt.Dimension[width=72,height=60]
Row 0 preferred size java.awt.Dimension[width=32,height=30]
Row 1 preferred size java.awt.Dimension[width=72,height=60]

在Java 9上,第二行显示得不够高,因此大部分行都被隐藏起来。

Java 9 screen shot showing problem

Java 9的输出结果:
9.0.4
Row 0 preferred size java.awt.Dimension[width=33,height=30]
Row 1 preferred size java.awt.Dimension[width=73,height=0]
Row 0 preferred size java.awt.Dimension[width=33,height=0]
Row 1 preferred size java.awt.Dimension[width=73,height=0]

一个可能的解决方法是,如果我通过改变getDefaultRenderer()的代码来每次创建一个新的渲染器组件:
return new MyRenderer();

现在表格看起来和Java 8一样好。如果有必要的话,我想我们可以在我们的生产代码中使用类似的修复方法,但这似乎是一种浪费。特别是如果只是在即将发布的Java版本中撤销行为更改之前才需要。在这里,我会非常感谢您的建议。
2个回答

9
我曾遇到类似的问题,但是与JLabel有关。我的解决办法是设置JLabel的大小,这似乎强制组件重新计算其首选大小。
尝试这个:
JEditorPane pane = new JEditorPane("text/html", "");

pane.setText("<html>One line</html>");
System.out.println(pane.getPreferredSize());
pane.setText("<html>Line one<br />Line 2<br />Third line<br />Line four</html>");

// get width from initial preferred size, height should be much larger than necessary
pane.setSize(61, 1000);

System.out.println(pane.getPreferredSize());

在这种情况下,输出结果是:
java.awt.Dimension[width=53,height=25]
java.awt.Dimension[width=61,height=82]

注意:我的字体不同,因此我得到了不同的尺寸。
编辑:在较长的示例中,我将您的getTableCellRendererComponent更改为以下内容,并且它对我有效。我正在使用jdk9.0.4,64位。
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean selected, boolean hasFocus, int row, int col) {
    setText(value.toString());

    Dimension preferredSize = getPreferredSize();
    setSize(new Dimension(preferredSize.width, 1000));
    preferredSize = getPreferredSize();

    System.out.format("Row %d preferred size %s%n", row, preferredSize);
    if (preferredSize.height > 0 && table.getRowHeight(row) != preferredSize.height) {
        table.setRowHeight(row, preferredSize.height);
    }
    return this;
}

谢谢!它可以正常工作。在我们的生产代码中也是如此。可能有点小幅度的修改,但我肯定更喜欢这种方式而不是我之前的浪费性修复方案。 - Ole V.V.
1
看起来相关的副作用是 invalidate();,它被 setSize(…) 隐含调用了,因此只需调用 pane.invalidate(); 就应该具有相同的效果,并且感觉比修改大小要好些。 - Holger
3
@Holger 我已经尝试了多种组合的 invalidate();, validate();, revalidate(), ... 但无法改变首选高度。我认为在这个技巧中设置组件的大小是很重要的一部分。我还发现,用 setHeight 设置的高度不必很大,只需比初始首选高度大即可。 - MatheM

0

基于之前的建议和深入研究JDK源代码,我提出了一个更简单的解决方案:

setSize(new Dimention(0, 0))

根据BasicTextUI.getPreferredSize的实现,它会强制重新计算根视图的大小。

            } else if (d.width == 0 && d.height == 0) {
            // Probably haven't been layed out yet, force some sort of
            // initial sizing.
            rootView.setSize(Integer.MAX_VALUE, Integer.MAX_VALUE);
        }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接