Java Swing : Hint Text on a JTextField

As part of my ongoing adventure to try to master Java Swing, the next challenge I faced was to improve the usability of a form submission in my customer's thick client application. Several of the fields on the form were for date input, and the date format to be used was specificed in an ugly JLabel centered underneath the field. This arrangement distorted the vertical space between the fields giving the form a ragged and disorganized look.

I wanted to bring a little more of a web like experience to this thick client, so needed to replace this JLabel with something more elegant. I settled on a "ghost text" approach - were I would overlay a string on the text field when it was empty but would disappear when the field had focus or the user had entered input.

Not as easy as it sounds. Trying to manage the state of the text field with keyboard and click listeners was impossible and unwieldy. So I needed something else.

After some digging, I learned that Java has a whole hiearchy of ComponentUI classes which provide "look and feel" rendering for different components. Each Component (JTextField, JLabel, and so forth) has a corresponding ComponentUI that is associated with it that provides the actual appearance of the Component (default colors, borders, general appearance) separate from the mechanics of the Component (in the case of a JTextField, storing text, firing listeners, etc.)

The ComponentUI subsystem allows the look and feel of a Java application to be switched out based on what platform the program is running on. Instead of having a distinct hierarchy of components for each platform (Windows, MaxOS, default Java, etc.) which would make your GUI code non-portable, Swing simply has a separate implementation of a ComponentUI for each platform and swaps them out on a platform by platform basis.

Applying this to the problem at hand, I decided to write a custom UI for a JTextField that would handle the overlaying of the ghost text using the Graphics object.

Here my simple attempt:

import java.awt.Color;  
import java.awt.Graphics;  
import java.awt.event.FocusEvent;  
import java.awt.event.FocusListener;  
import javax.swing.plaf.basic.BasicTextFieldUI;  
import javax.swing.text.JTextComponent;

public class JTextFieldHintUI extends BasicTextFieldUI implements FocusListener {  
    private String hint;
    private Color  hintColor;

    public JTextFieldHintUI(String hint, Color hintColor) {
        this.hint = hint;
        this.hintColor = hintColor;
    }

    private void repaint() {
        if (getComponent() != null) {
            getComponent().repaint();
        }
    }

    @Override
    protected void paintSafely(Graphics g) {
        // Render the default text field UI
        super.paintSafely(g);
        // Render the hint text
        JTextComponent component = getComponent();
        if (component.getText().length() == 0 && !component.hasFocus()) {
            g.setColor(hintColor);
            int padding = (component.getHeight() - component.getFont().getSize()) / 2;
            int inset = 3;
            g.drawString(hint, inset, component.getHeight() - padding - inset);
        }
    }

    @Override
    public void focusGained(FocusEvent e) {
        repaint();
    }

    @Override
    public void focusLost(FocusEvent e) {
        repaint();
    }

    @Override
    public void installListeners() {
        super.installListeners();
        getComponent().addFocusListener(this);
    }

    @Override
    public void uninstallListeners() {
        super.uninstallListeners();
        getComponent().removeFocusListener(this);
    }
}

Code available at: https://gist.github.com/marlhammer/6532046#file-jtextfieldhintui-java

The code is actually very simple. There are two key parts:

  1. Implementation of BasicTextFieldUI which provides the paintSafely() method to actually do the UI rendering and the installListeners() / uninstallListeners() methods which gives you the hooks necessary to do (2) below.
  2. Implementation the FocusListener interface which provides the focusGained() and focusLost() methods allowing you to enable or disable the hint text depending on whether or not the component has focus. The installListeners() / uninstallListeners() methods from above then allows the JTextFieldHintUI to install itself as a focus listener on its associated component.

The paintSafely() method is very simple. It calls super.paintSafely() to do the default rendering and then uses the Graphics object to draw a string over top of the underlying text field (first checking that the user has not entered any text yet.)

Note that depending on your UI you may need to adjust the inset and possibly padding variables to align the hint text properly.

To apply this UI to a text field, simply use the setUI() method:

JTextField field = new JTextField();  
field.setUI(new JTextFieldHintUI("This is a hint...", Color.gray));  

And that is all there is to it!

Questions? Comments? Email me at: [email protected]

Happy coding!