Search code examples
javaswingjmenujmenuitemjpopupmenu

Programmatically expand sub JMenuItems


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.


Solution

  • 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.