Listener di modifica valore in JTextField


215

Voglio che la finestra di messaggio appaia immediatamente dopo che l'utente ha modificato il valore nel campo di testo. Al momento, devo premere il tasto Invio per far apparire la finestra di messaggio. C'è qualcosa di sbagliato nel mio codice?

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

Qualsiasi aiuto sarebbe apprezzato!

Risposte:


373

Aggiungi un ascoltatore al Documento sottostante, che viene creato automaticamente per te.

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

buon formato per l'avviso / tipo cast. Lo stesso schema sarà utile per gestire doppi importi (cifre / prezzi di vendita inseriti o visualizzati)
Max West

funziona benissimo ma ho una domanda che, quando inserisco del testo nel campo di testo, voglio chiamare un metodo. non ho molta idea di come sia fatto ..

Avevo problemi con una JTable che non otteneva gli aggiornamenti delle caselle di testo da una JComboBox modificabile quando facevo clic su un'altra cella della tabella e la funzione insertUpdate qui era l'unico modo per farlo funzionare correttamente.
Winchella,

14
"Error Massage"
ungato

51

La solita risposta a questo è "usa un DocumentListener". Tuttavia, trovo sempre ingombrante quell'interfaccia. In verità l'interfaccia è troppo ingegnerizzata. Ha tre metodi, per l'inserimento, la rimozione e la sostituzione del testo, quando necessita di un solo metodo: la sostituzione. (Un inserimento può essere visualizzato come una sostituzione di un testo senza testo e una rimozione può essere vista come una sostituzione di un testo senza testo.)

Di solito tutto quello che vuoi sapere è quando il testo nella casella è cambiato , quindi DocumentListenerun'implementazione tipica ha i tre metodi che chiamano un metodo.

Pertanto ho creato il seguente metodo di utilità, che consente di utilizzare un metodo più semplice ChangeListeneranziché uno DocumentListener. (Utilizza la sintassi lambda di Java 8, ma se necessario puoi adattarla alla vecchia Java.)

/**
 * 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 differenza dell'aggiunta di un listener direttamente al documento, questo gestisce il caso (non comune) di installazione di un nuovo oggetto documento su un componente di testo. Inoltre, risolve il problema menzionato nella risposta di Jean-Marc Astesana , in cui il documento talvolta genera più eventi di quanti ne abbia bisogno.

Comunque, questo metodo ti consente di sostituire il codice fastidioso che assomiglia a questo:

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

Codice rilasciato al pubblico dominio. Divertiti!


5
Soluzione simile: crea un abstract class DocumentChangeListener implements DocumentListenercon un metodo extra astratto change(DocumentEvent e)che chiami da tutti e 3 gli altri metodi. Mi sembra più ovvio dal momento che utilizza più o meno la stessa logica degli abstract *Adapterascoltatori.
Geronimo,

+1 come changedUpdatemetodo deve essere invocato esplicitamente tramite una chiamata all'interno di ciascuno di essi insertUpdatee removeUpdate, al fine di farlo funzionare.
Kais

17

Basta creare un'interfaccia che estende DocumentListener e implementa tutti i metodi 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);
    }
}

e poi:

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

oppure puoi anche usare l'espressione lambda:

jTextField.getDocument().addDocumentListener((SimpleDocumentListener) e -> {
    // Your code here
});

1
Non dimenticare che questa soluzione richiede una classe astratta anziché un'interfaccia in tutte le versioni precedenti a Java 8.
klaar

15

Tenere presente che quando l'utente modifica il campo, DocumentListener può, a volte, ricevere due eventi. Ad esempio, se l'utente seleziona l'intero contenuto del campo, quindi preme un tasto, riceverai un removeUpdate (tutto il contenuto viene rimosso) e un insertUpdate. Nel tuo caso, non penso che sia un problema ma, in generale, lo è. Sfortunatamente, sembra che non ci sia modo di tracciare il contenuto di textField senza sottoclassare JTextField. Ecco il codice di una classe che fornisce una proprietà "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);
        }
    }

3
Swing ha già un tipo di textField che mappa le modifiche del documento a una proprietà - si chiama JFormattedTextField :-)
kleopatra il

11

So che questo si riferisce a un problema molto vecchio, tuttavia, mi ha causato anche alcuni problemi. Come Kleopatra ha risposto in un commento sopra, ho risolto il problema con a JFormattedTextField. Tuttavia, la soluzione richiede un po 'più di lavoro, ma è più ordinata.

Per JFormattedTextFieldimpostazione predefinita, non viene attivata una modifica della proprietà dopo ogni modifica del testo nel campo. Il costruttore predefinito di JFormattedTextFieldnon crea un formattatore.

Tuttavia, per fare ciò che l'OP ha suggerito, è necessario utilizzare un formattatore che invocherà il commitEdit()metodo dopo ogni modifica valida del campo. Il commitEdit()metodo è ciò che innesca il cambio di proprietà da quello che posso vedere e senza il formattatore, questo viene attivato di default su un cambio di focus o quando viene premuto il tasto Invio.

Vedi http://docs.oracle.com/javase/tutorial/uiswing/components/formattedtextfield.html#value per maggiori dettagli.

Creare un DefaultFormatteroggetto formatter ( ) predefinito da passare a JFormattedTextFieldtramite il suo costruttore o un metodo setter. Un metodo del formattatore predefinito è setCommitsOnValidEdit(boolean commit), che imposta il formatter in modo da attivare il commitEdit()metodo ogni volta che il testo viene modificato. Questo può quindi essere raccolto usando a PropertyChangeListenere il propertyChange()metodo.


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

Ma non vorrei solo analizzare qualsiasi cosa l'utente (forse in caso di incidente) tocchi sulla sua tastiera in un Integer. Dovresti prendere qualsiasi Exceptions lanciato e assicurarti che JTextFieldnon sia vuoto.


2

Se utilizziamo il metodo eseguibile SwingUtilities.invokeLater () mentre utilizziamo l'applicazione Listener di documenti a volte si blocca e impiega del tempo per aggiornare il risultato (secondo il mio esperimento). Invece, possiamo anche utilizzare l'evento KeyReleased per l'ascoltatore di modifiche ai campi di testo, come menzionato qui .

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

1

era la versione di aggiornamento di Codemwnci. il suo codice è abbastanza buono e funziona benissimo tranne il messaggio di errore. Per evitare errori è necessario modificare l'istruzione condition.

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

Il tuo adattamento attiva la finestra di dialogo del messaggio di errore ogni volta che nel campo di testo viene inserita una stringa più lunga di = 0. Quindi è praticamente una stringa diversa da una stringa vuota. Questa non è la soluzione richiesta.
klaar,

0

Puoi anche usare "MouseExited" per controllare. esempio:

 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) {

        }

    }   

6
non proprio: il requisito sta facendo qualcosa quando il testo viene modificato - che è estraneo a MouseEvents ;-)
Kleopatra

0

Sono nuovo di zecca per WindowBuilder e, in effetti, sono appena tornato in Java dopo alcuni anni, ma ho implementato "qualcosa", quindi ho pensato di cercarlo e mi sono imbattuto in questo thread.

Sono nel mezzo del test, quindi, basandomi sull'essere nuovo a tutto ciò, sono sicuro che mi mancherà qualcosa.

Ecco cosa ho fatto, in cui "runTxt" è una casella di testo e "runName" è un membro di dati della classe:

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

Sembra molto più semplice di quello che è qui finora, e sembra funzionare, ma, dato che sono nel bel mezzo della stesura di questo, apprezzerei molto sentire i gotcha trascurati. È un problema che l'utente possa entrare e uscire dalla casella di testo senza effettuare modifiche? Penso che tutto ciò che hai fatto sia un compito non necessario.


-1

Utilizzare un KeyListener (che si attiva su qualsiasi tasto) anziché ActionListener (che viene attivato su Invio)


Questo non funziona perché il valore del campo non viene acquisito correttamente, field.getText()restituisce il valore iniziale. e event ( arg0.getKeyChar()) restituisce il controllo dell'errore premuto per determinare se è necessario concatenarsi con il testo del campo.
Incolla il

@glend, è possibile utilizzare l'evento keyReleased anziché l'evento keyTyped. Ha funzionato per me e ottenere il valore completo.
Kakumanu siva krishna

-1

DocumentFilter ? Ti dà la possibilità di manipolare.

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

Scusate. Sto usando Jython (Python in Java) - ma facile da capire

# 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));
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.