4

I would like a different way to create a multi-colored JLabel. (Multi-colored = parts of the text in different foreground-colors)

The only solution I found so far (and which I currently use), is setting the text in html. But I'm having problems with that...

When the LayoutManager decides that the JLabel should be narrowed, with a plain-text in a JLabel, the text gets kind of cropped, and "..." is added. (e.g.: "My Long Text" -> becomes: "My Long T...")

With html inside a JLabel, the text is wrapped somewhere on a space-character, leaving the rest of outside the drawable area, and invisible as the JLabel's height is unchanged. (e.g.: "My Long Text" -> becomes: "My Long")

In my case the JLabel is rendered in a JTable, which gets resized by the user, not to mention in different screen resolutions.

I tried adding a "nowrap" attribute or a ""-tag to the html, but it looks like this is ignored.

Leaving me -I think- with one solution: painting the label myself. Or not? Any suggestions? Examples?

Thank you.

Here's a very simple example: Try to resize this panel horizontally, and see what happens with the text inside both JLabel's...

(there's no indication for the user, that the text of the second JLabel is not the complete content)

-> In the example, the JLabel's height changes, but when rendered inside the framework's JTable, the height of the rows doesn't change and I don't want it to change. Without the use of HTML it doesn't change the height either...


import java.awt.BorderLayout;

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;

public class MultiJLabel
    extends JFrame
    {
    public MultiJLabel()
        {
        super("Multi-colored JLabel test");

        JPanel pnl = new JPanel();
        pnl.setLayout(new BorderLayout());
        pnl.add(new JLabel("This is a test of My Long Text"), BorderLayout.NORTH);
        pnl.add(new JLabel("<html>This is a test of <font color='#ffbebe'>My Long Text</font></html>"), BorderLayout.SOUTH);

        this.getContentPane().add(pnl);
        this.pack();

        this.setDefaultCloseOperation(EXIT_ON_CLOSE);
        this.setVisible(true);
        }

    public static void main(String[] args)
        {
        new MultiJLabel();
        }
    }

Here's a picture of the original problem, where our users are not aware that the client's Order Number is not what the grid is showing, because this column has HTML-formatted text to show multi-colors.

c00lstef
  • 69
  • 5
  • Could you show a [mcve]. HTML is definitly the simplest. Here your problem is not about multi-color but about wrapped text. First, what are you expecting if your text is too long ? – AxelH Oct 03 '17 at 10:32
  • 1
    @all I vote for reopening, the duplicate is about a `JLabel` inside a `JList`, this is different that having different color inside the SAME `JLabel`. Note that there is a hidden question here, "if HTML is a solution, how to make the wrap work properly ?". – AxelH Oct 03 '17 at 11:02
  • @AxelH thank you for your quick response. I edited my initial question with a minimal example (because the original problem occured within a large framework). What I expect is some sort of indication, telling the user that the text shown is not complete. – c00lstef Oct 03 '17 at 11:41

2 Answers2

3

To prevent line wrapping when using html-text in JLabels, wrap the text in nobr (no-break) tags:

new JLabel("<html><nobr>This is a test of <font color='#ffbebe'>My Long Text</font></nobr></html>")

When using the nobr-tags, the line will not be wrapped, but it won't be truncated as well. So, there won't be any ellipsis (...) at the end of the shown text, but it will just cut off.

The missing ... might actually be of advantage in a table as there is no additional width lost by the ellipsis, and thus more content shown. But to the user it might be less obvious that there is more content without them.

cello
  • 5,356
  • 3
  • 23
  • 28
  • you should let the tooltip show the complete contents of the cell when not all content can be shown, I think ... is preferred to know if all is shown or not – Jurgen De Landsheer Oct 03 '17 at 14:52
3

Thank you all for your comments, but I was impatient and created my own JLabel. I know it may be a poor programmed version, but it works for me... You can test it by altering the above example with:

JMultiColorLabel lbl = new JMultiColorLabel("This is a test of My Long Text");
lbl.setColors(new int[]{10,15}, new Color[]{Color.RED,Color.BLUE});
lbl.setPreferredSize(new Dimension(200,20));
pnl.add(lbl, BorderLayout.SOUTH);

And use this class:

import java.awt.Color;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.RenderingHints;
import java.util.HashMap;

import javax.swing.JLabel;

public class JMultiColorLabel
    extends JLabel
    {
    private static final String             STRING_OVERFLOW     = "...";

    private HashMap<Integer, Color>     extraColors             = new HashMap<Integer, Color>();

    public JMultiColorLabel(String text)
        {
        super(text);
        }

    public void setColors(int[] indices, Color[] colors)
        {
        for (int i = 0; i < indices.length; i++)
            this.extraColors.put(indices[i], colors[i]);
        }

    protected void paintComponent(Graphics g)
        {
        // Get text-contents of Label
        String text = this.getText();

        // No text in the JLabel? -> No risk: super
        if (text == null || text.length() == 0)
            {
            super.paintComponent(g);
            return;
            }

        // Content Array of characters to paint
        char[] chars = text.toCharArray();

        // Draw nice and smooth
        Graphics2D g2d = (Graphics2D)g;
        g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);

         // Draw background
        if (this.isOpaque())
          {
          g2d.setColor(this.getBackground());
          g2d.fillRect(0, 0, this.getWidth(), this.getHeight());
          }

        // FontMetrics to calculate widths and height
        FontMetrics fm = g2d.getFontMetrics();

        // Available space
        Insets ins = this.getInsets();
        int maxSpace = this.getWidth()-(ins.left+ins.right);
        boolean overflow = (fm.stringWidth(text) > maxSpace);

        // Starting offset
        int offset = ins.left+1;

        // The start Color is the default
        g2d.setColor(this.getForeground());

        // Loop over characters
        for (int i = 0; i < chars.length; i++)
            {
                // Switch Color?
            if (this.extraColors.containsKey(i))
                g2d.setColor(this.extraColors.get(i));

                // Check if we still have enough room for this character
            if (overflow && offset >= (maxSpace-fm.stringWidth(STRING_OVERFLOW)))
                { // Draw overflow and stop painting
                g2d.drawString(STRING_OVERFLOW, offset, (fm.getHeight()+ins.top));
                return;
                }
            else // We have the space -> Draw the character
                g2d.drawString(String.valueOf(chars[i]), offset, (fm.getHeight()+ins.top));

                // Move cursor to the next horizontal position
            offset += fm.charWidth(chars[i]);
            }
        }
    }
c00lstef
  • 69
  • 5