JToggleButton addItemListener 似乎永远重复 ItemListener

JToggleButton addItemListener seems to repeat the ItemListener forever

我正在编写 JToggleButton 以从内存中加载 to/discard 元素的配置(telescope 配置),因此我添加了 JComboBoxJFrame 和它附近的按钮加载所选项目。选择 JToggleButton 时,会显示一个硬盘图标,否则会显示另一个图标。为此,我正在使用 IntelliJ IDEA GUI 编辑器。当然,我已经向该按钮添加了一个 ItemListener(根据网络建议):

    loadTelescopeButton.setSelected(true);
    System.out.println(loadTelescopeButton.isSelected());
    loadTelescopeButton.addItemListener(new ItemListener() {
        @Override
        public void itemStateChanged(ItemEvent e) {
            System.out.println("LAODACTION " + loadTelescopeButton.isSelected());
            try {
                if (e.getStateChange() == ItemEvent.SELECTED) {
                    String selected = telescopesList.getSelectedItem().toString();

                    if ((selected != null) && (!selected.equals("")) && (ObjUtils.isAlphaNumeric(selected))) {
                        //...

                    } else {
                        showErrorMessage("Invalid id selected!");
                    }

                } else if (e.getStateChange() == ItemEvent.DESELECTED) {
                    if ((configurationActivity != null) && (configurationActivity.getManager() != null) &&
                            (configurationActivity.getTelescope() != null) && (configurationActivity.getTelescope().isConnected())) {
                        //...

                    } else {
                        //...
                    }
                }

            } catch (Exception e1) {
                e1.printStackTrace();
            }
        }
    });

输出:
true
-> 当 window 显示时
LAOD_ACTION false
-> 当我点击按钮

我用一些新的切换按钮做了一些测试,它们给了我同样的错误:itemStateChanged(ItemEvent e) {...} 中的代码永远重复,没有停止!在那段代码中没有 forwhile 循环!结果是出现大量消息对话框(只应显示一个对话框),如果我在桌面上聚焦另一个 window,对话框后面的屏幕会变黑(parent window).我将侦听器更改为 ActionListener,现在所有内容都执行了 time/click.

为什么会出现这个错误?如您所见,我已经从 复制了该代码。

GitHub Here 上的完整代码,我已经突出显示了该切换按钮的代码。同样的错误发生在我的 MainActivity.java 文件中的其他 JToggleButtons 上,而且在调试 IntelliJ 时让我看到监听器中的代码永远重复。在几千次对话之后 Windows 向我显示了一条消息并关闭了 Java Platform Binary 并出现错误。

编辑:
同样的问题在一个新的class:

 import javax.swing.*;
 import java.awt.*;

 public class ErrorGUI extends JFrame {

     public ErrorGUI() throws HeadlessException {
         super("ciao");
         JPanel panel1 = new JPanel();
         setContentPane(panel1);

         JToggleButton ciaoToggleButton = new JToggleButton("cajs");
         ciaoToggleButton.setSelected(true);
         ciaoToggleButton.addItemListener(e -> {
             System.out.println("caiooasfsdvn");
             try {
                 JOptionPane.showMessageDialog(panel1, "skjngksfnb");

             } catch (Exception e2) {
                 e2.printStackTrace();
             }
         });
         panel1.add(ciaoToggleButton);

         pack();
         setVisible(true);
     }

     public static void main(String[] args) {
         new ErrorGUI();
     }
 }

我不能说我理解为什么您的代码会出现问题,但我同意您所看到的内容不太合理,并且可能是由于 JOptionPane 调用以某种方式影响了 JToggleButton 的状态更改。解决这个问题的一种方法是将 JOptionPane 调用包装在一个 Runnable 中,并通过 SwingUtilities.invokeLater(...) 在 Swing 事件队列中排队。例如:

import javax.swing.*;
import java.awt.*;

@SuppressWarnings("serial")
public class ErrorGUI extends JFrame {

    public ErrorGUI() throws HeadlessException {
        super("ciao");
        JPanel panel1 = new JPanel();
        setContentPane(panel1);

        JToggleButton ciaoToggleButton = new JToggleButton("cajs");
        ciaoToggleButton.setSelected(true);
        ciaoToggleButton.addItemListener(e -> {
            System.out.println("caiooasfsdvn");
            SwingUtilities.invokeLater(() -> {
                JOptionPane.showMessageDialog(panel1, "skjngksfnb");
            });
            // JOptionPane.showMessageDialog(panel1, "skjngksfnb");

        });
        panel1.add(ciaoToggleButton);

        pack();
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
        setVisible(true);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            new ErrorGUI();
        });

    }
}

一个有趣的变化:

ciaoToggleButton.setSelected(true);
System.out.println("0:" + ciaoToggleButton.isSelected());
ciaoToggleButton.addItemListener(e -> {
    System.out.println("1: " + ciaoToggleButton.isSelected());
    if (e.getStateChange() == ItemEvent.SELECTED) {
        JOptionPane.showMessageDialog(panel1, "skjngksfnb");
    }
    System.out.println("2: " + ciaoToggleButton.isSelected());

});

打印出来:

0:true
1: false
2: false
1: true
1: false
2: false
2: false
1: true
1: false
2: false
2: false

无论何时打开模式对话框,只有在对话框关闭后才会 return 打开方法调用。这对于 return 输入值或选择的对话框至关重要。

这意味着当对话框打开时,必须启动一个新的事件处理循环来对对话框中的输入做出反应。

因此,当您从侦听器打开模态对话框时,您将停止处理当前事件并开始处理后续事件,这可能会严重干扰当前事件的处理。最值得注意的是,当新对话框打开时,按钮会突然失去焦点。

通过将侦听器更改为

,可以轻松演示嵌套事件处理
ciaoToggleButton.addItemListener(e -> {
    System.out.println("entering");
    JOptionPane.showMessageDialog(panel1,
       e.getStateChange()==ItemEvent.SELECTED? "selected": "deselected");
    System.out.println("leaving");
});

这将打印

的序列
entering
entering
leaving
leaving

显示如何在旧事件的处理尚未完成时生成矛盾事件。

正如其他人所说,您可以通过在完成偶数处理后打开对话框来解决这个问题,比如

ciaoToggleButton.addItemListener(e -> {
    System.out.println("entering");
    EventQueue.invokeLater(() -> JOptionPane.showMessageDialog(panel1,
       e.getStateChange()==ItemEvent.SELECTED? "selected": "deselected"));
    System.out.println("leaving");
});

或者您强制执行非模态对话:

ciaoToggleButton.addItemListener(e -> {
    System.out.println("entering");
    JDialog d = new JOptionPane(
            e.getStateChange()==ItemEvent.SELECTED? "selected": "deselected",
            JOptionPane.INFORMATION_MESSAGE)
        .createDialog(panel1, UIManager.getString("OptionPane.messageDialogTitle"));
    d.setModal(false);
    d.setVisible(true);
    System.out.println("leaving");
});

(在实际应用程序中,您可以保留对话框供以后重用或在使用后调用 dispose


不幸的是,文档中没有足够强调打开模态对话框(或做任何其他创建 secondary event loop)的危险。您到处都可以看到,从其他线程访问 Swing 组件会造成不一致,但是在事件未完全处理时启动新的事件处理循环也会产生类似的影响。