Java 带箭头键的JRadioButton导航

Java 带箭头键的JRadioButton导航,java,swing,key-bindings,jradiobutton,Java,Swing,Key Bindings,Jradiobutton,我正在尝试使用箭头键使一组JRadioButtons可导航。我打算用KeyListeners手动实现这一点,但显然,这种行为至少在过去8年内都应该有效()。但是,这对我不起作用:按箭头键没有任何作用。Java版本在Windows上是7u45 一个独立的测试用例,看看我在说什么: import java.awt.*; import javax.swing.*; public class Test { public static void main(final String[] args)

我正在尝试使用箭头键使一组
JRadioButton
s可导航。我打算用KeyListeners手动实现这一点,但显然,这种行为至少在过去8年内都应该有效()。但是,这对我不起作用:按箭头键没有任何作用。Java版本在Windows上是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);
    }
}

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

我相信使用键绑定而不是键侦听器可以实现您的目标。在许多情况下,实际上建议绑定优于KeyListener,因为第二个绑定可能会产生许多问题(捕捉关键帧的活动必须是活动的,等等)。

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

    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 );

有关更多信息,请阅读Swing教程中的部分。

以下是我的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扩展了JPanel{
私有JRadioButton[]按钮;
公共JRadioButtonTest(int行){
ButtonGroup=新建ButtonGroup();
按钮=新的JRadioButton[行];
对于(int i=0;i0)
按钮[curRow-1].requestFocus();
打破
case KeyEvent.VK_向下:
如果(当前<按钮长度-1)
按钮[curRow+1].requestFocus();
打破
违约:
打破
}
}
});
添加组(按钮[i]);
添加(按钮[i]);
}
}
private KeyListener enter=new KeyAdapter(){
@凌驾
public void keyTyped(KeyEvent e){
如果(例如getKeyChar()==KeyEvent.VK_ENTER){
((JButton)e.getComponent()).doClick();
}
}
};
公共静态void main(字符串[]args){
JFrame=新JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(新的JRadioButton(3));
frame.pack();
frame.setVisible(true);
}
}
核心实现方法是在调用箭头键时对正确的JRadioButton调用requestFocus()。按下Enter键时的额外KeyListener

您可以在程序中使用此KeyListener并添加更多密钥


祝你好运

谢谢大家的回答

我发现了我困惑的原因。显然,当Sun bug报告系统说一个bug的状态为“关闭”,其“解决日期”为“2005-07-19”时,这并不意味着该bug已经修复。显然,它只是作为的副本记录的。自从它首次被报道以来已经将近16年了,它仍然没有被修复。不管怎样

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

  • 大多数按钮式组件:按钮、复选框和单选按钮,实现焦点导航的箭头键。在Java中,这与类相对应。(JMenuItem也是它的一个子类,但它有自己独特的箭头键行为。)
  • 在此导航过程中,仅选择/选中单选按钮
  • 必须跳过不可聚焦(包括禁用或不可见)组件
  • 尝试在组中的第一个按钮之前或最后一个按钮之后导航是不一致的:在某些对话框上,它从端到端循环;在其他情况下,它会不可逆转地移动到非按钮组件上;在其他方面,它什么也不做。我尝试了所有这些不同的行为,没有一个比其他行为更好
我在下面实现了一个循环行为,因为它感觉稍微流畅一些。导航悄无声息地跳过非抽象按钮组件,形成一种单独的按钮专用焦点循环。这是可疑的,但当一组相关复选框或单选按钮与其他组件混合时,有时需要这样做。测试公共父组件以识别组也是一种合理的行为,但这在一个对话框中不起作用,在这个对话框中,我使用单独的组件纯粹是为了布局(在FlowLayout中实现换行)

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

这段代码使用一种辅助的外观来为应用程序范围内的所有AbstractButton组件安装所需的行为(这是我发现的一种很好的技术)。我已经用几个不同的对话框和窗口测试过了,看起来还可以。如果它引起问题,我会更新这篇文章

电话:

在应用程序启动时安装它

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);
            }
        }
    }
}

@Boann遵循这个建议,将JRadioButtons添加到数组并覆盖isEnabled tooSwing已经有了一个用于焦点遍历的API。或Swing设计用于键绑定。不要使用KeyListener。另外,您应该
而不是
使用
requestFocus()
方法。阅读API,它会告诉您要使用的适当方法。
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);
            }
        }
    }
}