Okay I've a problem with a swing event listener... Short introduction I develop a Java application with a Swing UI structured by the MVC pattern.
- MyView -> The text is changed by the user and the view should inform model by controller
- MyModel -> Store the data and inform view about changes through controller
- MyController -> Interface used to inform model and view about changes
Based on this classes model and view are only connected through the controller class. The view class contains a text field for user input which should update the model class with the user input without pressing a button. This means I need a listener for JTextField that waits for user input/change of text...
I tried DocumentListener
but it doesn't work, a exception is thrown: java.lang.IllegalStateException: Attempt to mutate in notification
I think the problem here is that the model class called also the controller if properties changed and the controller informs/changes the view again -> Result: Infinity loop
Both solutions I found didn't work for me:
Swing JTextField on text change
JTextField listener when text changes that modifies textField's text
MyModel.java
public void setHost(String host) // Method called by controller to change model
{
String oldHost = this.host;
this.host = host;
this.firePropertyChange("Host", oldHost, this.host); // Model inform view about changes
}
MyView.java
@Override public void modelPropertyChange(final PropertyChangeEvent event)
{
// Method used to update view and called by controller
if(event.getPropertyName().equals("Username"))
{
String username = (String) event.getNewValue();
this.nameField.setText(username);
}
}
The problem is when the document listener is called because the user input something the model is changed call the property changed method of view and view replace the text with the same text which raise again a document changed event and the listener is called... Infinity loop
I tried to work with an ActionListener
it works fine but it is necessary for the user to press return to assign the changes... Are there any other options to listen for text changes in a JTextField without DocumentListener
? Or what should I changed by my MVC pattern to solve this issue?
EDIT
I tried the solution of Peter Walser but a new exception was thrown:
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.x1c1b.carrierpigeon.service.mvc.AbstractController.setModelProperty(AbstractController.java:62)
at org.x1c1b.carrierpigeon.desktop.ui.controller.LoginController.changeUsername(LoginController.java:12)
at org.x1c1b.carrierpigeon.desktop.ui.view.LoginView$UsernameChangedListener.updateFieldState(LoginView.java:221)
at org.x1c1b.carrierpigeon.desktop.ui.view.LoginView$UsernameChangedListener.insertUpdate(LoginView.java:203)
at javax.swing.text.AbstractDocument.fireInsertUpdate(AbstractDocument.java:201)
at javax.swing.text.AbstractDocument.handleInsertString(AbstractDocument.java:748)
at javax.swing.text.AbstractDocument.insertString(AbstractDocument.java:707)
at javax.swing.text.PlainDocument.insertString(PlainDocument.java:130)
at org.x1c1b.carrierpigeon.desktop.ui.util.TextFieldLimit.insertString(TextFieldLimit.java:26)
at javax.swing.text.AbstractDocument.replace(AbstractDocument.java:669)
at javax.swing.text.JTextComponent.replaceSelection(JTextComponent.java:1328)
at javax.swing.text.DefaultEditorKit$DefaultKeyTypedAction.actionPerformed(DefaultEditorKit.java:884)
at javax.swing.SwingUtilities.notifyAction(SwingUtilities.java:1668)
at javax.swing.JComponent.processKeyBinding(JComponent.java:2882)
at javax.swing.JComponent.processKeyBindings(JComponent.java:2929)
at javax.swing.JComponent.processKeyEvent(JComponent.java:2845)
at java.awt.Component.processEvent(Component.java:6316)
at java.awt.Container.processEvent(Container.java:2239)
at java.awt.Component.dispatchEventImpl(Component.java:4889)
at java.awt.Container.dispatchEventImpl(Container.java:2297)
at java.awt.Component.dispatchEvent(Component.java:4711)
at java.awt.KeyboardFocusManager.redispatchEvent(KeyboardFocusManager.java:1954)
at java.awt.DefaultKeyboardFocusManager.dispatchKeyEvent(DefaultKeyboardFocusManager.java:835)
at java.awt.DefaultKeyboardFocusManager.preDispatchKeyEvent(DefaultKeyboardFocusManager.java:1103)
at java.awt.DefaultKeyboardFocusManager.typeAheadAssertions(DefaultKeyboardFocusManager.java:974)
at java.awt.DefaultKeyboardFocusManager.dispatchEvent(DefaultKeyboardFocusManager.java:800)
at java.awt.Component.dispatchEventImpl(Component.java:4760)
at java.awt.Container.dispatchEventImpl(Container.java:2297)
at java.awt.Window.dispatchEventImpl(Window.java:2746)
at java.awt.Component.dispatchEvent(Component.java:4711)
at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:760)
at java.awt.EventQueue.access$500(EventQueue.java:97)
at java.awt.EventQueue$3.run(EventQueue.java:709)
at java.awt.EventQueue$3.run(EventQueue.java:703)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:74)
at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:84)
at java.awt.EventQueue$4.run(EventQueue.java:733)
at java.awt.EventQueue$4.run(EventQueue.java:731)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:74)
at java.awt.EventQueue.dispatchEvent(EventQueue.java:730)
at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:205)
at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:116)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:105)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:93)
at java.awt.EventDispatchThread.run(EventDispatchThread.java:82)
Caused by: java.lang.IllegalStateException: Attempt to mutate in notification
at javax.swing.text.AbstractDocument.writeLock(AbstractDocument.java:1338)
at javax.swing.text.AbstractDocument.replace(AbstractDocument.java:658)
at javax.swing.text.JTextComponent.setText(JTextComponent.java:1669)
at org.x1c1b.carrierpigeon.desktop.ui.view.LoginView.modelPropertyChange(LoginView.java:76)
at org.x1c1b.carrierpigeon.service.mvc.AbstractController.propertyChange(AbstractController.java:47)
at java.beans.PropertyChangeSupport.fire(PropertyChangeSupport.java:335)
at java.beans.PropertyChangeSupport.firePropertyChange(PropertyChangeSupport.java:327)
at java.beans.PropertyChangeSupport.firePropertyChange(PropertyChangeSupport.java:263)
at org.x1c1b.carrierpigeon.service.mvc.AbstractModel.firePropertyChange(AbstractModel.java:27)
at org.x1c1b.carrierpigeon.desktop.ui.model.LoginModel.setUsername(LoginModel.java:39)
... 52 more
It seems like the document of JTextField
is still locked during inform the model, because it calls the method setText
the exception is thrown and this operation is illegal but I can't figure out why?!
EDIT
For now I solved this bug by the instructions and the first solution of Peter Walser combined with executing the instructions set by the DocumentListener
on the EDT!