Écouteur de changement de valeur vers JTextField


215

Je veux que la boîte de message apparaisse immédiatement après que l'utilisateur modifie la valeur dans le champ de texte. Actuellement, je dois appuyer sur la touche Entrée pour faire apparaître la boîte de message. Y a-t-il un problème avec mon code?

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

Toute aide serait appréciée!

Réponses:


373

Ajoutez un écouteur au document sous-jacent, qui est automatiquement créé pour vous.

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

bon format pour la distribution d'avertissement / type. Le même modèle sera utile pour gérer les doubles montants (chiffres de vente / prix saisis ou affichés)
Max West

ça marche bien mais j'ai une requête qui, quand j'insère du texte dans textfield alors je veux appeler une méthode. je n'ai pas beaucoup d'idée sur la façon dont cela se fait ..

J'avais des problèmes avec une JTable qui n'obtenait pas de mises à jour de zone de texte à partir d'une JComboBox modifiable lorsque je cliquais sur une autre cellule de tableau, et la fonction insertUpdate ici était le seul moyen de le faire fonctionner correctement.
winchella

14
"Error Massage"
ungato

51

La réponse habituelle à cela est "utiliser un DocumentListener". Cependant, je trouve toujours cette interface encombrante. Vraiment, l'interface est sur-conçue. Il a trois méthodes, pour l'insertion, la suppression et le remplacement de texte, lorsqu'il n'a besoin que d'une seule méthode: le remplacement. (Une insertion peut être considérée comme un remplacement d'aucun texte par du texte et une suppression peut être considérée comme un remplacement d'un texte sans texte.)

Habituellement, tout ce que vous voulez, c'est savoir quand le texte dans la boîte a changé , donc une DocumentListenerimplémentation typique a les trois méthodes appelant une méthode.

Par conséquent, j'ai créé la méthode utilitaire suivante, qui vous permet d'utiliser une méthode plus simple ChangeListener que a DocumentListener. (Il utilise la syntaxe lambda de Java 8, mais vous pouvez l'adapter pour l'ancien Java si nécessaire.)

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

Contrairement à l'ajout d'un écouteur directement au document, cela gère le cas (inhabituel) où vous installez un nouvel objet de document sur un composant de texte. De plus, il contourne le problème mentionné dans la réponse de Jean-Marc Astesana , où le document déclenche parfois plus d'événements qu'il n'en a besoin.

Quoi qu'il en soit, cette méthode vous permet de remplacer le code ennuyeux qui ressemble à ceci:

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

Avec:

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

Code rendu public. S'amuser!


5
Solution similaire: créez un abstract class DocumentChangeListener implements DocumentListeneravec une méthode abstraite supplémentaire change(DocumentEvent e)que vous appelez à partir des 3 autres méthodes. Cela me semble plus évident car il utilise plus ou moins la même logique que les abstract *Adapterauditeurs.
geronimo

+1 en tant que changedUpdateméthode doit être invoqué explicitement via un appel au sein de chacun insertUpdateet removeUpdate, pour le faire fonctionner ..
Kais

17

Créez simplement une interface qui étend DocumentListener et implémente toutes les méthodes 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);
    }
}

puis:

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

ou vous pouvez même utiliser l'expression lambda:

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

1
N'oubliez pas que cette solution nécessite une classe abstraite au lieu d'une interface dans toutes les versions antérieures à Java 8.
klaar

15

N'oubliez pas que lorsque l'utilisateur modifie le champ, DocumentListener peut parfois recevoir deux événements. Par exemple, si l'utilisateur sélectionne tout le contenu du champ, puis appuyez sur une touche, vous recevrez un removeUpdate (tout le contenu est supprimé) et un insertUpdate. Dans votre cas, je ne pense pas que ce soit un problème mais, d'une manière générale, c'est le cas. Malheureusement, il semble qu'il n'y ait aucun moyen de suivre le contenu de textField sans sous-classer JTextField. Voici le code d'une classe qui fournit une propriété "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
Balancez déjà a un type de textField qui mappe documenter les changements à une propriété - il est appelé JFormattedTextField :-)
kleopatra

11

Je sais que cela se rapporte à un problème très ancien, mais cela m'a aussi causé des problèmes. Comme kleopatra a répondu dans un commentaire ci-dessus, j'ai résolu le problème avec a JFormattedTextField. Cependant, la solution nécessite un peu plus de travail, mais est plus nette.

Le JFormattedTextFieldne déclenche pas par défaut un changement de propriété après chaque changement de texte dans le champ. Le constructeur par défaut de JFormattedTextFieldne crée pas de formateur.

Cependant, pour faire ce que l'OP a suggéré, vous devez utiliser un formateur qui invoquera la commitEdit()méthode après chaque modification valide du champ. La commitEdit()méthode est ce qui déclenche le changement de propriété à partir de ce que je peux voir et sans le formateur, cela est déclenché par défaut lors d'un changement de focus ou lorsque la touche Entrée est enfoncée.

Voir http://docs.oracle.com/javase/tutorial/uiswing/components/formattedtextfield.html#value pour plus de détails.

Créez un DefaultFormatterobjet formatter ( ) par défaut à transmettre à l' JFormattedTextFieldun via son constructeur ou une méthode de définition. Une méthode du formateur par défaut est setCommitsOnValidEdit(boolean commit), qui définit le formateur pour déclencher la commitEdit()méthode chaque fois que le texte est modifié. Cela peut ensuite être récupéré en utilisant a PropertyChangeListeneret la propertyChange()méthode.


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

Mais je ne voudrais pas simplement analyser tout ce que l'utilisateur (peut-être par accident) touche sur son clavier en un Integer. Vous devez attraper tout Exceptions jeté et assurez-vous qu'il JTextFieldn'est pas vide.


2

Si nous utilisons la méthode exécutable, SwingUtilities.invokeLater () lors de l'utilisation de l'application d'écoute de document se bloque parfois et prend du temps pour mettre à jour le résultat (selon mon expérience). Au lieu de cela, nous pouvons également utiliser l'événement KeyReleased pour l'écouteur de changement de champ de texte comme mentionné ici .

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

1

c'était la version mise à jour de Codemwnci. son code est assez fin et fonctionne très bien sauf le message d'erreur. Pour éviter l'erreur, vous devez modifier l'énoncé de 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);
     }
  }
});

Votre adaptation déclenche la boîte de dialogue de message d'erreur chaque fois qu'une chaîne de plus de longueur = 0 est entrée dans le champ de texte. Il s'agit donc essentiellement d'une chaîne autre qu'une chaîne vide. Ce n'est pas la solution demandée.
klaar

0

Vous pouvez même utiliser "MouseExited" pour contrôler. exemple:

 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
pas vraiment: l'exigence fait quelque chose lorsque le texte est modifié - ce n'est pas lié à mouseEvents ;-)
kleopatra

0

Je suis tout nouveau à WindowBuilder et, en fait, je reviens juste à Java après quelques années, mais j'ai implémenté "quelque chose", puis j'ai pensé le chercher et j'ai trouvé ce fil.

Je suis en train de tester cela, donc, étant donné que je suis nouveau dans tout cela, je suis sûr que je dois manquer quelque chose.

Voici ce que j'ai fait, où "runTxt" est une zone de texte et "runName" est un membre de données de la 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);
    }
}

Cela semble beaucoup plus simple que ce qui est ici jusqu'à présent, et semble fonctionner, mais, étant donné que je suis en train d'écrire ceci, j'apprécierais d'entendre tous les pièges oubliés. Est-ce un problème que l'utilisateur pourrait entrer et quitter la zone de texte sans apporter de modification? Je pense que tout ce que vous avez fait est une mission inutile.


-1

Utilisez un KeyListener (qui se déclenche sur n'importe quelle touche) plutôt que l'ActionListener (qui se déclenche à l'entrée)


Cela ne fonctionne pas car la valeur du champ n'est pas capturée correctement, field.getText()renvoie la valeur initiale. et l'événement ( arg0.getKeyChar()) renvoie la touche d'erreur, la vérification des erreurs est nécessaire pour déterminer si vous devez concaténer avec le texte du champ.
fusion le

@glend, vous pouvez utiliser l'événement keyReleased au lieu de l'événement keyTyped. Cela a fonctionné pour moi et a obtenu la valeur complète.
Kakumanu siva krishna

-1

DocumentFilter ? Il vous donne la possibilité de manipuler.

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

Désolé. J'utilise Jython (Python en Java) - mais facile à comprendre

# 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));
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.