I have a menu item, "rename", for which F2 is set as an accelerator. Indeed when the menu is displayed there a little "F2" indication next to "rename".
Sadly, this does not work. This accelerator triggers no response. When I change the accelerator to CTRL+F2 - it works.
It seems that I should use an InpoutMpa/ActionMap. The problem with that is that I want this to work everywhere in the app so I am trying to associate it with the top-level JFrame. But, JFrame does not have a getInputMap() method.
Lost.
[Added]
ks = KeyStroke.getKeyStroke(KeyEvent.VK_F2, 0);
JMenuItem mi = new JMenuItem("Rename");
mi.setAccelerator(ks);
mi.addActionListener(action);
This is probably happening because JTable uses F2 to invoke the StartEditing action (I saw the same behavior on one of my programs and traced it to this).
There are a couple of solutions. The most drastic is to remove this input mapping (I believe this code actually removes the mapping from all JTables):
KeyStroke keyToRemove = KeyStroke.getKeyStroke(KeyEvent.VK_F2, 0);
InputMap imap = table.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
while (imap != null)
{
imap.remove(keyToRemove);
imap = imap.getParent();
}
Or, if you're using the table for display only, and don't plan to let the user edit it, you could make it non-focusable:
table.setFocusable(false);
On a completely different subject, I strongly recommend creating an AbstractAction subclass for your menu items, rather than creating them "from scratch". Aside from giving you very simple menu setup code, you can use the same action instance for both the main menu and a popup/toolbar, and enable/disable them both at the same time.
I know this is an old thread, but I struggled with the exact same thing as the original poster and found the solution. The JFrame itself doesn't have a getInputMap method, but its root pane does. So you have to use "getRootPane.getInputMap" instead.
Example code:
public class ApplicationFrame extends JFrame {
private AbstractAction f2Action = new AbstractAction() {
private static final long serialVersionUID = 1L;
public void actionPerformed(ActionEvent e) {
// Do something useful
}
};
public ApplicationFrame() {
InputMap inputMap = getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
ActionMap actionMap = getRootPane().getActionMap();
inputMap.put(KeyStroke.getKeyStroke("F2"), "f2Action");
actionMap.put("f2Action", f2Action);
}
}
Related
I am in the process of refactoring my application and my teacher recommended that I replace the GUI builder generated code with a more generic one.
Right now every JMenuItem has its own action listener. What I'm trying to achieve is a sort of generic control function for every menu item by using enums in a single action listener. The code below should give you a general idea. clE is the enum key and I believe the enum should implement an interface for reading its label.
I've been doing a bit of research and I'm sure it's something simple, but I can't get fully grasp it yet. Thanks in advance!
public class JECheckBox<E extends ENUM_Label_INTF<?>> extends JCheckBox {
private final E clE;
// +++++++ CONSTRUCTOR +++++++++
public JECheckBox(final E clE) {
super( ((GetLabelINTF) clE).GetLabel() );
this.clE = clE;
}
public E GetKey() {
return clE;
}
}
I believe the enum should implement an interface for reading its label.
If you want to read the text of the check box, then you create a generic listener by doing something like:
Action action = new AbstractAction()
{
#Override
public void actionPerformed(ActionEvent e)
{
JCheckBox checkbox = (JCheckBox)e.getSource();
System.out.println( checkbox.getText() );
}
};
Now you can add the Action to each check box.
Note an Action is a more versatile ActionListener. Read the section from the Swing tutorial on How to Use Actions for more information and examples.
Not suew what you mean by generic enums. Try giving every menu item (or any component) it's own name using component.setName(SomeEnum.soneValue.toString()). Then get the name in the action listener and do a switch(SomeEnum.valueOf(name).
I've just starting using Java Swing and I have a issue.
I tried to do a simple menuBar and a menuItem 'Exit', but before linking the button to the action the menuItem appeared, now that I've linked the button to a System.exit(0) action it disappeared. Help?
The code is the following:
in MainPanel (the autogenerated code from swing is excluded):
public void init() {
initComponents();
initActions();
setLocationRelativeTo(null);
pack();
setVisible(true);
}
private void initActions() {
this.menuItemExit.setAction(Application.getInstance().getPanelControl().getActionExit());
}
In PanelControl:
public class PanelControl {
private Action actionExit;
public Action getActionExit() {
return actionExit;
}
public class ActionExit extends AbstractAction{
public ActionExit(){
putValue(Action.NAME, "Exit");
putValue(Action.SHORT_DESCRIPTION, "Exit from the application");
putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke("ctrl e"));
putValue(Action.MNEMONIC_KEY, KeyEvent.VK_E);
}
#Override
public void actionPerformed(ActionEvent e) {
System.exit(0);
}
}
In Application:
private void init() {
viewMainPanel = new MainPanel();
controlPanel = new ControlPanel();
viewMainPanel.init();
}
i think the problem is somewhere in here but i can't figure out where. any help?
(there's other code but i just put the more relevant part, also i translated the code from italian so i'm sorry if there are any problems or a few names dont match up)
private Action actionExit;
public Action getActionExit() {
return actionExit;
}
Your actionExit variable is null.
Nowhere in your code do you create an instance of your ActionExit class.
Somewhere you need:
actionExit = new ActionExit();
Your design seems a bit complicated, I have no idea why you have a panel just to create an instance of the ActionExit class.
I would suggest you just create the ActionExit instance in your main class and get rid of the PanelControl class.
Instead of using an IDE to generate confusing code you should consider learning how to write the code yourself so you can better structure your classes. Read the section from the Swing tutorial on How to Use Menus for a working example to get you started.
A menu item has to be added to a Native Java Swing component. You have to add it to a JFrame. You can't add a MenuItem to a Panel. The Parent 'root' container in any Java Swing application is 'native' and a JFrame. Everything else in that container is 'drawn' into the rectangle using the look and feel of your choosing.
Then you CREATE a MenuItem using your TAbstractAction item. That object CAN be used to create a JButton, JMenuItem or ToolBar button. Keeping a reference to your TAbstractAction in your code, you can enable/disable the object and it implements an 'observable' pattern where it will enable/disable ALL UI controls you used to build with it. I actually wrote a Java Swing framework for doing Java Applications. It used to be on the Sun Open Source web site. If you wish I can put it up on GitLab for you to play with. Java Swing is nice but JavaFX should be the long term goal for UI on a JVM.
In your JFrame object you need to do this:
_menuBar = new JMenuBar();
// add controls to the frame
setJMenuBar(_menuBar);
Then you need to add your 'exitMenuItem' to your _MenuBar control.
Cheers
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?
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.
Note: this is not an answer, just a comment with too much code :-)
Getting keyStrokes via getKeyStroke(String) is the correct way - but needs careful reading of the 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_".
The last line should better be exact name, that is case matters: for the down key the exact key code name is VK_DOWN, so the parameter must be "DOWN" (not "Down" or any other variation of upper/lower case letters)
Not entirely intuitive (read: had to dig a bit myself) is getting a KeyStroke to a modifier key. Even with proper spelling, the following will not work:
KeyStroke control = getKeyStroke("CONTROL");
Deeper down in the awt event queue, a keyEvent for a single modifier key is created with itself as modifier. To bind to the control key, you need the stroke:
KeyStroke control = getKeyStroke("ctrl CONTROL");
Here is an easyway that would not require you to read hundreds of lines of code just learn a few lines long trick.
declare a new JLabel and add it to your JFrame (I didn't test it in other components)
private static JLabel listener= new JLabel();
The focus needs to stay on this for the keys to work though.
In constructor :
add(listener);
Use this method:
OLD METHOD:
private void setKeyBinding(String keyString, AbstractAction action) {
listener.getInputMap().put(KeyStroke.getKeyStroke(keyString), keyString);
listener.getActionMap().put(keyString, action);
}
KeyString must be written properly. It is not typesafe and you must consult the official list to learn what is the keyString(it is not an official term) for each button.
NEW METHOD
private void setKeyBinding(int keyCode, AbstractAction action) {
int modifier = 0;
switch (keyCode) {
case KeyEvent.VK_CONTROL:
modifier = InputEvent.CTRL_DOWN_MASK;
break;
case KeyEvent.VK_SHIFT:
modifier = InputEvent.SHIFT_DOWN_MASK;
break;
case KeyEvent.VK_ALT:
modifier = InputEvent.ALT_DOWN_MASK;
break;
}
listener.getInputMap().put(KeyStroke.getKeyStroke(keyCode, modifier), keyCode);
listener.getActionMap().put(keyCode, action);
}
In this new method you can simply set it using KeyEvent.VK_WHATEVER
EXAMPLE CALL:
setKeyBinding(KeyEvent.VK_CONTROL, new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("ctrl pressed");
}
});
Send an anonymous class (or use subclass) of AbstractAction. Override its public void actionPerformed(ActionEvent e) and make it do whatever you want the key to do.
PROBLEM:
I couldn't get it running for VK_ALT_GRAPH.
case KeyEvent.VK_ALT_GRAPH:
modifier = InputEvent.ALT_GRAPH_DOWN_MASK;
break;
does not make it work for me for some reason.
Here is an example of how to get key bindings working.
(Inside JFrame subclass using extends, which is called by the constructor)
// Create key bindings for controls
private void createKeyBindings(JPanel p) {
InputMap im = p.getInputMap(JPanel.WHEN_IN_FOCUSED_WINDOW);
ActionMap am = p.getActionMap();
im.put(KeyStroke.getKeyStroke("W"), MoveAction.Action.MOVE_UP);
im.put(KeyStroke.getKeyStroke("S"), MoveAction.Action.MOVE_DOWN);
im.put(KeyStroke.getKeyStroke("A"), MoveAction.Action.MOVE_LEFT);
im.put(KeyStroke.getKeyStroke("D"), MoveAction.Action.MOVE_RIGHT);
am.put(MoveAction.Action.MOVE_UP, new MoveAction(this, MoveAction.Action.MOVE_UP));
am.put(MoveAction.Action.MOVE_DOWN, new MoveAction(this, MoveAction.Action.MOVE_DOWN));
am.put(MoveAction.Action.MOVE_LEFT, new MoveAction(this, MoveAction.Action.MOVE_LEFT));
am.put(MoveAction.Action.MOVE_RIGHT, new MoveAction(this, MoveAction.Action.MOVE_RIGHT));
}
Separate class to handle those key bindings created above (where Window is the class that extends from JFrame)
// Handles the key bindings
class MoveAction extends AbstractAction {
enum Action {
MOVE_UP, MOVE_DOWN, MOVE_LEFT, MOVE_RIGHT;
}
private static final long serialVersionUID = /* Some ID */;
Window window;
Action action;
public MoveAction(Window window, Action action) {
this.window = window;
this.action = action;
}
#Override
public void actionPerformed(ActionEvent e) {
switch (action) {
case MOVE_UP:
/* ... */
break;
case MOVE_DOWN:
/* ... */
break;
case MOVE_LEFT:
/* ... */
break;
case MOVE_RIGHT:
/* ... */
break;
}
}
}
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?
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.
Note: this is not an answer, just a comment with too much code :-)
Getting keyStrokes via getKeyStroke(String) is the correct way - but needs careful reading of the 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_".
The last line should better be exact name, that is case matters: for the down key the exact key code name is VK_DOWN, so the parameter must be "DOWN" (not "Down" or any other variation of upper/lower case letters)
Not entirely intuitive (read: had to dig a bit myself) is getting a KeyStroke to a modifier key. Even with proper spelling, the following will not work:
KeyStroke control = getKeyStroke("CONTROL");
Deeper down in the awt event queue, a keyEvent for a single modifier key is created with itself as modifier. To bind to the control key, you need the stroke:
KeyStroke control = getKeyStroke("ctrl CONTROL");
Here is an easyway that would not require you to read hundreds of lines of code just learn a few lines long trick.
declare a new JLabel and add it to your JFrame (I didn't test it in other components)
private static JLabel listener= new JLabel();
The focus needs to stay on this for the keys to work though.
In constructor :
add(listener);
Use this method:
OLD METHOD:
private void setKeyBinding(String keyString, AbstractAction action) {
listener.getInputMap().put(KeyStroke.getKeyStroke(keyString), keyString);
listener.getActionMap().put(keyString, action);
}
KeyString must be written properly. It is not typesafe and you must consult the official list to learn what is the keyString(it is not an official term) for each button.
NEW METHOD
private void setKeyBinding(int keyCode, AbstractAction action) {
int modifier = 0;
switch (keyCode) {
case KeyEvent.VK_CONTROL:
modifier = InputEvent.CTRL_DOWN_MASK;
break;
case KeyEvent.VK_SHIFT:
modifier = InputEvent.SHIFT_DOWN_MASK;
break;
case KeyEvent.VK_ALT:
modifier = InputEvent.ALT_DOWN_MASK;
break;
}
listener.getInputMap().put(KeyStroke.getKeyStroke(keyCode, modifier), keyCode);
listener.getActionMap().put(keyCode, action);
}
In this new method you can simply set it using KeyEvent.VK_WHATEVER
EXAMPLE CALL:
setKeyBinding(KeyEvent.VK_CONTROL, new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("ctrl pressed");
}
});
Send an anonymous class (or use subclass) of AbstractAction. Override its public void actionPerformed(ActionEvent e) and make it do whatever you want the key to do.
PROBLEM:
I couldn't get it running for VK_ALT_GRAPH.
case KeyEvent.VK_ALT_GRAPH:
modifier = InputEvent.ALT_GRAPH_DOWN_MASK;
break;
does not make it work for me for some reason.
Here is an example of how to get key bindings working.
(Inside JFrame subclass using extends, which is called by the constructor)
// Create key bindings for controls
private void createKeyBindings(JPanel p) {
InputMap im = p.getInputMap(JPanel.WHEN_IN_FOCUSED_WINDOW);
ActionMap am = p.getActionMap();
im.put(KeyStroke.getKeyStroke("W"), MoveAction.Action.MOVE_UP);
im.put(KeyStroke.getKeyStroke("S"), MoveAction.Action.MOVE_DOWN);
im.put(KeyStroke.getKeyStroke("A"), MoveAction.Action.MOVE_LEFT);
im.put(KeyStroke.getKeyStroke("D"), MoveAction.Action.MOVE_RIGHT);
am.put(MoveAction.Action.MOVE_UP, new MoveAction(this, MoveAction.Action.MOVE_UP));
am.put(MoveAction.Action.MOVE_DOWN, new MoveAction(this, MoveAction.Action.MOVE_DOWN));
am.put(MoveAction.Action.MOVE_LEFT, new MoveAction(this, MoveAction.Action.MOVE_LEFT));
am.put(MoveAction.Action.MOVE_RIGHT, new MoveAction(this, MoveAction.Action.MOVE_RIGHT));
}
Separate class to handle those key bindings created above (where Window is the class that extends from JFrame)
// Handles the key bindings
class MoveAction extends AbstractAction {
enum Action {
MOVE_UP, MOVE_DOWN, MOVE_LEFT, MOVE_RIGHT;
}
private static final long serialVersionUID = /* Some ID */;
Window window;
Action action;
public MoveAction(Window window, Action action) {
this.window = window;
this.action = action;
}
#Override
public void actionPerformed(ActionEvent e) {
switch (action) {
case MOVE_UP:
/* ... */
break;
case MOVE_DOWN:
/* ... */
break;
case MOVE_LEFT:
/* ... */
break;
case MOVE_RIGHT:
/* ... */
break;
}
}
}
I'm writing a Java Swing application using the Metal look-and-feel. Every time there is a JButton in my application the user uses the Tab key to move the focus to the button and then hits the Enter key. Nothing happens! If he hits the Space key the button events are fired. How do I assign the Enter key to trigger the same events as the Space key? Thank you for your help.
I found the following:
http://tips4java.wordpress.com/2008/10/25/enter-key-and-button/
Where Rob Camick writes that when using JDK5 and later you simply add...
UIManager.put("Button.defaultButtonFollowsFocus", Boolean.TRUE);
...to the application to solve the problem. This did the trick for me! And I can't imagine anything simpler. However, when using older versions of Java you will have to do something like Richard and Peter describe in their answers to this question.
Here is complete example. Richard was close, but you also need to map pressed ENTER to action, not just released. To make it work for ALL buttons, I have put this mapping to default input map for buttons. Add imports, and it should be runnable.
public class Main implements Runnable {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Main());
}
#Override
public void run() {
setupEnterActionForAllButtons();
JFrame frame = new JFrame("Button test");
frame.getContentPane().add(createButton(), BorderLayout.NORTH);
frame.getContentPane().add(createButton(), BorderLayout.SOUTH);
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
private void setupEnterActionForAllButtons() {
InputMap im = (InputMap) UIManager.getDefaults().get("Button.focusInputMap");
Object pressedAction = im.get(KeyStroke.getKeyStroke("pressed SPACE"));
Object releasedAction = im.get(KeyStroke.getKeyStroke("released SPACE"));
im.put(KeyStroke.getKeyStroke("pressed ENTER"), pressedAction);
im.put(KeyStroke.getKeyStroke("released ENTER"), releasedAction);
}
private JButton createButton() {
JButton b = new JButton("press enter");
b.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("Pressed");
}
});
return b;
}
}
Actually, this is a look and feel issue. It is (and should be) up to the look and feel as to which key triggers the focused button.
The "default button" work-around works since the L&F you're using uses enter for the default button.
Peter's workaround explicitly changes the L&F default "focus action" key - which is somewhat more convincing if you ask me.
I would add that I don't think many users would want to tab to the button then hit enter (most won't even notice the focus indicator) - they want the default action to be the "right" one and work wherever they press enter. This can only be done with input maps as Richard suggests.
I would certainly suggest getting a very clear picture of what your users actually want and expect (preferably with reference to other apps they use) before changing anything globally.
You do it by assigning an input / action map for the Enter key. Something like the following:
// save the command mapping for space
Object spaceMap = button.getInputMap.get(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0, true));
// add a mapping from enter to the same command.
button.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0, true),spaceMap);
You can also set the "default button" to the button most recently focussed.
I did this on an application, and all methods of doing this are a nightmare to maintain and debug. The fact is, this is clearly not what the designers of Swing intended to happen.
Extension to above answers to do same with radio , checkboxes.
Called this before creating components
void setupEnterAction(String componentName){
String keyName = componentName + ".focusInputMap";
InputMap im = (InputMap) UIManager.getDefaults().get(keyName);
Object pressedAction = im.get(KeyStroke.getKeyStroke("pressed SPACE"));
Object releasedAction = im.get(KeyStroke.getKeyStroke("released SPACE"));
im.put(KeyStroke.getKeyStroke("pressed ENTER"), pressedAction);
im.put(KeyStroke.getKeyStroke("released ENTER"), releasedAction);
}
public void setEnterEvent(){
setupEnterAction("Button");
setupEnterAction("RadioButton");
setupEnterAction("CheckBox");
}