带箭头键的JRadioButton导航

时间:2013-11-28 19:46:51

标签: java swing key-bindings jradiobutton

我正在尝试使用箭头键导航一组JRadioButton s。我打算用KeyListeners手动实现它,但显然这种行为至少可以在过去8年中起作用(http://bugs.sun.com/view_bug.do?bug_id=4104452)。但是,它对我不起作用:按箭头键不起作用。 Windows版本的Java版本为7u45。

一个独立的测试用例,看看我在说什么:

import java.awt.*;
import javax.swing.*;

public class Test {
    public static void main(final String[] args) {
        if (!EventQueue.isDispatchThread()) {
            try {
                EventQueue.invokeAndWait(new Runnable() {
                    public void run() {
                        main(args);
                    }
                });
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
            return;
        }

        try {
            //UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
            //UIManager.setLookAndFeel("javax.swing.plaf.nimbus.NimbusLookAndFeel");
        } catch (Throwable t) {
            throw new RuntimeException(t);
        }

        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        ButtonGroup group = new ButtonGroup();
        JPanel panel = new JPanel();
        panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS));
        JRadioButton rb;

        rb = new JRadioButton("Option A");
        panel.add(rb);
        group.add(rb);

        rb = new JRadioButton("Option B");
        panel.add(rb);
        group.add(rb);

        rb = new JRadioButton("Option C");
        panel.add(rb);
        group.add(rb);

        frame.add(panel);
        frame.pack();
        frame.setVisible(true);
    }
}

我尝试过使用不同的外观&感觉,不同的容器和不同的布局管理器,但它仍然不起作用。

4 个答案:

答案 0 :(得分:3)

您需要将右/左(上/下?)键添加到每个单选按钮的焦点遍历策略中。例如,添加右/左箭头键:

    Set set = new HashSet( rb.getFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS ) );
    set.add( KeyStroke.getKeyStroke( "RIGHT" ) );
    rb.setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, set );

    set = new HashSet( rb.getFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS ) );
    set.add( KeyStroke.getKeyStroke( "LEFT" ) );
    rb.setFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, set );

阅读How to Use the Focus Subsystem上的Swing教程中的部分以获取更多信息。

答案 1 :(得分:2)

我相信你可以使用KeyBindings而不是KeyListeners来实现你的目标。在许多情况下,实际上建议绑定比KeyListeners,因为第二个可以产生许多问题(捕获关键活动的帧必须是活动的等等。)

答案 2 :(得分:2)

谢谢大家的答案。

我发现了我混淆的原因。显然,当Sun bug报告系统说错误状态为“已关闭”且其“已解决日期”为“2005-07-19”时,这并不意味着该错误已得到修复。显然,它只是作为some other (newer?) bug的副本记录。自首次报道以来已近16年,它仍未得到修复。不管。

所需的行为比我意识到的要微妙得多。我在各种程序中的本机Windows对话框中进行了实验:

  • 大多数类似按钮的组件:按钮,复选框和单选按钮,实现用于焦点导航的箭头键。在Java中,这对应于AbstractButton类。 (JMenuItem也是它的子类,但它有自己独特的箭头键行为。)
  • 在此导航期间仅选择/选中单选按钮。
  • 必须跳过不可聚焦(包括禁用或不可见)组件。
  • 尝试在组中的第一个按钮之前或最后一个按钮之后导航不一致:在某些对话框中,它从头到尾循环;在其他人身上,它不可逆转地移动到非按钮组件上;而在其他人看来它什么也没做。我尝试了所有这些不同的行为,但没有一个比其他行为特别好。

我在下面实现了一个循环行为,因为它感觉稍微流畅一点。导航静默地跳过非AbstractButton组件,形成一个独立的焦点循环私有按钮。这是可疑的,但有时需要将一组相关的复选框或单选按钮与其他组件混合使用。测试一个共同的父组件以识别组也是一种合理的行为,但是这在一个对话框中不起作用,我只是出于布局原因使用单独的组件(在FlowLayout中实现换行符)。

正如所建议的,我研究了InputMaps和ActionMaps,而不是使用KeyListener。我总是避免使用地图,因为它们看起来过于复杂,但我想我看到了能够轻松覆盖绑定的优势。

此代码使用辅助外观来为应用程序范围内的所有AbstractButton组件安装所需的行为(这是我发现的关于here的一种很好的技术)。我用几个不同的对话框和窗口测试了它,似乎没问题。如果它导致问题我会更新这篇文章。

呼叫:

ButtonArrowKeyNavigation.install();

在应用程序启动时安装它。

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class ButtonArrowKeyNavigation {
    private ButtonArrowKeyNavigation() {}

    public static void install() {
        UIManager.addAuxiliaryLookAndFeel(lookAndFeel);
    }

    private static final LookAndFeel lookAndFeel = new LookAndFeel() {
        private final UIDefaults defaults = new UIDefaults() {
            @Override
            public javax.swing.plaf.ComponentUI getUI(JComponent c) {
                if (c instanceof AbstractButton && !(c instanceof JMenuItem)) {
                    if (c.getClientProperty(this) == null) {
                        c.putClientProperty(this, Boolean.TRUE);
                        configure(c);
                    }
                }
                return null;
            }
        };
        @Override public UIDefaults getDefaults() { return defaults; };
        @Override public String getID() { return "ButtonArrowKeyNavigation"; }
        @Override public String getName() { return getID(); }
        @Override public String getDescription() { return getID(); }
        @Override public boolean isNativeLookAndFeel() { return false; }
        @Override public boolean isSupportedLookAndFeel() { return true; }
    };

    private static void configure(JComponent c) {
        InputMap im = c.getInputMap(JComponent.WHEN_FOCUSED);
        ActionMap am = c.getActionMap();
        im.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT,  0), "focusPreviousButton");
        im.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP,    0), "focusPreviousButton");
        im.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), "focusNextButton");
        im.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN,  0), "focusNextButton");
        am.put("focusPreviousButton", focusPreviousButton);
        am.put("focusNextButton",     focusNextButton);
    }

    private static final Action focusPreviousButton = new AbstractAction() {
        public void actionPerformed(ActionEvent e) {
            move((AbstractButton)e.getSource(), -1);
        }
    };

    private static final Action focusNextButton = new AbstractAction() {
        public void actionPerformed(ActionEvent e) {
            move((AbstractButton)e.getSource(), +1);
        }
    };

    private static void move(AbstractButton ab, int direction) {
        Container focusRoot = ab.getFocusCycleRootAncestor();
        FocusTraversalPolicy focusPolicy = focusRoot.getFocusTraversalPolicy();
        Component toFocus = ab, loop = null;
        for (;;) {
            toFocus = direction > 0
                ? focusPolicy.getComponentAfter(focusRoot, toFocus)
                : focusPolicy.getComponentBefore(focusRoot, toFocus);
            if (toFocus instanceof AbstractButton) break;
            if (toFocus == null) return;
            // infinite loop protection; should not be necessary, but just in
            // case all buttons are somehow unfocusable at the moment this
            // method is called:
            if (loop == null) loop = toFocus; else if (loop == toFocus) return;
        }
        if (toFocus.requestFocusInWindow()) {
            if (toFocus instanceof JRadioButton) {
                ((JRadioButton)toFocus).setSelected(true);
            }
        }
    }
}

答案 3 :(得分:0)

以下是我使用箭头键(向上和向下)可以导航的JRadioButtons示例,并为您修改了几个代码。

public class JRadioButton extends JPanel {
    private JRadioButton[] buttons;

    public JRadioButtonTest(int row) {

       ButtonGroup group = new ButtonGroup();
       buttons = new JRadioButton[row];

       for (int i = 0; i < buttons.length; i++) {

            final int curRow = i;

            buttons[i] = new JRadioButton("Option " + i);
            buttons[i].addKeyListener(enter);
            buttons[i].addKeyListener(new KeyAdapter() {
               @Override
               public void keyPressed(KeyEvent e) {
                  switch (e.getKeyCode()) {
                  case KeyEvent.VK_UP:
                     if (curRow > 0)
                        buttons[curRow - 1].requestFocus();
                     break;
                  case KeyEvent.VK_DOWN:
                     if (curRow < buttons.length - 1)
                        buttons[curRow + 1].requestFocus();
                     break;

                  default:
                     break;
                  }
               }
            });
            group.add(buttons[i]);
            add(buttons[i]);

      }
   }

   private KeyListener enter = new KeyAdapter() {
      @Override
      public void keyTyped(KeyEvent e) {
         if (e.getKeyChar() == KeyEvent.VK_ENTER) {
            ((JButton) e.getComponent()).doClick();
         }
      }
   };

   public static void main(String[] args) {
      JFrame frame = new JFrame();
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      frame.add(new JRadioButton(3));
      frame.pack();
      frame.setVisible(true);
   }
}

当调用箭头键时,核心实现方法在正确的JRadioButton上调用requestFocus()。按Enter键时的额外KeyListener。

您可以将此KeyListener用于您的程序并添加更多密钥。

祝你好运!