【问题标题】:Getting 'Attempt to mutate notification' exception获取“尝试改变通知”异常
【发布时间】:2025-11-30 01:00:01
【问题描述】:

我的目标是实现将用户写入 JTextPane 的关键字的蓝色着色。这就是我的代码的样子:

private class DocumentHandler implements DocumentListener {

        @Override
        public void changedUpdate(DocumentEvent ev) {
        }

        @Override
        public void insertUpdate(DocumentEvent ev) {
            highlight();
        }

        @Override
        public void removeUpdate(DocumentEvent ev) {
            highlight();
        }

        private void highlight() {
            String code = codePane.getText();
            SimpleAttributeSet defSet = new SimpleAttributeSet();
            StyleConstants.setForeground(defSet, Color.BLACK);
            doc.setCharacterAttributes(0, code.length(), defSet, true);
            SimpleAttributeSet set = new SimpleAttributeSet();
            StyleConstants.setForeground(set, Color.BLUE);
            for (String keyword : keywords) {
                Pattern pattern = Pattern.compile(keyword + "(\\[\\])*");
                Matcher matcher = pattern.matcher(code);
                while (matcher.find()) {

                    //Just for test
                    System.out.print("Start index: " + matcher.start());
                    System.out.print(" End index: " + matcher.end());
                    System.out.println(" Found: " + matcher.group());

                    doc.setCharacterAttributes(matcher.start(), keyword.length(), set, true);
                }
            }
        }
    }

在窗格中输入任何内容后,我得到:

Exception in thread "AWT-EventQueue-0" java.lang.IllegalStateException: Attempt to mutate in notification
    at javax.swing.text.AbstractDocument.writeLock(AbstractDocument.java:1338)
    at javax.swing.text.DefaultStyledDocument.setCharacterAttributes(DefaultStyledDocument.java:500)
    at jnotepad.MainPanel$DocumentHandler.highlight(MainPanel.java:121)
    at jnotepad.MainPanel$DocumentHandler.insertUpdate(MainPanel.java:108)
    at javax.swing.text.AbstractDocument.fireInsertUpdate(AbstractDocument.java:202)
    at javax.swing.text.AbstractDocument.handleInsertString(AbstractDocument.java:749)

如何解决我的问题?也许我应该使用 DocumentListener 以外的东西?

【问题讨论】:

    标签: java swing jtextpane


    【解决方案1】:

    您需要从 Event Dispatcher 线程调用对文档的更改。

    试试这个:

    private void highlight() {
    
        Runnable doHighlight = new Runnable() {
            @Override
            public void run() {
                // your highlight code
            }
        };       
        SwingUtilities.invokeLater(doHighlight);
    }
    

    【讨论】:

    • 问题不在于highlight() 是从错误的线程执行的。相反,invokeLater(Runnable) 解决了问题,因为它将执行推迟到文档锁被释放。
    • @HollisWaite 这就是我的想法。如果发生不幸的调度,我猜这会失败?
    • @MoritzSchmidt,不应该发生不幸的调度。排队的 EDT 任务在处理后续任务之前运行完成。
    • @HollisWaite 很高兴知道。谢谢你。但该解决方案对我来说仍然看起来像是一个 hack。或者这真的是最干净的解决方案吗?
    • @MoritzSchmidt,我不是 JTextPane 内部的专家。我认为这是最优雅的解决方案,但我可能是错的。无论如何,如果您正在构建一个中等复杂的 Swing 应用程序,您将不可避免地发现自己在利用“invokeLater”习语。在这种特殊情况下,最简洁的选择是使用一些第三方库(例如 JSyntaxPane)。
    【解决方案2】:

    我遇到了同样的问题,我用这个解决了:

    expiration_timeTF.getDocument().addDocumentListener(
                new DocumentListener() {
                    @Override
                    public void removeUpdate(DocumentEvent e) {
                        System.out.println("remove");
                    }
    
                    private void assistDateText() {
                        Runnable doAssist = new Runnable() {
                            @Override
                            public void run() {
                                // when input "2013",will add to "2013-";when
                                // input "2013-10",will add to "2013-10-"
                                String input = expiration_timeTF.getText();
                                if (input.matches("^[0-9]{4}")) {
                                    expiration_timeTF.setText(input + "-");
                                } else if (input.matches("^[0-9]{4}-[0-9]{2}")) {
                                    expiration_timeTF.setText(input + "-");
                                }
                            }
                        };
                        SwingUtilities.invokeLater(doAssist);
                    }
    
                    @Override
                    public void insertUpdate(DocumentEvent e) {
                        // System.out.println("insert");
                        assistDateText();
                    }
    
                    @Override
                    public void changedUpdate(DocumentEvent e) {
                        // System.out.println("change");
                    }
                });
    

    【讨论】: