How to make Ctrl-Tab switch tabs in a JTabbedPane

Submitted by davidc on Sun, 03/07/2011 - 17:19

When using tabbed panes, the user expects Ctrl-TAB to move to the next tab, and Ctrl-Shift-TAB to move to the previous tab. This is not the default behaviour in Java, which uses Ctrl-TAB to move to the next focusable component. This allows you to escape from a text box, where pressing TAB alone would insert a tab character into the box, but it is not what most users would expect to happen in most instances.

Since Java 1.5, when using the Windows look and feel, the expected behaviour is restored (see bug 4736672). However other LaFs, including the default cross-platform LaF, continue to exhibit the original behaviour of having Ctrl-TAB change the focused component.

Getting tabs to work as expected is a little tricky to achieve, so here's how it's done.

Adding the keys to the input map

Adding the keys to the input map is easy, as the JTabbedPane's UI delegate already has "navigatePrevious" and "navigateNext" actions. The following example allows you to use the 1 and 2 keys to change tabs.

    InputMap inputMap = tabbedPane.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
    inputMap.put(KeyStroke.getKeyStroke("1"), "navigatePrevious");
    inputMap.put(KeyStroke.getKeyStroke("2"), "navigateNext");

However, as soon as you change the keystroke to "ctrl TAB", it no longer works. This is because the DefaultKeyboardFocusManager handles the keypress and consumes the event before the input map sees it.

Removing the default focus traversal keys

In order to stop the focus manager consuming these keys, you need to remove them from the default focus traversal keys for the tabbed pane.

    tabbedPane.setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, Collections.singleton(KeyStroke.getKeyStroke("TAB")));
    tabbedPane.setFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, Collections.singleton(KeyStroke.getKeyStroke("shift TAB")));

Of course, it is better to take the existing traversal keys and just remove the unwanted ones.

Final solution

This method makes TAB keys work as expected in the passed JTabbedPane, by removing the default behaviour of Ctrl-TAB and Ctrl-Shift-TAB, then setting them up as tabbed pane traversal keys instead.

  private static void setupTabTraversalKeys(JTabbedPane tabbedPane)
  {
    KeyStroke ctrlTab = KeyStroke.getKeyStroke("ctrl TAB");
    KeyStroke ctrlShiftTab = KeyStroke.getKeyStroke("ctrl shift TAB");
 
    // Remove ctrl-tab from normal focus traversal
    Set<AWTKeyStroke> forwardKeys = new HashSet<AWTKeyStroke>(tabbedPane.getFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS));
    forwardKeys.remove(ctrlTab);
    tabbedPane.setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, forwardKeys);
 
    // Remove ctrl-shift-tab from normal focus traversal
    Set<AWTKeyStroke> backwardKeys = new HashSet<AWTKeyStroke>(tabbedPane.getFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS));
    backwardKeys.remove(ctrlShiftTab);
    tabbedPane.setFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, backwardKeys);
 
    // Add keys to the tab's input map
    InputMap inputMap = tabbedPane.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
    inputMap.put(ctrlTab, "navigateNext");
    inputMap.put(ctrlShiftTab, "navigatePrevious");
  }

Do you know an easier and less ugly way to achieve this? Let me know in the comments.