Scott M
Scott M

Reputation: 744

How do I get a JLabel with multiline capability? Is HTML really the only way?

I think the answer is "you can't", but there's a lot I don't know about Swing. So:

I need to display read-only multiline text, character for character with no magical interpretation, in a rectangle just large enough to hold it. Being able to select a region (and copy from it) is a plus.

Failed approaches so far:

JLabel("a\nb"); simply doesn't work, and I can't imagine why because this should be the only correct answer. JLabel is documented to display the text it is handed. But not newline, apparently.

JLabel has another hideous issue: if text happens to start with <html>, it again doesn't display the text it is handed - it reinterprets it as HTML. Not only does the documentation make no mention this, but now I have the problem that if users are allowed to input text that shows up in a JLabel (and they are), they can stick in formatting tags and cause all sorts of trickery (and they will).

I don't see a way to turn this off; so to get an accurate display of the given text I'd have to pre-cook the text to do full HTML quoting, plus changing \n to <br>, and then wrap it all in <html>...</html>. I can't find anything in core Java that does the quoting - apparently you have to write your own or rely on other libraries like apache? Recommended method for escaping HTML in Java gives a lot of reasons not to rely on 3rd party libraries here. And now my java code is going to contain an HTML layout engine just so I can display text reliably? I very much would like to avoid this undocumented and fragile hack.

JTextArea will handle multiline text, and it can be set to read-only. But I can't see how to tell it to shrink down to the size needed to hold just the text. (JLabel at least did that right.[Edit: or not - I reverted to JLabel and now the labels are centering in their row or expanding to full size - I'm clearly missing a setting somewhere]) I'd think there must be a way to tell it to stop expanding to fill rows, but how do I? setSize(1,1) doesn't do it. I feel like the right answer is down this path but I need a pointer to getting it to auto resize to minimum dimensions.

Or is there some better component for this? Is there a way to render a text string as a minimally sized image? I could display that in a JLabel...

I'm also trying to avoid scanning all text and making up a vertical box of left justified JLabels, one per line - probably possible but definitely more complex than I want when there's (maybe?) a simpler solution out there.

Note that I can't hardcode the size of any component. Component descriptions are generated by a server and shipped to the application, which generates the widgets based on the descriptions on the fly, and adds them to Box containers. The server can't do calculations in pixels; I need to rely on Swing to do layout simply based on the order of things in Boxes. And in most all cases that means widgets have to take up their minimum possible size.

How do I?

As directed I tried this. Only one pane appeared, and it had no border so I can't see how much space it would have taken up.

import javax.swing.JFrame;
import javax.swing.JTextPane;
import javax.swing.BorderFactory; 
import javax.swing.border.Border;

public class MainTextPane {
    public static void main(final String[] args) {
        Border loweredbevel = BorderFactory.createLoweredBevelBorder();

        final JTextPane pane = new JTextPane();
        pane.setText("Hello, this is a\ntwo lined string!");
        final JTextPane pane2 = new JTextPane();
        pane2.setText("Next!");
        pane.setBorder(loweredbevel); //no effect
        pane2.setBorder(loweredbevel); //no effect


        final JFrame frame = new JFrame("TextPane with new lines.");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(pane);
        frame.getContentPane().add(pane2); //clobbered previous pane
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }
}

Upvotes: 3

Views: 158

Answers (1)

gthanop
gthanop

Reputation: 3311

Just use a JTextPane which is a JEditorPane. If you want the preferred size, just call getPreferredSize() any time (after setting the text). You can set it to editable or not, and copy-paste from it easily, and it will handle new lines. It does not need to be complex if you use it the simple way:

import javax.swing.JFrame;
import javax.swing.JTextPane;

public class MainTextPane {
    public static void main(final String[] args) {
        final JTextPane pane = new JTextPane();
        pane.setText("Hello, this is a\nnew lined string! Hope JTextPane is what we are looking for here.");

        final JFrame frame = new JFrame("TextPane with new lines.");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(pane);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }
}

If you are going to load it with arbitrary text though, I would suggest using it inside a JScrollPane, and setting the preferred size of the JScrollPane according to your needs.

Also tested JEditorPane and it is not going to be as simple as JTextPane.

If you have more requirements, let us know in the comments or update your question.

Edit 1

Concerning the code you provided, you see that the first pane clobbers the second one, because you added them both to the content pane, without setting the LayoutManager. Try setting the LayoutManager to FlowLayout for example (which will place the two panes next to each other).

As per the bevel border, I can see it clearly, but it is indeed a bit thin, so that's probably why you did not see it. I ran your provided code also, and it is visible, but not easily. Anyway, try this code instead:

import java.awt.FlowLayout;
import javax.swing.JFrame;
import javax.swing.JTextPane;
import javax.swing.BorderFactory; 
import javax.swing.border.Border;

public class MainTextPane {
    public static void main(final String[] args) {
        Border loweredbevel = BorderFactory.createLoweredBevelBorder();

        final JTextPane pane = new JTextPane();
        pane.setText("Hello, this is a\ntwo lined string!");
        final JTextPane pane2 = new JTextPane();
        pane2.setText("Next!");
        pane.setBorder(loweredbevel);
        pane2.setBorder(loweredbevel);


        final JFrame frame = new JFrame("TextPane with new lines.");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().setLayout(new FlowLayout()); //Added this line of code...
        frame.getContentPane().add(pane);
        frame.getContentPane().add(pane2);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }
}

Upvotes: 1

Related Questions