How do you hide a Swing Popup when you click somewhere else

15,217

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.

Share:
15,217

Related videos on Youtube

Casey Watson
Author by

Casey Watson

Updated on April 21, 2022

Comments

  • Casey Watson
    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:

    1. The user clicks somewhere else in the application. (The background panel for example)
    2. 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
    Casey Watson about 14 years
    Good suggestion. This gets tough with a big application because I can't add a MouseListener to every component on the screen.
  • pajton
    pajton about 14 years
    It is a pity that Popup itself is not a JComponent. Then you could attach some listener to catch events when it loses focus. Maybe consider using JDialog and then simply MouseListener and its mouseExited() method.
  • Casey Watson
    Casey Watson about 14 years
    JDialog 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
    pajton about 14 years
    It extends Object so... But, maybe adding a MouseListener to the contents Component that is being passed in Popup constructor would work?
  • Casey Watson
    Casey Watson about 14 years
    This 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
    Casey Watson about 14 years
    I 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
    CarlG almost 13 years
    I 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
    Michal Vician over 10 years
    I tried more attitudes (borderless JDialog, javax.swing.Popup) and this seems to be the best solution.