2010-03-11 21 views
7

Necesitaba un JButton con un menú de estilo desplegable adjunto. Así que tomé un JPopupMenu y lo adjunté al JButton de la manera que se puede ver en el siguiente código. Lo que tiene que hacer es lo siguiente:Mostrar/ocultar un JPopupMenu desde un JButton; FocusListener no funciona?

  • mostrar la ventana emergente cuando se hace clic
  • ocultar que si se hace clic una segunda vez
  • ocultar que si se selecciona un elemento en la ventana emergente
  • ocultar que si el usuario clics en otro lugar en la pantalla

Estas 4 cosas funcionan, pero debido a la bandera booleana que estoy usando, si el usuario hace clic en otro lugar o selecciona un elemento, tengo que hacer clic dos veces en el botón antes de que se muestre de nuevo Es por eso que traté de agregar un FocusListener (que no responde en absoluto) para arreglarlo y configurar el indicador como falso en estos casos.

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

Éstos son los oyentes: (. Está en una clase JButton se extiende, 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 peleando con esto por mucho tiempo ahora. Si alguien puede darme una pista sobre lo que está mal con esto, ¡sería genial!

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

} 
+0

Entonces, el principal problema que tengo es que focusGained() y focusLost() nunca se disparan, aunque sigo haciendo que aparezca y desaparezca. – Joanis

Respuesta

1

Aquí hay otro enfoque que no es demasiado malo de un truco, si no elegante, y que, por lo que pude ver, funciona. Primero, en la parte superior, agregué un segundo booleano llamado showPopup.

El FocusListener tiene que ser de la siguiente manera:

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 puedan ser cambiados en cualquier otro lugar - si gana el foco, se asume que se ha mostrado y si se pierde el foco, se supone que ISN' t.

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í, simplemente configuramos showPopup en false, de modo que el método actionPerformed no muestra el menú una vez que se llama (después de soltar el mouse).

Esto se comportó como se esperaba en todos los casos menos uno: si el menú se mostraba y el usuario presionaba el mouse sobre el botón pero lo soltó fuera de él, nunca se llamó al actionPerformed. Esto significaba que showPopup permanecía en falso y que el menú no se mostraba la próxima vez que se presionó el botón. Para solucionar esto, el método mouseReleased restablece showPopup. El método mouseReleased se llama después de actionPerformed, por lo que 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 eventos siempre sucedan en el mismo orden.

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

+0

Guau, no sé por qué el FocusListener funciona ahora (¡también llamé a requestFocus()!) ... ¡pero acabo de probar todo esto y parece funcionar perfectamente! ¡Eso es exactamente lo que faltaba! ¡Buen trabajo! ¡Muchas gracias! – Joanis

+1

Creo que el problema radicaba en que llamó a requestFocus(): lo llamé justo después de menu.show(), lo que hizo que se enfocara justo después de mostrarse, mientras lo llamaba en el bloque que se activó si el menú ya estaba mostrando "if (isShowingPopup) ..." que hace que trate de enfocarse en el momento equivocado. –

+0

No hará la prueba, probablemente tenga razón, ¡ya que funciona ahora! Gracias de nuevo. – Joanis

1

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

+1

Intenté hacer eso al principio, pero el problema es este: Tan pronto como haga clic en el botón (o en cualquier otro lugar) mientras esté visible la ventana emergente, la ventana emergente se cerrará automáticamente de inmediato. Entonces isVisible() devuelve falso sin importar qué. Este problema (obviamente) también se aplica a isFocusOwner() y isShowing(). – Joanis

0

Bueno, no puedo estar seguro sin ver todo tu código, pero ¿es posible que la ventana emergente nunca se enfoque en absoluto? He tenido problemas con las cosas que antes no me enfocaban correctamente en Swing, por lo que podría ser el culpable. Intente llamar al setFocusable(true) en el menú y luego llamar al requestFocus() cuando aparezca el menú.

+0

Acabo de intentar; tampoco funciona Publicaré el código en un segundo. – Joanis

1

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 que escuchar los cambios de enfoque sea necesariamente el enfoque correcto.

+0

Esto parece ser una muy buena idea, pero al igual que el FocusListener, el ComponentListener no responde (bueno, solo una vez: cuando aparece la ventana emergente la primera vez que recibo una llamada para "cambiar el tamaño"). Puse una llamada println ("") en cada uno de sus métodos y nunca se imprime nada. Publicaré el código completo. – Joanis

1

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

Inserté esto en su código y comprobó que funciona.

+0

Gracias por su contribución. Intenté este enfoque ya ... Esto soluciona los problemas discutidos anteriormente, pero crea uno nuevo. Con esto, no podemos cerrar la ventana emergente haciendo clic en el botón, ya que se abrirá sin importar si está cerrado o abierto al principio. – Joanis

+0

Cuando el menú emergente se vuelve visible, puede modificar el oyente de acción en el botón para que no abra la ventana emergente (es decir, simplemente elimine el oyente). Luego, cuando el menú se vuelva invisible, configúrelo de nuevo (agregue al oyente nuevamente). – Dave

+0

Tiene razón. No estoy seguro de si hay una manera de hacer esto "correctamente" ya que el JPopupMenu fusiona todos los otros eventos del mouse en el PopupMenuEvent. Aquí hay un truco (GRANDE): guarde el System.currentTimeMillis() dentro de cada evento popupMenuWillBecomeInvisible, y luego dentro de actionPerformed, haga "if (isShowingPopup || (System.currentTimeMillis() - savedTime) <100) luego muestre ... Ok, no estoy sugiriendo que lo mantengas de esta manera, pero si tiene que funcionar así ... Lo único que puedo pensar es escribir tu propia implementación de JPopupMenu y manejar los eventos del mouse de la forma que desees. –

3

Aquí hay una variante de la sugerencia de "big hack" de Amber Shah que acabo de hacer. Sin el indicador isShowingPopup ...

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

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!

0

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). :-(

lectura 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 depende de la plataforma."

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

BTW: Creé una nueva cuenta en stackoverflow para dar esta pista. Parece que no puedo comentar su respuesta (debido a reputación). Pero parece que tengo un botón para editarlo. Este stackoverflow es algo muy gracioso.:-)

Cuestiones relacionadas