Pregunta

necesitaba un JButton con un menú desplegable estilo adjunto. Así que tomé un JPopupMenu y unido a la JButton en el camino se puede ver en el siguiente código. Lo que tiene que hacer es lo siguiente:

  • mostrar la ventana emergente cuando se hace clic
  • ocultarlo si se hace clic una segunda vez
  • ocultarlo si se selecciona un elemento en la ventana emergente
  • ocultarlo si el usuario hace clic en otra parte de la pantalla

Estos 4 cosas trabajar, pero debido a la bandera booleana que estoy usando, si el usuario hace clic en otro lugar o selecciona un elemento, tengo que hacer doble clic en el botón antes de que aparezca de nuevo. Es por eso que he intentado añadir un FocusListener (que absolutamente no responde) para arreglar eso y establecer el indicador falso en estos casos.

EDIT: Último intento en un puesto de respuesta ...

Aquí están los oyentes: (Está en una clase que se extiende JButton, por lo que el segundo oyente está en el 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;
  }
 }
});

He estado luchando con esto durante demasiado tiempo ahora. Si alguien me puede dar una pista sobre lo que está mal con esto, sería muy bueno!

Gracias!

Código:

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

}
¿Fue útil?

Solución

Aquí hay otro enfoque que no es demasiado malo de un truco, si no elegante, y que, por lo que pude ver, funciona. En primer lugar, en la parte superior, añadí una segunda llamada booleano showPopup.

El FocusListener tiene que ser como sigue:

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

El booleano isShowingPopup no se ve modificado en cualquier otro lugar -. Si gana el foco, se asume que se ha mostrado y si se pierde el foco, se supone que no es

A continuación, el ActionListener en el botón es diferente:

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

Ahora viene lo realmente nuevo bit. Es un MouseListener en el botón:

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

Básicamente, mousePressed es llamado antes del menú pierde el foco, por lo isShowingPopup refleja si la ventana emergente se mostró antes de pulsar el botón. Entonces, si el menú estaba allí, acabamos de fijar showPopup a false, por lo que el método actionPerformed no muestra el menú una vez que se llama (ir después de que el ratón se deja).

Esto se comportó como se esperaba en todos los casos menos uno: si el menú se muestra y el usuario pulsa el ratón sobre el botón, pero lo lanzó fuera de ella, actionPerformed nunca fue llamado. Esto significaba que showPopup permaneció falsa y el menú no se demostró la próxima vez que se pulsa el botón. Para solucionar este problema, el método mouseReleased restablece showPopup. El método se llama a mouseReleased después actionPerformed, por lo que yo puedo decir.

He jugado un poco con el botón que resulta un poco, haciendo todas las cosas que se me ocurrieron al botón, y funcionó como se esperaba. Sin embargo, no estoy 100% seguro de que los acontecimientos ocurrirán siempre en el mismo orden.

En última instancia, creo que esto es, al menos, vale la pena probar.

Otros consejos

Esto es una variante de la sugestión "gran truco" de Amber Shah acabo de hacer. Sin la bandera isShowingPopup ...

No es a prueba de balas, pero funciona bastante bien hasta que alguien llega con un increíblemente lento para cerrar la ventana emergente (o una muy rápida segundo clic para volver a abrirla ...).

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

}

Como ya he dicho en los comentarios, esa no es la solución más elegante, pero es terriblemente simple y funciona en el 98% de los casos.

Abierto a sugerencias!

Se puede utilizar el JPopupMenu.isVisible () en lugar de la variable booleana para comprobar el estado actual del menú emergente.

¿Usted ha intentado añadir un ComponentListener a la JPopupMenu, para que sepa cuando se ha demostrado y se oculta (y actualizar su bandera isShowingPopup en consecuencia)? No estoy seguro de escuchar los cambios de enfoque es necesariamente el enfoque correcto.

Lo que necesita es 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;                     
            }
        });

He introducido esto en su código y verificó que funciona.

Bueno, no puedo estar seguro sin ver todo el código, pero es posible que la ventana emergente en realidad nunca se centran en absoluto? He tenido problemas con las cosas no conseguir el enfoque correctamente en la oscilación antes, por lo que podría ser el culpable. Trate de llamar a setFocusable(true) en el menú y luego llamar requestFocus() cuando se hace aparecer el menú.

Probé la respuesta de Tikhon Jelvis (introducción de una inteligente combinación de focusListener y MouseListener). No funciona para mí en Linux (Java7 / GTK). : - (

http: / /docs.oracle.com/javase/7/docs/api/javax/swing/JComponent.html#requestFocus%28%29 no está escrito "Tenga en cuenta que el uso de este método no se recomienda debido a que su comportamiento es la plataforma dependiente ".

Puede ser que el orden de las llamadas de oyentes cambió con Java7 o se cambia con GTK vs de Windows. No recomendaría esta solución si desea ser independiente de la plataforma.

Por cierto: he creado una nueva cuenta en stackoverflow dar esta receta. Parece que no se me permite hacer comentarios a su respuesta (debido a la reputación). Pero parece que tengo un botón para editarla. Este stackoverflow es una cosa muy divertida. : -)

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top