5

I want to be notified when my JPopupMenu is hidden — whether because an item was selected, the menu was dismissed, or setVisible(false) was called on it. Here is my test code:

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

public class A extends ComponentAdapter implements Runnable, ActionListener {
    private JButton b;

    public static void main(String[] args) {
        EventQueue.invokeLater(new A());
    }

    public void run() {
        JFrame f = new JFrame("Test");
        b = new JButton("Click me");
        b.addActionListener(this);
        f.add(b);
        f.pack();
        f.setVisible(true);
    }

    public void actionPerformed(ActionEvent e) {
        JPopupMenu pm = new JPopupMenu();
        pm.addComponentListener(this);
        pm.add("Popup...");
        pm.add("...menu!");
        pm.show(b, 10, 10);
    }

    public void componentShown(ComponentEvent e) { System.out.println("componentShown"); }
    public void componentHidden(ComponentEvent e) { System.out.println("componentHidden"); }
}

Regardless of how I interact with the menu, neither of the two ComponentListener methods are being called. Why is that? Is there different/better/correct way of finding out when my JPopupMenu is hidden?

Thanks,

Cameron

heycam
  • 2,188
  • 15
  • 19
  • 2
    Related question (@heycam some of the discussion in it may be useful): http://stackoverflow.com/questions/2421914/showing-hiding-a-jpopupmenu-from-a-jbutton-focuslistener-not-working – Ash Jun 14 '10 at 03:29
  • 1
    As @Ash suggests, look at `PopupMenuListener`. – trashgod Jun 14 '10 at 03:36
  • Excellent, thanks Ash and trashgod. No idea how I overlooked PopupMenuListeners! Still kind of funny that ComponentListeners aren't called for JPopupMenus, though. – heycam Jun 14 '10 at 03:48
  • +1 Good http://sscce.org/. I like your `implements Runnable` approach. – trashgod Jun 14 '10 at 03:55
  • I had three inner classes before making my main class implement the interfaces I needed. Just wanted to make the test code more compact and readable. :) – heycam Jun 14 '10 at 04:03
  • I used to implement interfaces in the main class, but I've really gone away from it (except in cases like this maybe, as heycam says, for test code). It publicly exposes stuff in a class that I don't think should be. – Ash Jun 14 '10 at 04:08
  • The only reason I see for the specialized `PopupMenuListener` is the impedance mismatch with plain `ComponentListener`. I'm always awed by the sheer number of classes implementing `EventListener`: file:///Users/Shared/javadoc/jdk6/api/java/util/EventListener.html – trashgod Jun 14 '10 at 04:12
  • @Ash: I agree, particularly for listeners. It can lead to anti-patterns of then having to have a big if-block depending on what the source of the event is, when having a different instance of the listener per source would be more suitable. For an SSCCE it's perfect though. – Mark Peters Jun 14 '10 at 04:36

1 Answers1

6

JPopupMenu has a special listener for visibility change events:

pm.addPopupMenuListener(new PopupMenuListener() {
    @Override
    public void popupMenuCanceled(PopupMenuEvent e) {
        System.out.println("cancelled");
    }

    @Override
    public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
        System.out.println("vanishing");
    }

    @Override
    public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
        System.out.println("appearing");
    }
});

Note, however, as method names hint, they are called before visibility changes, so if you're calling isVisible() somewhere in the event handlers, you should be aware of that, for example:

@Override
public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
    updateMenu();
}

private void updateMenu() {
    if (!menu.isVisible()) { // this won't work!
        // perform some updates
    }
}

With regards to why ComponentListener isn't sending you events on the menu disappearing, this might explain:

The component-hidden and component-shown events occur only as the result of calls to a Component 's setVisible method. For example, a window might be miniaturized into an icon (iconified) without a component-hidden event being fired.

Source: ComponentListener tutorial (non-canonical perhaps, but from the horse's mouth.)

Consider that in conjunction with JPopupMenu's implementation of setVisible:

    public void setVisible(boolean b) {
        // Not supported for MenuComponents
    }

And you might know how it so happens, but not why it happens (what is the justification and where is that properly documented?)

Sergei Tachenov
  • 24,345
  • 8
  • 57
  • 73
Mark Peters
  • 80,126
  • 17
  • 159
  • 190
  • I would've accepted one of their comments had they been answers. :) I'll leave the question open to see if anyone can tell me why ComponentListeners aren't called for JPopupMenus. – heycam Jun 14 '10 at 03:52
  • Thanks for the pointer to tutorial. I just checked the JDK sources, and JPopUpMenu does indeed have an implemented setVisible(boolean) method. (And I use it with setVisible(false) to dismiss my menus, too.) From the code I can see that at least the "visible" property change event will be dispatched. Also, show(Component,int,int) calls setVisible(true) at the end. Given this, I can't think of any justification for componentHidden not to be called. – heycam Jun 14 '10 at 04:42
  • Digging further, Component.setVisible(boolean) will dispatch the component show/hide events. JPopupMenu.setVisible(boolean), which overrides it, doesn't call super.setVisible() or explicitly dispatch the events. – heycam Jun 14 '10 at 04:50
  • Whoops, sorry about that I was saying one thing but looking at another. My edit applied to PopupMenu, but the principle is the same for JPopupMenu. Yes, what I was trying to get at is that code doesn't filter the call up to Component.setVisible. – Mark Peters Jun 14 '10 at 04:54