Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use Key Bindings instead of Key Listeners

I'm using KeyListeners in my code (game or otherwise) as the way for my on-screen objects to react to user key input. Here is my code:

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

I have problems with the responsiveness:

  • I need to click on the object for it to work.
  • The response I get for pressing one of the keys is not how I wanted it to work - too responsive or too unresponsive.

Why does this happen and how do I fix this?

like image 833
user1803551 Avatar asked Mar 30 '14 06:03

user1803551


People also ask

How do I set key bindings?

To reassign a keyConnect the keyboard that you want to configure. Select the Start button, and then select Microsoft Mouse and Keyboard Center. From the displayed list of key names, select the key that you want to reassign. In the command list of the key that you want to reassign, select a command.

What is key binding in keyboard?

A key binding is an association between a physical key on a keyboard and a parameter. A parameter can have any number of key bindings associated with it, and a particular key binding can control any number of parameters. Key bindings detects individual keys being pressed.

How do I use KeyListener?

A Simple KeyListener Java ClassCreate a new KeyListener object. Override the methods that correspond to the key events you want to monitor e.g keyPressed , keyReleased , keyTyped . Create a JTextField component. Use it's addKeyListener method to add to it the KeyListener you've created.


1 Answers

This answer explains and demonstrates how to use key bindings instead of key listeners for educational purpose. It is not

  • How to write a game in Java.
  • How good code writing should look like (e.g. visibility).
  • The most efficient (performance- or code-wise) way to implement key bindings.

It is

  • What I would post as an answer to anyone who is having trouble with key listeners.

Answer; Read the Swing tutorial on key bindings.

I don't want to read manuals, tell me why I would want to use key bindings instead of the beautiful code I have already!

Well, the Swing tutorial explains that

  • Key bindings don't require you to click the component (to give it focus):
    • Removes unexpected behavior from the user's point of view.
    • If you have 2 objects, they can't move simultaneously as only 1 of the objects can have the focus at a given time (even if you bind them to different keys).
  • Key bindings are easier to maintain and manipulate:
    • Disabling, rebinding, re-assigning user actions is much easier.
    • The code is easier to read.

OK, you convinced me to try it out. How does it work?

The tutorial has a good section about it. Key bindings involve 2 objects InputMap and ActionMap. InputMap maps a user input to an action name, ActionMap maps an action name to an Action. When the user presses a key, the input map is searched for the key and finds an action name, then the action map is searched for the action name and executes the action.

Looks cumbersome. Why not bind the user input to directly to the action and get rid of the action name? Then you need only one map and not two.

Good question! You will see that this is one of the things that make key bindings more manageable (disable, rebind etc.).

I want you to give me a full working code of this.

No (the Swing tutorial has working examples).

You suck! I hate you!

Here is how to make a single key binding:

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

Note that there are 3 InputMaps reacting to different focus states:

myComponent.getInputMap(JComponent.WHEN_FOCUSED);
myComponent.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
myComponent.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
  • WHEN_FOCUSED, which is also the one used when no argument is supplied, is used when the component has focus. This is similar to the key listener case.
  • WHEN_ANCESTOR_OF_FOCUSED_COMPONENT is used when a focused component is inside a component which is registered to receive the action. If you have many crew members inside a spaceship and you want the spaceship to continue receiving input while any of the crew members has focus, use this.
  • WHEN_IN_FOCUSED_WINDOW is used when a component which is registered to receive the action is inside a focused component. If you have many tanks in a focused window and you want all of them to receive input at the same time, use this.

The code presented in the question will look something like this assuming both objects are to be controlled at the same time:

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

You can see that separating the input map from the action map allow reusable code and better control of bindings. In addition, you can also control an Action directly if you need the functionality. For example:

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

See the Action tutorial for more information.

I see that you used 1 action, move, for 4 keys (directions) and 1 action, fire, for 1 key. Why not give each key its own action, or give all keys the same action and sort out what to do inside the action (like in the move case)?

Good point. Technically you can do both, but you have to think what makes sense and what allows for easy management and reusable code. Here I assumed moving is similar for all directions and firing is different, so I chose this approach.

I see a lot of KeyStrokes used, what are those? Are they like a KeyEvent?

Yes, they have a similar function, but are more appropriate for use here. See their API for info and on how to create them.


Questions? Improvements? Suggestions? Leave a comment. Have a better answer? Post it.

like image 186
user1803551 Avatar answered Oct 30 '22 10:10

user1803551