6

I would like to programmatically expand a particular JMenuItem in a JPopup. For example in the code below

import java.awt.event.ActionEvent;

import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;

public class AutoExpandSubMenusDemo extends JFrame {

    private final JPopupMenu popup = new JPopupMenu("Popup");

    public AutoExpandSubMenusDemo() {
        setDefaultCloseOperation(EXIT_ON_CLOSE);

        JMenu menuB = new JMenu("B");

        menuB.add(new JMenuItem("X"));
        JMenuItem menuY = menuB.add(new JMenuItem("Y"));
        menuB.add(new JMenuItem("Z"));

        popup.add(new JMenuItem("A"));
        popup.add(menuB);
        popup.add(new JMenuItem("C"));

        final JButton button = new JButton("Show Popup Menu");
        button.addActionListener(new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                popup.show(button, 0, button.getHeight());

                // Show menuY
            }
        });

        JPanel buttonPanel = new JPanel();
        buttonPanel.add(button);
        getContentPane().add(buttonPanel);
    }

    public static void main(String[] args) {
        AutoExpandSubMenusDemo f = new AutoExpandSubMenusDemo();
        f.setSize(500, 300);
        f.setVisible(true);
    }
}

I would like to expand the popup menu so that the items B(menuB)/Y(menuY) are expanded and selected when the button is pressed.

Sorry if this is something that's easy to do but I've searched around and can't figure it out.

I did find the

MenuSelectionManager.defaultManager().setSelectedPath(...)

however this didn't work when I tried it and the javadoc specifies that it is called from the LaF and should not be called by clients.

Any help is much appreciated.

Jamesy82
  • 369
  • 3
  • 10
  • If you just want the same action (from button and menu item) to occur when you press the button, just use the [`Action`](http://docs.oracle.com/javase/8/docs/api/javax/swing/Action.html) interface. See more at [How to use Actions](http://docs.oracle.com/javase/tutorial/uiswing/misc/action.html). Both the button and menu item button can share the same `Action`. Don't really see the point in opening the popup just to select it, unless its just for eye candy – Paul Samsotha Aug 12 '14 at 09:57
  • By selected I'm referring to the highlighting that occurs when the mouse is over a menuitem or it has been moved to using the keyboard, not the actual triggering of the associated action. The reason I'm doing this is that I'm trying to implement a help feature that shows users where certain options are by expanding the relevant menus. – Jamesy82 Aug 12 '14 at 10:03
  • OK +1 good question :-D – Paul Samsotha Aug 12 '14 at 10:32

2 Answers2

8

While I don't recommend doing this, since the documentation itself advises against it, here's how you could do it:

import java.awt.event.ActionEvent;
import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.MenuElement;
import javax.swing.MenuSelectionManager;
import javax.swing.SwingUtilities;

public class AutoExpandSubMenusDemo extends JFrame {

    private final JPopupMenu popup = new JPopupMenu("Popup");

    public AutoExpandSubMenusDemo() {
        setDefaultCloseOperation(EXIT_ON_CLOSE);

        final JMenu menuB = new JMenu("B");

        menuB.add(new JMenuItem("X"));
        final JMenuItem menuY = menuB.add(new JMenuItem("Y"));
        menuB.add(new JMenuItem("Z"));

        popup.add(new JMenuItem("A"));
        popup.add(menuB);
        popup.add(new JMenuItem("C"));

        final JButton button = new JButton("Show Popup Menu");
        button.addActionListener(new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                popup.show(button, 0, button.getHeight());

                SwingUtilities.invokeLater(new Runnable() {

                    public void run() {
                        menuB.setPopupMenuVisible(true);
                        MenuSelectionManager.defaultManager().setSelectedPath(new MenuElement[]{popup, menuB, menuY});
                    }
                });

            }
        });

        JPanel buttonPanel = new JPanel();
        buttonPanel.add(button);
        getContentPane().add(buttonPanel);
    }

    public static void main(String[] args) {
        AutoExpandSubMenusDemo f = new AutoExpandSubMenusDemo();
        f.setSize(500, 300);
        f.setVisible(true);
    }
}

Most of this code is yours. I only added:

SwingUtilities.invokeLater(new Runnable() {

    public void run() {
        menuB.setPopupMenuVisible(true);
        MenuSelectionManager.defaultManager().setSelectedPath(new MenuElement[]{popup, menuB, menuY});
    }
});

which seems to work.

You could avoid abusing MenuSelectionManager via 'MenuItem.setArmed(boolean)'.

SwingUtilities.invokeLater(new Runnable() {

    public void run() {
        menuB.setPopupMenuVisible(true);
        menuB.setArmed(true);
        menuY.setArmed(true);
    }
});

The popup staying visible after selecting another menu item or dismissing the JPopupMenu still needs to be addressed though.

Another way is to fake a mouse event... :D

SwingUtilities.invokeLater(new Runnable() {

    public void run() {                        
        MouseEvent event = new MouseEvent(
                menuB, MouseEvent.MOUSE_ENTERED, 0, 0, 0, 0, 0, false);
        menuB.dispatchEvent(event);
        menuY.setArmed(true);
    }
});

This way it is as if the user actually used the mouse.

predi
  • 5,528
  • 32
  • 60
  • +1 for the mouse event hack, it even gives the additional eye-candy of a slight delay as the menus open. When I tested these out, it seems like the mouse event way is the best solution for OP's question. – Nick Meyer Aug 12 '14 at 16:25
2

Another example:

MenuSelectionManager.defaultManager().setSelectedPath(
    new MenuElement[] {popup, menuB, menuB.getPopupMenu()});

enter image description here

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

public class AutoExpandSubMenusDemo2 extends JFrame {
  private final JPopupMenu popup = new JPopupMenu("Popup");

  public AutoExpandSubMenusDemo2() {
    setDefaultCloseOperation(EXIT_ON_CLOSE);

    final JMenu menuB = new JMenu("B");

    menuB.add(new JMenuItem("X"));
    final JMenuItem menuY = menuB.add(new JMenuItem("Y"));
    menuB.add(new JMenuItem("Z"));

    popup.add(new JMenuItem("A"));
    popup.add(menuB);
    popup.add(new JMenuItem("C"));

    JPanel buttonPanel = new JPanel();
    buttonPanel.add(new JButton(new AbstractAction("Show menuB Popup") {
      @Override public void actionPerformed(ActionEvent e) {
        JButton button = (JButton) e.getSource();
        popup.show(button, 0, button.getHeight());
        //[Bug ID: JDK-6949414 JMenu.buildMenuElementArray() endless loop]
        //( http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6949414 )
        //menuB.doClick();
        MenuSelectionManager.defaultManager().setSelectedPath(
          new MenuElement[] {popup, menuB, menuB.getPopupMenu()});
      }
    }));
    buttonPanel.add(new JButton(new AbstractAction("Select menuY") {
      @Override public void actionPerformed(ActionEvent e) {
        JButton button = (JButton) e.getSource();
        popup.show(button, 0, button.getHeight());
        MenuSelectionManager.defaultManager().setSelectedPath(
          new MenuElement[] {popup, menuB, menuB.getPopupMenu(), menuY});
      }
    }));
    getContentPane().add(buttonPanel);
  }

  public static void main(String[] args) {
    EventQueue.invokeLater(new Runnable() {
      @Override public void run() {
        createAndShowGUI();
      }
    });
  }
  public static void createAndShowGUI() {
    JFrame f = new AutoExpandSubMenusDemo2();
    f.setSize(500, 300);
    f.setVisible(true);
  }
}
aterai
  • 9,658
  • 4
  • 35
  • 44