Search code examples
javaswingjtextfield

How to display faint gray "ghost text" in a JTextField?


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.


Solution

  • 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();
        }
    }