Affichage / masquage un JPopupMenu à partir d'un DButton; FocusListener ne fonctionne pas?

StackOverflow https://stackoverflow.com/questions/2421914

Question

Il me fallait un JButton avec un menu de style déroulant ci-joint. J'ai donc pris JPopupMenu et attaché à la JButton de la manière que vous pouvez voir dans le code ci-dessous. Ce qu'il faut faire est la suivante:

  • afficher le menu contextuel lorsque vous cliquez dessus
  • cacher si vous cliquez dessus une seconde fois
  • cacher si un élément est sélectionné dans le menu contextuel
  • cacher si l'utilisateur clique sur un autre endroit dans l'écran

Ces 4 choses fonctionnent, mais à cause du drapeau booléen j'utilise, si l'utilisateur clique sur un autre endroit ou sélectionne un élément, je dois cliquer deux fois sur le bouton avant qu'il apparaisse à nouveau. Voilà pourquoi j'ai essayé d'ajouter un pour résoudre ce problème et mettre le drapeau faux dans ces cas focusListener (ce qui est tout à fait ne répond pas).

EDIT: La dernière tentative dans un poste de réponse ...

Voici les auditeurs: (Il est dans une classe JButton extension, de sorte que le second auditeur est sur le JButton.)

// Show popup on left click.
menu.addFocusListener(new FocusListener() {
 @Override
 public void focusLost(FocusEvent e) {
  System.out.println("LOST FOCUS");
  isShowingPopup = false;
 }

 @Override
 public void focusGained(FocusEvent e) {
  System.out.println("GAINED FOCUS");
 }
});

addActionListener(new ActionListener() {
 @Override
 public void actionPerformed(ActionEvent e) {
  System.out.println("isShowingPopup: " + isShowingPopup);
  if (isShowingPopup) {
   isShowingPopup = false;
  } else {
   Component c = (Component) e.getSource();
   menu.show(c, -1, c.getHeight());
   isShowingPopup = true;
  }
 }
});

Je me bats avec ce depuis trop longtemps maintenant. Si quelqu'un peut me donner la moindre idée de ce qui est mal à cela, ce serait génial!

Merci!

Code:

public class Button extends JButton {

    // Icon.
    private static final ImageIcon ARROW_SOUTH = new ImageIcon("ArrowSouth.png");

    // Unit popup menu.
    private final JPopupMenu menu;

    // Is the popup showing or not?
    private boolean isShowingPopup = false;

    public Button(int height) {
        super(ARROW_SOUTH);
        menu = new JPopupMenu(); // menu is populated somewhere else

        // FocusListener on the JPopupMenu
        menu.addFocusListener(new FocusListener() {
            @Override
            public void focusLost(FocusEvent e) {
                System.out.println("LOST FOCUS");
                isShowingPopup = false;
            }

            @Override
            public void focusGained(FocusEvent e) {
                System.out.println("GAINED FOCUS");
            }
        });

        // ComponentListener on the JPopupMenu
        menu.addComponentListener(new ComponentListener() {
            @Override
            public void componentShown(ComponentEvent e) {
                System.out.println("SHOWN");
            }

            @Override
            public void componentResized(ComponentEvent e) {
                System.out.println("RESIZED");
            }

            @Override
            public void componentMoved(ComponentEvent e) {
                System.out.println("MOVED");
            }

            @Override
            public void componentHidden(ComponentEvent e) {
                System.out.println("HIDDEN");
            }
        });

        // ActionListener on the JButton
        addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                System.out.println("isShowingPopup: " + isShowingPopup);
                if (isShowingPopup) {
                    menu.requestFocus();
                    isShowingPopup = false;
                } else {
                    Component c = (Component) e.getSource();
                    menu.show(c, -1, c.getHeight());
                    isShowingPopup = true;
                }
            }
        });

        // Skip when navigating with TAB.
        setFocusable(true); // Was false first and should be false in the end.

        menu.setFocusable(true);
    }

}
Était-ce utile?

La solution

Voici une autre approche qui est pas trop mal d'un hack, sinon élégant, et qui, pour autant que je sache, fonctionne. Tout d'abord, au sommet, j'ai ajouté une deuxième booléen appelé showPopup.

Le FocusListener doit être comme suit:

    menu.addFocusListener(new FocusListener() {
        @Override
        public void focusLost(FocusEvent e) {
            System.out.println("LOST FOCUS");
            isShowingPopup = false;
        }

        @Override
        public void focusGained(FocusEvent e) {
            System.out.println("GAINED FOCUS");
            isShowingPopup = true;
        }
    });

Le booléen isShowingPopup ne soit pas changé nulle part ailleurs -. Si elle obtient le focus, il suppose qu'il est montré et si elle perd le focus, il suppose qu'il est pas

Ensuite, le ActionListener sur le bouton est différent:

   addActionListener(new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent e) {
            System.out.println("isShowingPopup: " + isShowingPopup);
            if (showPopup) {
                Component c = (Component) e.getSource();
                menu.show(c, -1, c.getHeight());
                menu.requestFocus();
            } else {
                showPopup = true;
            }
        }
    });

Maintenant vient le bit vraiment nouveau. Il est un MouseListener sur le bouton:

    addMouseListener(new MouseAdapter() {
        @Override
        public void mousePressed(MouseEvent e) {
            System.out.println("ispopup?: " + isShowingPopup);
            if (isShowingPopup) {
                showPopup = false;
            }
        }

        @Override
        public void mouseReleased(MouseEvent e) {
            showPopup = true;
        }
    });

En fait, mousePressed est appelé avant que le menu perd le focus, donc isShowingPopup indique si la fenêtre a été montré avant que le bouton est enfoncé. Ensuite, si le menu était là, nous avons mis juste showPopup à false, de sorte que la méthode actionPerformed ne montre pas le menu une fois qu'il est appelé (après que la souris se laisse aller).

comportés comme prévu dans tous les cas sauf un: si le menu montrait et l'utilisateur a appuyé la souris sur le bouton, puis relâché à l'extérieur de celui-ci, actionPerformed n'a jamais été appelé. Cela signifie que showPopup est resté faux et le menu n'a pas été montré la prochaine fois que le bouton a été pressé. Pour résoudre ce problème, la méthode de mouseReleased remet à zéro showPopup. La méthode est appelée mouseReleased après actionPerformed, pour autant que je peux dire.

J'ai joué avec le bouton résultant pour un peu, faire toutes les choses que je pouvais penser au bouton, et cela a fonctionné comme prévu. Cependant, je ne suis pas sûr à 100% que les événements se produisent toujours dans le même ordre.

En fin de compte, je pense que cela est, au moins, la peine d'essayer.

Autres conseils

Voici une variante de suggestion « grand hack » Amber Shah je viens de faire. Sans le drapeau isShowingPopup ...

Ce n'est pas l'épreuve des balles, mais cela fonctionne très bien jusqu'à ce que quelqu'un arrive avec un clic incroyablement lent pour fermer la fenêtre (ou une seconde très rapide Cliquez pour rouvrir ...).

public class Button extends JButton {

 // Icon.
 private static final ImageIcon ARROW_SOUTH = new ImageIcon("ArrowSouth.png");

 // Popup menu.
 private final JPopupMenu menu;

 // Last time the popup closed.
 private long timeLastShown = 0;

 public Button(int height) {
  super(ARROW_SOUTH);
  menu = new JPopupMenu(); // Populated somewhere else.

  // Show and hide popup on left click.
  menu.addPopupMenuListener(new PopupMenuListener() {
   @Override
   public void popupMenuWillBecomeInvisible(PopupMenuEvent arg0) {
    timeLastShown = System.currentTimeMillis();
   }
   @Override public void popupMenuWillBecomeVisible(PopupMenuEvent arg0) {}
   @Override public void popupMenuCanceled(PopupMenuEvent arg0) {}
  });
  addActionListener(new ActionListener() {
   @Override
   public void actionPerformed(ActionEvent e) {
    if ((System.currentTimeMillis() - timeLastShown) > 300) {
     Component c = (Component) e.getSource();
     menu.show(c, -1, c.getHeight());
    }
   }
  });

  // Skip when navigating with TAB.
  setFocusable(false);
 }

}

Comme je l'ai dit dans les commentaires, ce n'est pas la solution la plus élégante, mais il est horriblement simple et il travaille dans 98% des cas.

Ouvert aux suggestions!

Vous pouvez utiliser le JPopupMenu.isVisible () au lieu de votre variable booléenne pour vérifier l'état actuel du menu contextuel.

Avez-vous essayé d'ajouter un ComponentListener au JPopupMenu, afin que vous sachiez quand il a été montré et caché (et mettre à jour le drapeau de votre isShowingPopup en conséquence)? Je ne sais pas l'écoute des changements de mise au point est nécessairement la bonne approche.

Qu'est-ce que vous avez besoin est un PopupMenuListener:

        menu.addPopupMenuListener(new PopupMenuListener() {

            @Override
            public void popupMenuWillBecomeVisible(PopupMenuEvent arg0) {

            }

            @Override
            public void popupMenuWillBecomeInvisible(PopupMenuEvent arg0) {
                System.out.println("MENU INVIS"); 
                isShowingPopup = false;     
            }

            @Override
            public void popupMenuCanceled(PopupMenuEvent arg0) {
                System.out.println("MENU CANCELLED"); 
                isShowingPopup = false;                     
            }
        });

J'inséré cela dans votre code et vérifié que cela fonctionne.

Eh bien, je ne peux pas être sûr sans voir l'ensemble de votre code, mais est-il possible que la fenêtre ne se fait concentrer du tout? J'ai eu des problèmes avec les choses ne pas se concentrer correctement Swing avant, afin qu'il puisse être le coupable. Essayez d'appeler setFocusable(true) dans le menu puis appeler requestFocus() lorsque vous faites apparaître le menu.

J'ai essayé la réponse de Tikhon Jelvis (introduction d'une combinaison intelligente de focusListener et mouseListener). Il ne fonctionne pas pour moi sur Linux (java7 / gtk). : - (

Lecture http: / /docs.oracle.com/javase/7/docs/api/javax/swing/JComponent.html#requestFocus%28%29 il est écrit « Notez que l'utilisation de cette méthode est déconseillée, car son comportement est la plate-forme dépendante. "

Il se peut que l'ordre des appels d'écoute a changé avec java7 ou il change avec GTK vs de Windows. Je ne recommanderais pas cette solution si vous voulez être la plate-forme indépendante.

BTW: J'ai créé un nouveau compte sur stackoverflow pour donner cette indication. Il semble que je ne suis pas autorisé à commenter sa réponse (en raison de la réputation). Mais il semble que j'ai un bouton pour le modifier. Ce stackoverflow est une chose très drôle. : -)

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top