Listener de alteração de valor para JTextField


215

Quero que a caixa de mensagem apareça imediatamente após o usuário alterar o valor no campo de texto. Atualmente, preciso pressionar a tecla Enter para que a caixa de mensagem seja exibida. Há algo de errado com o meu 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);
        }       
    }
}

Qualquer ajuda seria apreciada!

Respostas:


373

Adicione um ouvinte ao documento subjacente, criado automaticamente para você.

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

bom formato para o aviso / tipo de elenco. Mesmo padrão será útil para lidar com quantidades duplas (números de vendas / preços entrado ou exibido)
— Max Oeste

está funcionando bem, mas tenho uma consulta que, quando insiro algum texto no campo de texto, desejo chamar um método. eu não tenho muita idéia sobre como é feito ..

Eu estava tendo problemas com uma JTable para não obter atualizações da caixa de texto de um JComboBox editável ao clicar em outra célula da tabela, e a função insertUpdate aqui era a única maneira de fazê-la funcionar corretamente.
— Winchella #

14
"Massagem com erro"
— ungato

51

A resposta usual para isso é "use um DocumentListener ". No entanto, eu sempre acho essa interface complicada. Sinceramente, a interface está com excesso de engenharia. Ele possui três métodos, para inserção, remoção e substituição de texto, quando ele precisa apenas de um método: substituição. (Uma inserção pode ser vista como uma substituição de nenhum texto por algum texto, e uma remoção pode ser vista como uma substituição de algum texto sem texto.)

Normalmente, tudo o que você deseja saber é quando o texto na caixa foi alterado , portanto, umDocumentListener implementação possui os três métodos que chamam um método.

Portanto, criei o seguinte método utilitário, que permite usar um método mais simples ChangeListenerdo que um DocumentListener. (Ele usa a sintaxe lambda do Java 8, mas você pode adaptá-lo ao Java antigo, se necessário.)

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

Ao contrário da adição de um ouvinte diretamente ao documento, isso lida com o caso (incomum) de instalar um novo objeto de documento em um componente de texto. Além disso, ele soluciona o problema mencionado na resposta de Jean-Marc Astesana , onde o documento às vezes dispara mais eventos do que o necessário.

De qualquer forma, esse método permite substituir um código irritante que se parece com isso:

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

Com:

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

Código liberado para domínio público. Diverta-se!


5
Solução semelhante: crie um abstract class DocumentChangeListener implements DocumentListenercom um método abstrato extra change(DocumentEvent e)que você chamará dos três outros métodos. Parece mais óbvio para mim, pois usa mais ou menos a mesma lógica que os abstract *Adapterouvintes.
— Geronimo 22/12

+1 como changedUpdatemétodo deve ser chamado explicitamente através de uma chamada dentro de cada um insertUpdatee removeUpdate, a fim de obter-lhe o trabalho ..
— Kais

16

Basta criar uma interface que estenda o DocumentListener e implemente todos os métodos do 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 depois:

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

ou você pode até usar a expressão lambda:

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

1
Não se esqueça que esta solução requer uma classe abstrata, em vez de uma interface em todas as versões anteriores ao Java 8.
— Klaar

15

Esteja ciente de que quando o usuário modificar o campo, o DocumentListener poderá, em algum momento, receber dois eventos. Por exemplo, se o usuário selecionar todo o conteúdo do campo e pressionar uma tecla, você receberá um removeUpdate (todo o conteúdo é removido) e um insertUpdate. No seu caso, não acho que seja um problema, mas, de um modo geral, é. Infelizmente, parece que não há como rastrear o conteúdo do textField sem subclassificar o JTextField. Aqui está o código de uma classe que fornece uma propriedade "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
Balançar já tem um tipo de textField que mapeia alterações do documento para uma propriedade - é chamado JFormattedTextField :-)
— kleopatra

11

Eu sei que isso se refere a um problema muito antigo, no entanto, também me causou alguns problemas. Como kleopatra respondeu em um comentário acima, resolvi o problema com a JFormattedTextField. No entanto, a solução requer um pouco mais de trabalho, mas é mais organizada.

O JFormattedTextFieldpadrão não aciona uma alteração de propriedade após cada alteração de texto no campo. O construtor padrão de JFormattedTextFieldnão cria um formatador.

No entanto, para fazer o que o OP sugeriu, você precisa usar um formatador que chamará o commitEdit()método após cada edição válida do campo. ocommitEdit() método é o que aciona a alteração de propriedade do que eu posso ver e, sem o formatador, isso é acionado por padrão em uma alteração de foco ou quando a tecla Enter é pressionada.

Consulte http://docs.oracle.com/javase/tutorial/uiswing/components/formattedtextfield.html#value para obter mais detalhes.

Crie um DefaultFormatterobjeto formatador ( ) padrão a ser passado para o JFormattedTextFieldmétodo através do construtor ou do método setter. Um método do formatador padrão é setCommitsOnValidEdit(boolean commit), que define o formatador para acionar o commitEdit()método toda vez que o texto é alterado. Isso pode ser obtido usando ae PropertyChangeListenero propertyChange()método


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

Mas eu não analisaria apenas qualquer coisa que o usuário (talvez por acidente) toque em seu teclado em um Integer. Você deve pegar qualquer Exceptions jogado e verifique se ele JTextFieldnão está vazio.


2

Se usarmos o método runnable SwingUtilities.invokeLater () enquanto estiver usando o aplicativo ouvinte de documentos, às vezes, ele fica travado e leva tempo para atualizar o resultado (conforme meu experimento). Em vez disso, também podemos usar o evento KeyReleased para o ouvinte de alteração de campo de texto, conforme mencionado aqui .

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

1

era a versão de atualização do Codemwnci. seu código é muito bom e funciona muito bem, exceto a mensagem de erro. Para evitar erros, você deve alterar a instrução de condição.

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

Sua adaptação dispara o diálogo da mensagem de erro sempre que qualquer sequência maior que length = 0 for inserida no campo de texto. Então isso é basicamente qualquer outra string que não seja uma string vazia. Essa não é a solução solicitada.
— Klaar

0

Você pode usar até "MouseExited" para controlar. exemplo:

 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
não é realmente: a exigência está fazendo algo quando o texto for alterado - que é sem relação com MouseEvents ;-)
— kleopatra

0

Sou novato no WindowBuilder e, de fato, estou voltando ao Java depois de alguns anos, mas implementei "alguma coisa", depois pensei em procurar e encontrar esse segmento.

Estou no meio de testar isso, então, com base em ser novo em tudo isso, tenho certeza de que devo estar perdendo alguma coisa.

Aqui está o que eu fiz, onde "runTxt" é uma caixa de texto e "runName" é um membro de dados da 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);
    }
}

Parece muito mais simples do que o que está aqui até agora e parece estar funcionando, mas, como estou escrevendo isso, gostaria de ouvir qualquer dica negligenciada. É um problema que o usuário pode entrar e sair da caixa de texto sem fazer alterações? Acho que tudo que você fez é uma tarefa desnecessária.


-1

Use um KeyListener (que aciona qualquer tecla) em vez do ActionListener (que aciona ao entrar)


Isso não funciona porque o valor do campo não é capturado corretamente, field.getText()retorna o valor inicial. e o evento ( arg0.getKeyChar()) retorna a verificação de erro pressionada pela tecla para determinar se você deve concatenar com o texto do campo.
— glEnd

@glend, você pode usar o evento keyReleased em vez do evento keyTyped. Funcionou para mim e obteve o valor completo.
— Kakumanu siva krishna 01/03/19

-1

DocumentFilter ? Dá-lhe a capacidade de manipular.

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

Desculpe. Estou usando Jython (Python em Java) - mas 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));
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.