2013-11-28 128 views
2

我想要使用箭头键可以导航一组JRadioButton s。我打算用KeyListeners手动实现这一点,但显然这种行为至少应该在过去的8年中(http://bugs.sun.com/view_bug.do?bug_id=4104452)。但是,它不适合我:按箭头键不会执行任何操作。 Windows上的Java版本是7u45。带箭头键的JRadioButton导航

一个独立的测试案例,看看我说的是:

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

我一直在使用不同的外观&感觉,不同的容器,和不同的布局管理器试过了,但它仍然无法正常工作。

回答

1

谢谢大家的回答。

我发现了我混乱的原因。显然,当Sun的缺陷报告系统称错误状态为“已关闭”且其“已解决日期”为“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); 
      } 
     } 
    } 
} 
2

我相信你可以用KeyBindings而不是KeyListeners实现你的目标。在很多情况下,绑定实际上是通过KeyListeners推荐的,因为第二个绑定可能会产生很多问题(帧捕获的关键活动必须是活动的等)

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的教程部分获取更多信息。

+0

@Boann遵循这一建议,增加JradioButton将数组和覆盖的IsEnabled太 – mKorbel

0

这里是我的JRadioButtons的例子,可以使用箭头键(UP和DOWN)进行导航并修改了几个代码。

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用于您的程序并添加更多密钥。

祝你好运!

+2

秋千已经为焦点遍历的API。或Swing被设计为与键绑定一起使用。不要使用KeyListener。另外,你应该使用'requestFocus()'方法。阅读API会告诉你使用的拨用方法。 – camickr