How do you hide a Swing Popup when you click somewhere else
Solution 1
As pajton noted in a previous comment, Popup is not a JComponent to which listeners can be readily bound. But, as its documentation states, "implementations of Popup are responsible for creating and maintaining their own Components to render [its subject] to the user."
So in using it as your presentation mechanism, your Popup is going to have to present itself as an actual Swing component anyway. Have it register itself to that component. Have it hide itself when the component loses focus.
import java.awt.FlowLayout;
import java.awt.Frame;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.WindowEvent;
import java.awt.event.WindowFocusListener;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.Popup;
public class PopupTester extends JFrame {
private static class MessagePopup extends Popup
implements WindowFocusListener
{
private final JDialog dialog;
public MessagePopup(Frame base, String message) {
super();
dialog = new JOptionPane().createDialog( base, "Message" );
dialog.setModal( false );
dialog.setContentPane( new JLabel( message ) );
}
@Override public void show() {
dialog.addWindowFocusListener( this );
dialog.setVisible( true );
}
@Override public void hide() {
dialog.setVisible( false );
dialog.removeWindowFocusListener( this );
}
public void windowGainedFocus( WindowEvent e ) {
// NO-OP
}
public void windowLostFocus( WindowEvent e ) {
hide();
}
}
public static void main(String[] args) {
final PopupTester popupTester = new PopupTester();
popupTester.setLayout(new FlowLayout());
popupTester.setSize(300, 100);
popupTester.add(new JButton("Click Me") {
@Override
protected void fireActionPerformed(ActionEvent event) {
Point location = getLocationOnScreen();
MessagePopup popup = new MessagePopup( popupTester, "Howdy" );
popup.show();
}
});
popupTester.add(new JButton("No Click Me"));
popupTester.setVisible(true);
popupTester.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
Solution 2
Use a JPopupMenu. You can add any component to it, not just menu items.
Solution 3
You can add MouseListener to your background panel and hide the popup when somebody clicks on the panel.
To react on application minimization, use WindowListener attached to a JFrame.
Etc, etc. May seem tedious, but surely will work.
Solution 4
Thanks pajton and Noel Ang for getting me pointed in the right direction! Here is the solution that I ended up with. I'm just including it here so that others may benefit from it.
I ended up going with a JWindow since it doesn't get the window decorations but does get focus events.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class PopupTester extends JFrame {
private static class MessagePopup extends Popup implements WindowFocusListener {
private final JWindow dialog;
public MessagePopup(Frame base, JLabel component, int x, int y) {
super();
dialog = new JWindow(base);
dialog.setFocusable(true);
dialog.setLocation(x, y);
dialog.setContentPane(component);
component.setBorder(new JPopupMenu().getBorder());
dialog.setSize(component.getPreferredSize());
dialog.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
dialog.setVisible(false);
}
}
});
}
@Override
public void show() {
dialog.addWindowFocusListener(this);
dialog.setVisible(true);
}
@Override
public void hide() {
dialog.setVisible(false);
dialog.removeWindowFocusListener(this);
}
public void windowGainedFocus(WindowEvent e) {
// NO-OP
}
public void windowLostFocus(WindowEvent e) {
hide();
}
}
public static void main(String[] args) {
final PopupTester popupTester = new PopupTester();
popupTester.setLayout(new FlowLayout());
popupTester.setSize(300, 100);
popupTester.add(new JButton("Click Me") {
@Override
protected void fireActionPerformed(ActionEvent event) {
Point location = getLocationOnScreen();
int x = (int) location.getX();
int y = (int) (location.getY() + getHeight());
JLabel myComponent = new JLabel("Howdy");
MessagePopup popup = new MessagePopup(popupTester, myComponent, x, y);
popup.show();
}
});
popupTester.add(new JButton("No Click Me"));
popupTester.setVisible(true);
popupTester.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
Solution 5
You could add a FocusListener to your popup-window, and dispose it when it loses focus. However, that will cause you some troubles when the focus loss is due to some other application (new windows comes to the foreground, you switch virtual desktops, etc.)
But perhaps you (a) know that that cannot happen in your case or (b) would want to close the popup in such cases anyway, a focus-based approach may still be interesting to you.
Related videos on Youtube
Casey Watson
Updated on April 21, 2022Comments
-
Casey Watson about 2 years
I have a Popup that is shown when a user clicks on a button. I would like to hide the popup when any of the following events occur:
- The user clicks somewhere else in the application. (The background panel for example)
- The user minimizes the application.
The JPopupMenu has this behavior, but I need more than just JMenuItems. The following code block is a simplified illustration to demonstrate the current usage.
import java.awt.*; import java.awt.event.ActionEvent; import javax.swing.*; public class PopupTester extends JFrame { public static void main(String[] args) { final PopupTester popupTester = new PopupTester(); popupTester.setLayout(new FlowLayout()); popupTester.setSize(300, 100); popupTester.add(new JButton("Click Me") { @Override protected void fireActionPerformed(ActionEvent event) { Point location = getLocationOnScreen(); int y = (int) (location.getY() + getHeight()); int x = (int) location.getX(); JLabel myComponent = new JLabel("Howdy"); Popup popup = PopupFactory.getSharedInstance().getPopup(popupTester, myComponent, x, y); popup.show(); } }); popupTester.add(new JButton("No Click Me")); popupTester.setVisible(true); popupTester.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } }
-
Casey Watson about 14 yearsGood suggestion. This gets tough with a big application because I can't add a MouseListener to every component on the screen.
-
pajton about 14 yearsIt is a pity that
Popup
itself is not aJComponent
. Then you could attach some listener to catch events when it loses focus. Maybe consider usingJDialog
and then simplyMouseListener
and itsmouseExited()
method. -
Casey Watson about 14 yearsJDialog comes with the baggage of window decorations. I was able to mock something up that uses JWindow but there is a ton of manual event handling and it still ends up pretty quirky. Is Popup really fundamentally broken that it can't support something like this?
-
pajton about 14 yearsIt extends
Object
so... But, maybe adding aMouseListener
to the contentsComponent
that is being passed inPopup
constructor would work? -
Casey Watson about 14 yearsThis is a good solution. Adding the WindowFocusListener interface to the Popup does the trick. I ended up using a JWindow instead of a JDialog because I didn't want the window decorations. I'll post the final solution.
-
Casey Watson about 14 yearsI was able to get this approach to work too. It ends up being much simpler. Apparently the behavior to hide when the menu loses focus only works when you pass the main frame as the invoker on the show() method. If you use the setVisible(true) you will not get the desired behavior.
-
CarlG almost 13 yearsI also do this routinely. Simply set the layout of the JPopupMenu to a BorderLayout and add your contents with the CENTER constraint. JPopupMenu has no problem rendering arbitrary swing contents.
-
Michal Vician over 10 yearsI tried more attitudes (borderless JDialog, javax.swing.Popup) and this seems to be the best solution.