Samuel Sjöberg's weblog

Skip to navigation

Multiline labels in Swing with drop shadow

Continuing the Swing trail, I'm presenting my solution for multiline text labels in Swing. I wrote this during this spring and had simply forgotten about it.

The problem with JLabel is the way in which it supports multiple lines (with wrapping) and centered text. You need to use HTML to activate this functionality. Enabling HTML is pretty simple:

JLabel label = new JLabel(
        "<html>Text that'll wrap if necessary");

To center the text, you'll add the <center> tag to your string. Not pretty, but it gets the job done.

This approach has two drawbacks. First, it will bloat your code or resource strings since you'll need to add the HTML tags somewhere. Second, you'll loose control of how text is being drawn once HTML mode is set. When you use HTML in a Swing component, it will be rendered as a View (see javax.swing.plaf.basic.BasicHTML for implementation details).

It's all about text effects

The motivation behind implementing multiline support for labels is:

To be honest, my main motivation was that I needed drop shadow on the text in labels, like on the desktop in OS X and Windows. I tried two solutions, the obvious one with a offscreen image that you apply a gaussian filter on and one that simply painted the text in various offset positions and alpha blends until it looked kind of like a drop shadow.

I'd read about this second approach in the book Filthy Rich Clients where an example produces a text glow effect in this way. After playing around with this approach I decided to go for it. The results looked better than with a gausiann blur and it felt like a bad idea to have an offscreen image for each drop shadow label in the GUI.

I first implemented the solution as an UI delegate that painted my custom text effect. Happy to see it work, I quickly realized I wanted this effect in combination with multiline support.

Introducing the general purpose MultiLineLabelUI

A couple (okay, many) hours later, I'd written a general purpose MultiLineLabelUI that enabled any JLabel to support multiline text with line wrapping and preserving hard line breaks. On top of that, I'd made the MultiLineShadowUI, enabling text drop shadow in any JLabel.

To enable support for mulitple lines, simply do this:

JLabel label = new JLabel("Text that'll wrap if necessary");
label.setUI(MultiLineLabelUI.labelUI);

To use the drop shadow, instead to this:

label.setUI(MultiLineShadowUI.labelUI);

Details, details, details...

The multiline labels will only wrap the text when needed. It can be useful to know that the preferred height reported by the UI delegate will be the height required to render lines without any additional wrapping (i.e., hard wraps, inserted by the user, is accounted for). It is not until the label is painted, and the actual width budget is known, that the line wrapping can be correctly computed. The wrapped lines are stored as a client property on the label to avoid unnecessary calculations.

The label listens to dimension changes and recalculate line breaks as the component is resized. Resizing the window on OS X will for example rearrange the line wraps (note: this will not work on windows since component bounds don't seem to be updated correctly when the window is resized).

Improving alignment in the JLabel

To enable better support for text alignment in the JLabel I've created the MultiLineLabel class. It's using the MultiLineLabelUI by default and adds the properties verticalTextAlignment and horizontalTextAlignment.

This makes it possible (and really easy) to have a multiline label that's for example centered at the bottom of the component.

MultiLineLabel label = new MultiLineLabel(
        "Text that'll wrap if necessary");
label.setHorizontalTextAlignment(JLabel.CENTER);
label.setVerticalTextAlignment(JLabel.BOTTOM);

Demo and source

Feel free to try out the Java Web Start demo. It requires Java 1.5+.

Here's the binary and source distribution.

The code is released under the MIT-license, so you're basically free to use it in any ways you find useful.

Reader comments

  1. Really cool component. However, here's a bug I demonstrated with the code below. As you keep pressing the button the panel keeps growing. I tracked it down to MultiLaneLabelUI.getPreferredSize(). You remove the insets of parent component from parent component's size, but it seems like it's not enough. Especially when I'm using MultiLineLabel inside a MigLayout with two columns that want to grow equally, the column with the label keeps growing as I revalidate().

    import java.awt.Color;
    import java.awt.FlowLayout;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;

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

    import sas.swing.MultiLineLabel;

    public class MultiLineBug {

    public static void main(String[] args) {
    JFrame frame = new JFrame();
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.getContentPane().setLayout(new FlowLayout());

    final JPanel pnl = new JPanel(new FlowLayout());
    frame.add(pnl);

    MultiLineLabel lbl = new MultiLineLabel("aaa");
    lbl.setBorder(BorderFactory.createLineBorder(Color.BLACK));
    pnl.add(lbl);

    JButton btn = new JButton("go");
    frame.add(btn);
    btn.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent e) {
    pnl.revalidate();
    }
    });

    frame.pack();
    frame.setVisible(true);
    }

    }

    26th February 2010, 04:25 CET. 
  2. My temporary solution is to return new Dimension(0, 0); from MultiLineLabelUI.getPreferredSize() and put this in a container that stretches it (such as "grow" in MigLayout). It works pretty fine this way.

    26th February 2010, 06:00 CET. 
  3. I too have problems getting MLL to work properly, if I have a label that is too big, it does wrap text until I call revalidate() repaint() (or setText()) @Gazi, I could not get your suggestion to work,

    29th June 2010, 19:45 CET. 

Pages linking to this entry

Pingback is enabled on all archived entries. Read more about pingback in the Pingback 1.0 Specification.

About this post

Created 19th October 2009 20:37 CET. Filed under Java.

3 Comments
0 Pingbacks