Detektor zmian wartości na JTextField


215

Chcę, aby okno komunikatu pojawiło się natychmiast po zmianie wartości przez użytkownika w polu tekstowym. Obecnie muszę nacisnąć klawisz Enter, aby wyskoczyło okno komunikatu. Czy coś jest nie tak z moim kodem?

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

Każda pomoc będzie mile widziana!

Odpowiedzi:


373

Dodaj detektor do bazowego dokumentu, który jest tworzony automatycznie.

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

dobry format rzutowania ostrzeżenia / typu. Ten sam wzór będzie przydatny do obsługi podwójnych kwot (wprowadzonych lub wyświetlonych danych / cen sprzedaży)
Max West

działa dobrze, ale mam zapytanie, że kiedy wstawię jakiś tekst w polu tekstowym, chcę wywołać metodę. nie mam pojęcia o tym, jak to się robi.

Miałem problemy z tym, że JTable nie otrzymywał aktualizacji pól tekstowych z edytowalnego JComboBox po kliknięciu innej komórki tabeli, a funkcja insertUpdate była tutaj jedynym sposobem, aby działał poprawnie.
winchella,

14
„Błąd masażu”
ungato

51

Zazwyczaj odpowiedź brzmi „użyj DocumentListener”. Jednak zawsze uważam ten interfejs za kłopotliwy. Naprawdę interfejs jest przeprojektowany. Ma trzy metody wstawiania, usuwania i zastępowania tekstu, gdy potrzebuje tylko jednej metody: zamiany. (Wstawienie może być postrzegane jako zastąpienie tekstu bez tekstu, a usunięcie może być postrzegane jako zastąpienie tekstu bez tekstu).

Zwykle wszystko, co chcesz wiedzieć, to kiedy tekst w polu się zmienił , więc typowa DocumentListenerimplementacja ma trzy metody wywołujące jedną metodę.

Dlatego stworzyłem następującą metodę narzędzia, która pozwala używać prostszego ChangeListenerniż DocumentListener. (Wykorzystuje składnię lambda Java 8, ale w razie potrzeby można ją dostosować do starej 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);
}

W przeciwieństwie do dodawania detektora bezpośrednio do dokumentu, obsługuje to (niecodzienne) przypadki, w których instalujesz nowy obiekt dokumentu na komponencie tekstowym. Ponadto rozwiązuje problem wymieniony w odpowiedzi Jean-Marc Astesana , w którym dokument czasami wywołuje więcej zdarzeń, niż jest to konieczne.

W każdym razie ta metoda pozwala zastąpić irytujący kod, który wygląda następująco:

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

Z:

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

Kod udostępniony publicznie. Baw się dobrze!


5
Podobne rozwiązanie: utwórz abstract class DocumentChangeListener implements DocumentListenerdodatkową metodę abstrakcyjną change(DocumentEvent e), którą wywołujesz ze wszystkich 3 innych metod. Wydaje mi się to bardziej oczywiste, ponieważ używa mniej więcej tej samej logiki co abstract *Adaptersłuchacze.
geronimo

+1 jako changedUpdatemetodę należy wywoływać jawnie poprzez połączenie w ramach każdego z nich, insertUpdatei removeUpdateżeby to zadziałało.
Kais

16

Wystarczy utworzyć interfejs, który rozszerza DocumentListener i implementuje wszystkie metody 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);
    }
}

i wtedy:

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

lub możesz nawet użyć wyrażenia lambda:

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

1
Nie zapominaj, że to rozwiązanie wymaga klasy abstrakcyjnej zamiast interfejsu we wszystkich wersjach wcześniejszych niż Java 8.
klaar

15

Należy pamiętać, że gdy użytkownik zmodyfikuje pole, DocumentListener może czasami odebrać dwa zdarzenia. Na przykład, jeśli użytkownik wybierze całą zawartość pola, a następnie naciśnij klawisz, otrzymasz removeUpdate (cała zawartość jest usuwana) i insertUpdate. W twoim przypadku nie sądzę, że jest to problem, ale ogólnie rzecz biorąc, jest. Niestety wydaje się, że nie ma możliwości śledzenia zawartości pola tekstowego bez podklasy JTextField. Oto kod klasy, która udostępnia właściwość „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 ma już typ textField, który mapuje zmiany dokumentu we właściwości - nazywa się JFormattedTextField :-)
kleopatra

11

Wiem, że odnosi się to do naprawdę starego problemu, jednak spowodowało to również pewne problemy. Gdy kleopatra odpowiedziała w powyższym komentarzu, rozwiązałem problem za pomocą JFormattedTextField. Jednak rozwiązanie wymaga nieco więcej pracy, ale jest fajniejsze.

JFormattedTextFieldNie domyślnie wywołać zmianę właściwości po każdym tekst zmienia się w tej dziedzinie. Domyślny konstruktor JFormattedTextFieldnie tworzy formatera.

Jednak, aby zrobić to, co sugerował PO, musisz użyć formatera, który wywoła commitEdit()metodę po każdej poprawnej edycji pola. commitEdit()Metody jest to, co powoduje zmianę właściwości z tego co widzę i bez formater, to jest wyzwalany domyślnie na zmianę ostrości lub gdy zostanie naciśnięty klawisz Enter.

Aby uzyskać więcej informacji, zobacz http://docs.oracle.com/javase/tutorial/uiswing/components/formattedtextfield.html#value .

Utwórz domyślny obiekt formatter ( DefaultFormatter), który zostanie przekazany do niego JFormattedTextFieldza pośrednictwem jego konstruktora lub metody ustawiającej. Jedną z metod domyślnego formatera jest setCommitsOnValidEdit(boolean commit)ustawienie formatera na wyzwalanie commitEdit()metody przy każdej zmianie tekstu. Można to następnie odebrać za pomocą a PropertyChangeListeneri propertyChange()metody.


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

Ale nie chciałbym po prostu parsować niczego, co użytkownik (może przypadkiem) dotknie jego klawiatury na Integer. Powinieneś złapać każde Exceptionrzucone i upewnić się, że JTextFieldnie jest puste.


2

Jeśli użyjemy uruchamialnej metody SwingUtilities.invokeLater () podczas korzystania z programu nasłuchiwania dokumentów aplikacja czasami się zacina i wymaga czasu na aktualizację wyniku (zgodnie z moim eksperymentem). Zamiast tego możemy również użyć zdarzenia KeyReleased dla detektora zmian pola tekstowego, jak wspomniano tutaj .

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

1

była to zaktualizowana wersja Codemwnci. jego kod jest całkiem w porządku i działa świetnie, z wyjątkiem komunikatu o błędzie. Aby uniknąć błędu, musisz zmienić instrukcję warunku.

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

Twoja adaptacja uruchamia okno dialogowe komunikatu o błędzie za każdym razem, gdy w polu tekstowym zostanie wprowadzony dowolny ciąg dłuższy niż długość = 0. Więc to w zasadzie dowolny ciąg inny niż pusty ciąg. To nie jest wymagane rozwiązanie.
klaar

0

Do sterowania można użyć nawet „MouseExited”. przykład:

 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
nie bardzo: wymaganie polega na robieniu czegoś po zmianie tekstu - to nie ma związku z mouseEvent ;-)
kleopatra

0

Jestem zupełnie nowy w WindowBuilder i tak naprawdę dopiero po kilku latach wracam do Javy, ale zaimplementowałem „coś”, a potem pomyślałem, że to sprawdzę i napotkałem ten wątek.

Jestem w trakcie testowania tego, więc biorąc pod uwagę to, że jestem nowy w tym wszystkim, jestem pewien, że czegoś mi brakuje.

Oto co zrobiłem, gdzie „runTxt” jest polem tekstowym, a „runName” jest członkiem danych klasy:

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

Wydaje się o wiele prostsze niż to, co jest tutaj do tej pory, i wydaje się, że działa, ale skoro jestem w trakcie pisania tego, doceniłbym usłyszenie o jakichkolwiek przeoczonych problemach. Czy to problem, że użytkownik może wprowadzić i pozostawić pole tekstowe bez wprowadzania zmian? Myślę, że wszystko, co zrobiłeś, to niepotrzebne zadanie.


-1

Użyj KeyListener (który uruchamia się na dowolnym kluczu) zamiast ActionListener (który uruchamia się po wejściu)


To nie działa, ponieważ wartość pola nie została poprawnie przechwycona, field.getText()zwraca wartość początkową. a event ( arg0.getKeyChar()) zwraca wciśnięty klawisz. Sprawdzanie błędów jest potrzebne do ustalenia, czy powinieneś połączyć tekst z polem.
glend

@glend, możesz użyć zdarzenia keyReleased zamiast zdarzenia keyTyped. To zadziałało dla mnie i uzyskałem pełną wartość.
Kakumanu siva krishna

-1

DocumentFilter ? Daje to możliwość manipulacji.

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

Przepraszam. Używam Jython (Python w Javie) - ale łatwy do zrozumienia

# 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));
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.