javaswingjtextfieldillegalstateexceptiondocumentlistener

How to remove content of JTextField while DocumentListener is running?


for the following Code I get an IllegalStateException (Attempt to mutate in notification):

private class DocumentHandler implements DocumentListener {
    public void changedUpdate(DocumentEvent ev) {
        // unused
    }
    public void insertUpdate(DocumentEvent ev) {    
        if(textInput.getText().equals("...")) {
        JOptionPane.showMessageDialog(null, "...");
        textInput.setText("");
    }
}

Why can´t I change a TextField while a DocumentListener is active?

I tried to remove the DocumentListener while the TextField is set to " " but that didn´t help at all. I know that someone asked a very similar question before, but I don´t get that answer...

Thanks


Solution

  • In general, you don't -- you don't change the state of the Document while listening to it when using a DocumentListener. The two possible solutions that I know of:

    Unrelated side issue: your code shows a worrisome degree of coupling in that you try to change the text in a specific text component from within your listener. DocumentListeners should be fully agnostic of the text component whose document that they listen to, and in fact may be added to more than one Document.


    A DocumentFilter has 3 methods that need to be overridden and do what you expect them to do: what you would expect them to do:

    What is more, these methods do their actions before the text component renders the changes to the document.

    So within my method overrides, I extract the current document's text, and use the parameters to create what the new text will look like, for example for the replace method I did:

    @Override
    public void replace(FilterBypass fb, int offset, int length, String text, AttributeSet attrs)
            throws BadLocationException {
        String currentText = fb.getDocument().getText(0, fb.getDocument().getLength());
        StringBuilder sb = new StringBuilder(currentText);
    
        String newText = sb.replace(offset, offset + length, text).toString();
    

    I then do a boolean test on the newText to see if it is "good", and if so, call the super's method, here replace(...), passing in all the parameters. If not, if the newText fails the test, then I remove all the text from the document and show a JOptionPane.

    So in this example, I use this as my test method:

    private boolean isTextOk(String text) {
        return !BAD_TEXTS.contains(text);
    }
    

    which tests to see if the text is any of the disallowed Strings, here "...", " ", "oops", "OOPS", but it could be any Strings that you desire. Again, if the text passes the text, call the super's method, else remove the text:

    if (isTextOk(newText)) {
        super.replace(fb, offset, length, text, attrs);
    } else {
        badText(fb);
    }
    

    Where badText(fb) does:

    private void badText(FilterBypass fb) throws BadLocationException {
        remove(fb, 0, fb.getDocument().getLength());
        JOptionPane.showMessageDialog(null, "Don't do this!", "Bad Text Entered",
                JOptionPane.WARNING_MESSAGE);
    }
    

    The whole example is:

    import java.util.Arrays;
    import java.util.List;
    import javax.swing.*;
    import javax.swing.text.AttributeSet;
    import javax.swing.text.BadLocationException;
    import javax.swing.text.DocumentFilter;
    import javax.swing.text.PlainDocument;
    
    @SuppressWarnings("serial")
    public class ClearThreeDots extends JPanel {
        private JTextField textField = new JTextField(40);
    
        public ClearThreeDots() {
            ((PlainDocument) textField.getDocument()).setDocumentFilter(new MyDocFilter());
            add(textField);
        }
    
        private static void createAndShowGui() {
            ClearThreeDots mainPanel = new ClearThreeDots();
    
            JFrame frame = new JFrame("Clear Three Dots");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.getContentPane().add(mainPanel);
            frame.pack();
            frame.setLocationRelativeTo(null);
            frame.setVisible(true);
        }
    
        public static void main(String[] args) {
            SwingUtilities.invokeLater(() -> createAndShowGui());
        }
    }
    
    class MyDocFilter extends DocumentFilter {
        private static final List<String> BAD_TEXTS = Arrays.asList("...", "   ", "oops", "OOPS");
    
        @Override
        public void insertString(FilterBypass fb, int offset, String string, AttributeSet attr)
                throws BadLocationException {
            String currentText = fb.getDocument().getText(0, fb.getDocument().getLength());
            StringBuilder sb = new StringBuilder(currentText);
    
            String newText = sb.insert(offset, string).toString();
    
            if (isTextOk(newText)) {
                super.insertString(fb, offset, string, attr);
            } else {
                badText(fb);
            }
        }
    
        @Override
        public void remove(FilterBypass fb, int offset, int length) throws BadLocationException {
            String currentText = fb.getDocument().getText(0, fb.getDocument().getLength());
            StringBuilder sb = new StringBuilder(currentText);
    
            String newText = sb.replace(offset, offset + length, "").toString();
    
            if (isTextOk(newText)) {
                super.remove(fb, offset, length);
            } else {
                badText(fb);
            }
    
        }
    
        @Override
        public void replace(FilterBypass fb, int offset, int length, String text, AttributeSet attrs)
                throws BadLocationException {
            String currentText = fb.getDocument().getText(0, fb.getDocument().getLength());
            StringBuilder sb = new StringBuilder(currentText);
    
            String newText = sb.replace(offset, offset + length, text).toString();
    
            if (isTextOk(newText)) {
                super.replace(fb, offset, length, text, attrs);
            } else {
                badText(fb);
            }
    
        }
    
        private boolean isTextOk(String text) {
            return !BAD_TEXTS.contains(text);
        }
    
        private void badText(FilterBypass fb) throws BadLocationException {
            remove(fb, 0, fb.getDocument().getLength());
            JOptionPane.showMessageDialog(null, "Don't do this!", "Bad Text Entered",
                    JOptionPane.WARNING_MESSAGE);
        }
    
    }