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

