I have an issue where I am trying to make "player" icon move around my JFrame using keyboard controls. I have one object that I want to move around with the w, a, s, and d keys. I am using key bindings because in my research it seems that they are better suited to this task.
I have managed to attach all the desired keys to my icon, and they all call the action, the only issue is that I have no way to distinguish which button is being pressed. I was hoping this could be accomplished in some way by using the getActionCommand() on my action event. So far it hasn't worked.
Other examples that I have seen seem to have a solution to this, but they also have a lot of extra code with few comments, making it extremely difficult to determine what is actually happening. They all seem to involve multiple classes, with methods and fields. I am hoping to make this code a little less involved.
What I want to know: What is the best way to get this to work? Can I do it with key bindings? Does it need to be really complicated?
I really appreciate any help, even if it's just sources that I can use to help find an answer on my own.
Here's my existing code:
//My method that gets called by the constructor
//This is all based on a tutorial I found: ftp://ecs.csus.edu/clevengr/133/handouts/UsingJavaKeyBindings.pdf
//player is just an image icon
public static void setUpKeys() {
InputMap playerMap = player.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
KeyStroke wKey = KeyStroke.getKeyStroke('w');
KeyStroke aKey = KeyStroke.getKeyStroke('a');
KeyStroke sKey = KeyStroke.getKeyStroke('s');
KeyStroke dKey = KeyStroke.getKeyStroke('d');
KeyStroke wSprint = KeyStroke.getKeyStroke((char) ('w' + KeyEvent.SHIFT_DOWN_MASK)); //As a side note, what's the best way to get it to move faster when the shift key is held down? Not that important, but if someone happens to know, that'd be great
playerMap.put(wKey, "moveUp");
playerMap.put(aKey, "moveLeft");
playerMap.put(sKey, "moveDown");
playerMap.put(dKey, "moveRight");
playerMap.put(wSprint, "moveFast");
ActionMap playerAction = player.getActionMap();
playerAction.put("moveUp", playerMoved);
playerAction.put("moveLeft", playerMoved);
playerAction.put("moveDown", playerMoved);
playerAction.put("moveRight", playerMoved);
playerAction.put("moveFast", playerMoved);
}
And here is my playerMoved action:
static Action playerMoved = new AbstractAction() {
//This isn't important, just put it in instead of suppressing the warning
private static final long serialVersionUID = 2L;
//This doesn't do anything yet, just gives console confirmation
public void actionPerformed(ActionEvent e) {
System.out.println("Activated");
//Here's what I was talking about with the getActionCommand() not working
if (e.getActionCommand().equals("moveUp")) {
System.out.println("up");
}
}
};
If anyone needs any other parts of my code, I can provide it. I just wanted to cut it down to what I feel is important for this question
I was hoping this could be accomplished in some way by using the getActionCommand() on my action event.
You don't want to do this because you would be attempting to use the "action command" for "processing". That is not a good design as it will result in nested if/else statements.
Instead you need to create an Action that accepts parameters to control the movement. So you will need 4 separate Actions to control the movement. See the MotionWithKeyBindings example found in Motion Using the Keyboard, for a complete working example of this approach.
It demonstrates how a simple Action can be made reusable by specifying parameters for the Action. This provides far more flexibility than your current Action.
Note 1:
You use the following debug code in your Action:
System.out.println("Activated");
Instead of simply displaying a hard coded value it would be better to do something like
System.out.println( e.getActionCommand() );
In which case you should notice the value is "a, s, w, d", which is the KeyStroke you use to invoke the Action.
So this would mean the if statement should be testing for either of the above characters, not the String "moveUp" which is the String used to identify the Action in the ActionMap.
However, as mentioned above, this is not the solution you should be using, I just wanted to better explain how the "action command" is determined.
Note 2:
The only time you might want to use a single Action and the getActionCommand() method is when you want to use the "action command" as "data" for the Action.
For an example of this approach check out: how to put actionlistenerand actioncommand to multiple jbuttons. It is an example of a simple numeric entry panel where the number key pressed is added to a text field. So therefore the key character becomes the data for the text field and no special processing is required.
Related
What is the closest equivalent of Java's KeyBindings in C#? I am attempting to port a Swing application to C#, but it is unclear which method I should use.
Here is a sample of the Java I am porting:
Action goUp = new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
panel.upPressed = true;
}
};
Action stopUp = new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
panel.upPressed = false;
}
};
InputMap getInputMap = panel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
getInputMap.put(KeyStroke.getKeyStroke("W"), "goUp");
getInputMap.put(KeyStroke.getKeyStroke("released W"), "stopUp");
panel.getActionMap().put("goUp", goUp);
panel.getActionMap().put("stopUp", stopUp);
In Winforms, I am not aware of anything that is exactly like InputMap. Keyboard input is usually handled in one of two ways:
Shortcut keys, i.e. MenuItem.Shortcut. This is assignable in the Designer, or in code. Pressing the associated key will invoke the menu item's Click event handler(s). Of course, to use this method requires that the action you want to take is represented in the UI as a menu item.
Various key event handling in the UI. This can get fairly complicated depending on your needs, but is often the most appropriate way, and is the primary way to handle key input not associated with a menu item. The simplest event is the KeyPress event. Given your example, you probably want to handle KeyDown and KeyUp so that you can track the actual state of the key. Note that you will receive multiple KeyDown events as the auto-repeat kicks in for a key; if this would be a problem for your handler, you'll need to track the key state and for a key for which you've already seen KeyDown, only handle the KeyDown event if you've seen the corresponding KeyUp event.
Note that WPF does have a key binding model that is more similar to InputMap. Of course, the syntax is entirely different, but it breaks apart the idea of the menu items, commands, and key bindings, so you can mix and match as necessary (and in particular, have key bindings that don't correspond to any menu item). So if using WPF is an option, you might find that part of the transition easier (and probably only that part, since Swing is otherwise much more like Winforms than WPF :) ).
I am trying to get my application to respond to keyboard input. Eventually, I would like it to register ctrl+f and initiate a search, but I started simple and tried for the space bar. The Java tutorials on using Key Bindings got me this far, but no matter what I apply the key binding to, nothing registers. In the below code, panel is a JPanel and the others are assorted swing objects which have been added to panel.
Action ctrlF = new AbstractAction() {
public void actionPerformed(ActionEvent e) {
System.out.println("Action performed");
}
};
panel.getInputMap().put(KeyStroke.getKeyStroke("SPACE"),"crtlF");
openStallsList.getInputMap().put(KeyStroke.getKeyStroke("SPACE"),"crtlF");
openStalls.getInputMap().put(KeyStroke.getKeyStroke("SPACE"),"crtlF");
stallScroller.getInputMap().put(KeyStroke.getKeyStroke("SPACE"),"crtlF");
assignLabel.getInputMap().put(KeyStroke.getKeyStroke("SPACE"),"crtlF");
tenantInfo.getInputMap().put(KeyStroke.getKeyStroke("SPACE"),"crtlF");
unitSpinner.getInputMap().put(KeyStroke.getKeyStroke("SPACE"),"crtlF");
buildingAddress.getInputMap().put(KeyStroke.getKeyStroke("SPACE"),"crtlF");
buildingLogo.getInputMap().put(KeyStroke.getKeyStroke("SPACE"),"crtlF");
What am I missing here? Does it have something to do with focus? There are a couple of assorted labels and buttons which are not included on that list. Is there any way to get panel to register all of the input from all of it's children?
Thanks
First, you need bind a KeyStroke to some kind of "key". Now personally, it's eaiser to specifiy the virtual key then using a String, as the String value can be a little temperamental, but that's me
panel.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0),"crtlF");
Next, you need to bind the "key" to an Action
panel.getActionMap().put("crtlF", ctrlF);
See How to use key bindings for more details.
The next problem you will have is the component will need to be focused before the key binding can be triggered
You could try and get the InputMap with a different focus requirement using either WHEN_ANCESTOR_OF_FOCUSED_COMPONENT or WHEN_IN_FOCUSED_WINDOW which will allow you to change the focus level required by the component in order for the key binding to be triggered.
i.e.,
int condition = JComponent.WHEN_IN_FOCUSED_WINDOW;
InputMap inputMap = panel.getInputMap(condition);
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0),"crtlF");
//... etc
My question is the inverse of this one: Is there a way to only have the OK button in a JOptionPane showInputDialog (and no CANCEL button)?
One solution to that was (if I read correctly) to add an arbitrary JPanel, in that instance a label. My problem is that I need a JComboBox object in the message window, and (in the same way that solved Coffee_Table's problem) having the JComboBox seemingly removes the cancel button. It doesn't matter if I replace YES_NO_CANCEL_OPTION with OK_CANCEL_OPTION or QUESTION_MESSAGE.
I'm still at the mindless-copying stage of learning about the JOptionPane family, so I presume the solution is obvious and I just don't know it because I haven't seen any specific examples to mindlessly copy. (Which also means that once I learn how to add a cancel button, I'll need to work on how to access whether the user hit it. EDIT: And I'm half-sure how I'd do it, so you don't need to answer it if you don't want to.)
public static void main(String[] args) {
int numCh1 = 1;
String[] moves = {"rock","paper","scissors"};
JComboBox<?> optionList = new JComboBox<Object>(moves);
JOptionPane.showMessageDialog(
null,
optionList,
"Player One: Choose a Move",
JOptionPane.YES_NO_CANCEL_OPTION
);
numCh1 = optionList.getSelectedIndex();
System.out.println(moves[numCh1]);
}
Note: The combo box is non-negotiable (as opposed to, say, three buttons) because my actual project is to simulate rps101; I just figured you didn't need to see all 100 moves (or anything else irrelevant to this question).
You're using the showMessageDialog() method, which shows just that: a message. It doesn't have a cancel option. For that, use one of the other methods.
In fact, that last parameter isn't even valid. It's not looking for an option type like you provided, it's looking for a message type (ERROR_MESSAGE, INFORMATION_MESSAGE, WARNING_MESSAGE, QUESTION_MESSAGE, or PLAIN_MESSAGE).
As always, the API is your best friend: http://docs.oracle.com/javase/7/docs/api/javax/swing/JOptionPane.html
So far I've got ESC key to close the window, using the following code:
KeyStroke escapeKeyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, false);
Action escapeAction = new AbstractAction() {
public void actionPerformed(ActionEvent e) {
screen.dispose();
}
};
screen.getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(escapeKeyStroke, "ESCAPE");
screen.getRootPane().getActionMap().put("ESCAPE", escapeAction);
But I am wondering how I would go about adding a CTRL+A event? I remember reading about a way where you set booleans for keypressed/released, but I don't see that working with this piece of code, so I am wondering how I can implement CTRL+A.
Thank You
It's the second parameter of the KeyStroke.getKeyStroke(...) method that matters, as you'll want to use the InputEvent.CTRL_DOWN_MASK there to let the KeyEvent.VK_A be a control-A.
e.g.,
KeyStroke ctrlAKeyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_A,
InputEvent.CTRL_DOWN_MASK);
I wouldn't worry about using the 3 parameter method that uses a boolean since you're more interested in key press, not key down or key release.
Regarding your comment:
Correction to my earlier comment. It works, if I make it let's say Ctrl+W. Ctrl+A just attempts to do its native "select all" function in a textfield in the frame. Any way to avoid that?
From the little I understand, this will be a problem if the component that has focus (such as a JTextArea) responds directly to the ctrl-A key press. To get around this, you can add the same binding to this component, but being sure to bind it to its InputMap that uses the JComponent.WHEN_FOCUSED; condition.
I've a problem with setAccelerator(). Right now, I have the code that works for Ctrl+X for DELETE operation. I want to set the accelerator to Shift+Delete as well for same JMenuItem.
My code as follows:
JMenuItem item = new JMenuItem(menuText);
item.setAccelerator(KeyStroke.getKeyStroke(
KeyEvent.VK_X, KeyEvent.CTRL_MASK));
item.setAccelerator(KeyStroke.getKeyStroke(
KeyEvent.VK_DELETE, KeyEvent.SHIFT_MASK));
but this is working only for Shift+Delete operation. Seems it is overriding the Ctrl+X operation. Can we make both these keystrokes work at the same time?
Please guide.
Yes it can be done. Behind the scenes the setAccelerator() is just creating a Key Binding, however as you noticed the second binding replaces the first.
So, you need to create an Action (not an ActionListener) to add the to the menu item. Read the section from the Swing tutorial on How to Use Actions for more information. Now that you have created the Action, you can share the Action with another KeyStroke by manually creating a Key Binding. You can read the section from the Swing tutorial on How to Use Key Bindings for a detailed explanation. Or you can read my blog on Key Bindings which give some simple code examples.
This second binding will not show up on the menu item itself.
From: http://java.sun.com/j2se/1.4.2/docs/api/java/awt/AWTEvent.html
The masks are also used to specify to which types of events an AWTEventListener should listen.
So you can combine the mask for two keys, but not the KeyEvents.
item.setAccelerator(
KeyStroke.getKeyStroke(
KeyEvent.VK_X, KeyEvent.CTRL_MASK + KeyEvent.SHIFT_MASK));
A workaround solution would be to catch the KeyEvent in the middle (after your component fired it, but before your listeners will act on it) and check, whether its one of the two combinations. Then fire one event, on which you programmatically agree to represent the action you wanted.
The second call indeed overrides the accelerator. If the method starts with set, there will be only one. If the method starts with add, you can have more than one (for example for a number of listeners).
If you want multiple keystrokes to do the same, I think you should add a keyListener to the top frame (or panel, dialog, ...) which invokes the action listeners added to the menuItem.