2017-01-06 9 views
4

Je suis la programmation d'un JToggleButton pour charger à/jeter de la mémoire la configuration d'un élément (une configuration du télescope), donc je l'ai ajouté un JComboBox dans un JFrame et près de lui sur le bouton charger l'élément sélectionné. Lorsque le JToggleButton est sélectionné, une icône de disque dur est affichée, une autre icône sinon. J'utilise l'éditeur IntelliJ IDEA GUI pour cela. Bien sûr, je l'ai ajouté un ItemListener (comme l'a suggéré à partir du Web) à ce bouton:JToggleButton addItemListener semble répéter la ItemListener pour toujours

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

Sortie:
true
-> Lorsque la fenêtre est affichée
LAOD_ACTION false
-> Quand je cliquez sur le bouton

J'ai fait quelques tests avec quelques nouveaux boutons à bascule et ils m'ont donné la même erreur: le code à l'intérieur itemStateChanged(ItemEvent e) {...} est répété pour toujours, sans stoppi ng! Dans ce morceau de code, il n'y a pas de boucles for et while! Le résultat est un grand nombre de boîtes de dialogue de message (une seule boîte de dialogue doit être affichée), et si je mets une autre fenêtre sur mon bureau, l'écran derrière les boîtes de dialogue devient noir (la zone de la fenêtre parent). J'ai changé l'écouteur à ActionListener et maintenant tout est exécuté une fois/clic.

Pourquoi cette erreur? J'ai copié ce code de https://stackoverflow.com/a/7524627/6267019, comme vous pouvez le voir.

Code complet sur GitHub Here, J'ai mis en évidence le code pour ce bouton bascule. La même erreur se produit avec d'autres JToggleButton s dans mon fichier MainActivity.java, et aussi lorsque le débogage IntelliJ me permet de voir que le code dans l'écouteur est répété pour toujours. Après quelques milliers de dialogues Windows me montre un message et ferme Java Platform Binary avec une erreur.

EDIT:
Le même problème dans une nouvelle classe:

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

Je sais ce fait et j'en suis désolé, mais comment puis-je poster 1200 lignes de code ou le code XML du générateur IntelliJ IDEA GUI? Quel exemple puis-je fournir, si les exemples du Web fonctionnent bien? – SquareBoot

+0

C'est le problème !! Les exemples fonctionnent! Si je crée un nouveau projet, j'ajoute un 'main' et j'écris le code pour un' JFrame' avec un bouton bascule, ça marche! – SquareBoot

+0

Mon idée du bogue est qu'il est causé par une erreur interne du générateur IntelliJ IDEA GUI. Maintenant, je crée la même erreur dans un nouveau prj. – SquareBoot

Répondre

3

Chaque fois que vous ouvrez une boîte de dialogue modale, l'appel de la méthode d'ouverture sera de retour qu'après la boîte de dialogue a été fermé. Ceci est crucial pour les boîtes de dialogue qui renvoient une valeur ou un choix saisi. Cela signifie que, même si la boîte de dialogue est ouverte, une nouvelle boucle de gestion des événements doit être démarrée pour réagir à l'entrée dans la boîte de dialogue. Par conséquent, lorsque vous ouvrez une boîte de dialogue modale à partir d'un programme d'écoute, vous arrêtez la gestion de l'événement en cours et commencez le traitement des événements suivants, ce qui peut perturber considérablement le traitement de l'événement en cours.Plus particulièrement, le bouton perdra soudainement le focus lors de l'ouverture de la nouvelle boîte de dialogue.

La gestion des événements imbriqués peut être facilement démontré en changeant l'auditeur

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

qui imprime des séquences de

entering 
entering 
leaving 
leaving 

montrant comment l'événement contredisant est générée alors que le traitement de l'ancien n'a pas été complété.

Comme dit par d'autres, vous pouvez résoudre ce problème en ouvrant la dialogue après l'achèvement de la manipulation même, comme

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

ou vous appliquer une boîte de dialogue non modale:

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

(dans une application réelle, vous conservez la boîte de dialogue pour une réutilisation ultérieure ou appelez le dispose après utilisation)


Malheureusement, le risque d'ouvrir des boîtes de dialogue modales (ou de faire quoi que ce soit qui crée un secondary event loop) n'a pas été suffisamment souligné dans la documentation. Vous pouvez lire partout que l'accès aux composants Swing à partir d'autres threads peut créer des incohérences, mais démarrer une nouvelle boucle de gestion d'événements alors qu'il y a des événements incomplètement traités peut avoir un impact similaire.

+0

Notez que dans votre code d'origine, vous utilisez ce paramètre en appelant 'setSelected (...);' sur la source d'événement depuis l'écouteur, ce qui déclenchera bien sûr une nouvelle livraison d'événement signalant le changement à ce stade, ce qui est une recette pour des boucles infinies, même sans dialogues. – Holger

+0

Merci pour votre temps et votre explication. 1+. 10+ si je pouvais –

+0

Wow, c'est intéressant! J'étudierai cette question plus tard, merci pour votre réponse! – SquareBoot

3

Je ne peux pas dire que je comprends pourquoi votre code se conduit mal, mais je suis d'accord que ce que vous voyez ne pas tout à fait a un sens, et est probablement dû à l'appel JOptionPane qui affecte d'une manière ou d'une autre le changement d'état de JToggleButton. Une façon de contourner ce problème consiste à placer l'appel JOptionPane dans un Runnable et à le mettre en file d'attente dans la file d'attente d'événements Swing via SwingUtilities.invokeLater(...). Par exemple:

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

    } 
} 

Une variante intéressante:

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

}); 

imprime:

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

Merci, mais voici une autre chose étrange: j'ai écrit 'loadTelescopeButton.setSelected (true); System.out.println (loadTelescopeButton.isSelected()); loadTelescopeButton.addActionListener (e -> { System.out.println ("LAOD_ACTION" + loadTelescopeButton.isSelected()); // ... autre code' dans le constructeur Dans la console, je vois 'LAOD_ACTION false' quand je cliquez sur le bouton! – SquareBoot

+0

@SquareBoot: s'il vous plaît modifier votre question Code dans les commentaires est difficile à lire à moins que ce soit très petit –

+0

Vraiment merci pour la réponse! Donc, il semble être un bug Java, à mon avis. passer beaucoup de temps sur le bouton bascule que je vais faire un 'JComponent' personnalisé – SquareBoot