Слухач зміни значення на JTextField


215

Я хочу, щоб поле повідомлень з’являлося відразу після того, як користувач змінює значення в текстовому полі. Наразі мені потрібно натиснути клавішу введення, щоб отримати поле з повідомленням. Чи є щось погано з моїм кодом?

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

Будь-яка допомога буде вдячна!

Відповіді:


373

Додайте слухача до базового документа, який автоматично створюється для вас.

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

хороший формат для передачі попереджень / типів. Цей же малюнок буде корисним для обробки подвійних сум (цифри продажів / ціни, введені або відображені)
Макс Вест

він працює добре, але у мене є запит, який, коли я вставляю текст у текстове поле, то я хочу викликати метод. Я не маю особливих уявлень про те, як це робиться ..

У мене виникли проблеми з тим, що JTable не отримував оновлення текстового поля від редагованого JComboBox при натисканні іншої комірки таблиці, і функція insertUpdate тут була єдиним способом змусити її правильно працювати.
winchella

14
"Масаж помилок"
ungato

51

Звичайна відповідь на це - «використовувати а DocumentListener». Однак я завжди вважаю цей інтерфейс громіздким. По-справжньому інтерфейс перероблений. У ньому є три способи вставки, видалення та заміни тексту, коли йому потрібен лише один метод: заміна. (Вставлення може розглядатися як заміна тексту без тексту, а видалення може розглядатися як заміна тексту без тексту.)

Зазвичай все, що ви хочете, це знати, коли текст у полі змінився , тому типовою DocumentListenerреалізацією є три методи, що викликають один метод.

Тому я зробив наступний корисний метод, який дозволяє використовувати простіший, ChangeListenerа не a DocumentListener. (Він використовує лямбда-синтаксис Java 8, але за потреби ви можете адаптувати його до старої 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);
}

На відміну від додавання слухача безпосередньо до документа, це обробляє (нечастий) випадок встановлення нового об’єкта документа на текстовий компонент. Крім того, він вирішує проблему, згадану у відповіді Жана-Марка Астесани , де документ іноді спричиняє більше подій, ніж потрібно.

У будь-якому випадку цей метод дозволяє замінити дратівливий код, який виглядає приблизно так:

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

З:

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

Код переданий у загальнодоступне надбання. Весело!


5
Подібне рішення: створіть abstract class DocumentChangeListener implements DocumentListenerдодатковий абстрактний метод, change(DocumentEvent e)який ви викликаєте з усіх трьох інших методів. Мені це здається більш очевидним, оскільки він використовує більш-менш ту ж логіку, що і abstract *Adapterслухачі.
geronimo

+1 , як changedUpdateметод повинен викликатися явно за допомогою з'єднання кожному з insertUpdateі для removeUpdateтого, щоб змусити його працювати ..
Кайса

16

Просто створіть інтерфейс, який розширює DocumentListener та реалізує всі методи 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);
    }
}

і потім:

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

або ви навіть можете використовувати лямбда-вираз:

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

1
Не забувайте, що для цього рішення потрібен абстрактний клас замість інтерфейсу у всіх версіях до Java 8.
klaar

15

Майте на увазі, що коли користувач модифікує поле, DocumentListener може колись отримати дві події. Наприклад, якщо користувач вибирає весь вміст поля, а потім натискає клавішу, ви отримаєте deleteUpdate (весь вміст видалено) та insertUpdate. У вашому випадку я не думаю, що це проблема, але, взагалі кажучи, це є. На жаль, здається, немає способу відстежувати вміст textField без підкласифікації JTextField. Ось код класу, який надає властивість "текст":

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 вже є тип textField, який відображає зміни документа у властивості - він називається JFormattedTextField :-)
kleopatra

11

Я знаю, що це стосується дійсно старої проблеми, однак, це викликало і деякі проблеми. Як kleopatra відповів у коментарі вище, я вирішив проблему з a JFormattedTextField. Однак рішення вимагає трохи більше роботи, але акуратніше.

За JFormattedTextFieldзамовчуванням не запускає зміну властивості після кожної зміни тексту в полі. Конструктор за замовчуванням JFormattedTextFieldне створює форматер.

Однак, щоб зробити те, що запропонував ОП, вам потрібно використовувати формат, який буде викликати commitEdit()метод після кожного дійсного редагування поля. commitEdit()Метод , що викликає зміна властивостей від того, що я можу побачити і без форматер, це спрацьовує за замовчуванням на зміну фокуса або коли клавіша введення натиснута.

Докладнішу інформацію див. У розділі http://docs.oracle.com/javase/tutorial/uiswing/components/formattedtextfield.html#value .

Створіть об'єкт formatter ( DefaultFormatter) за замовчуванням, який слід передати JFormattedTextFieldабо через його конструктор, або метод setter. Одним із методів форматування за замовчуванням єsetCommitsOnValidEdit(boolean commit) , що встановлює форматтер запускати commitEdit()метод щоразу, коли текст змінюється. Це може потім бути підібрано з допомогою PropertyChangeListenerі на propertyChange()методі.


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

Але я б не просто розібрав те, що користувач (можливо, випадково) торкнеться своєї клавіатури в антені Integer. Ви повинні зловити будь-який Exceptionкинутий і переконатися, що JTextFieldце не порожнє.


2

Якщо ми користуємося методом запуску SwingUtilities.invokeLater () під час використання програми Document listener іноді застрягає і потрібен час для оновлення результату (відповідно до мого експерименту). Замість цього ми можемо також використовувати подію KeyReleased для слухача змін текстових полів, як згадувалося тут .

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

1

це була оновлена ​​версія Codemwnci. його код досить чудовий і чудово працює, окрім повідомлення про помилку. Щоб уникнути помилок, потрібно змінити оператор умови.

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

Ваша адаптація запускає діалогове вікно повідомлення про помилку, коли будь-яка рядок, довший довжини = 0, вводиться в текстове поле. Отже, це в основному будь-який рядок, окрім порожнього рядка. Це не запитуване рішення.
klaar

0

Ви можете використовувати навіть "MouseExited" для управління. приклад:

 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
насправді: вимога щось робить, коли текст змінено - це не пов’язано з mouseEvents ;-)
kleopatra

0

Я абсолютно новий для WindowBuilder, і насправді просто повернувся до Java через кілька років, але я реалізував "щось", а потім подумав, що перегляну його і натрапив на цю тему.

Я в середині тестую це, тож, будучи новим у всьому цьому, я впевнений, що мені щось не вистачає.

Ось що я зробив, де "runTxt" - це текстове поле, а "runName" - член даних класу:

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

Здається, це набагато простіше, ніж тут, і, здається, працює, але, оскільки я в середині цього пишу, я буду вдячний почути будь-які недоглянуті готчі. Це проблема, через яку користувач може ввести та залишити текстове поле без внесення змін? Я думаю, що все, що ви зробили, - це зайве завдання.


-1

Використовуйте KeyListener (який запускає будь-яку клавішу), а не ActionListener (який спрацьовує при введенні)


Це не працює, оскільки значення поля неправильно захоплено, field.getText()повертає початкове значення. і event ( arg0.getKeyChar()) повертає перевірку помилок натиснутою клавішею, щоб визначити, чи слід поєднувати текст з полем.
глед

@glend, ви можете використовувати keyReleased події замість keyTyped події. Це працювало для мене і отримувало повну цінність.
Kakumanu siva krishna

-1

DocumentFilter ? Це дає вам можливість маніпулювати.

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

Вибачте. Я використовую Jython (Python на Java) - але легко зрозуміти

# 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));
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.