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!

user236501
fonte

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);
     }
  }
});
Codemwnci
fonte
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!

Boann
fonte
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
});
Andrey Megvinov
fonte
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);
        }
    }
Jean-Marc Astesana
fonte
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

Astridax
fonte
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.

DerBobby
fonte
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());
    }
});
Kakumanu siva krishna
fonte
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);
     }
  }
});
Bipul Chandra Dev Nath
fonte
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) {

        }

    }   
fishgold192
fonte
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.

Homem foguete
fonte
-1

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

usuario
fonte
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));
papuga
fonte