Domanda

Ho bisogno di un JButton con allegato un elenco a discesa menu stile.Così ho preso un JPopupMenu e allegata al JButton in modo che si può vedere nel codice riportato di seguito.Quello che si deve fare è questo:

  • visualizzare il popup quando si fa clic
  • nascondere se cliccato una seconda volta
  • nascondere se un elemento è selezionato nella finestra popup
  • nascondere se l'utente fa clic da qualche altra parte nello schermo

Queste 4 cose di lavoro, ma a causa del flag booleano che sto usando, se l'utente fa clic da qualche altra parte o seleziona una voce, devo cliccare due volte sul pulsante prima mostra di nuovo.Ecco perché ho cercato di aggiungere un FocusListener (assolutamente non risponde) per risolvere questo e impostare il flag false in questi casi.

EDIT:Ultimo tentativo in un post di risposta...

Qui ci sono gli ascoltatori:(È in una classe che estende JButton, in modo che il secondo ascolto è sul 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;
  }
 }
});

Ho combattuto con questo per troppo tempo ormai.Se qualcuno mi può dare un indizio su che cosa c'è di sbagliato con questo, sarebbe bello!

Grazie!

Codice:

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

}
È stato utile?

Soluzione

Qui è un altro approccio che non è troppo male di un hack, se non elegante, e che, per quanto ho potuto dire, funziona.Il primo, in alto, ho aggiunto un secondo boolean chiamato showPopup.

Il FocusListener deve essere come segue:

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

Il isShowingPopup boolean non vengono modificate in qualsiasi altro luogo, se si ottiene lo stato attivo, si assume che viene visualizzato, e se si perde la messa a fuoco, si assume che non è così.

A quel punto, il ActionListener il pulsante è diverso:

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

Ora arriva il vero nuovo bit.Si tratta di un MouseListener sul pulsante:

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

Fondamentalmente, mousePressed viene chiamato prima che il menu perde la messa a fuoco, in modo isShowingPopup riflette se il popup è stato mostrato prima che il pulsante viene premuto.Quindi, se il menu era lì, abbiamo appena impostato showPopup per false, in modo che il actionPerformed metodo non vedi il menu una volta che viene chiamato (dopo il mouse è lasciato andare).

Questa comportato come previsto, in ogni caso, ma:se il menu è stata la mostra e l'utente ha premuto il mouse sul pulsante, ma rilasciato al di fuori di esso, actionPerformed non è mai stato chiamato.Questo significava che showPopup rimase false e il menu non è stata indicata la prossima volta che il pulsante è stato premuto.Per risolvere questo problema, il mouseReleased metodo azzera showPopup.Il mouseReleased il metodo viene chiamato dopo actionPerformed, per quanto posso dire.

Ho suonato in giro con il conseguente pulsante per un po', facendo tutte le cose che posso pensare al pulsante, e ha funzionato come previsto.Tuttavia, io non sono sicuro al 100% che gli eventi si verificano sempre nello stesso ordine.

In definitiva, credo che questo è, almeno, vale la pena di provare.

Altri suggerimenti

Ecco una variante di Ambra Shah "big hack" il suggerimento che ho appena fatto.Senza isShowingPopup bandiera...

Non è a prova di proiettile, ma funziona abbastanza bene fino a quando qualcuno arriva con un incredibilmente lento, fare clic su per chiudere il popup (o una molto veloce secondo clic per riaprirlo...).

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

}

Come ho detto nei commenti, che non è la soluzione più elegante, ma è terribilmente semplice e funziona nel 98% dei casi.

Aperto a suggerimenti!

Si potrebbe utilizzare il JPopupMenu.e ' visibile() invece della variabile Booleana per verificare lo stato attuale del menu a comparsa.

Hai provato ad aggiungere un ComponentListener per il JPopupMenu, in modo da sapere quando è stato mostrato e nascosto (e aggiornare il vostro isShowingPopup bandiera di conseguenza)?Io non sono sicuro di ascolto per la messa a fuoco cambia è necessariamente il giusto approccio.

Quello che vi serve è 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;                     
            }
        });

Ho inserito questo nel codice e verificato che funziona.

Beh, non posso essere sicuro senza vedere tutto il codice, ma è possibile che il popup non viene effettivamente messa a fuoco a tutti?Ho avuto problemi con le cose' non sempre a fuoco correttamente in Swing prima, quindi potrebbe essere il colpevole.Provare a chiamare setFocusable(true) sul menu e quindi la chiamata requestFocus() quando si effettua il menu visualizzato.

Ho cercato la Risposta di Tichon Jelvis (introduzione di una intelligente combinazione di focusListener e mouseListener).Non mi funziona su Linux (Java7/gtk).:-(

La lettura http://docs.oracle.com/javase/7/docs/api/javax/swing/JComponent.html#requestFocus%28%29 c'è scritto "si noti che l'uso di questo metodo è sconsigliato, perché il suo comportamento è dipendente dalla piattaforma."

Può essere che l'ordine di ascoltatore chiama cambiato con Java7 o cambia con GTK vs Windows.Io non consiglierei questa soluzione, se si vuole essere indipendente dalla piattaforma.

BTW:Ho creato un nuovo account su stackoverflow per dare questo suggerimento.Sembra che non mi è permesso un commento alla sua risposta (a causa della reputazione).Ma mi sembra di avere un pulsante per la modifica.Questo stackoverflow è molto divertente la cosa.:-)

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top