Как использовать ключевые привязки вместо ключевых прослушивателей

Я использую KeyListener s в своем коде (игра или как-то иначе), чтобы мои экранные объекты реагировали на ввод пользовательского ключа. Вот мой код:

 public class MyGame extends JFrame { static int up = KeyEvent.VK_UP; static int right = KeyEvent.VK_RIGHT; static int down = KeyEvent.VK_DOWN; static int left = KeyEvent.VK_LEFT; static int fire = KeyEvent.VK_Q; public MyGame() { // Do all the layout management and what not... JLabel obj1 = new JLabel(); JLabel obj2 = new JLabel(); obj1.addKeyListener(new MyKeyListener()); obj2.addKeyListener(new MyKeyListener()); add(obj1); add(obj2); // Do other GUI things... } static void move(int direction, Object source) { // do something } static void fire(Object source) { // do something } static void rebindKey(int newKey, String oldKey) { // Depends on your GUI implementation. // Detecting the new key by a KeyListener is the way to go this time. if (oldKey.equals("up")) up = newKey; if (oldKey.equals("down")) down = newKey; // ... } public static void main(String[] args) { new MyGame(); } private static class MyKeyListener extends KeyAdapter { @Override public void keyPressed(KeyEvent e) { Object source = e.getSource(); int action = e.getExtendedKeyCode(); /* Will not work if you want to allow rebinding keys since case variables must be constants. switch (action) { case up: move(1, source); case right: move(2, source); case down: move(3, source); case left: move(4, source); case fire: fire(source); ... } */ if (action == up) move(1, source); else if (action == right) move(2, source); else if (action == down) move(3, source); else if (action == left) move(4, source); else if (action == fire) fire(source); } } } 

У меня проблемы с отзывчивостью:

  • Мне нужно щелкнуть по объекту, чтобы он работал.
  • Ответ, который я получаю для нажатия одного из ключей, – это не то, как я хотел, чтобы он работал – слишком отзывчивый или слишком невосприимчивый.

Почему это происходит и как я могу это исправить?

Этот ответ объясняет и демонстрирует, как использовать ключевые привязки вместо ключевых слушателей для образовательной цели. Это не

  • Как написать игру на Java.
  • Как должно выглядеть хорошее написание кода (например, видимость).
  • Самый эффективный (с точки зрения производительности или кода) способ реализации привязок ключей.

это

  • То, что я поставил бы как ответ любому, кто испытывает проблемы с ключевыми слушателями .

Ответ; Прочтите руководство Swing по привязкам клавиш .

Я не хочу читать руководства, скажите, почему я хотел бы использовать привязки клавиш вместо красивого кода, который у меня уже есть!

Ну, в учебнике Swing объясняется, что

  • Связывание клавиш не требует щелчка по компоненту (для его фокусировки):
    • Удаляет неожиданное поведение с точки зрения пользователя.
    • Если у вас есть 2 объекта, они не могут перемещаться одновременно, так как только один из объектов может иметь фокус в данный момент времени (даже если вы привязываете их к разным клавишам).
  • Ключевые привязки легче поддерживать и манипулировать:
    • Отключение, повторное связывание, переназначение действий пользователя намного проще.
    • Код легче читать.

Хорошо, ты убедил меня попробовать. Как это работает?

В этом уроке есть хороший раздел. InputMap клавиш включает в себя 2 объекта InputMap и ActionMap . InputMap сопоставляет вход пользователя с именем действия, ActionMap сопоставляет имя Action . Когда пользователь нажимает клавишу, входная карта ищет ключ и находит имя действия, тогда в карте действий выполняется поиск имени действия и выполняется действие.

Выглядит громоздко. Почему бы не связать пользовательский ввод напрямую с действием и избавиться от имени действия? Тогда вам нужна только одна карта, а не две.

Хороший вопрос! Вы увидите, что это одна из тех вещей, которые делают привязки клавиш более управляемыми (отключить, переинформировать и т. Д.).

Я хочу, чтобы вы дали мне полный рабочий код этого.

Нет (в учебнике Swing есть рабочие примеры ).

Ты полный отстой! Я ненавижу тебя!

Вот как сделать одно ключевое связывание:

 myComponent.getInputMap().put("userInput", "myAction"); myComponent.getActionMap().put("myAction", action); 

Обратите внимание, что есть 3 InputMap s, реагирующих на разные состояния фокусировки:

 myComponent.getInputMap(JComponent.WHEN_FOCUSED); myComponent.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); myComponent.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); 
  • WHEN_FOCUSED , который также используется при отсутствии аргументов, используется, когда компонент имеет фокус. Это похоже на случай прослушивания ключа.
  • WHEN_ANCESTOR_OF_FOCUSED_COMPONENT используется, когда сфокусированный компонент находится внутри компонента, который зарегистрирован для получения действия. Если у вас много членов экипажа внутри космического корабля, и вы хотите, чтобы космический корабль продолжал получать вход, в то время как любой из членов экипажа сосредоточен, используйте это.
  • WHEN_IN_FOCUSED_WINDOW используется, когда компонент, который зарегистрирован для получения действия, находится внутри сфокусированного компонента. Если у вас много танков в сфокусированном окне, и вы хотите, чтобы все они получали вход одновременно, используйте это.

Код, представленный в вопросе, будет выглядеть примерно так, если предположить, что оба объекта должны контролироваться одновременно:

 public class MyGame extends JFrame { private static final int IFW = JComponent.WHEN_IN_FOCUSED_WINDOW; private static final String MOVE_UP = "move up"; private static final String MOVE_DOWN = "move down"; private static final String FIRE = "move fire"; static JLabel obj1 = new JLabel(); static JLabel obj2 = new JLabel(); public MyGame() { // Do all the layout management and what not... obj1.getInputMap(IFW).put(KeyStroke.getKeyStroke("UP"), MOVE_UP); obj1.getInputMap(IFW).put(KeyStroke.getKeyStroke("DOWN"), MOVE_DOWN); // ... obj1.getInputMap(IFW).put(KeyStroke.getKeyStroke("control CONTROL"), FIRE); obj2.getInputMap(IFW).put(KeyStroke.getKeyStroke("W"), MOVE_UP); obj2.getInputMap(IFW).put(KeyStroke.getKeyStroke("S"), MOVE_DOWN); // ... obj2.getInputMap(IFW).put(KeyStroke.getKeyStroke("T"), FIRE); obj1.getActionMap().put(MOVE_UP, new MoveAction(1, 1)); obj1.getActionMap().put(MOVE_DOWN, new MoveAction(2, 1)); // ... obj1.getActionMap().put(FIRE, new FireAction(1)); obj2.getActionMap().put(MOVE_UP, new MoveAction(1, 2)); obj2.getActionMap().put(MOVE_DOWN, new MoveAction(2, 2)); // ... obj2.getActionMap().put(FIRE, new FireAction(2)); // In practice you would probably create your own objects instead of the JLabels. // Then you can create a convenience method obj.inputMapPut(String ks, String a) // equivalent to obj.getInputMap(IFW).put(KeyStroke.getKeyStroke(ks), a); // and something similar for the action map. add(obj1); add(obj2); // Do other GUI things... } static void rebindKey(KeyEvent ke, String oldKey) { // Depends on your GUI implementation. // Detecting the new key by a KeyListener is the way to go this time. obj1.getInputMap(IFW).remove(KeyStroke.getKeyStroke(oldKey)); // Removing can also be done by assigning the action name "none". obj1.getInputMap(IFW).put(KeyStroke.getKeyStrokeForEvent(ke), obj1.getInputMap(IFW).get(KeyStroke.getKeyStroke(oldKey))); // You can drop the remove action if you want a secondary key for the action. } public static void main(String[] args) { new MyGame(); } private class MoveAction extends AbstractAction { int direction; int player; MoveAction(int direction, int player) { this.direction = direction; this.player = player; } @Override public void actionPerformed(ActionEvent e) { // Same as the move method in the question code. // Player can be detected by e.getSource() instead and call its own move method. } } private class FireAction extends AbstractAction { int player; FireAction(int player) { this.player = player; } @Override public void actionPerformed(ActionEvent e) { // Same as the fire method in the question code. // Player can be detected by e.getSource() instead, and call its own fire method. // If so then remove the constructor. } } } 

Вы можете видеть, что разделение карты ввода с картой действий допускает многократный код и лучший контроль привязок. Кроме того, вы можете также управлять действием напрямую, если вам нужна функциональность. Например:

 FireAction p1Fire = new FireAction(1); p1Fire.setEnabled(false); // Disable the action (for both players in this case). 

Дополнительную информацию см. В руководстве по Action .

Я вижу, что вы использовали 1 действие, движение, для 4 ключей (направлений) и 1 действие, огонь, для 1 клавиши. Почему бы не дать каждому ключу собственное действие или дать всем ключам одно и то же действие и разобраться, что делать внутри действия (например, в случае перемещения)?

Хорошая точка зрения. Технически вы можете сделать то и другое, но вы должны думать, что имеет смысл и что позволяет легко управлять и использовать многоразовый код. Здесь я предполагал, что перемещение аналогично для всех направлений, и стрельба различна, поэтому я выбрал этот подход.

Я вижу много используемого KeyStroke , что это такое? Они похожи на KeyEvent ?

Да, у них есть аналогичная функция, но они более подходят для использования здесь. Посмотрите их API для информации и о том, как их создавать.


Вопросов? Улучшения? Предложения? Оставить комментарий. Получите лучший ответ? Отправьте его.

Примечание: это не ответ, просто комментарий со слишком большим количеством кода 🙂

Получение keyStrokes через getKeyStroke (String) является правильным способом, но требует тщательного чтения api doc:

 modifiers := shift | control | ctrl | meta | alt | altGraph typedID := typed  typedKey := string of length 1 giving Unicode character. pressedReleasedID := (pressed | released) key key := KeyEvent key code name, ie the name following "VK_". 

Последняя строка должна быть точнее – это то , что важно: для клавиши «вниз» точное ключевое кодовое имя – VK_DOWN , поэтому параметр должен быть «DOWN» (не «Down» или любой другой вариант букв верхнего и нижнего регистра)

Не совсем интуитивно понятный (читай: пришлось самому копать) получает ключ-ключ к ключу-модификатору. Даже при правильном написании, следующее не будет работать:

 KeyStroke control = getKeyStroke("CONTROL"); 

Более глубже в очереди событий awt, keyEvent для одного ключа-модификатора создается самим как модификатор. Чтобы привязать к управляющему ключу, вам нужен штрих:

 KeyStroke control = getKeyStroke("ctrl CONTROL"); 
  • Проблемы с добавлением JTextArea
  • Поверните JLabel или ImageIcon на Java Swing
  • Многослойное стекло GlassPane в корневом контейнере
  • вызов java-метода для рисования графики
  • Как переключить JPanels внутри JFrame
  • Как генерировать исключения из RepaintManager
  • Нельзя ли добавить компонент Swing в несколько контейнеров?
  • Настройка Tree.collapsedIcon для одного JTree
  • Браузер Webkit в приложении Java
  • Java Swing - как показать панель поверх другой панели?
  • Swing: кнопки переключения каналов вместе с группой кнопок вместе с соответствующими пунктами меню
  • Давайте будем гением компьютера.