5

I have a user request to add an accelerator to a sub menu (JMenu) which would allow the user to press the short cut and have the corresponding sub menu "fold out", showing its contained menu items.

I don't recall every having seen something like this (either in Java or any other language). Our application is written in Java using Swing. We have a number of JMenuItems with accelerators that work well, but when I attempted to add an accelerator to JMenu I get the following exception:

java.lang.Error: setAccelerator() is not defined for JMenu. Use setMnemonic() instead.

I've tried to use the MenuDemo! code to experiment with this a bit further.

This is what I tried:

//a submenu
menu.addSeparator();
submenu = new JMenu("A submenu");
submenu.setMnemonic(KeyEvent.VK_S);
submenu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_U, InputEvent.CTRL_MASK));

The last line is the one added by me, which causes the exception.

I've tried extensive googling but all I can find is articles on how to add accelerators to JMenuItem.

It seems the JMenu does not support this natively. Is there any workaround to achieve this behaviour?

mKorbel
  • 109,525
  • 20
  • 134
  • 319
Urs Beeli
  • 746
  • 1
  • 13
  • 30

2 Answers2

7

Another option is to override the accelerator get/set and reproduce the JMenuItem behaviour. Then the UI will do the rest of the job.

The important thing is to fire the property change and have a consistent get/set for the accelerator. The advantage of this solution is that it also provides a visual indication of the shortcut/accelerator.

Here is a small demo code:

import java.awt.event.KeyEvent;

import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class TestMenu {

    protected void initUI() {
        JFrame frame = new JFrame(TestMenu.class.getSimpleName());
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        JMenuBar bar = new JMenuBar();
        JMenu topMenu = new JMenu("Top Menu");
        JMenu subMenu = new JMenu("Sub menu") {
            private KeyStroke accelerator;

            @Override
            public KeyStroke getAccelerator() {
                return accelerator;
            }

            @Override
            public void setAccelerator(KeyStroke keyStroke) {
                KeyStroke oldAccelerator = accelerator;
                this.accelerator = keyStroke;
                repaint();
                revalidate();
                firePropertyChange("accelerator", oldAccelerator, accelerator);
            }
        };
        subMenu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F4, KeyEvent.CTRL_MASK));
        JMenuItem item1 = new JMenuItem("Item 1");
        JMenuItem item2 = new JMenuItem("Item 2");
        subMenu.add(item1);
        subMenu.addSeparator();
        subMenu.add(item2);
        topMenu.add(subMenu);
        bar.add(topMenu);
        frame.setJMenuBar(bar);
        frame.setSize(400, 300);
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } catch (InstantiationException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } catch (UnsupportedLookAndFeelException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                new TestMenu().initUI();
            }
        });
    }

}
Guillaume Polet
  • 47,259
  • 4
  • 83
  • 117
6

I don't think that is possible just like that. But what you could do is adding an AbstractAction, which simulates a click.

submenu.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("control U"), "expand");
    submenu.getActionMap().put("expand", new AbstractAction("expand") {
        private static final long serialVersionUID = 1L;

        @Override
        public void actionPerformed(ActionEvent evt) {
            submenu.doClick();
        }
    }
);

I hope this also works for you.

1r0n1k
  • 405
  • 1
  • 4
  • 14
  • 1
    my first guess was that wouldn't work - but was wrong :-) +1 - the usability catch is that there's no visual indication of which keyStroke to use – kleopatra Oct 16 '12 at 09:05
  • It works like a charm as far as opening the sub menu goes, thanks a lot for that. As kleopatra says, there is no indicator to show which key-binding is used. Is it possible, to add this information somehow? Then, the solution would be perfect :-) – Urs Beeli Oct 16 '12 at 09:34
  • The only choices you have are afaik either juggling around with fonts so it looks like the Accelerators indicator or adding a JMenuItem instead of a JMenu and adding a PopUp or something for the selection... (But then you dont have a "real" menu anymore) Greetings from Switzerland ;-) – 1r0n1k Oct 16 '12 at 10:48
  • 1
    @kleopatra Actually, by simply overriding get/set accelerator and firing the appropriate property change event, you can have the keystroke indication and let the UI do the rest of the work (see also my example below). – Guillaume Polet Oct 16 '12 at 10:55
  • 1
    @UrsBeeli See my answer below which also provides a visual feedback of the keystroke. – Guillaume Polet Oct 16 '12 at 10:56
  • don't unserstand, focus and selection could be painted, with little and API built_in effort .... – mKorbel Oct 16 '12 at 12:20
  • Thanks everyone for your help. @1r0n1k: Greetings from CH to CH :-) – Urs Beeli Oct 17 '12 at 07:17