javaswingitemlistenergui-builderjtogglebutton

JToggleButton addItemListener seems to repeat the ItemListener forever


I'm programming a JToggleButton to load to/discard from memory the configuration of an element (a telescope config), so I've added a JComboBox in a JFrame and near it the button to load the selected item. When the JToggleButton is selected, an hard disk icon is displayed, another icon if otherwise. I'm using the IntelliJ IDEA GUI editor for that. Of course, I've added an ItemListener (as suggested from the web) to that button:

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

Output:
true
-> When the window is displayed
LAOD_ACTION false
-> When I click the button

I've made some tests with some new toggle buttons and they gave me same error: the code inside itemStateChanged(ItemEvent e) {...} is repeated forever, without stopping! In that piece of code there are no for and while loops! The result is a great number of message dialogs (only one dialog should be displayed), and if I focus another window in my desktop the screen behind the dialogs becomes black (the area of the parent window). I changed the listener to ActionListener and now everything is executed one time/click.

Why this error? I've copied that code from https://stackoverflow.com/a/7524627/6267019, as you can see.

Full code on GitHub Here, I've highlighted the code for that toggle button. The same error happens with other JToggleButtons in my MainActivity.java file, and also when debugging IntelliJ lets me see that the code in the listener is repeated forever. After some thousand of dialogs Windows shows me a message and closes Java Platform Binary with an error.

EDIT:
The same problem in a new 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();
     }
 }

Solution

  • Whenever you open a modal dialog, the opening method call will return only after the dialog has been closed. This is crucial for the dialogs that return an entered value or choice.

    This implies that while the dialog is open, a new event handling loop has to be started to react on the input in the dialog.

    So when you open a modal dialog from a listener, you are stopping the handling of the current event and start processing of subsequent events, which can disturb the handling of the current event significantly. Most notably, the button will suddenly loose the focus when the new dialog is opened.

    The nested event handling can be easily demonstrated by changing the listener to

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

    which will print sequences of

    entering
    entering
    leaving
    leaving
    

    showing how the contradicting event is generated while the processing of the old one hasn’t completed.

    As said by others, you can fix this by opening the dialog after the completion of the even handling, like

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

    or you enforce a non-modal dialog:

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

    (in a real application you would either keep the dialog for later reuse or call dispose after use)


    Unfortunately, the danger of opening modal dialogs (or doing anything else that creates a secondary event loop) hasn’t been emphasized enough in the documentation. You can read everywhere that accessing Swing components from other threads can create inconsistencies, but starting new event handling loop while there are incompletely processed events can have a similar impact.