Zeige / a JPopupMenu von einem JButton versteckt; Focuslistener nicht?
-
19-09-2019 - |
Frage
brauchte ich einen JButton mit einem Drop-Down-Menü Stil angebracht. Also nahm ich einen JPopupMenu und befestigte es an der JButton in der Art und Weise Sie in der unten stehenden Code sehen können. Was es tun muss, ist dies:
- das Popup zeigen, wenn darauf geklickt
- es verstecken, wenn ein zweites Mal angeklickt
- verstecken, wenn ein Element in dem Popup ausgewählt ist
- versteckt es, wenn der Benutzer irgendwo anders auf dem Bildschirm klickt
Diese 4 Dinge funktionieren, aber wegen der boolean flag Ich verwende, wenn der Benutzer woanders klickt oder ein Element auswählt, ich habe zweimal auf die Schaltfläche klicken, bevor es wieder auftaucht. Deshalb habe ich versucht, einen Focuslistener hinzuzufügen (was absolut nicht reagiert), das zu beheben und die Flagge falsch in diesen Fällen eingestellt.
EDIT: Letzter Versuch in einer Antwort Post ...
Hier sind die Zuhörer: (es ist in einer Klasse JButton erstreckt, so dass der zweite Hörer auf der JButton ist.)
// 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;
}
}
});
Ich habe mit diesem für Art und Weise kämpft nun zu lang. Wenn mir jemand eine Ahnung geben kann, was mit diesem falsch ist, wäre es toll!
Danke!
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);
}
}
Lösung
Hier ist ein weiterer Ansatz, der nicht so schlecht wie ein Hack, wenn nicht elegant, und die, soweit ich das beurteilen kann, funktioniert. Zuerst an der Spitze, habe ich eine zweite boolean genannt showPopup
.
Die FocusListener
muss wie folgt aussehen:
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;
}
});
Die isShowingPopup
boolean bekommt nirgendwo sonst nicht verändert -. Wenn es den Fokus erhält, nimmt sie es gezeigt, und wenn es den Fokus verliert, nimmt sie es nicht
Als nächstes wird die ActionListener
auf der Schaltfläche ist anders:
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;
}
}
});
Jetzt kommt die wirklich neue Bit. Es ist ein MouseListener
auf die Schaltfläche:
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;
}
});
Im Grunde mousePressed
aufgerufen wird, bevor das Menü des Fokus verliert, so isShowingPopup
reflektiert, ob das Popup angezeigt wurde, bevor die Taste gedrückt wird. Wenn dann das Menü dort ist, haben wir gerade eingestellt showPopup
false
, so dass die actionPerformed
Methode nicht das Menü nicht zeigen, sobald sie aufgerufen wird (nachdem die Maus losgelassen wird).
Das in jedem Fall wie erwartet verhielten sich aber ein: wenn das Menü wurde zeigt, und der Benutzer betätigt die Maus auf den Button aber veröffentlichte es außerhalb davon wurde actionPerformed
nie genannt. Dies bedeutete, dass showPopup
blieb falsch und das Menü war nicht das nächste Mal, wenn die Taste gedrückt wurde, gezeigt. Um dies zu beheben, setzt die mouseReleased
Methode showPopup
. Die mouseReleased
Methode wird nach actionPerformed
genannt, soweit ich das beurteilen kann.
Ich spielte mit der resultierenden Taste für ein bisschen, all die Dinge, die ich von der Taste denken konnte, und es funktionierte wie erwartet. Aber ich bin nicht 100% sicher, dass die Ereignisse immer in der gleichen Reihenfolge geschehen werden.
Letztendlich denke ich, das ist zumindest ein Versuch wert.
Andere Tipps
Hier ist eine Variante von Amber Shahs „großen Hack“ Vorschlag, den ich gerade gemacht. Ohne die isShowingPopup Flagge ...
Es ist nicht kugelsicher, aber es funktioniert ganz gut, bis jemand mit einem unglaublich langsam Klick kommt das Popup (oder einen sehr schnellen zweiten Klick wieder zu öffnen, es ...) zu schließen.
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);
}
}
Wie ich in den Kommentaren gesagt, das ist nicht die eleganteste Lösung, aber es ist schrecklich einfach und es funktioniert in 98% der Fälle.
Offen für Vorschläge!
Sie könnten die JPopupMenu.isVisible () statt Ihrer Boolesche Variable den aktuellen Zustand der Popup-Menüs zu überprüfen.
Haben Sie versucht, eine ComponentListener
zum JPopupMenu
hinzufügen, so dass Sie wissen, wann es gezeigt und versteckt (und Ihre isShowingPopup
Flagge entsprechend aktualisieren)? Ich bin nicht sicher für Fokus Änderungen hören ist unbedingt der richtige Ansatz.
Was Sie brauchen, ist ein 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;
}
});
I eingefügt dies in Ihren Code und überprüft, dass es funktioniert.
Nun, ich kann nicht sicher sein, ohne die gesamten Code zu sehen, aber es ist möglich, dass das Pop-up nie bekommt eigentlich überhaupt konzentrieren? Ich habe Probleme gehabt mit den Dingen nicht vor dem Fokus richtig in Swing bekommen, so dass es könnte die Ursache sein. Versuchen Sie fordern setFocusable(true)
auf dem Menü und dann requestFocus()
aufrufen, wenn Sie das Menü erscheinen zu lassen.
Ich habe versucht, die Antwort von Tichon Jelvis (eine intelligente Kombination von focuslistener und mouselistener Einführung). Es ist nicht für mich auf Linux (Java7 / gtk) arbeiten. : - (
http: / /docs.oracle.com/javase/7/docs/api/javax/swing/JComponent.html#requestFocus%28%29 Sie haben geschrieben: „Beachten Sie, dass die Anwendung dieser Methode abgeraten, da sein Verhalten Plattform abhängig. "
Es kann sein, dass die Reihenfolge der Zuhörer Anrufe mit Java7 geändert oder es ändert sich mit GTK vs Sie Windows. Ich würde diese Lösung nicht empfehlen, wenn Sie plattformunabhängig sein soll.
BTW: Ich habe ein neues Konto auf Stackoverflow diesen Hinweis zu geben. Es scheint, ich bin nicht auf seine Antwort äussern darf (wegen des Rufes). Aber es scheint, ich habe eine Schaltfläche zu bearbeiten. Dieser Stackoverflow ist eine sehr lustige Sache. : -)