JToggleButton addItemListener, кажется, повторяет ItemListener навсегда

Я программирую JToggleButton загрузить / удалить из памяти конфигурацию элемента (конфигурацию телескопа), поэтому я добавил JComboBox в JFrame а рядом с ним кнопка для загрузки выбранного элемента. Когда JToggleButton выбран, значок жесткого диска отображается, другой значок, если в противном случае. Для этого я использую графический редактор IntelliJ IDEA. Конечно, я добавил 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
-> Когда отображается окно
LAOD_ACTION false
-> Когда я нажимаю кнопку

Я сделал несколько тестов с некоторыми новыми кнопками переключения, и они дали мне ту же ошибку: код внутри itemStateChanged(ItemEvent e) {...} повторяется вечно, без остановки! В этом куске кода нет for а также while петля! В результате получается большое количество диалоговых окон сообщений (должен отображаться только один диалог), и если я фокусирую другое окно на рабочем столе, экран позади диалогов становится черным (область родительского окна). Я сменил слушателя на ActionListener и теперь все выполняется один раз / клик.

Почему эта ошибка? Я скопировал этот код с /questions/25086333/poluchenie-sostoyaniya-jtogglebutton/25086346#25086346, как вы можете видеть.

Полный код на GitHub Здесь я выделил код для этой кнопки переключения. Такая же ошибка происходит с другими JToggleButtonв моем MainActivity.java файл, а также при отладке IntelliJ позволяет мне видеть, что код в слушателе повторяется навсегда. После нескольких тысяч диалогов Windows показывает мне сообщение и закрывает Java Platform Binary с ошибкой.

РЕДАКТИРОВАТЬ:
Та же проблема в новом классе:

 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();
     }
 }

2 ответа

Решение

Всякий раз, когда вы открываете модальное диалоговое окно, вызов метода открытия будет возвращаться только после закрытия диалогового окна. Это важно для диалогов, которые возвращают введенное значение или выбор.

Это означает, что, пока диалог открыт, необходимо запустить новый цикл обработки событий, чтобы реагировать на входные данные в диалоге.

Поэтому, когда вы открываете модальное диалоговое окно от слушателя, вы прекращаете обработку текущего события и начинаете обработку последующих событий, что может значительно помешать обработке текущего события. В частности, кнопка внезапно потеряет фокус при открытии нового диалога.

Вложенная обработка событий может быть легко продемонстрирована путем изменения слушателя на

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 после использования)


К сожалению, опасность открытия модальных диалогов (или выполнения чего-либо еще, что создает вторичный цикл событий) недостаточно подчеркнута в документации. Вы можете прочитать везде, что доступ к компонентам Swing из других потоков может создать несоответствия, но запуск нового цикла обработки событий при наличии не полностью обработанных событий может оказать аналогичное влияние.

Я не могу сказать, что понимаю, почему ваш код плохо себя ведет, но я согласен, что то, что вы видите, не совсем имеет смысла, и, вероятно, из-за вызова JOptionPane, каким-то образом влияющего на изменение состояния JToggleButton. Один из способов обойти это - заключить вызов JOptionPane в Runnable и поставить его в очередь событий Swing через SwingUtilities.invokeLater(...), Например:

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
Другие вопросы по тегам