19

I don't know if I got the right name for it, but I'm looking to see if there is a specific way to implement a text field so that while it doesn't have focus and is empty, a faint gray string of text is displayed in the field. When the field is clicked, the text should go away, exactly like how the Search bar like that of StackOverflow works. I know that I can change use setForeground() and focus listeners to accomplish this, but I was just wondering if anyone knew of some Java implementation that could handle this for me.

Franz Kafka
  • 10,623
  • 20
  • 93
  • 149
jez
  • 1,239
  • 1
  • 7
  • 14
  • AFAIK, no. But I would be glad to be told otherwise – Guillaume Polet May 08 '12 at 21:35
  • I can tell you that Swing doesn't provide this natively (but someone may have written a 3rd party library to do it). – Greg Kopff May 08 '12 at 21:35
  • 2
    This might help: http://stackoverflow.com/questions/1738966/java-jtextfield-with-input-hint – Ranga May 08 '12 at 21:36
  • 1
    Use JXTextField from SwingX: http://swingx.java.net/. – elias May 08 '12 at 21:49
  • @Elias, while I think that your link is exactly what I'm looking for, the site was really confusing... Something about transferring servers and it only looked half finished with no end in sight. And while I'm sure the code below will certainly work, I think it's a bit over the top for what I need. I guess I'm back to where I started: looks like I'll just do it myself :P – jez May 08 '12 at 23:59
  • This widget is called a "placeholder". – martinez314 Jun 20 '14 at 21:34

2 Answers2

42

For what it's worth, I found it interesting to actually implement it, so I thought to share it with you (I am not looking for votes).

It's really non-invasive since all you have to do is call new GhostText(textField, "Please enter some text here...");. The rest of the code is only to make it run.

import java.awt.Color;
import java.awt.Dimension;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;

public class Test {

    public static class GhostText implements FocusListener, DocumentListener, PropertyChangeListener {
        private final JTextField textfield;
        private boolean isEmpty;
        private Color ghostColor;
        private Color foregroundColor;
        private final String ghostText;

        protected GhostText(final JTextField textfield, String ghostText) {
            super();
            this.textfield = textfield;
            this.ghostText = ghostText;
            this.ghostColor = Color.LIGHT_GRAY;
            textfield.addFocusListener(this);
            registerListeners();
            updateState();
            if (!this.textfield.hasFocus()) {
                focusLost(null);
            }
        }

        public void delete() {
            unregisterListeners();
            textfield.removeFocusListener(this);
        }

        private void registerListeners() {
            textfield.getDocument().addDocumentListener(this);
            textfield.addPropertyChangeListener("foreground", this);
        }

        private void unregisterListeners() {
            textfield.getDocument().removeDocumentListener(this);
            textfield.removePropertyChangeListener("foreground", this);
        }

        public Color getGhostColor() {
            return ghostColor;
        }

        public void setGhostColor(Color ghostColor) {
            this.ghostColor = ghostColor;
        }

        private void updateState() {
            isEmpty = textfield.getText().length() == 0;
            foregroundColor = textfield.getForeground();
        }

        @Override
        public void focusGained(FocusEvent e) {
            if (isEmpty) {
                unregisterListeners();
                try {
                    textfield.setText("");
                    textfield.setForeground(foregroundColor);
                } finally {
                    registerListeners();
                }
            }

        }

        @Override
        public void focusLost(FocusEvent e) {
            if (isEmpty) {
                unregisterListeners();
                try {
                    textfield.setText(ghostText);
                    textfield.setForeground(ghostColor);
                } finally {
                    registerListeners();
                }
            }
        }

        @Override
        public void propertyChange(PropertyChangeEvent evt) {
            updateState();
        }

        @Override
        public void changedUpdate(DocumentEvent e) {
            updateState();
        }

        @Override
        public void insertUpdate(DocumentEvent e) {
            updateState();
        }

        @Override
        public void removeUpdate(DocumentEvent e) {
            updateState();
        }

    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                init();
            }
        });
    }

    public static void init() {
        JFrame frame = new JFrame("Test ghost text");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        JPanel panel = new JPanel();
        JTextField textField = new JTextField();
        JButton button = new JButton("Grab focus");
        GhostText ghostText = new GhostText(textField, "Please enter some text here...");
        textField.setPreferredSize(new Dimension(300, 24));
        panel.add(textField);
        panel.add(button);
        frame.add(panel);
        frame.pack();
        frame.setVisible(true);
        button.grabFocus();
    }
}
Guillaume Polet
  • 47,259
  • 4
  • 83
  • 117
  • 1
    This may be a little nit-picky, but could you accomplish this without removing the ghost text until a character is typed? Firefox is able to show what I'm talking about: if you open up a new tab, the address bar has ghost text that says "Search or enter address" which will not disappear when you focus the field but will stay until you type the first character. – ryvantage Nov 07 '15 at 22:18
  • this is awesome, but as of 2018 and using java 8, is this built in yet? it really should be. – ryan_m Nov 17 '18 at 06:00
  • 1
    @ryan_m I have no idea, but I wouldn't be surprised that this not the case. Apparently, Swing will no longer be updated and it will only be maintained. – Guillaume Polet Nov 21 '18 at 08:45
9

Thank you very much Guillaume, this is very good!

I just changed a few things for ease of use:

  1. used JTextComponent instead of JTextField so it works with all text inputs
  2. took out the test class and made it public and non-static to make it stand-alone

Here is the code:

import java.awt.Color;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.JTextComponent;

public class GhostText implements FocusListener, DocumentListener, PropertyChangeListener
{
    private final JTextComponent textComp;
    private boolean isEmpty;
    private Color ghostColor;
    private Color foregroundColor;
    private final String ghostText;

    public GhostText(final JTextComponent textComp, String ghostText)
    {
        super();
        this.textComp = textComp;
        this.ghostText = ghostText;
        this.ghostColor = Color.LIGHT_GRAY;
        textComp.addFocusListener(this);
        registerListeners();
        updateState();
        if (!this.textComp.hasFocus())
        {
            focusLost(null);
        }
    }

    public void delete()
    {
        unregisterListeners();
        textComp.removeFocusListener(this);
    }

    private void registerListeners()
    {
        textComp.getDocument().addDocumentListener(this);
        textComp.addPropertyChangeListener("foreground", this);
    }

    private void unregisterListeners()
    {
        textComp.getDocument().removeDocumentListener(this);
        textComp.removePropertyChangeListener("foreground", this);
    }

    public Color getGhostColor()
    {
        return ghostColor;
    }

    public void setGhostColor(Color ghostColor)
    {
        this.ghostColor = ghostColor;
    }

    private void updateState()
    {
        isEmpty = textComp.getText().length() == 0;
        foregroundColor = textComp.getForeground();
    }

    @Override
    public void focusGained(FocusEvent e)
    {
        if (isEmpty)
        {
            unregisterListeners();
            try
            {
                textComp.setText("");
                textComp.setForeground(foregroundColor);
            }
            finally
            {
                registerListeners();
            }
        }

    }

    @Override
    public void focusLost(FocusEvent e)
    {
        if (isEmpty)
        {
            unregisterListeners();
            try
            {
                textComp.setText(ghostText);
                textComp.setForeground(ghostColor);
            }
            finally
            {
                registerListeners();
            }
        }
    }

    @Override
    public void propertyChange(PropertyChangeEvent evt)
    {
        updateState();
    }

    @Override
    public void changedUpdate(DocumentEvent e)
    {
        updateState();
    }

    @Override
    public void insertUpdate(DocumentEvent e)
    {
        updateState();
    }

    @Override
    public void removeUpdate(DocumentEvent e)
    {
        updateState();
    }

}
Xav
  • 101
  • 1
  • 1