【问题标题】:JTextField DocumentListener exception mutate errorJTextField DocumentListener 异常变异错误
【发布时间】:2017-05-02 13:06:33
【问题描述】:

编辑:您认为可能重复的问题的解决方案(java.lang.IllegalStateException while using Document Listener in TextArea, Java) 正是我在代码中输入的内容来解决这个问题。我在编写自定义 DocumentListener 时使用了 3 种覆盖方法。但是,这并没有解决问题。

由于某种原因,在使用 actionListener 时这可以正常工作(请参阅此处 - Update JTextField.addActionListener without pressing "enter")。 actionListener 的问题是每次我想要更新标签时都必须点击“输入”。所以有人建议改用DocumentListener。做了这件事。我遇到了一个我无法弄清楚的错误,以及非常奇怪的行为。

由于代码太大,无法在此处发布,我已将项目压缩(与 Intellij 和 JRE 1.8 一起使用)> https://www.dropbox.com/s/pf4hiuk9y0jby7y/FF7LevelUpStatCalculator.zip?dl=0

隔离了问题所在的代码块(以便快速查看):

private void setHpBaseStatsTextFieldAction(){
    hpBaseStatsTextField.getDocument().addDocumentListener(
            new DocumentListener() {
                public void insertUpdate(DocumentEvent e) {
                    updateStatGridLabels();
                }
                public void removeUpdate(DocumentEvent e) {
                    updateStatGridLabels();
                }
                public void changedUpdate(DocumentEvent e) {
                    //Plain text components do not fire these events
                }

                public void updateStatGridLabels() {
                    String currCharacter = charSelCombo.getSelectedItem().toString();
                    String checkBaseHpInGui = hpBaseStatsTextField.getText();
                    int baseHpInGui = 0;
                    if (isInteger(checkBaseHpInGui)){
                        baseHpInGui = Integer.parseInt(checkBaseHpInGui);
                    }
                    if ((!(currCharacter.equals(guiCharSelDefaultValue[unselectedDefaultElement]))) && (isInteger(checkBaseHpInGui))){
                        characters[getSelectedCharactersIndex()].setBaseHp(baseHpInGui);
                        hpBaseStatsTextField.setText(Integer.toString(characters[getSelectedCharactersIndex()].getBaseHp()));
                        setHpStatGridRowValues();
                    }
                }
            }
    );
}

重现问题:

第 1 步:运行 StartGui.java

第 2 步:在字符组合框中选择 Cloud

第 3 步:输入整数数字并识别问题

我注意到它在尝试访问对象并在第 1338 行执行 setValue() method 时出现抱怨...有人知道为什么会发生这种情况吗?

更新:

DocumentFilter 似乎做得正确,只要我注释掉一行(检查最大值规则时需要)。这是到目前为止的代码:

private void setHpBaseStatsTextFieldAction(){
    ((AbstractDocument) hpBaseStatsTextField.getDocument()).setDocumentFilter(
            new DocumentFilter() {
                public void replace(DocumentFilter.FilterBypass fb, int offset, int length,
                                    String text, AttributeSet attrs) throws BadLocationException {
                    if (offset >= fb.getDocument().getLength()) {
                        System.out.println("Added: " + text);
                    } else {
                        String old = fb.getDocument().getText(offset, length);
                        System.out.println("Replaced " + old + " with " + text);
                    }
                    super.replace(fb, offset, length, text, attrs);
                    updateStatGridLabels();
                }

                public void insertString(DocumentFilter.FilterBypass fb, int offset,
                                         String text, AttributeSet attr) throws BadLocationException {
                    System.out.println("Added: " + text);
                    super.insertString(fb, offset, text, attr);
                    updateStatGridLabels();
                }

                public void remove(DocumentFilter.FilterBypass fb, int offset, int length)
                        throws BadLocationException {
                    System.out.println("Removed: " + fb.getDocument().getText(offset, length));
                    super.remove(fb, offset, length);
                    updateStatGridLabels();
                }

                public void updateStatGridLabels() {
                    String currCharacter = charSelCombo.getSelectedItem().toString();
                    String checkBaseHpInGui = hpBaseStatsTextField.getText();
                    int baseHpInGui = 0;
                    if (isInteger(checkBaseHpInGui)){
                        baseHpInGui = Integer.parseInt(checkBaseHpInGui);
                    }
                    if ((!(currCharacter.equals(guiCharSelDefaultValue[unselectedDefaultElement]))) && (isInteger(checkBaseHpInGui))){
                        characters[getSelectedCharactersIndex()].setBaseHp(baseHpInGui);
                        //hpBaseStatsTextField.setText(Integer.toString(characters[getSelectedCharactersIndex()].getBaseHp()));
                        setHpStatGridRowValues();
                    }
                }
            }
    );
}

我仍然需要一些帮助。

更新 2: 我开始按照 camickr 的建议使用 SwingUtilities.invokeLater。它适用于我在上面的代码 sn-p 中注释掉的行,但是,它将它设置为无限循环...

private void setHpBaseStatsTextFieldAction(){
    ((AbstractDocument) hpBaseStatsTextField.getDocument()).setDocumentFilter(
            new DocumentFilter() {
                boolean newTextReplaceSet = false;
                public void replace(DocumentFilter.FilterBypass fb, int offset, int length,
                                    String text, AttributeSet attrs) throws BadLocationException {
                    if (offset >= fb.getDocument().getLength()) {
                        System.out.println("Added: " + text);
                    } else {
                        String old = fb.getDocument().getText(offset, length);
                        System.out.println("Replaced " + old + " with " + text);
                    }
                    super.replace(fb, offset, length, text, attrs);
                    updateStatGridLabels();
                    newTextReplaceSet = true;
                }
                boolean newTextInsertSet = false;
                public void insertString(DocumentFilter.FilterBypass fb, int offset,
                                         String text, AttributeSet attr) throws BadLocationException {
                    System.out.println("Added: " + text);
                    super.insertString(fb, offset, text, attr);
                    updateStatGridLabels();
                    newTextInsertSet = true;
                }
                boolean newTextRemoveSet = false;
                public void remove(DocumentFilter.FilterBypass fb, int offset, int length)
                        throws BadLocationException {
                    System.out.println("Removed: " + fb.getDocument().getText(offset, length));
                    super.remove(fb, offset, length);
                    updateStatGridLabels();
                    newTextRemoveSet = true;
                }

                public void updateStatGridLabels() {
                    String currCharacter = charSelCombo.getSelectedItem().toString();
                    String checkBaseHpInGui = hpBaseStatsTextField.getText();
                    int baseHpInGui = 0;
                    if (isInteger(checkBaseHpInGui)){
                        baseHpInGui = Integer.parseInt(checkBaseHpInGui);
                    }
                    if ((!(currCharacter.equals(guiCharSelDefaultValue[unselectedDefaultElement]))) && (isInteger(checkBaseHpInGui))){
                        characters[getSelectedCharactersIndex()].setBaseHp(baseHpInGui);
                        if (!newTextReplaceSet || !newTextInsertSet || newTextRemoveSet) {
                            SwingUtilities.invokeLater(new Runnable() {
                                public void run() {
                                    System.out.println("run");
                                    hpBaseStatsTextField.setText(Integer.toString(characters[getSelectedCharactersIndex()].getBaseHp()));
                                }
                            });
                        }
                        //hpBaseStatsTextField.setText(Integer.toString(characters[getSelectedCharactersIndex()].getBaseHp()));
                        setHpStatGridRowValues();
                    }
                }
            }
    );
}

我似乎无法正确设置布尔标志的组合以阻止它执行无限循环,但仍然执行System.out.println("run"); 行,并在 JTextField 中进行每次更改。如果我将if statementif (!newTextReplaceSet || !newTextInsertSet || newTextRemoveSet) 更改为if (!newTextReplaceSet || newTextInsertSet || newTextRemoveSet),它将从以无限循环执行System.out.println("run"); 变为只执行一次(而不是在对JTextField 进行另一次更改时再次执行)。谁能帮帮我?

【问题讨论】:

  • 您似乎在为正在进行的更改触发 documentEvent 时尝试对文档进行修改。在actionPerformed中,文档更改结束,没有冲突,那么。
  • @Laurent G - 如果有办法让它更新用户输入的每个数字,那就太好了。有什么方法可以用 Swing 做到这一点?
  • @Berger 您认为可能重复的问题的解决方案正是我在代码中输入的内容来解决这个问题。我在编写自定义 DocumentListener 时使用了 3 种覆盖方法。但是,这并没有解决问题...

标签: java swing


【解决方案1】:

由于某种原因,这在使用 actionListener 时可以正常工作

这是因为 ActionListener 代码是在 Document 更新之后调用的。

我在编写自定义 DocumentListener 时使用了 3 种覆盖方法。但是,这并没有解决问题

问题是您无法更改 DocumentListener 中的 Document,因为 Document 尚未使用更改后的文本进行更新。

真正的问题是,您为什么要在文本字段中键入文本时尝试更新文本字段?

如果你真的需要这样做,那么有两个常见的解决方案:

  1. 不要使用 DocumentListener。相反,您可以使用DocumentFilter,它允许您在添加/删除测试时操作文档。

  2. 您需要延迟更新文档。你这样做的方法是使用SwingUtilities.invokeLater(...)。这会将代码放在Event Dispatch Thread (EDT) 的末尾。

baseHp 不能迭代到 9999 以上)。

然后这是基于编辑的逻辑,应该在 DocumentFilter 中完成。或者你甚至可以使用 JFormattedTextField 或者 JSpinner。

阅读Swing Tutorial。有使用 JFormattedTextField 和 JSpinner 的示例。您还可以在“文本组件功能”部分找到 DocumentFilter 的示例。

编辑:

但是,它会将其设置为无限循环...

嗯,首先你需要了解使用 DocumentListener 和 DocumentFilter 之间的区别。

  1. DocumentListener 用于在从 Document 中添加/删除数据后在您的应用程序中进行处理

  2. DocumentFilter 用于在将数据添加到 Document 之前编辑数据

但是,两种情况下的循环问题是一样的,解决方法也是一样的。

问题是你:

  1. 在文本字段中输入文本
  2. 一个监听器被调用
  3. 您操作文本并使用 setText() 重置数据
  4. 再次调用监听器。
  5. ... 3 和 4 不断重复。

所以一个解决方案是在调用 setText(...) 方法之前移除 Listener,然后恢复 Listener。

doc.removeDocumentListener( this );
textfield.setText(...);
doc.addDocumentListener( this );

虽然我会说实际更改输入的数据是不正常的。通常,您验证数据并在出现错误时显示消息,而不是尝试修复数据并重置文本。通过不更改文本字段,您不必担心会导致无限循环。

这就是为什么你会使用 JFormattedTextField 或者 JSpinner 作为编辑组件的原因。您可以轻松地将数据强制为具有最大位数的数字。

【讨论】:

  • 听起来很乱,我想我得再看看SwingUtilities.invokeLater(...)是怎么做的。
  • 已更新问题,无法使用 SwingUtilities.invokeLater(...) 设置布尔标志。仍然可以使用帮助。谢谢。
  • @FiddleFreak,您不要将 invokeLater() 与 DocumentFilter 一起使用。你所做的只是转储一些代码并说它不起作用。我不知道您要做什么,所以我无法提出解决方案。当您提出问题时,您需要说明实际需求。一旦我们知道实际需求是什么,何时可以建议 DocumentListener 或 DocumentFilter 是更好的解决方案。
  • “我不知道你想做什么,所以我无法提出解决方案。”每次用户在JTextField 中输入新数字、删除数字或替换数字时,我都会尝试在object.setString(editStringWithRules) 后跟JTextField.setText(getStringFromObject)
【解决方案2】:

问题,正如 cmets 中的 Laurent G 所述,是您尝试在触发 DocumentListener 更改事件时更改 hpBaseStatsTextField 的内容。

hpBaseStatsTextField.setText([...]);

这一行在技术上位于public void insertUpdate(DocumentEvent e) 方法中(即使在被调用的子方法中)。因为hpBaseStatsTextField 的文本已更改,Java 必须在已经位于该方法中的同时调用insertUpdate 方法。所以,这就是你得到异常的原因。

Derekhere 的答案几乎总结了我想提出的建议:

如果您想在侦听器中进行变异,您可以启动一个单独的线程,以便稍后使用 SwingUtilities.invokeLater 执行此操作。要小心,因为来自单独线程的修改会再次调用监听器,所以在启动线程之前设置一个布尔值,如果设置了,则立即从监听器返回,并在单独线程中完成修改后重置它。

无论如何,您为什么要更改用户当前正在输入的同一文本字段的内容?这似乎有点奇怪。也许setText 出现在另一个文本字段上以显示结果?

【讨论】:

  • 我这样做的原因是因为有规则(例如:baseHp 不能迭代到 9999 以上)。这些规则被留在 Characters 类中,并被排除在 Gui 之外。 Gui 说“嘿,角色,你需要为此设置你的值”。角色说,“由于该值高于我的最大限制,这就是我的值将被设置的值”。桂说“好”。 (我记得看过 Robert C. Martin 的演讲,他说将您的业务规则排除在 Gui 之外,因为它只是一种交付机制)。
  • 好的,那么你应该使用SwingUtilities.invokeLater(...)DocumentFilter
猜你喜欢
  • 2011-01-18
  • 1970-01-01
  • 2013-05-23
  • 2013-03-10
  • 2013-02-16
  • 2015-02-27
  • 2017-03-31
  • 2020-08-21
  • 2013-12-15
相关资源
最近更新 更多