I want two textfields (from now A and B) sharing the same content as the user inputs on any of them. I can make one mirror the other (B mirrors A) or the opposite (A mirrors B). But when I keep both DocumentListeners the execution starts to throw Exceptions.
According to the Oracle's Docs I can't use a DocumentListener to mutate the content of a document from within the Listener itself. Which I find weird since I already did it in the first case (B mirrors A) or the opposite case. Anyway the code still "works" but with an Exception thrown every two events triggered.
KeyListeners are not reliable for this particular case and I refuse to use buttons because I like the real-time look DocumentListener gives.
Any suggestions?
Here's my code:
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
public class Mirror {
private JTextField oriText;
private JTextField mirrorText;
private static int debugCounter; //Counts the times an Event is Triggered.
public static void main(String[] args) {
Mirror gui = new Mirror();
gui.build();
}
public void build(){
JFrame frame = new JFrame();
JPanel panel = new JPanel();
panel.setLayout(new GridBagLayout());
GridBagConstraints c = new GridBagConstraints();
frame.getContentPane().add(panel);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
c.gridx = 0;
c.gridy = 0;
JLabel original = new JLabel("Original");
panel.add(original, c);
c.gridy = 1;
oriText = new JTextField(10);
panel.add(oriText,c);
c.gridy = 2;
JLabel mirror = new JLabel("Mirror");
panel.add(mirror, c);
c.gridy = 3;
mirrorText = new JTextField(10);
panel.add(mirrorText, c);
mirrorText.getDocument().addDocumentListener(new MyDocumentListenerII()); // Comment this line to see only the 1st Case (B mirrors A)
oriText.getDocument().addDocumentListener(new MyDocumentListener()); // Comment this line to see only the 2nd Case (A mirrors B)
frame.pack();
frame.setVisible(true);
}
class MyDocumentListener implements DocumentListener{
@Override
public void changedUpdate(DocumentEvent e) {
//Does nothing.
}
@Override
public void insertUpdate(DocumentEvent e) {
mirror();
}
@Override
public void removeUpdate(DocumentEvent e) {
mirror();
}
}
class MyDocumentListenerII implements DocumentListener{
@Override
public void changedUpdate(DocumentEvent e) {
// Does nothing.
}
@Override
public void insertUpdate(DocumentEvent e) {
mirror1();
}
@Override
public void removeUpdate(DocumentEvent e) {
mirror1();
}
}
public void mirror(){
if (!oriText.getText().equals(mirrorText.getText())){ //Without this each Event trigger the other in some sort of Paradoxical cycle.
mirrorText.setText(oriText.getText());
debugCounter++;
System.out.println(debugCounter+" events triggered");
}
}
public void mirror1(){
if (!mirrorText.getText().equals(oriText.getText())){
oriText.setText(mirrorText.getText());
debugCounter++;
System.out.println(debugCounter+" events triggered");
}
}
}
The problem you're having is that since both JTextField
s need to be sync, each field's DocumentListener
needs to update the other field. However, that update causes the other DocumentListener
to attempt to update the first field, causing the thrown IllegalStateException
, since something is attempting to modify the field while a DocumentListener
for it is executing.
The solution is to block calls to setText(String)
when a DocumentListener
is being executed for that field. This can be done with boolean
variables. Below is the code for one of the DocumentListener
s:
textFieldA.getDocument().addDocumentListener(new DocumentListener() {
@Override
public void removeUpdate (DocumentEvent e) {
blockA = true;
if (!blockB) textFieldB.setText(textFieldA.getText());
blockA = false;
}
@Override
public void insertUpdate (DocumentEvent e) {
blockA = true;
if (!blockB) textFieldB.setText(textFieldA.getText());
blockA = false;
}
@Override
public void changedUpdate (DocumentEvent e) {
blockA = true;
if (!blockB) textFieldB.setText(textFieldA.getText());
blockA = false;
}
});
where blockA
and blockB
are boolean
instance fields (or possibly final
variables in the method). Now, when a method fires in textFieldA
's DocumentListener
, a flag is set to indicate not to use textFieldA.setText(String)
. We, also see how textFieldB.setText(String)
is blocked when blockB
is set. The DocumentListener
for textFieldB
looks about the same.
Now, textFieldA
won't be set during a call inside its DocumentListener
, same for the other field. Such a call would be redundant anyway: the text of each field would be same at that point.