49

I would like to add a hint value to my javax.swing.JTextField. It should look like Firefox rendering of <input type="text" title="bla">. This creates an edit field with the text 'bla' in the background. If the textbox has focus the title-text disappears and just reappears if the user leaves the editbox without text.

Is there a (free) swing component that does something like this?

Yugerten
  • 878
  • 1
  • 11
  • 30
Wienczny
  • 3,958
  • 4
  • 30
  • 35

9 Answers9

63

You could create your own:

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import javax.swing.*;

public class Main {

  public static void main(String[] args) {

    final JFrame frame = new JFrame();

    frame.setLayout(new BorderLayout());

    final JTextField textFieldA = new HintTextField("A hint here");
    final JTextField textFieldB = new HintTextField("Another hint here");

    frame.add(textFieldA, BorderLayout.NORTH);
    frame.add(textFieldB, BorderLayout.CENTER);
    JButton btnGetText = new JButton("Get text");

    btnGetText.addActionListener(new ActionListener() {
      @Override
      public void actionPerformed(ActionEvent e) {
        String message = String.format("textFieldA='%s', textFieldB='%s'",
            textFieldA.getText(), textFieldB.getText());
        JOptionPane.showMessageDialog(frame, message);
      }
    });

    frame.add(btnGetText, BorderLayout.SOUTH);
    frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    frame.setVisible(true);
    frame.pack();
  }
}

class HintTextField extends JTextField implements FocusListener {

  private final String hint;
  private boolean showingHint;

  public HintTextField(final String hint) {
    super(hint);
    this.hint = hint;
    this.showingHint = true;
    super.addFocusListener(this);
  }

  @Override
  public void focusGained(FocusEvent e) {
    if(this.getText().isEmpty()) {
      super.setText("");
      showingHint = false;
    }
  }
  @Override
  public void focusLost(FocusEvent e) {
    if(this.getText().isEmpty()) {
      super.setText(hint);
      showingHint = true;
    }
  }

  @Override
  public String getText() {
    return showingHint ? "" : super.getText();
  }
}

If you're still on Java 1.5, replace the this.getText().isEmpty() with this.getText().length() == 0.

Bart Kiers
  • 166,582
  • 36
  • 299
  • 288
  • This solution is nice, too. You would have to overload getText() and filter the hint-text. – Wienczny Nov 15 '09 at 22:45
  • 1
    I would rather use a flag in getText() which indicates if a hint is currently shown or not. Otherwise if the user happens to enter the hint text, getText() would return an empty string, too. – Michael Jess Aug 12 '13 at 23:47
  • @MichaelJess, yes, you're right. I edited my example to include a boolean flag instead. – Bart Kiers Aug 13 '13 at 11:22
  • @BartKiers Could you tell me how the getText() Method in HintTextField works? I don't understand the return ^^ – Gerret Aug 21 '13 at 07:59
  • 1
    @Gerret, if the text field is displaying the "hint", it returns an empty string, otherwise it returns the actual contents of the text field (through `super.getText()`). If you're unsure about the `... ? ... : ...` construct, it's called a ternary operator (ternary if): http://en.wikipedia.org/wiki/%3F: – Bart Kiers Aug 21 '13 at 11:12
22

Here is a simple way that looks good in any L&F:

public class HintTextField extends JTextField {
    public HintTextField(String hint) {
        _hint = hint;
    }
    @Override
    public void paint(Graphics g) {
        super.paint(g);
        if (getText().length() == 0) {
            int h = getHeight();
            ((Graphics2D)g).setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
            Insets ins = getInsets();
            FontMetrics fm = g.getFontMetrics();
            int c0 = getBackground().getRGB();
            int c1 = getForeground().getRGB();
            int m = 0xfefefefe;
            int c2 = ((c0 & m) >>> 1) + ((c1 & m) >>> 1);
            g.setColor(new Color(c2, true));
            g.drawString(_hint, ins.left, h / 2 + fm.getAscent() / 2 - 2);
        }
    }
    private final String _hint;
}
Adam Gawne-Cain
  • 247
  • 2
  • 2
  • 2
    I like this because the hint ability becomes a feature of the `JTextField` rather than an external add-on (which is the case for most other solutions I've seen). But maybe you should explain a bit more what the code does and why it works. Are there any side effects ? What guarantees it will paint the hint in the font that the field uses? – peterh Nov 13 '14 at 20:43
  • Just amazing. This should be the selected answer. I had so much trouble with other solutions when using Synthetica LAF. Added italic fonts too by simply doing `g.setFont(g.getFont().deriveFont(Font.ITALIC));` – m4heshd Jul 17 '19 at 10:52
  • Explanation: The hint text color should be halfway between the foreground and background colors so it is always gently visible. The variables c0,c1,m,c2 calculate the halfway color's ARGB fields simultaneously without overflowing 8 bits. The font metric ascent is used to center the text vertically. Swing sets the Graphics' font to match the JTextField's font property before calling the "paint" method, so the hint font will match the JTextField's font. Don't think there are any side effects because Swing discards the Graphics after painting. – Adam Gawne-Cain Aug 06 '19 at 15:55
  • I like this. It's one of the few proposed solutions I've found that is not buggy. I've posted a fully functioning example separately on this question. Only difference: I found using `getHeight() - fm.getDescent() - ins.bottom` gave a slightly better y-coordinate. – Dave The Dane Apr 10 '20 at 12:48
  • @DaveTheDane could You please post the complete expression considering Your suggestion, please? Thank you so much. Is it something like `g.drawString(hint, ins.left, h - fm.getDescent() - ins.bottom)` ? – Marco Ottina Oct 07 '20 at 09:08
  • 1
    @MarcoOttina please see my Posting below from Apr 10, 2020 @ 12:41 Does that help? – Dave The Dane Nov 09 '20 at 10:09
17

Here is a single class copy/paste solution:

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 HintTextFieldUI extends BasicTextFieldUI implements FocusListener {

    private String hint;
    private boolean hideOnFocus;
    private Color color;

    public Color getColor() {
        return color;
    }

    public void setColor(Color color) {
        this.color = color;
        repaint();
    }

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

    public boolean isHideOnFocus() {
        return hideOnFocus;
    }

    public void setHideOnFocus(boolean hideOnFocus) {
        this.hideOnFocus = hideOnFocus;
        repaint();
    }

    public String getHint() {
        return hint;
    }

    public void setHint(String hint) {
        this.hint = hint;
        repaint();
    }
    public HintTextFieldUI(String hint) {
        this(hint,false);
    }

    public HintTextFieldUI(String hint, boolean hideOnFocus) {
        this(hint,hideOnFocus, null);
    }

    public HintTextFieldUI(String hint, boolean hideOnFocus, Color color) {
        this.hint = hint;
        this.hideOnFocus = hideOnFocus;
        this.color = color;
    }

    @Override
    protected void paintSafely(Graphics g) {
        super.paintSafely(g);
        JTextComponent comp = getComponent();
        if(hint!=null && comp.getText().length() == 0 && (!(hideOnFocus && comp.hasFocus()))){
            if(color != null) {
                g.setColor(color);
            } else {
                g.setColor(comp.getForeground().brighter().brighter().brighter());              
            }
            int padding = (comp.getHeight() - comp.getFont().getSize())/2;
            g.drawString(hint, 2, comp.getHeight()-padding-1);          
        }
    }

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

    }

    @Override
    public void focusLost(FocusEvent e) {
        if(hideOnFocus) repaint();
    }
    @Override
    protected void installListeners() {
        super.installListeners();
        getComponent().addFocusListener(this);
    }
    @Override
    protected void uninstallListeners() {
        super.uninstallListeners();
        getComponent().removeFocusListener(this);
    }
}

Use it like this:

TextField field = new JTextField();
field.setUI(new HintTextFieldUI("Search", true));

Note that it is happening in protected void paintSafely(Graphics g).

Ryan Wersal
  • 3,210
  • 1
  • 20
  • 29
culmat
  • 1,156
  • 11
  • 12
  • How might one make it so that the hint is italicized but the user entered text is not? – Jefferson Hudson Mar 22 '13 at 18:19
  • In the `paintSafely()`, you would have to call `setFont(fontHint)` or `setFont(fontOriginal)` according to whether `getText().isEmpty()`, where `fontHint` would have been derived from the original `getFont()` in constructor. I also had to override `setFont()` to regenerate it: `fontOriginal = getFont(); hintFont = new Font(fontOriginal.getName(), fontOriginal.getStyle() | Font.ITALIC, fontOriginal.getSize());` Note that I didn't use `font.deriveFont()` because it seems that it eats up a lot of memory and never gives it back... – Matthieu Jun 15 '13 at 08:16
  • I made a little modification and worked nicely. Thanks. – Xam Jul 16 '18 at 02:39
15

Take a look at this one: http://code.google.com/p/xswingx/

It is not very difficult to implement it by yourself, btw. A couple of listeners and custom renderer and voila.

Dmitry
  • 3,740
  • 15
  • 17
4

For any Swing component (that is, anything that extends JComponent), you can call the setToolTipText(String) method.

For more information, reference the following links:

Matt
  • 43,482
  • 6
  • 101
  • 102
  • 7
    I think he is not talking about tooltip, he wants something like "Type here to search" grey text which disappears when one starts typing – Dmitry Nov 15 '09 at 22:04
  • Hmm, you might be right, but that fit with the HTML he provided. OP.. if you're looking to clear/set default text when the input is focused/blurred, look into FocusListener: http://java.sun.com/docs/books/tutorial/uiswing/events/focuslistener.html – Matt Nov 15 '09 at 22:08
3

Have look at WebLookAndFeel at https://github.com/mgarin/weblaf/

WebTextField txtName = new com.alee.laf.text.WebTextField();

txtName.setHideInputPromptOnFocus(false);

txtName.setInputPrompt("Name");

txtName.setInputPromptFont(new java.awt.Font("Ubuntu", 0, 18));

txtName.setInputPromptForeground(new java.awt.Color(102, 102, 102));

txtName.setInputPromptPosition(0);
user2473015
  • 1,392
  • 3
  • 22
  • 47
2

If you still look for a solution, here's one that combined other answers (Bart Kiers and culmat) for your reference:

import javax.swing.*;
import javax.swing.text.JTextComponent;
import java.awt.*;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;


public class HintTextField extends JTextField implements FocusListener
{

    private String hint;

    public HintTextField ()
    {
        this("");
    }

    public HintTextField(final String hint)
    {
        setHint(hint);
        super.addFocusListener(this);
    }

    public void setHint(String hint)
    {
        this.hint = hint;
        setUI(new HintTextFieldUI(hint, true));
        //setText(this.hint);
    }


    public void focusGained(FocusEvent e)
    {
        if(this.getText().length() == 0)
        {
            super.setText("");
        }
    }

    public void focusLost(FocusEvent e)
    {
        if(this.getText().length() == 0)
        {
            setHint(hint);
        }
    }

    public String getText()
    {
        String typed = super.getText();
        return typed.equals(hint)?"":typed;
    }
}

class HintTextFieldUI extends javax.swing.plaf.basic.BasicTextFieldUI implements FocusListener
{

    private String hint;
    private boolean hideOnFocus;
    private Color color;

    public Color getColor()
    {
        return color;
    }

    public void setColor(Color color)
    {
        this.color = color;
        repaint();
    }

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

    public boolean isHideOnFocus()
    {
        return hideOnFocus;
    }

    public void setHideOnFocus(boolean hideOnFocus)
    {
        this.hideOnFocus = hideOnFocus;
        repaint();
    }

    public String getHint()
    {
        return hint;
    }

    public void setHint(String hint)
    {
        this.hint = hint;
        repaint();
    }

    public HintTextFieldUI(String hint)
    {
        this(hint, false);
    }

    public HintTextFieldUI(String hint, boolean hideOnFocus)
    {
        this(hint, hideOnFocus, null);
    }

    public HintTextFieldUI(String hint, boolean hideOnFocus, Color color)
    {
        this.hint = hint;
        this.hideOnFocus = hideOnFocus;
        this.color = color;
    }


    protected void paintSafely(Graphics g)
    {
        super.paintSafely(g);
        JTextComponent comp = getComponent();
        if(hint != null && comp.getText().length() == 0 && (!(hideOnFocus && comp.hasFocus())))
        {
            if(color != null)
            {
                g.setColor(color);
            }
            else
            {
                g.setColor(Color.gray);
            }
            int padding = (comp.getHeight() - comp.getFont().getSize()) / 2;
            g.drawString(hint, 5, comp.getHeight() - padding - 1);
        }
    }


    public void focusGained(FocusEvent e)
    {
        if(hideOnFocus) repaint();

    }


    public void focusLost(FocusEvent e)
    {
        if(hideOnFocus) repaint();
    }

    protected void installListeners()
    {
        super.installListeners();
        getComponent().addFocusListener(this);
    }

    protected void uninstallListeners()
    {
        super.uninstallListeners();
        getComponent().removeFocusListener(this);
    }
}



Usage:
HintTextField field = new HintTextField();
field.setHint("Here's a hint");
user814168
  • 131
  • 1
  • 3
  • Nice, but please compact the "single code line" and brackets in just one line, just to make it shorter and easier to read (and open brackets not on new line: it help to figure out the scope without looking for brackets, but just reading the text. The color will help to spot where a function/scope begins). – Marco Ottina Oct 07 '20 at 09:34
2

This can be achieved by using a focus listener to update the text field content.

Make the class implement the focus listener interface:

class YourClass implements FocusListener

Add a method to catch when focus is gained that blanks the field:

public void focusGained(FocusEvent e) {
    if(JTextField1.getText().equals("Username")) {
        JTextField1.setText("");
    }
}

Add a method to catch when focus is lost to redisplay the default entry if the field was blank:

public void focusLost(FocusEvent e) {
    if(JTextField1.getText().equals("")) {
        JTextField1.setText("Username");
        // you should prevent the form from being processed in this state
        // as it will literally contain "Username" for the username
    }
}

Register your class as the focus listener for text field:

textField.addFocusListener(this);

Learn more at How to Write a Focus Listener in the Java Tutorials.

Alain O'Dea
  • 21,033
  • 1
  • 58
  • 84
  • 1
    First of all, welcome to StackOverflow. Following the advices of the community, it is recommended to include context to your source code in order to clarify better your reponse. Please check the documentation [on how to write a good question](https://stackoverflow.com/help/how-to-ask) – Carlos Cavero Mar 07 '19 at 15:07
0

Here is a fully working example based on Adam Gawne-Cain's earlier Posting. His solution is simple and actually works exceptionally well.

I've used the following text in a Grid of multiple Fields:

H__|__WWW__+__XXXX__+__WWW__|__H

this makes it possible to easily verify the x/y alignment of the hinted text.

A couple of observations:
- there are any number of solutions out there, but many only work superficially and/or are buggy
- sun.tools.jconsole.ThreadTab.PromptingTextField is a simple solution, but it only shows the prompting text when the Field doesn't have the focus & it's private, but nothing a little cut-and-paste won't fix.

The following works on JDK 8 and upwards:

import java.awt.*;
import java.util.stream.*;
import javax.swing.*;
/**
 * @author DaveTheDane, based on a suggestion from Adam Gawne-Cain
 */
public final class JTextFieldPromptExample extends JFrame {

    private static JTextField newPromptedJTextField (final String text, final String prompt) {

        final String promptPossiblyNullButNeverWhitespace = prompt == null || prompt.trim().isEmpty()  ?  null  :  prompt;

        return new JTextField(text) {
            @Override
            public void paintComponent(final Graphics    USE_g2d_INSTEAD) {
                final Graphics2D     g2d =  (Graphics2D) USE_g2d_INSTEAD;

                super.paintComponent(g2d);

//              System.out.println("Paint.: " + g2d);

                if (getText().isEmpty()
                &&  promptPossiblyNullButNeverWhitespace != null) {
                    g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);

                    final Insets      ins = getInsets();
                    final FontMetrics fm  = g2d.getFontMetrics();

                    final int cB = getBackground().getRGB();
                    final int cF = getForeground().getRGB();
                    final int m  = 0xfefefefe;
                    final int c2 = ((cB & m) >>> 1) + ((cF & m) >>> 1); // "for X in (A, R, G, B) {Xnew = (Xb + Xf) / 2}"
                    /*
                     * The hint text color should be halfway between the foreground and background colors so it is always gently visible.
                     * The variables c0,c1,m,c2 calculate the halfway color's ARGB fields simultaneously without overflowing 8 bits.
                     * Swing sets the Graphics' font to match the JTextField's font property before calling the "paint" method,
                     * so the hint font will match the JTextField's font.
                     * Don't think there are any side effects because Swing discards the Graphics after painting.
                     * Adam Gawne-Cain, Aug 6 2019 at 15:55
                     */
                    g2d.setColor(new Color(c2, true));
                    g2d.drawString(promptPossiblyNullButNeverWhitespace, ins.left, getHeight() - fm.getDescent() - ins.bottom);
                    /*
                     * y Coordinate based on Descent & Bottom-inset seems to align Text spot-on.
                     * DaveTheDane, Apr 10 2020
                     */
                }
            }
        };
    }

    private static final GridBagConstraints GBC_LEFT  = new GridBagConstraints();
    private static final GridBagConstraints GBC_RIGHT = new GridBagConstraints();
    /**/    static {
        GBC_LEFT .anchor    = GridBagConstraints.LINE_START;
        GBC_LEFT .fill      = GridBagConstraints.HORIZONTAL;
        GBC_LEFT .insets    = new Insets(8, 8, 0, 0);

        GBC_RIGHT.gridwidth = GridBagConstraints.REMAINDER;
        GBC_RIGHT.fill      = GridBagConstraints.HORIZONTAL;
        GBC_RIGHT.insets    = new Insets(8, 8, 0, 8);
    }

    private <C extends Component> C addLeft (final C component) {
        this    .add           (component);
        this.gbl.setConstraints(component, GBC_LEFT);
        return                  component;
    }
    private <C extends Component> C addRight(final C component) {
        this    .add           (component);
        this.gbl.setConstraints(component, GBC_RIGHT);
        return                  component;
    }

    private static final String ALIGN = "H__|__WWW__+__XXXX__+__WWW__|__H";

    private final GridBagLayout gbl = new GridBagLayout();

    public JTextFieldPromptExample(final String title) {
        super(title);
        this.setLayout(gbl);

        final java.util.List<JTextField> texts = Stream.of(
                addLeft (newPromptedJTextField(ALIGN + ' ' + "Top-Left"    , ALIGN)),
                addRight(newPromptedJTextField(ALIGN + ' ' + "Top-Right"   , ALIGN)),

                addLeft (newPromptedJTextField(ALIGN + ' ' + "Middle-Left" , ALIGN)),
                addRight(newPromptedJTextField(                       null , ALIGN)),

                addLeft (new        JTextField("x"        )),
                addRight(newPromptedJTextField("x",   ""  )),

                addLeft (new        JTextField(null       )),
                addRight(newPromptedJTextField(null,  null)),

                addLeft (newPromptedJTextField(ALIGN + ' ' + "Bottom-Left" , ALIGN)),
                addRight(newPromptedJTextField(ALIGN + ' ' + "Bottom-Right", ALIGN)) ).collect(Collectors.toList());

        final JButton button = addRight(new JButton("Get texts"));
        /**/                   addRight(Box.createVerticalStrut(0)); // 1 last time forces bottom inset

        this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        this.setPreferredSize(new Dimension(740, 260));
        this.pack();
        this.setResizable(false);
        this.setVisible(true);

        button.addActionListener(e -> {
            texts.forEach(text -> System.out.println("Text..: " + text.getText()));
        });
    }

    public static void main(final String[] args) {
        SwingUtilities.invokeLater(() -> new JTextFieldPromptExample("JTextField with Prompt"));
    }
}
Dave The Dane
  • 650
  • 1
  • 7
  • 18