JEditorPane.getPreferredSize not always working in Java 9?
Asked Answered
A

2

12

This question is about different behaviour of JEditorPane in Java 8 and Java 9. I’d like to know if others have experienced the same, whether it could be a bug in Java 9, and if possible have your input to how to handle it in Java 9.

Context: In our (age-old) code base we are using a subclass of JTable, and for rendering multi-line HTML in one of the columns we are using a subclass of JEditorPane. We are using JEditorPane.getPreferredSize() for determining the height of the content and using it for setting the height of the table row. It’s been working well for many years. It doesn’t work in Java 9; the rows are displayed just 10 pixels high. Seen both on Windows and Mac.

I should like to show you two code examples. If the first and shorter one suffices for you, feel free to skip the second and longer one.

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());

Output in Java 8 (1.8.0_131):

java.awt.Dimension[width=48,height=15]
java.awt.Dimension[width=57,height=60]

And on Java 9 (jdk-9.0.4):

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

In Java 9 the first time I set the text, the preferred height reflects it. Every subsequent time it doesn’t.

I have searched to see if I could find any information on a bug that might account for this, did not find anything relevant.

Question: Is this intended (change of) behaviour? Is it a bug??

Longer example

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;
    }
    
}

Result on Java 8 is as expected:

Java 8 screen shot

Output from 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]

On Java 9 the second row is not shown high enough so most of the lines are hidden:

Java 9 screen shot showing problem

Output from 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]

A possible fix is if I create a new renderer component each time by changing the body of getDefaultRenderer() to:

return new MyRenderer();

Now the table looks good as on Java 8. If necessary I suppose we could live with a similar fix in our production code, but it seems quite a waste. Especially if it’s only necessary until the behaviour change is reverted in a coming Java version. I’d be grateful for your suggestions here.

Arrow answered 14/3, 2018 at 8:44 Comment(0)
E
9

I have faced similar problem, but with JLabel. My solution was to set the size of the JLabel which seems to force the component to recalculate its preferred size.

Try this:

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());

In this casse the output is

java.awt.Dimension[width=53,height=25]
java.awt.Dimension[width=61,height=82]

Note: my fonts are different, that is why I get different dimensions.

Edit: I changed your getTableCellRendererComponent in the longer example like this and it is working for me. I am using jdk9.0.4, 64 bit.

@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;
}
Epistemic answered 14/3, 2018 at 9:11 Comment(3)
Thanks! That works. Also in our production code. A bit of a hack, maybe, but I certainly prefer it over the wasteful fix I had.Arrow
That looks like the relevant side effect is the invalidate(); call implied by setSize(…), so just calling pane.invalidate(); should have the same effect and feel less nasty than modifying the size.Endblown
@Endblown I have tried various combinations of invalidate();, validate();, revalidate(), ... and it doesn't change the preferred height. I think that setting the size of the component is important part in this hack. I've also found out that height set with setHeight doesn't have to be large it just needs to be larger than initial preferred height.Epistemic
F
0

Based on previous suggestion and digging in JDK sources I'm suggesting a little bit simpler solution:

setSize(new Dimention(0, 0))

Based on BasicTextUI.getPreferredSize implementation it forces to recalculate root view size

            } 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);
        }
Farrier answered 17/1, 2022 at 20:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.