2

I want to bind a Java Swing JTextField to a String attribute of my data-model.

Therefore I want to use a PropertyChangeListener which listens for changes in the text-attribute in the model and then updates the textfield in the GUI (model-->GUI).

For the other direction (GUI-->model) I want to use a DocumentListener on the JTextField's document which should update the model, when the user changes the text in the field.

When I do so, I get a IllegalStateException as soon as i change the text in the textfield.

java.lang.IllegalStateException: Attempt to mutate in notification
  at javax.swing.text.AbstractDocument.writeLock(AbstractDocument.java:1323)
  at javax.swing.text.AbstractDocument.replace(AbstractDocument.java:644)
  at javax.swing.text.JTextComponent.setText(JTextComponent.java:1693)
      ...

How can I avoid this?

BTW: BeansBinding is not really a statisfying solution for me as it is abandoned and in addition I have to instantiate some objects in the object-graph before beeing able to write to them.

t3chris
  • 1,390
  • 1
  • 15
  • 25
  • Is your `PropertyChangeListener` updating the text field from the event dispatch thread? Can you post some code? – Duncan Jones Jul 31 '12 at 14:39
  • Yes, it is firing it's updates from EDT. If I add SwingUtilities.invokeLater(...) to the DocumentListener, everything works. The drawback is having a lot of SwingUtilities and Runnables in my code. Maybe there's a more elegant solution... – t3chris Jul 31 '12 at 15:09

3 Answers3

4

It is not a race condition, since everything happens on the same thread. The problem is as the exception says: "Attempt to change the text while notifying listeners that text has been changed".

Since in this scenario you trying to replace the text with the same text, you could just skip the update in your property change listener:

if(!modelText.equals(textField.getText())) {
  textField.setText(modelText);
}
Walter Laan
  • 2,956
  • 17
  • 15
2

This issue has been covered before: https://stackoverflow.com/a/2789307/474189.

In short, document listeners should not modify the contents of the document.

Community
  • 1
  • 1
Duncan Jones
  • 67,400
  • 29
  • 193
  • 254
2

Well, probably an ugly workaround would be to use SwingUtilities.invokeLater() to defer the GUI update to outside the notification.

However I think the whole approach of synchronizing a second level model object with the GUI instantly is flawed. The GUI has its own model (the Document the TextField uses). Either your model should implement that, then you don't need to do anything special (then your model object would replace the normal Document as model).

Or you decide on a proper point where to synchronize GUI and your data model. Usually good point are when a Window/Dialog opens and when the user clicks Ok/Save. How would you implement 'Cancel' if you synch your models instantly?

Durandal
  • 19,919
  • 4
  • 36
  • 70
  • I've implemented 'Cancel' by having my DataModel implementing UndoableEditSupport. I register a fresh instance of UndoManager when the user opens the dialog, if it clicks OK, the UndoManager is discarded, in case of cancel I call undo on all operations in the UndoManager. My idea was to just "bind" all attributes of the data-model to the gui using the above described combination of PropertyChangeListeners and DocumentListeners. – t3chris Jul 31 '12 at 15:05
  • Sounds overengineered to me - but then you could probably use the 'replace the TextField model with your own model' approach and it should fit with your architecture. – Durandal Jul 31 '12 at 15:11
  • So if I get you right, you would recommend to manually sync the GUI with the model when it's brought to screen and then, when the user clicks okay to check for modifications and write them back if there are any? Are there any default approaches on how to handle such situations? – t3chris Jul 31 '12 at 15:13
  • Sadly, there seems to be no *standard* approach to bind model/GUI. GUI frameworks usually leave the problem to the application as do ORM's on the other side. I've never seen a *simple*, yet *flexible* solution for this. About the 'instant' synchronization, just ask yourself: what does it *gain* you at what cost? Just because I think its overengineered doesn't necessarily mean it is - I don't know your specific needs. – Durandal Jul 31 '12 at 15:18
  • You may want to take a look at data binding frameworks (I haven't worked with any of them, so I can't comment on those) mentioned in [this question](http://stackoverflow.com/questions/2400998/swing-data-binding-frameworks). – Durandal Jul 31 '12 at 15:21