You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java 17可编辑JComboBox动态弹窗实现异常求助

可编辑JComboBox动态过滤弹窗项问题解决

问题描述

在Java 17 + Windows 11环境下,使用WindowsBuilder开发应用,需实现可编辑JComboBox的以下功能:

  • 弹窗随用户输入/删除文本动态更新,仅显示匹配输入前缀的项
  • 输入为空时恢复原菜单列表
  • 弹窗始终保持可见

尝试在JComboBox内部JTextField的DocumentListener中调用setModel()修改模型时,触发IllegalStateException: Attempt to mutate in notification异常;尝试使用MutableComboBoxModel但在Eclipse中无法导入,也未找到可用示例。

错误堆栈

Exception in thread "AWT-EventQueue-0" java.lang.IllegalStateException: Attempt to mutate in notification
    at java.desktop/javax.swing.text.AbstractDocument.writeLock(AbstractDocument.java:1372)
    at java.desktop/javax.swing.text.AbstractDocument.replace(AbstractDocument.java:688)
    at java.desktop/javax.swing.text.JTextComponent.setText(JTextComponent.java:1725)
    at java.desktop/javax.swing.plaf.metal.MetalComboBoxEditor$1.setText(MetalComboBoxEditor.java:63)
    at java.desktop/javax.swing.plaf.basic.BasicComboBoxEditor.setItem(BasicComboBoxEditor.java:93)
    at java.desktop/javax.swing.JComboBox.configureEditor(JComboBox.java:1439)
    at java.desktop/javax.swing.plaf.basic.BasicComboBoxUI$Handler.propertyChange(BasicComboBoxUI.java:1812)
    at java.desktop/javax.swing.plaf.basic.BasicComboBoxUI$PropertyChangeHandler.propertyChange(BasicComboBoxUI.java:704)
    at java.desktop/javax.swing.plaf.metal.MetalComboBoxUI$MetalPropertyChangeListener.propertyChange(MetalComboBoxUI.java:224)
    at java.desktop/java.beans.PropertyChangeSupport.fire(PropertyChangeSupport.java:343)
    at java.desktop/java.beans.PropertyChangeSupport.firePropertyChange(PropertyChangeSupport.java:335)
    at java.desktop/java.beans.PropertyChangeSupport.firePropertyChange(PropertyChangeSupport.java:268)
    at java.desktop/java.awt.Component.firePropertyChange(Component.java:8717)
    at java.desktop/javax.swing.JComboBox.setModel(JComboBox.java:362)
    at cfgs.prove$3.removeUpdate(prove.java:136)
    at java.desktop/javax.swing.text.AbstractDocument.fireRemoveUpdate(AbstractDocument.java:285)
    at java.desktop/javax.swing.text.AbstractDocument.handleRemove(AbstractDocument.java:652)
    at java.desktop/javax.swing.text.AbstractDocument.remove(AbstractDocument.java:620)
    at java.desktop/javax.swing.text.AbstractDocument.replace(AbstractDocument.java:696)
    at java.desktop/javax.swing.text.JTextComponent.setText(JTextComponent.java:1725)
    at java.desktop/javax.swing.plaf.metal.MetalComboBoxEditor$1.setText(MetalComboBoxEditor.java:63)
    at cfgs.prove$4.mousePressed(prove.java:156)
    at java.desktop/java.awt.AWTEventMulticaster.mousePressed(AWTEventMulticaster.java:288)
    at java.desktop/java.awt.Component.processMouseEvent(Component.java:6618)
    at java.desktop/javax.swing.JComponent.processMouseEvent(JComponent.java:3398)
    at java.desktop/java.awt.Component.processEvent(Component.java:6386)
    at java.desktop/java.awt.Container.processEvent(Container.java:2266)
    at java.desktop/java.awt.Component.dispatchEventImpl(Component.java:4996)
    at java.desktop/java.awt.Container.dispatchEventImpl(Container.java:2324)
    at java.desktop/java.awt.Component.dispatchEvent(Component.java:4828)
    at java.desktop/java.awt.LightweightDispatcher.retargetMouseEvent(Container.java:4948)
    at java.desktop/java.awt.LightweightDispatcher.processMouseEvent(Container.java:4572)
    at java.desktop/java.awt.LightweightDispatcher.dispatchEvent(Container.java:4516)
    at java.desktop/java.awt.Container.dispatchEventImpl(Container.java:2310)
    at java.desktop/java.awt.Window.dispatchEventImpl(Window.java:2780)
    at java.desktop/java.awt.Component.dispatchEvent(Component.java:4828)
    at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:775)
    at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:720)
    at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:714)
    at java.base/java.security.AccessController.doPrivileged(AccessController.java:400)
    at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:87)
    at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:98)
    at java.desktop/java.awt.EventQueue$5.run(EventQueue.java:747)
    at java.desktop/java.awt.EventQueue$5.run(EventQueue.java:745)
    at java.base/java.security.AccessController.doPrivileged(AccessController.java:400)
    at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:87)
    at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:744)
    at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:203)
    at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:124)
    at java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:113)
    at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:109)
    at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)

原代码

package cfgs;

import java.awt.EventQueue;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.border.EmptyBorder;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.JLabel;
import javax.swing.SwingConstants;
import java.awt.Font;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.regex.Pattern;

import javax.swing.JComboBox;
import javax.swing.DefaultComboBoxModel;

public class prove extends JFrame {

    private static final long serialVersionUID = 1L;
    private JPanel contentPane;
    protected JComboBox<String> comboBox;
    protected static String[] usuarios = {"James", "Ana", "Andres", "Maria", "John", "gustavo"};
    protected static DefaultComboBoxModel<String> comb;
    
    public static String[] FiltrarUsuarios(String str) {
        int i = 1, N = usuarios.length;
        ArrayList<String> filtrados = new ArrayList<String>();
        Pattern filtro = Pattern.compile("^"+str+".*"); //匹配前缀为str的字符串
        comb = new DefaultComboBoxModel<String>(usuarios);
        String elemento;
        String[] retorno;
        while (i<N) {
            elemento = comb.getElementAt(i);
            if (filtro.matcher(elemento).matches()) //符合条件则加入列表
                filtrados.add(elemento);
            i++;
        }
        N = filtrados.size();
        i = 1;              //复用变量i, N
        retorno = new String[++N];
        retorno[0] = str;
        while (i<N) {
            retorno[i] = filtrados.get(i-1);
            i++;
        }
        return retorno;
    }
    /**
     * 启动应用
     */
    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                try {
                    prove frame = new prove("请输入用户名", true);
                    frame.setVisible(true);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    /**
     * 创建窗体
     */
    public prove(String mensaje, boolean bool) {
        
        comb = new DefaultComboBoxModel<String>(usuarios);
        
        setTitle("动态JComboBox");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setBounds(100, 100, 450, 300);
        contentPane = new JPanel();
        contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
        setContentPane(contentPane);
        contentPane.setLayout(null);
        
        JLabel lblNewLabel = new JLabel("动态JComboBox");
        lblNewLabel.setFont(new Font("Tahoma", Font.BOLD, 17));
        lblNewLabel.setHorizontalAlignment(SwingConstants.CENTER);
        lblNewLabel.setBounds(116, 11, 184, 39);
        contentPane.add(lblNewLabel);
        
        JLabel a_message = new JLabel(mensaje);
        a_message.setHorizontalAlignment(SwingConstants.CENTER);
        a_message.setFont(new Font("Tahoma", Font.BOLD, 14));
        a_message.setBounds(116, 83, 184, 39);
        contentPane.add(a_message);
        
        JComboBox<String> comboBox = new JComboBox<String>();
        comboBox.setModel(new DefaultComboBoxModel<String>(usuarios));
        comboBox.setVisible(bool);
        comboBox.setModel(comb);
        comboBox.setEditable(true);
        comboBox.setFont(new Font("Tahoma", Font.BOLD, 13));
        comboBox.addFocusListener(new FocusAdapter() {
            @Override
            public void focusLost(FocusEvent e) {
                new DefaultComboBoxModel<String>(usuarios);
                comboBox.setModel(comb);
                comboBox.setPopupVisible(false);
            }
        });
        JTextField editor = (JTextField) comboBox.getEditor().getEditorComponent(); //强转为JTextField以获取文本内容
        editor.getDocument().addDocumentListener(new DocumentListener()  {
            
            @Override
            public void insertUpdate(DocumentEvent e) {
                String src = editor.getText();
                String[] lista;
                System.out.println("当前内容:"+src);
               
                lista = FiltrarUsuarios(src);
                comb = new DefaultComboBoxModel<String>(lista); //usuarios
                comboBox.setModel(comb);
                comboBox.setPopupVisible(false);
            }
            
            @Override
            public void removeUpdate(DocumentEvent e) {
                
                String src = editor.getText();
                String[] lista;
                
                System.out.println("删除内容后,当前内容:"+src);
                if (src.equals("")) {
                    comb = new DefaultComboBoxModel<String>(usuarios);
                    comboBox.setModel(comb);
                    comboBox.setPopupVisible(false);
                } else {
                    lista = FiltrarUsuarios(src);
                    comb = new DefaultComboBoxModel<String>(lista); //usuarios
                    comboBox.setModel(comb);
                    comboBox.setPopupVisible(false);
                }
            }

            @Override
            public void changedUpdate(DocumentEvent e) {
                System.out.println("格式变更");
            }
        });
        
        editor.addMouseListener(new MouseAdapter() {
            
            @Override
            public void mousePressed(MouseEvent e) {
                editor.setText("");
            }
        });
        comboBox.setBounds(116, 133, 184, 39);
        contentPane.add(comboBox);

    }
}

解决方案

异常原因

DocumentListener的回调方法(insertUpdate/removeUpdate)执行时,文档处于写入锁定状态,此时直接修改JComboBox的模型会触发锁冲突,导致Attempt to mutate in notification异常。

修复步骤

  1. 延迟模型修改操作:使用SwingUtilities.invokeLater()将模型修改逻辑放到AWT事件队列末尾,等待当前文档更新完成后再执行。
  2. 复用现有模型:DefaultComboBoxModel本身就是MutableComboBoxModel的实现类,无需额外导入,直接调用removeAllElements()和addElement()修改内容即可,避免频繁新建模型。
  3. 修正过滤逻辑:原代码从索引1开始遍历,会漏掉第一个元素,改为从0开始;同时过滤时忽略大小写,提升用户体验。
  4. 保持弹窗可见:将setPopupVisible(true)加入过滤逻辑,确保弹窗始终显示。

修改后的完整代码

package cfgs;

import java.awt.EventQueue;
import java.awt.Font;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.regex.Pattern;

import javax.swing.DefaultComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.border.EmptyBorder;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;

public class prove extends JFrame {

    private static final long serialVersionUID = 1L;
    private JPanel contentPane;
    protected JComboBox<String> comboBox;
    protected static final String[] usuarios = {"James", "Ana", "Andres", "Maria", "John", "gustavo"};
    // 复用同一个模型,避免频繁创建
    protected DefaultComboBoxModel<String> originalModel;
    protected DefaultComboBoxModel<String> currentModel;
    
    /**
     * 过滤匹配前缀的用户列表
     */
    private ArrayList<String> filtrarUsuarios(String str) {
        ArrayList<String> filtrados = new ArrayList<>();
        // 忽略大小写的前缀匹配
        Pattern filtro = Pattern.compile("^" + Pattern.quote(str) + ".*", Pattern.CASE_INSENSITIVE);
        for (String usuario : usuarios) {
            if (filtro.matcher(usuario).matches()) {
                filtrados.add(usuario);
            }
        }
        return filtrados;
    }

    /**
     * 启动应用
     */
    public static void main(String[] args) {
        EventQueue.invokeLater(() -> {
            try {
                prove frame = new prove("请输入用户名", true);
                frame.setVisible(true);
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
    }

    /**
     * 创建窗体
     */
    public prove(String mensaje, boolean bool) {
        // 初始化原始模型和当前模型
        originalModel = new DefaultComboBoxModel<>(usuarios);
        currentModel = new DefaultComboBoxModel<>(usuarios);
        
        setTitle("动态JComboBox");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setBounds(100, 100, 450, 300);
        contentPane = new JPanel();
        contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
        setContentPane(contentPane);
        contentPane.setLayout(null);
        
        JLabel lblNewLabel = new JLabel("动态JComboBox");
        lblNewLabel.setFont(new Font("Tahoma", Font.BOLD, 17));
        lblNewLabel.setHorizontalAlignment(SwingConstants.CENTER);
        lblNewLabel.setBounds(116, 11, 184, 39);
        contentPane.add(lblNewLabel);
        
        JLabel a_message = new JLabel(mensaje);
        a_message.setHorizontalAlignment(SwingConstants.CENTER);
        a_message.setFont(new Font("Tahoma", Font.BOLD, 14));
        a_message.setBounds(116, 83, 184, 39);
        contentPane.add(a_message);
        
        comboBox = new JComboBox<>(currentModel);
        comboBox.setVisible(bool);
        comboBox.setEditable(true);
        comboBox.setFont(new Font("Tahoma", Font.BOLD, 13));
        
        // 失去焦点时恢复原始模型并关闭弹窗
        comboBox.addFocusListener(new FocusAdapter() {
            @Override
            public void focusLost(FocusEvent e) {
                currentModel.removeAllElements();
                for (int i = 0; i < originalModel.getSize(); i++) {
                    currentModel.addElement(originalModel.getElementAt(i));
                }
                comboBox.setPopupVisible(false);
            }
        });
        
        JTextField editor = (JTextField) comboBox.getEditor().getEditorComponent();
        editor.getDocument().addDocumentListener(new DocumentListener() {
            
            @Override
            public void insertUpdate(DocumentEvent e) {
                actualizarLista(editor.getText());
            }
            
            @Override
            public void removeUpdate(DocumentEvent e) {
                actualizarLista(editor.getText());
            }

            @Override
            public void changedUpdate(DocumentEvent e) {
                actualizarLista(editor.getText());
            }
            
            /**
             * 延迟更新下拉列表,避免文档锁定冲突
             */
            private void actualizarLista(String input) {
                SwingUtilities.invokeLater(() -> {
                    currentModel.removeAllElements();
                    
                    if (input.isEmpty()) {
                        // 输入为空,恢复原始列表
                        for (int i = 0; i < originalModel.getSize(); i++) {
                            currentModel.add
相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.01 13:09:55