33

I would like to create an application wide keyboard shortcut for a Java Swing application. Looping over all components and adding the shortcut on each, has focus related side effects, and seems like a brute force solution.

Anyone has a cleaner solution?

stommestack
  • 2,431
  • 7
  • 23
  • 39
Louis Jacomet
  • 12,590
  • 2
  • 28
  • 37

6 Answers6

41

For each window, use JComponent.registerKeyboardAction with a condition of WHEN_IN_FOCUSED_WINDOW. Alternatively use:

JComponent.getInputMap(WHEN_IN_FOCUSED_WINDOW).put(keyStroke, command);
JComponent.getActionMap().put(command,action);

as described in the registerKeyboardAction API docs.

daniel kullmann
  • 12,992
  • 6
  • 51
  • 64
Tom Hawtin - tackline
  • 143,103
  • 30
  • 210
  • 299
  • +1 The best, easiest answer I have found. I will upvote this x1000 – Alba Mendez Oct 26 '11 at 16:05
  • But JFrame does not have a getInputMap or getActionMap method – Can't Tell Jul 30 '15 at 09:49
  • @Can'tTell `JFrame` is not a `JComponent`. These methods need to be called on components within the top-level window. – Tom Hawtin - tackline Jul 30 '15 at 10:01
  • For an example of the first solution: `window.getRootPane().registerKeyboardAction(save.getActionListeners()[0], KeyStroke.getKeyStroke(KeyEvent.VK_S, KeyEvent.CTRL_MASK), JComponent.WHEN_IN_FOCUSED_WINDOW);` Replace `save` with the name of your JButton, and `window` with the name of your JFrame. – Zezombye May 01 '18 at 20:42
19

Install a custom KeyEventDispatcher. The KeyboardFocusManager class is also a good place for this functionality.

KeyEventDispatcher

Karl
  • 3,102
  • 1
  • 20
  • 28
13

For people wondering (like me) how to use KeyEventDispatcher, here is an example that I put together. It uses a HashMap for storing all global actions, because I don't like large if (key == ..) then .. else if (key == ..) then .. else if (key ==..) .. constructs.

/** map containing all global actions */
private HashMap<KeyStroke, Action> actionMap = new HashMap<KeyStroke, Action>();

/** call this somewhere in your GUI construction */
private void setup() {
  KeyStroke key1 = KeyStroke.getKeyStroke(KeyEvent.VK_A, KeyEvent.CTRL_DOWN_MASK);
  actionMap.put(key1, new AbstractAction("action1") {
    @Override
    public void actionPerformed(ActionEvent e) {
      System.out.println("Ctrl-A pressed: " + e);
    }
  });
  // add more actions..

  KeyboardFocusManager kfm = KeyboardFocusManager.getCurrentKeyboardFocusManager();
  kfm.addKeyEventDispatcher( new KeyEventDispatcher() {

    @Override
    public boolean dispatchKeyEvent(KeyEvent e) {
      KeyStroke keyStroke = KeyStroke.getKeyStrokeForEvent(e);
      if ( actionMap.containsKey(keyStroke) ) {
        final Action a = actionMap.get(keyStroke);
        final ActionEvent ae = new ActionEvent(e.getSource(), e.getID(), null );
        SwingUtilities.invokeLater( new Runnable() {
          @Override
          public void run() {
            a.actionPerformed(ae);
          }
        } ); 
        return true;
      }
      return false;
    }
  });
}

The use of SwingUtils.invokeLater() is maybe not necessary, but it is probably a good idea not to block the global event loop.

daniel kullmann
  • 12,992
  • 6
  • 51
  • 64
6

When you have a menu, you can add global keyboard shortcuts to menu items:

    JMenuItem item = new JMenuItem(action);
    KeyStroke key = KeyStroke.getKeyStroke(
        KeyEvent.VK_R, KeyEvent.CTRL_DOWN_MASK);
    item.setAccelerator(key);
    menu.add(item);
daniel kullmann
  • 12,992
  • 6
  • 51
  • 64
2

A little simplified example:

KeyboardFocusManager keyManager;

keyManager=KeyboardFocusManager.getCurrentKeyboardFocusManager();
keyManager.addKeyEventDispatcher(new KeyEventDispatcher() {

  @Override
  public boolean dispatchKeyEvent(KeyEvent e) {
    if(e.getID()==KeyEvent.KEY_PRESSED && e.getKeyCode()==27){
      System.out.println("Esc");
      return true;
    }
    return false;
  }

});
mortalis
  • 1,882
  • 21
  • 32
-1

Use the following piece of code

ActionListener a=new ActionListener(){
   public void actionPerformed(ActionEvent ae)
   {
    // your code
   }
};
getRootPane().registerKeyboardAction(a,KeyStroke.getKeyStroke("ctrl D"),JComponent.WHEN_IN_FOCUSED_WINDOW);

Replace "ctrl D" with the shortcut you want.

JavaTechnical
  • 7,632
  • 8
  • 54
  • 88
  • 1
    no, that's outdated api (superceded by actionMap/inputMap since jdk 1.2 or 1.3 - way back in stone age) – kleopatra Jul 05 '13 at 06:34
  • @kleopatra Hmm. Thanks for the comment. I want to know the reason. I didn't find it! – JavaTechnical Jul 08 '13 at 14:47
  • don't quite understand - reason for what? – kleopatra Jul 08 '13 at 14:52
  • Why is registerKeyboardAction() obselete – JavaTechnical Jul 08 '13 at 14:56
  • that's a question for the swing team 10+ years ago :-) There used to be an article (old swingconnection?) introducing keyBindings that also argued the why .. don't have a reference, though, sorry. – kleopatra Jul 08 '13 at 14:59
  • 1
    Please, see the javadoc (JComponent.registerKeyboardAction(java.awt.event.ActionListener, java.lang.String, javax.swing.KeyStroke, int)): This method is now obsolete, please use a combination of getActionMap() and getInputMap() for similiar behavior. – serg.nechaev Sep 23 '13 at 01:50