摆动JTextField文本已更改侦听器DocumentListener无限循环

时间:2018-07-13 14:02:34

标签: java swing model-view-controller jtextfield documentlistener

好的,我有一个挥杆事件监听器问题...简短的介绍,我开发了一个Java应用程序,它具有由MVC模式构成的Swing UI。

  • MyView->文本由用户更改,并且视图应由控制器通知模型
  • MyModel->存储数据,并通过控制器将有关更改的信息通知视图
  • MyController->用于通知模型和查看更改的接口

基于此类,模型和视图仅通过控制器类连接。视图类包含用于用户输入的文本字段,该字段应使用用户输入的按钮更新模型类。这意味着我需要一个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设置的说明,解决了该错误!

2 个答案:

答案 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中的“用户名”属性中,您可以:

  1. 从文本字段中删除DocumentListener
  2. 更新文本字段
  3. DocumentListener添加回文本字段。
  

我尝试了DocumentListener,但是它不起作用,抛出了异常:java.lang.IllegalStateException:尝试在通知中进行更改

仅供参考,要摆脱该消息,您可以将代码包装在Swing utilities.invokeLater()中,以便在侦听器代码完成执行之后执行该代码。尽管我认为您仍然会遇到无限循环。