Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 851
jocki Avatar asked Mar 04 '13 19:03

jocki


People also ask

What events does JTextField generate?

JTextField: generates "ActionEvent" using the ActionListener interface. When a user press "Enter", an ActionEvent is fired and performs tasks defined in actionPerformed() method. JComboBox: generates "ItemEvent" and "ActionEvent" implementing the ActionListener and ItemListener interface respectively.

What is the role of JTextField?

JTextField is a lightweight component that allows the editing of a single line of text. For information on and examples of using text fields, see How to Use Text Fields in The Java Tutorial. JTextField is intended to be source-compatible with java.

What is DocumentListener?

public interface DocumentListener extends EventListener. Interface for an observer to register to receive notifications of changes to a text document. The default implementation of the Document interface (AbstractDocument) supports asynchronous mutations.

What is JFormattedTextField?

A JFormattedTextField is like a normal text field except that is controls the validity of the characters the user type and it can be associated with a formatter that specifies the characters the user can enter. A JFormattedTextField is a subclass of Format class to build a formatted text field.


1 Answers

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.

like image 92
tenorsax Avatar answered Sep 27 '22 19:09

tenorsax