好的,我有一个挥杆事件监听器问题...简短的介绍,我开发了一个Java应用程序,它具有由MVC模式构成的Swing UI。
基于此类,模型和视图仅通过控制器类连接。视图类包含用于用户输入的文本字段,该字段应使用用户输入的不按钮更新模型类。这意味着我需要一个JTextField侦听器,以等待用户输入/更改文本...
我尝试了DocumentListener
,但是它不起作用,抛出了异常:java.lang.IllegalStateException: Attempt to mutate in notification
我认为这里的问题是,如果属性发生了变化并且控制器再次通知/更改了视图,则模型类也调用了控制器->结果:无限循环
我发现的两种解决方案都不适合我
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);
}
}
问题是,由于用户输入了要更改模型的内容而调用文档侦听器时,调用了属性更改的view方法,而view用相同的文本替换了该文本,这再次引发了文档更改事件并调用了侦听器。 ..无限循环
我尝试使用ActionListener
正常工作,但是用户必须按回车键才能指定更改... 是否还有其他选项可以侦听JTextField中的文本更改?没有DocumentListener
?还是应该通过我的MVC模式更改什么才能解决此问题?
编辑
我尝试了 Peter Walser 的解决方案,但引发了新的异常:
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
在通知模型期间,JTextField
的文档似乎仍然被锁定,因为它调用了方法setText
引发了异常,并且此操作是非法的,但我不知道为什么? !
编辑
现在,我通过 Peter Walser 的说明和第一个解决方案,并执行了EDT上DocumentListener
设置的说明,解决了该错误!
答案 0 :(得分:1)
有两种方法可以正确解决此问题:
没有必要通知,如果属性值与以前相同(根本没有更改)。避免不必要的事件将有效地打破循环:
public void setHost(String host) {
// check if property actually changed
if (Objects.equals(this.host, host) return;
String oldHost = this.host;
this.host = host;
this.firePropertyChange("Host", oldHost, this.host);
}
或(精美的紧凑形式):
public void setHost(String host) {
if (!Objects.equals(this.host, host)) {
firePropertyChange("Host", this.host, this.host=host);
}
}
更改模型中的属性可以更改视图中的属性,可以更改模型中的属性可以更改... -这可以快速运行。
要打破这些级联,请执行单向同步:
当模型通知视图有关更改时,忽略任何级联更新。
为此,您需要在控制器上设置一个标志(在您的情况下,该视图包含微控制器,也称为Swing侦听器):
MyView.java :
boolean updating;
@Override public void modelPropertyChange(final PropertyChangeEvent event)
{
if (updating) {
// cascading update, ignore
return;
}
updating=true;
try {
if(event.getPropertyName().equals("Username")) {
{
String username = (String) event.getNewValue();
this.nameField.setText(username);
}
...
}
finally {
updating=false;
}
}
第一种方法非常简单(但是在处理复杂的对象和集合时可能会变得复杂)。 第二种方法很容易,并且在设计上更容易理解-视图始终代表模型(不丢失任何更改),并且级联更新被阻止。
答案 1 :(得分:0)
在您的PropertyChangeListener中的“用户名”属性中,您可以:
DocumentListener
DocumentListener
添加回文本字段。 我尝试了DocumentListener,但是它不起作用,抛出了异常:java.lang.IllegalStateException:尝试在通知中进行更改
仅供参考,要摆脱该消息,您可以将代码包装在Swing utilities.invokeLater()中,以便在侦听器代码完成执行之后执行该代码。尽管我认为您仍然会遇到无限循环。