Спасибо всем за ответы.
Я обнаружил причину моего замешательства. По-видимому, когда система отчетов об ошибках 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);
}
}
}
}
@Boann следует этому совету, добавьте JRadioButton, к массиву и переопределить IsEnabled слишком – mKorbel