Key bindings vs. key listeners in Java
when should you prefer one rather than the other?
Prefer Key Bindings since they were introduced. A KeyListener
is a lower level connection with events.
That page for the key bindings covers a lot of the reasons I would tend to use them rather than a KeyListener
. It lists many things which are simply 'not available' to a KeyListener
. E.G. choices of:
WHEN_FOCUSED
WHEN_ANCESTOR_OF_FOCUSED_COMPONENT
WHEN_IN_FOCUSED_WINDOW
The more I read the linked document, the less I can understand the need to ask the question. E.G.:
An alternative to key bindings is using key listeners. Key listeners have their place as a low-level interface to keyboard input, but for responding to individual keys key bindings are more appropriate and tend to result in more easily maintained code. Key listeners are also difficult if the key binding is to be active when the component doesn't have focus. Some of the advantages of key bindings are they're somewhat self documenting, take the containment hierarchy into account, encourage reusable chunks of code (
Action
objects), and allow actions to be easily removed, customized, or shared. Also, they make it easy to change the key to which an action is bound. Another advantage ofActions
is that they have an enabled state which provides an easy way to disable the action without having to track which component it is attached to.
Text components
As noted by @Robin, text components also have DocumentListener
& DocumentFilter
which can be added for functionality more suited to text documents. See Text Component Features for more information on document listeners & filters.
Java KeyListener vs Keybinding
Generally speaking, where you have a limited set of key inputs, key bindings are a better choice.
KeyListener
suffers from issues related to focusability and with other controls in the GUI, focus will constantly be moving away from the component (with the KeyListener
) all the time.
A simple solution would be to use the Action
s API. This allows you to define a self contained "action" which acts as a ActionListener
but also carries configuration information that can be used to configure other UI components, in particular, buttons
For example...
Take a generic NumberAction
which could represent any number (lets limit it to 0-9 for now)...
public class NumberAction extends AbstractAction {
private int number;
public NumberAction(int number) {
putValue(NAME, String.valueOf(number));
}
public int getNumber() {
return number;
}
@Override
public void actionPerformed(ActionEvent e) {
int value = getNumber();
// Do something with the number...
}
}
You could do something like...
// Create the action...
NumberAction number1Action = new NumberAction(1);
// Create the button for number 1...
JButton number1Button = new JButton(number1Action);
InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW);
// Create a key mapping for number 1...
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_1, 0), "number1");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_NUMPAD1, 0), "number1");
ActionMap am = getActionMap();
// Make the input key to the action...
am.put("number1", number1Action);
And you're done...
You can also create any number of instance of the NumberAction
for the same number, meaning you could configure the UI and the bindings separately, but know that when triggered, they will execute the same code logic, for example...
How to use Key Bindings instead of Key Listeners
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 InputMap
s 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
KeyStroke
s used, what are those? Are they like aKeyEvent
?
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.
How to use Key Bindings instead of Key Listener?
You could register the key bindings with the GamePanel, or the frame content pane (both extend from JComponent). E.g.:
public class DemoFrame extends JFrame {
public static void main(String[] args) throws Exception {
new DemoFrame().setVisible(true);
}
public DemoFrame() {
setSize(800, 600);
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
{
String actionId = "left";
KeyStroke keyStroke = KeyStroke.getKeyStroke("LEFT");
getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(keyStroke, actionId);
getRootPane().getActionMap().put(actionId, new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
actionLeft();
}
});
}
}
private void actionLeft() {
System.out.println("left");
}
}
What is the difference between KeyListeners and KeyBinders?
KeyListener
is the original mechanism used by AWT and Swing to allow components to register interest in key events. It is a low level API, which provides considerable information about the key event (like modifiers).
This method of monitoring key events is discouraged under most circumstance for a number of reasons. The first is the fact that in order for a component to receive key events (via this API) it must be focusable and have current keyboard focus. This is a trap that 99% of questions about KeyListeners
relate to.
The second relates to how the code looks, traditionally, using KeyListener
would require you to have a large if-else
statement, taking into account the various key strokes and their potential modifiers, which could become cumbersome over time - the same criticism could be leveled at ActionListener
as well.
Key Bindings is a newer API (and is part of a larger input binding API) that can be used to monitor for very specific key events. This includes the individual key stroke and modifiers.
The Key Bindings also use the Actions API, which means you can reuse the Action
in many different parts of your program without the need to re-implement the logic each time.
From How to use Key Bindings
An alternative to key bindings is using key listeners. Key listeners
have their place as a low-level interface to keyboard input, but for
responding to individual keys key bindings are more appropriate and
tend to result in more easily maintained code. Key listeners are also
difficult if the key binding is to be active when the component
doesn't have focus. Some of the advantages of key bindings are they're
somewhat self documenting, take the containment hierarchy into
account, encourage reusable chunks of code (Action objects), and allow
actions to be easily removed, customized, or shared. Also, they make
it easy to change the key to which an action is bound. Another
advantage of Actions is that they have an enabled state which provides
an easy way to disable the action without having to track which
component it is attached to.
Comparing functionality between KeyListeners and Key Bindings
How to Use Key Bindings explains the basics.
Bindings are used to map specific Actions to a specific KeyStroke.
A KeyListener is the last resort when all other abstractions won't work. Maybe you would use a KeyListener when you are listening for ANY KeyStroke. So it wouldn't be practical to create 26 bindings for all letters of the alphabet.
But again in many cases there are better API's to use. For example, instead of listening for KeyEvents on a text field you would use a DocumentListener to listen for changes to the Document.
KeyListeners vs Keybindings?
The Keylistener is an older interface from the AWT days, its still Ok to use it with swing but is more of a general listener. It binds to all keys.
KeyBindings are a bit different in that they specifically bind a specific action to a specific key and other keys remain unaffected. The upshot of this is if you are going to listen for any key then a KeyListener is appropriate or you will have to implement seperate KeyBindings all for all keys which is patently silly.
To avoid event handlers with big switch statements the KeyBinding exists.
Related Topics
How to Switch JPAnels Inside a Jframe
Java Heap Terminology: Young, Old and Permanent Generations
Nullpointerexception in Java with No Stacktrace
Difference Between _Java_Options, Java_Tool_Options and Java_Opts
Integrating Tomcat and Eclipse as a Hot-Deploy Environment
Import Maven Dependencies in Intellij Idea
Convert Existing Eclipse Project to Maven Project
Differences Between System.Out.Println() and Return in Java
Java.Lang.Classnotfoundexception in Spite of Using Classpath Environment Variable
What Is the Purpose of Java's Unary Plus Operator
Getting Fonts, Sizes, Bold,...Etc
Why Does Priorityqueue.Tostring Return the Wrong Element Order
Can the Android Sdk Work with Jdk 1.7
Place Cursor at the End of Text in Edittext