2010-03-11 8 views
7

Avevo bisogno di un JButton con un menu di stile a discesa allegato. Così ho preso un JPopupMenu e l'ho collegato al JButton nel modo in cui puoi vedere nel codice qui sotto. Che ha bisogno di fare è questo:Mostrare/nascondere un JPopupMenu da un JButton; FocusListener non funziona?

  • mostrano la pop-up quando si fa clic
  • nascondere se cliccato una seconda volta
  • nascondere se un elemento è selezionato nel popup
  • nascondere, se l'utente clicca da qualche altra parte sullo schermo

Queste 4 cose funzionano, ma a causa della bandiera booleana che sto usando, se l'utente fa clic da qualche altra parte o seleziona un elemento, devo cliccare due volte sul pulsante prima che mostri di nuovo. Ecco perché ho provato ad aggiungere un FocusListener (che non risponde assolutamente) per correggerlo e impostare il flag false in questi casi.

EDIT: Ultimo tentativo in un posto risposta ...

Ecco gli ascoltatori: (. E 'in una classe che estende JButton, quindi la seconda ascoltatore è in 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 adesso. Se qualcuno mi può dare un indizio su cosa c'è di sbagliato in questo, sarebbe fantastico!

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

} 
+0

Quindi, il problema principale che ho è che focusGained() e focusLost() non vengono mai attivati, anche se continuo a far apparire e scomparire il popup. – Joanis

risposta

1

Ecco un altro approccio che non è male di un trucco, se non elegante, e che, per quanto posso dire, funziona. Per prima cosa, in cima, ho aggiunto un secondo booleano chiamato showPopup.

La 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 booleana non viene cambiato in qualsiasi altro luogo - se guadagna messa a fuoco, si presuppone che è mostrato e se perde messa a fuoco, si presuppone esso isn' t.

Avanti, il ActionListener sul 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 veramente nuovo bit. E '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; 
     } 
    }); 

In sostanza, mousePressed viene chiamato prima che il menu non è più attivo, in modo da isShowingPopup riflette se il popup è stato mostrato prima che venga premuto il pulsante. Quindi, se il menu era presente, impostiamo semplicemente showPopup su false, in modo che il metodo actionPerformed non mostri il menu una volta chiamato (dopo che il mouse è stato rilasciato).

Questo comportamento si è comportato come previsto in ogni caso tranne uno: se il menu era visualizzato e l'utente ha premuto il mouse sul pulsante ma lo ha rilasciato al di fuori di esso, actionPerformed non è mai stato chiamato. Ciò significava che showPopup rimaneva falso e che il menu non veniva mostrato alla successiva pressione del pulsante. Per risolvere questo problema, il metodo mouseReleased si azzera showPopup. Il metodo mouseReleased viene chiamato dopo actionPerformed, per quanto posso dire.

Ho giocato un po 'con il pulsante risultante, facendo tutte le cose che potevo pensare al pulsante, e ha funzionato come previsto. Tuttavia, non sono sicuro al 100% che gli eventi si verifichino sempre nello stesso ordine.

In definitiva, penso che questo sia, almeno, vale la pena provare.

+0

Wow, non so perché il FocusListener funzioni ora (ho chiamato anche requestFocus()!) ... ma ho appena provato tutto questo e sembra funzionare perfettamente! Questo è esattamente ciò che mancava! Buon lavoro! Grazie mille! – Joanis

+1

Penso che il problema risieda nel punto in cui hai chiamato requestFocus() - l'ho chiamato subito dopo menu.show(), che ha reso il focus subito dopo essere stato mostrato, mentre lo hai chiamato nel blocco che ha sparato se il menu era già mostra "if (isShowingPopup) ..." che lo fa cercare di focalizzarsi nel momento sbagliato. –

+0

Non farai il test, probabilmente hai ragione sin da quando funziona! Grazie ancora. – Joanis

1

è possibile utilizzare la JPopupMenu.isVisible() al posto della variabile booleana per verificare lo stato attuale del menu a comparsa.

+1

All'inizio ho provato a farlo, ma il problema è questo: Non appena si fa clic sul pulsante (o altrove) mentre il popup è visibile, il popup viene automaticamente chiuso immediatamente. Quindi isVisible() restituisce false indipendentemente da cosa. Questo problema (ovviamente) si applica anche a isFocusOwner() e isShowing(). – Joanis

0

Beh, non posso essere sicuro di non vedere tutto il tuo codice, ma è possibile che il popup non ottenga mai la messa a fuoco? Ho avuto problemi con le cose 'non ottenere correttamente la messa a fuoco in Swing prima, quindi potrebbe essere il colpevole. Prova a chiamare setFocusable(true) nel menu e quindi chiama requestFocus() quando viene visualizzato il menu.

+0

Appena provato; non funziona neanche. Pubblicherò il codice in un secondo. – Joanis

1

Hai provato l'aggiunta di un ComponentListener al JPopupMenu, in modo da sapere quando è stato mostrato e nascosto (e aggiornare il isShowingPopup bandiera di conseguenza)? Non sono sicuro che ascoltare i cambiamenti di messa a fuoco sia necessariamente l'approccio giusto.

+0

Questa sembra essere una buona idea, ma proprio come FocusListener, ComponentListener non risponde (beh, solo una volta: quando il popup viene mostrato la prima volta che ricevo una chiamata a "ridimensionato"). Ho messo una chiamata println ("") in ognuno dei suoi metodi e nulla è mai stato stampato. Posterò il codice completo – Joanis

1

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.

+0

Grazie per l'input. Ho già provato questo approccio ... Questo risolve i problemi discussi sopra, ma ne crea uno nuovo. Con questo, non possiamo chiudere il popup facendo clic sul pulsante, perché si aprirà non importa se è chiuso o aperto all'inizio. – Joanis

+0

Quando il menu popup diventa visibile, è possibile modificare il listener di azioni sul pulsante in modo che non apra il popup (ovvero, rimuovi solo l'ascoltatore). Quindi quando il menu diventa invisibile, reimpostalo (aggiungi l'ascoltatore indietro). – Dave

+0

Sei corretto. Non sono sicuro se c'è un modo per farlo in modo "corretto" dal momento che JPopupMenu combina tutti gli altri eventi del mouse in PopupMenuEvent. Ecco un (BIG) hack: salva System.currentTimeMillis() all'interno di ogni evento popupMenuWillBecomeInvisible, e poi all'interno di actionPerformed, fai "if (isShowingPopup || (System.currentTimeMillis() - savedTime) <100) quindi mostra ... Ok, non ti sto suggerendo di tenerlo così, ma se deve funzionare così ... L'unica altra cosa che posso pensare è scrivere la tua implementazione JPopupMenu e gestire gli eventi del mouse nel modo che preferisci. –

3

Ecco una variante del suggerimento "big hack" di Amber Shah che ho appena creato. Senza il flag isShowingPopup ...

Non è a prova di proiettile, ma funziona abbastanza bene fino a quando qualcuno arriva con un clic incredibilmente lento per chiudere il popup (o un secondo clic molto veloce 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 ai suggerimenti!

0

Ho provato la risposta di Tikhon Jelvis (introducendo una combinazione intelligente di focusListener e mouseListener). Non funziona per me su Linux (Java7/gtk). :-(

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ò darsi che l'ordine delle chiamate ascoltatore cambiato con Java7 o cambia con GTK vs Windows Non consiglierei questa soluzione se vuoi essere indipendente dalla piattaforma

BTW: Ho creato un nuovo account su StackOverflow per dare questo suggerimento. Sembra che non sia autorizzato a commentare la sua risposta (a causa di reputazione), ma sembra che abbia un pulsante per modificarlo Questo stackoverflow è una cosa molto divertente.:-)

Problemi correlati