Escucha de cambio de valor para JTextField

215

Quiero que el cuadro de mensaje aparezca inmediatamente después de que el usuario cambie el valor en el campo de texto. Actualmente, necesito presionar la tecla Intro para que aparezca el cuadro de mensaje. ¿Hay algún problema con mi código?

textField.addActionListener(new java.awt.event.ActionListener() {
    public void actionPerformed(java.awt.event.ActionEvent e) {

        if (Integer.parseInt(textField.getText())<=0){
            JOptionPane.showMessageDialog(null,
                    "Error: Please enter number bigger than 0", "Error Message",
                    JOptionPane.ERROR_MESSAGE);
        }       
    }
}

¡Cualquier ayuda sería apreciada!

user236501
fuente

Respuestas:

373

Agregue un oyente al documento subyacente, que se crea automáticamente para usted.

// Listen for changes in the text
textField.getDocument().addDocumentListener(new DocumentListener() {
  public void changedUpdate(DocumentEvent e) {
    warn();
  }
  public void removeUpdate(DocumentEvent e) {
    warn();
  }
  public void insertUpdate(DocumentEvent e) {
    warn();
  }

  public void warn() {
     if (Integer.parseInt(textField.getText())<=0){
       JOptionPane.showMessageDialog(null,
          "Error: Please enter number bigger than 0", "Error Message",
          JOptionPane.ERROR_MESSAGE);
     }
  }
});
Codemwnci
fuente
buen formato para la advertencia / tipo de reparto. El mismo patrón será útil para manejar montos dobles (cifras de ventas / precios ingresados ​​o mostrados)
Max West
funciona bien, pero tengo una consulta que, cuando inserto texto en el campo de texto, quiero llamar a un método. No tengo mucha idea de cómo se hace ..
Estaba teniendo problemas con un JTable que no recibía actualizaciones de cuadros de texto de un JComboBox editable al hacer clic en otra celda de la tabla, y la función insertUpdate aquí era la única forma de hacerlo funcionar correctamente.
winchella
14
"Error Massage"
ungato
51

La respuesta habitual a esto es "usar a DocumentListener". Sin embargo, siempre encuentro esa interfaz engorrosa. A decir verdad, la interfaz está sobredimensionada. Tiene tres métodos, para inserción, eliminación y reemplazo de texto, cuando solo necesita un método: reemplazo. (Una inserción se puede ver como un reemplazo de ningún texto con algún texto, y una eliminación se puede ver como un reemplazo de un texto sin texto).

Por lo general, todo lo que desea saber es cuándo ha cambiado el texto en el cuadro , por lo que una DocumentListenerimplementación típica tiene los tres métodos que llaman a un método.

Por lo tanto, hice el siguiente método de utilidad, que le permite usar un método ChangeListenermás simple en lugar de a DocumentListener. (Utiliza la sintaxis lambda de Java 8, pero puede adaptarla para Java antiguo si es necesario).

/**
 * Installs a listener to receive notification when the text of any
 * {@code JTextComponent} is changed. Internally, it installs a
 * {@link DocumentListener} on the text component's {@link Document},
 * and a {@link PropertyChangeListener} on the text component to detect
 * if the {@code Document} itself is replaced.
 * 
 * @param text any text component, such as a {@link JTextField}
 *        or {@link JTextArea}
 * @param changeListener a listener to receieve {@link ChangeEvent}s
 *        when the text is changed; the source object for the events
 *        will be the text component
 * @throws NullPointerException if either parameter is null
 */
public static void addChangeListener(JTextComponent text, ChangeListener changeListener) {
    Objects.requireNonNull(text);
    Objects.requireNonNull(changeListener);
    DocumentListener dl = new DocumentListener() {
        private int lastChange = 0, lastNotifiedChange = 0;

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

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

        @Override
        public void changedUpdate(DocumentEvent e) {
            lastChange++;
            SwingUtilities.invokeLater(() -> {
                if (lastNotifiedChange != lastChange) {
                    lastNotifiedChange = lastChange;
                    changeListener.stateChanged(new ChangeEvent(text));
                }
            });
        }
    };
    text.addPropertyChangeListener("document", (PropertyChangeEvent e) -> {
        Document d1 = (Document)e.getOldValue();
        Document d2 = (Document)e.getNewValue();
        if (d1 != null) d1.removeDocumentListener(dl);
        if (d2 != null) d2.addDocumentListener(dl);
        dl.changedUpdate(null);
    });
    Document d = text.getDocument();
    if (d != null) d.addDocumentListener(dl);
}

A diferencia de agregar un agente de escucha directamente al documento, esto maneja el caso (poco común) de que instale un nuevo objeto de documento en un componente de texto. Además, resuelve el problema mencionado en la respuesta de Jean-Marc Astesana , donde el documento a veces dispara más eventos de los que necesita.

De todos modos, este método le permite reemplazar el código molesto que se ve así:

someTextBox.getDocument().addDocumentListener(new DocumentListener() {
    @Override
    public void insertUpdate(DocumentEvent e) {
        doSomething();
    }

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

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

Con:

addChangeListener(someTextBox, e -> doSomething());

Código liberado al dominio público. ¡Que te diviertas!

Boann
fuente
55
Solución similar: cree un abstract class DocumentChangeListener implements DocumentListenermétodo abstracto adicional al change(DocumentEvent e)que llame desde los otros 3 métodos. Me parece más obvio ya que usa más o menos la misma lógica que los abstract *Adapteroyentes.
geronimo
+1 como changedUpdatemétodo se invocará explícitamente a través de una llamada dentro de cada uno de , insertUpdatey removeUpdatepara que funcione ..
Kais
16

Simplemente cree una interfaz que extienda DocumentListener e implemente todos los métodos DocumentListener:

@FunctionalInterface
public interface SimpleDocumentListener extends DocumentListener {
    void update(DocumentEvent e);

    @Override
    default void insertUpdate(DocumentEvent e) {
        update(e);
    }
    @Override
    default void removeUpdate(DocumentEvent e) {
        update(e);
    }
    @Override
    default void changedUpdate(DocumentEvent e) {
        update(e);
    }
}

y entonces:

jTextField.getDocument().addDocumentListener(new SimpleDocumentListener() {
    @Override
    public void update(DocumentEvent e) {
        // Your code here
    }
});

o incluso puedes usar la expresión lambda:

jTextField.getDocument().addDocumentListener((SimpleDocumentListener) e -> {
    // Your code here
});
Andrey Megvinov
fuente
1
No olvide que esta solución requiere una clase abstracta en lugar de una interfaz en todas las versiones anteriores a Java 8.
klaar
15

Tenga en cuenta que cuando el usuario modifica el campo, DocumentListener puede, en algún momento, recibir dos eventos. Por ejemplo, si el usuario selecciona todo el contenido del campo, luego presiona una tecla, recibirá un removeUpdate (todo el contenido es eliminar) y un insertUpdate. En su caso, no creo que sea un problema pero, en general, lo es. Desafortunadamente, parece que no hay forma de rastrear el contenido de textField sin subclasificar JTextField. Aquí está el código de una clase que proporciona una propiedad "text":

package net.yapbam.gui.widget;

import javax.swing.JTextField;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.PlainDocument;

/** A JTextField with a property that maps its text.
 * <br>I've found no way to track efficiently the modifications of the text of a JTextField ... so I developed this widget.
 * <br>DocumentListeners are intended to do it, unfortunately, when a text is replace in a field, the listener receive two events:<ol>
 * <li>One when the replaced text is removed.</li>
 * <li>One when the replacing text is inserted</li>
 * </ul>
 * The first event is ... simply absolutely misleading, it corresponds to a value that the text never had.
 * <br>Anoter problem with DocumentListener is that you can't modify the text into it (it throws IllegalStateException).
 * <br><br>Another way was to use KeyListeners ... but some key events are throw a long time (probably the key auto-repeat interval)
 * after the key was released. And others events (for example a click on an OK button) may occurs before the listener is informed of the change.
 * <br><br>This widget guarantees that no "ghost" property change is thrown !
 * @author Jean-Marc Astesana
 * <BR>License : GPL v3
 */

public class CoolJTextField extends JTextField {
    private static final long serialVersionUID = 1L;

    public static final String TEXT_PROPERTY = "text";

    public CoolJTextField() {
        this(0);
    }

    public CoolJTextField(int nbColumns) {
        super("", nbColumns);
        this.setDocument(new MyDocument());
    }

    @SuppressWarnings("serial")
    private class MyDocument extends PlainDocument {
        private boolean ignoreEvents = false;

        @Override
        public void replace(int offset, int length, String text, AttributeSet attrs) throws BadLocationException {
            String oldValue = CoolJTextField.this.getText();
            this.ignoreEvents = true;
            super.replace(offset, length, text, attrs);
            this.ignoreEvents = false;
            String newValue = CoolJTextField.this.getText();
            if (!oldValue.equals(newValue)) CoolJTextField.this.firePropertyChange(TEXT_PROPERTY, oldValue, newValue);
        }

        @Override
        public void remove(int offs, int len) throws BadLocationException {
            String oldValue = CoolJTextField.this.getText();
            super.remove(offs, len);
            String newValue = CoolJTextField.this.getText();
            if (!ignoreEvents && !oldValue.equals(newValue)) CoolJTextField.this.firePropertyChange(TEXT_PROPERTY, oldValue, newValue);
        }
    }
Jean-Marc Astesana
fuente
3
Swing ya tiene un tipo de textField que asigna cambios de documentos a una propiedad: se llama JFormattedTextField :-)
kleopatra
11

Sé que esto se relaciona con un problema realmente antiguo, sin embargo, también me causó algunos problemas. Como kleopatra respondió en un comentario anterior, resolví el problema con unJFormattedTextField . Sin embargo, la solución requiere un poco más de trabajo, pero es más ordenada.

El JFormattedTextFieldno lo hace por defecto de disparo de un cambio de propiedad después de cada texto cambia en el campo. El constructor predeterminado deJFormattedTextField no crea un formateador.

Sin embargo, para hacer lo que sugirió el OP, debe usar un formateador que invoque el commitEdit()método después de cada edición válida del campo. loscommitEdit() método es lo que activa el cambio de propiedad de lo que puedo ver y sin el formateador, esto se activa de forma predeterminada en un cambio de foco o cuando se presiona la tecla Intro.

Consulte http://docs.oracle.com/javase/tutorial/uiswing/components/formattedtextfield.html#value para obtener más detalles.

Cree un DefaultFormatterobjeto formateador ( ) predeterminado para pasarlo a JFormattedTextFieldtravés de su constructor o un método de establecimiento. Un método del formateador predeterminado es setCommitsOnValidEdit(boolean commit), que establece el formateador para activar el commitEdit()método cada vez que se cambia el texto. Esto se puede recoger utilizando ay PropertyChangeListenerel propertyChange()método.

Astridax
fuente
2
textBoxName.getDocument().addDocumentListener(new DocumentListener() {
   @Override
   public void insertUpdate(DocumentEvent e) {
       onChange();
   }

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

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

Pero no solo analizaría cualquier cosa que el usuario (tal vez por accidente) toque en su teclado en un Integer. Debes atrapar cualquier Exceptioncosa arrojada y asegurarte de que JTextFieldno esté vacía.

DerBobby
fuente
2

Si usamos el método ejecutable SwingUtilities.invokeLater () mientras usamos la aplicación de escucha de documentos, a veces se atasca y toma tiempo actualizar el resultado (según mi experimento). En lugar de eso, también podemos usar el evento KeyReleased para escuchar el cambio de campo de texto como se menciona aquí .

usernameTextField.addKeyListener(new KeyAdapter() {
    public void keyReleased(KeyEvent e) {
        JTextField textField = (JTextField) e.getSource();
        String text = textField.getText();
        textField.setText(text.toUpperCase());
    }
});
Kakumanu siva krishna
fuente
1

Era la versión actualizada de Codemwnci. su código está bastante bien y funciona muy bien, excepto el mensaje de error. Para evitar errores, debe cambiar la declaración de condición.

  // Listen for changes in the text
textField.getDocument().addDocumentListener(new DocumentListener() {
  public void changedUpdate(DocumentEvent e) {
    warn();
  }
  public void removeUpdate(DocumentEvent e) {
    warn();
  }
  public void insertUpdate(DocumentEvent e) {
    warn();
  }

  public void warn() {
     if (textField.getText().length()>0){
       JOptionPane.showMessageDialog(null,
          "Error: Please enter number bigger than 0", "Error Massage",
          JOptionPane.ERROR_MESSAGE);
     }
  }
});
Bipul Chandra Dev Nath
fuente
Su adaptación activa el cuadro de diálogo de mensaje de error cada vez que se ingresa una cadena más larga que longitud = 0 en el campo de texto. Así que básicamente es cualquier cadena que no sea una cadena vacía. Esa no es la solución solicitada.
klaar
0

Puede usar incluso "MouseExited" para controlar. ejemplo:

 private void jtSoMauMouseExited(java.awt.event.MouseEvent evt) {                                    
        // TODO add your handling code here:
        try {
            if (Integer.parseInt(jtSoMau.getText()) > 1) {
                //auto update field
                SoMau = Integer.parseInt(jtSoMau.getText());
                int result = SoMau / 5;

                jtSoBlockQuan.setText(String.valueOf(result));
            }
        } catch (Exception e) {

        }

    }   
fishgold192
fuente
66
en realidad no: el requisito es hacer algo cuando se cambia el texto - eso no está relacionado con mouseEvents ;-)
kleopatra
0

Soy nuevo en WindowBuilder y, de hecho, acabo de volver a Java después de unos años, pero implementé "algo", luego pensé en buscarlo y me encontré con este hilo.

Estoy a punto de probar esto, así que, dado que soy nuevo en todo esto, estoy seguro de que me falta algo.

Esto es lo que hice, donde "runTxt" es un cuadro de texto y "runName" es un miembro de datos de la clase:

public void focusGained(FocusEvent e) {
    if (e.getSource() == runTxt) {
        System.out.println("runTxt got focus");
        runTxt.selectAll();
    }
}

public void focusLost(FocusEvent e) {
    if (e.getSource() == runTxt) {
        System.out.println("runTxt lost focus");
        if(!runTxt.getText().equals(runName))runName= runTxt.getText();
        System.out.println("runText.getText()= " + runTxt.getText() + "; runName= " + runName);
    }
}

Parece mucho más simple que lo que hay aquí hasta ahora, y parece estar funcionando, pero, dado que estoy escribiendo esto, agradecería saber de cualquier error que se haya pasado por alto. ¿Es un problema que el usuario pueda ingresar y salir del cuadro de texto sin hacer un cambio? Creo que todo lo que has hecho es una tarea innecesaria.

RocketMan
fuente
-1

Use un KeyListener (que se activa en cualquier tecla) en lugar del ActionListener (que se activa al ingresar)

mella
fuente
Esto no funciona porque el valor del campo no se captura correctamente, field.getText()devuelve el valor inicial. y el evento ( arg0.getKeyChar()) devuelve la tecla presionada, la comprobación de errores es necesaria para determinar si debe concatenar con el texto del campo.
Glend
@glend, puede usar el evento keyReleased en lugar del evento keyTyped. Funcionó para mí y obtuve el valor completo.
Kakumanu siva krishna
-1

DocumentFilter ? Te da la capacidad de manipular.

[ http://www.java2s.com/Tutorial/Java/0240__Swing/FormatJTextFieldstexttouppercase.htm ]

Lo siento. Estoy usando Jython (Python en Java), pero fácil de entender

# python style
# upper chars [ text.upper() ]

class myComboBoxEditorDocumentFilter( DocumentFilter ):
def __init__(self,jtext):
    self._jtext = jtext

def insertString(self,FilterBypass_fb, offset, text, AttributeSet_attrs):
    txt = self._jtext.getText()
    print('DocumentFilter-insertString:',offset,text,'old:',txt)
    FilterBypass_fb.insertString(offset, text.upper(), AttributeSet_attrs)

def replace(self,FilterBypass_fb, offset, length, text, AttributeSet_attrs):
    txt = self._jtext.getText()
    print('DocumentFilter-replace:',offset, length, text,'old:',txt)
    FilterBypass_fb.replace(offset, length, text.upper(), AttributeSet_attrs)

def remove(self,FilterBypass_fb, offset, length):
    txt = self._jtext.getText()
    print('DocumentFilter-remove:',offset, length, 'old:',txt)
    FilterBypass_fb.remove(offset, length)

// (java style ~example for ComboBox-jTextField)
cb = new ComboBox();
cb.setEditable( true );
cbEditor = cb.getEditor();
cbEditorComp = cbEditor.getEditorComponent();
cbEditorComp.getDocument().setDocumentFilter(new myComboBoxEditorDocumentFilter(cbEditorComp));
papuga
fuente