¿Cómo se muestra el débil “texto fantasma” gris en un JTextField?

No sé si obtuve el nombre correcto para ello, pero estoy buscando si hay una manera específica de implementar un campo de texto para que, aunque no tenga el foco y esté vacío, una débil cadena gris de el texto se muestra en el campo. Cuando se hace clic en el campo, el texto debe desaparecer, exactamente como funciona la barra de búsqueda como la de StackOverflow. Sé que puedo cambiar el uso de setForeground() y centrar a los oyentes para lograr esto, pero me preguntaba si alguien sabía de alguna implementación de Java que pudiera manejar esto por mí.

Por lo que vale, me pareció interesante implementarlo realmente, así que pensé en compartirlo contigo (no estoy buscando votos).

Realmente no es invasivo ya que todo lo que tiene que hacer es llamar al new GhostText(textField, "Please enter some text here..."); . El rest del código es solo para hacer que se ejecute.

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

Muchas gracias, Guillaume, ¡esto es muy bueno!

Acabo de cambiar algunas cosas para facilitar su uso:

  1. usó JTextComponent en lugar de JTextField para que funcione con todas las entradas de texto
  2. sacó la clase de prueba y la hizo pública y no estática para que sea independiente

Aquí está el código:

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