15

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

6 Answers 6

14

Use a JPopupMenu. You can add any component to it, not just menu items.

3
  • 4
    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. Commented Apr 7, 2010 at 19:25
  • 2
    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.
    – CarlG
    Commented Jul 11, 2011 at 23:50
  • I tried more attitudes (borderless JDialog, javax.swing.Popup) and this seems to be the best solution. Commented Nov 20, 2013 at 14:34
6

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);
    }
}
1
  • 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. Commented Apr 7, 2010 at 16:17
2

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.

4
  • Good suggestion. This gets tough with a big application because I can't add a MouseListener to every component on the screen. Commented Apr 5, 2010 at 21:47
  • 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.
    – pajton
    Commented Apr 5, 2010 at 21:52
  • 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? Commented Apr 5, 2010 at 22:38
  • It extends Object so... But, maybe adding a MouseListener to the contents Component that is being passed in Popup constructor would work?
    – pajton
    Commented Apr 5, 2010 at 22:57
2

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

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.

0

I know this is an old question but I really needed the Popup to work in my case. So I tried a few things and the following is my solution.

Add a FocusListener to the component you add to the popup and program the focusLost event on that component to hide the popup when focus is lost. Call the requestFocus method on your component just after showing the popup.

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;

import javax.swing.*;

public class PopupTester extends JFrame {
    JButton myButton = new JButton("Click Me");
    JLabel myComponent = new JLabel("Howdy");
    Popup popup = null;

    public PopupTester() {
        setLayout(new FlowLayout());
        setSize(300, 100);
        add(myButton);
        add(new JButton("No Click Me"));
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        myComponent.addFocusListener(new FocusAdapter() {
            public void focusLost(FocusEvent e) {
                if (popup != null) {
                    popup.hide();
                }
            }
        });

        myButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent arg0) {
                if (popup != null) {
                    popup.hide();
                    popup = null;
                }

                Point location = myButton.getLocationOnScreen();
                int y = (int) (location.getY() + myButton.getHeight());
                int x = (int) location.getX();
                popup = PopupFactory.getSharedInstance().getPopup(PopupTester.this, myComponent, x, y);
                popup.show();
                myComponent.requestFocus();
            }
        });
    }

    public static void main(String[] args) {
        PopupTester popupTester = new PopupTester();
        popupTester.setVisible(true);
    }
}

Not the answer you're looking for? Browse other questions tagged or ask your own question.