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?
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();
}
});
}
}
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.