vk_back_space teclado keypressed docs java swing key-bindings keyevent key-events

java - teclado - Cómo usar los enlaces clave en lugar de los oyentes clave



keyevent vk_back_space java (2)

Nota: esta no es una respuesta, solo un comentario con demasiado código :-)

Obtener keyStrokes a través de getKeyStroke (String) es la forma correcta, pero necesita una lectura cuidadosa del API doc .:

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

La última línea debería ser el nombre exacto , ese es el caso: para la tecla Abajo el nombre del código de la tecla es VK_DOWN , por lo que el parámetro debe ser "ABAJO" (no "Abajo" o cualquier otra variación de letras mayúsculas / minúsculas)

No del todo intuitivo (léase: tuve que cavar un poco por mi cuenta) está recibiendo un golpe de tecla en una tecla de modificación. Incluso con la ortografía correcta, lo siguiente no funcionará:

KeyStroke control = getKeyStroke("CONTROL");

Más profundo en la cola de eventos awt, se crea un keyEvent para una sola tecla modificadora como modificador. Para enlazar a la tecla de control, necesita el trazo:

KeyStroke control = getKeyStroke("ctrl CONTROL");

Estoy usando KeyListener s en mi código (juego u otro) como la forma en que mis objetos en pantalla reaccionan a la entrada de la clave del usuario. Aquí está mi código:

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

Tengo problemas con la capacidad de respuesta:

  • Necesito hacer clic en el objeto para que funcione.
  • La respuesta que recibo al presionar una de las teclas no es cómo quería que funcione, demasiado receptiva o demasiado insensible.

¿Por qué sucede esto y cómo soluciono esto?


Esta respuesta explica y demuestra cómo utilizar enlaces de teclas en lugar de oyentes clave con fines educativos. No lo es

  • Cómo escribir un juego en Java.
  • Qué tan buena debe ser la escritura de código (por ejemplo, visibilidad).
  • La forma más eficiente (rendimiento o código) de implementar enlaces de teclas.

Es

  • Lo que publicaría como respuesta a cualquier persona que tenga problemas con oyentes clave .

Responder; Lea el tutorial de Swing sobre enlaces de teclas .

No quiero leer manuales, dime por qué me gustaría utilizar enlaces de teclas en lugar del hermoso código que ya tengo.

Bueno, el tutorial de Swing explica que

  • Las asociaciones de teclas no requieren que haga clic en el componente (para darle más énfasis):
    • Elimina el comportamiento inesperado desde el punto de vista del usuario.
    • Si tiene 2 objetos, no se pueden mover simultáneamente, ya que solo 1 de los objetos puede tener el foco en un momento dado (incluso si los vincula a diferentes teclas).
  • Las asociaciones de teclas son más fáciles de mantener y manipular:
    • Desactivar, volver a vincular, volver a asignar acciones de usuario es mucho más fácil.
    • El código es más fácil de leer.

OK, me convenciste para que lo probara. ¿Como funciona?

El tutorial tiene una buena sección al respecto. Las vinculaciones de teclas incluyen 2 objetos InputMap y ActionMap . InputMap asigna una entrada de usuario a un nombre de acción, ActionMap asigna un nombre de acción a una Action . Cuando el usuario presiona una tecla, se busca la clave en el mapa de entrada y se encuentra un nombre de acción, luego se busca en el mapa de acción el nombre de la acción y se ejecuta la acción.

Parece engorroso. ¿Por qué no vincular la entrada del usuario directamente a la acción y deshacerse del nombre de la acción? Entonces solo necesitas un mapa y no dos.

¡Buena pregunta! Verá que esta es una de las cosas que hace que las asociaciones de teclas sean más manejables (desactivar, volver a enlazar, etc.).

Quiero que me proporciones un código de trabajo completo de esto.

No (el tutorial de Swing tiene ejemplos de trabajo ).

¡Eres un inútil! ¡Te odio!

A continuación, se explica cómo hacer una sola combinación de teclas:

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

Tenga en cuenta que hay 3 InputMap s que reaccionan a diferentes estados de foco:

myComponent.getInputMap(JComponent.WHEN_FOCUSED); myComponent.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); myComponent.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);

  • WHEN_FOCUSED , que también es el utilizado cuando no se proporciona ningún argumento, se usa cuando el componente tiene foco. Esto es similar al caso del oyente clave.
  • WHEN_ANCESTOR_OF_FOCUSED_COMPONENT se usa cuando un componente enfocado está dentro de un componente que está registrado para recibir la acción. Si tiene muchos miembros de la tripulación dentro de una nave espacial y desea que la nave continúe recibiendo información mientras que cualquiera de los miembros de la tripulación tiene enfoque, utilícela.
  • WHEN_IN_FOCUSED_WINDOW se usa cuando un componente que está registrado para recibir la acción está dentro de un componente enfocado. Si tiene muchos tanques en una ventana enfocada y desea que todos ellos reciban entrada al mismo tiempo, use esto.

El código presentado en la pregunta tendrá un aspecto similar a este suponiendo que ambos objetos se controlarán al mismo tiempo:

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. } } }

Puede ver que separar el mapa de entrada del mapa de acción permite un código reutilizable y un mejor control de las vinculaciones. Además, también puede controlar una Acción directamente si necesita la funcionalidad. Por ejemplo:

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

Vea el tutorial de Acción para más información.

Veo que usaste 1 acción, mueve, para 4 teclas (direcciones) y 1 acción, dispara, para 1 tecla. ¿Por qué no le damos a cada tecla su propia acción, o le damos a todas las teclas la misma acción y ordenamos qué hacer dentro de la acción (como en el caso de movimiento)?

Buen punto. Técnicamente puedes hacer ambas cosas, pero debes pensar qué tiene sentido y qué permite una administración fácil y un código reutilizable. Aquí asumí que moverme es similar para todas las direcciones y disparar es diferente, así que elegí este enfoque.

Veo muchos KeyStroke s usados, ¿cuáles son? ¿Son como un KeyEvent ?

Sí, tienen una función similar, pero son más apropiados para usar aquí. Consulte su API para obtener información y sobre cómo crearlos.

¿Preguntas? Mejoras? Sugerencias? Deja un comentario. ¿Tienes una mejor respuesta? Publícalo.