Search code examples
javaswingjtextfield

Why JTextField.setText will fire DocumentListener's removeUpdate() before changedUpdate()?


This is my code:

import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.Document;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class Frame extends JFrame {

    private JTextField txt1 = new JTextField(10);
    private JTextField txt2 = new JTextField(10);
    private JButton btn = new JButton("Set Text");

    public Frame() {
        super("Latihan");
        setLayout(new FlowLayout());
        btn.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                txt1.setText("TEST"); txt2.setText("TEST2");
            }
        });

        txt1.getDocument().addDocumentListener(new TheDocumentListener("txt1"));
        txt2.getDocument().addDocumentListener(new TheDocumentListener("txt2"));

        add(txt1);
        add(txt2);
        add(btn);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        pack();
        setVisible(true);
    }

    public static void main (String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                new Frame();
            }
        });
    }
}

class TheDocumentListener implements DocumentListener {

    private String source;

    public TheDocumentListener(String source) {
        this.source = source;
    }
    @Override
    public void insertUpdate(DocumentEvent e) {
        System.out.println("insertUpdate from " + source);
    }

    @Override
    public void removeUpdate(DocumentEvent e) {
        System.out.println("removeUpdate from " + source);
    }

    @Override
    public void changedUpdate(DocumentEvent e) {
        System.out.println("changedUpdate from " + source);
    }
}

When I click on the JButton for the first time, only insertUpdate() will be called:

insertUpdate from txt1
insertUpdate from txt2

But if I click the button again, removeUpdate() will be called before insertUpdate():

removeUpdate from txt1
insertUpdate from txt1
removeUpdate from txt2
insertUpdate from txt2

Is this expected behaviour or something wrong in my code?

Can I make insertUpdate the only method that was being called when performing JTextField.setText? I want to make sure removeUpdate is being called only when user delete text in the text field. How to do that?


Solution

  • This is the expected behavior of string replacement. What setText() actually does is remove the whole string and set a new one. Here is the implementation of JTextField.setText():

    public void setText(String t) {
        try {
            Document doc = getDocument();
            if (doc instanceof AbstractDocument) {
                ((AbstractDocument)doc).replace(0, doc.getLength(), t,null);
            }
            else {
                doc.remove(0, doc.getLength());
                doc.insertString(0, t, null);
            }
        } catch (BadLocationException e) {
        UIManager.getLookAndFeel().provideErrorFeedback(JTextComponent.this);
        }
    }
    

    As you can see, AbstractDocument.replace() is executed for AbstractDocument docs. Otherwise, remove() and insert() are executed.

    From AbstractDocument.replace() documentation:

    Deletes the region of text from offset to offset + length, and replaces it with text. It is up to the implementation as to how this is implemented, some implementations may treat this as two distinct operations: a remove followed by an insert, others may treat the replace as one atomic operation.

    So it depends on the document implementation. PlainDocument for example inherits basic implementation of AbstractDocument. A PlainDocument is the default document for text fields.

    You can always create you own document implementation if needed, or maybe installing a document filter. See Using Text Components tutorial for details. Not sure though, what is the reason behind this behavior change you're trying to achieve.