How do I handle simultaneous key presses in Java?
I'm trying to write a game and need to handle multiple keys presses at once.
When I'm holding a key (let's say to move forward) and then I hold another key (for example, to turn left), the new key is detected but the old pressed key isn't being detected anymore.
One way would be to keep track yourself of what keys are currently down.
When you get a keyPressed event, add the new key to the list; when you get a keyReleased event, remove the key from the list.
Then in your game loop, you can do actions based on what's in the list of keys.
Here is a code sample illustrating how to perform an action when CTRL+Z is pressed:
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
class p4 extends Frame implements KeyListener
{
int i=17;
int j=90;
boolean s1=false;
boolean s2=false;
public p4()
{
Frame f=new Frame("Pad");
f.setSize(400,400);
f.setLayout(null);
Label l=new Label();
l.setBounds(34,34,88,88);
f.add(l);
f.setVisible(true);
f.addKeyListener(this);
}
public static void main(String arg[]){
new p4();
}
public void keyReleased(KeyEvent e) {
//System.out.println("re"+e.getKeyChar());
if(i==e.getKeyCode())
{
s1=false;
}
if(j==e.getKeyCode())
{
s2=false;
}
}
public void keyTyped(KeyEvent e) {
//System.out.println("Ty");
}
/** Handle the key pressed event from the text field. */
public void keyPressed(KeyEvent e) {
System.out.println("pre"+e.getKeyCode());
if(i==e.getKeyCode())
{
s1=true;
}
if(j==e.getKeyCode())
{
s2=true;
}
if(s1==true && s2==true)
{
System.out.println("EXIT NOW");
System.exit(0);
}
}
/** Handle the key released event from the text field. */
}
Generally speaking, what you are describing can be achieved using bitmasks.
Oh, I had this problem too!
I tried making a simple Pong game following a tutorial I found.
But a lot of the time if I was holding one button and pressed another the new keypress would seem to register while the previous seemed to stop registering even if I was still holding the key pressed.
I thought that it was perhaps a limitation with the KeyListener feature so I tried rewriting my program to use Keybindings instead.
But if anything it made it all worse.
So I realized the problem was in the tutorial I was following.
The way it handled the logic after listening to the keys.
So if you are experiencing this type of issues I'd recommend you to troubleshoot the code that you are working with!
Also take note that the behaviour of keys apparently can be different depending on your Operative system.
So in Windows when a key is pressed it will repeat like you know it will in a text editor.
So when a key is pressed it will actually call repeatedly and then when you release it it will give you one release event.
So take note of that!
Also worth noting is that different keyboards will have different limitations on how many keys you can push simultaneously and that number will usually also be different depending on exactly the combination of keys that are being pressed.
Related
I have a java swing program that I was previously controlling with a the KeyAdapter class. For several reasons, I have decided to switch over to using swing's built in key binding system (using InputMap and ActionMap) instead. While switching, I have run into some confusing behaviors.
In order to test these systems, I have a simple JPanel:
public class Board extends JPanel {
private final int WIDTH = 500;
private final int HEIGHT = 500;
private boolean eventTest = false;
public Board() {
initBoard();
initKeyBindings();
}
// initialization
// -----------------------------------------------------------------------------------------
private void initBoard() {
setPreferredSize(new Dimension(WIDTH, HEIGHT));
setFocusable(true);
}
private void initKeyBindings() {
getInputMap().put((KeyStroke.getKeyStroke(KeyEvent.VK_SHIFT, 0), "Shift Pressed");
getActionMap().put("Shift Pressed", new AbstractAction() {
public void actionPerformed(ActionEvent e) {
eventTest = true;
}
});
}
// drawing
// -----------------------------------------------------------------------------------------
#Override
protected void paintComponent(Graphics g) {
// paint background
super.paintComponent(g);
g.setColor(Color.black);
g.drawString("Test: " + eventTest, 10, 10);
eventTest = false;
}
Also in my program, I have a loop calling the repaint() method 10 times per second, so that I can see eventTest get updated. I am expecting this system to display eventTest as true on a frame where the shift key becomes pressed, and false otherwise. I also have tested other keys by changing the relevant key codes.
When I want to test the KeyAdapter, I add this block to the initBoard() method, and comment out initKeyBindings() in the constructor:
this.addKeyListener(new KeyAdapter() {
#Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_SHIFT) {
eventTest = true;
}
}
});
When using the KeyAdapter class, this works as expected. However, when I switch over to using key bindings, it becomes confusing. For some reason, eventTest is only displayed as true when I press down both shift keys. If I hold either shift key down, event test becomes true on the frame when I press the other, and then returns to false. I would like it to do this when one shift key is pressed, without having to hold the other one.
Additionally, when I set it to trigger on right arrow presses instead, a slightly different behavior happens. In both the KeyAdapter and key bindings modes, what happens is that eventTest becomes true on the frame I press the right arrow, returns to false for a short time, and then becomes true for as long as I hold the arrow. From reading the documentation online, it appears that this is caused by an OS dependent behavior (I am running Ubuntu 18.04) to continue sending out KeyPressed events while a key is held down. What I am confused about is why this behavior would be different for the shift key than for the right arrow. If possible, I would like to find a way to make eventTest true only on the first frame a key is pressed.
Any ideas as to what is causing this? Thanks!
I have found at least a partial answer.
For the issue where I had to hold down both shift keys to generate a key pressed event when using key bindings, there is a simple fix. All that needs to be done is to change the what is added to the InputMap from:
getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_SHIFT, 0), "pressed");
to
getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_SHIFT, KeyEvent.SHIFT_DOWN_MASK), "pressed");
I am not completely sure why the input map counts pressing a single shift key as as KeyEvent with a key code of VK_SHIFT AND the SHIFT_DOWN_MASK, but that appears to be what it is doing. It would make more intuitive sense to me if the mask was applied only for if there is already one shift key pressed and the user attempts to press the other one, but interestingly, this binding no longer detects events for if one shift key is held and the other is pressed. Weird.
The problems with other keys have slightly less clean solutions. As to the question of why shift behaves differently than other keys. I believe this is an intentional design built into the OS. For example, if the user presses and holds the right arrow (or many other keys, such as the every text character key), it is reasonable to assume that they want to repeat the action that is tied to that key. I.e. if a user is typing, and presses and holds "a", they likely want to input multiple "a" characters in quick succession into the text document. However, auto-repeating the shift key in a similar manner is not (in most cases) useful to the user. Therefore, it makes sense that no repeated events for the shift key are generated. I don't have any sources to back this up, it is just a hypothesis, but it makes sense to me.
In order to remove these extra events, there doesn't seem to be a good solution. One thing that works, but is sloppy, is to store a list of all keys currently pressed, and then have your action map check if the key is pressed before executing its action. Another approach would be to use timers and ignore events that occur to close in time to one another (see this post for more details). Both of these implementations require more memory usage and code for every key you wish to track, so they are not ideal.
A slightly better solution (IMO) can be achieved using KeyAdapter instead of Key Bindings. The key to this solution lies in the fact that pressing down one key while another is held will interrupt the stream of auto-repeat events, and it will not resume again for the original key (even if the second key is released). Because of this, we really only have to track the last key pressed in order to accurately filter out all auto-repeat events, because that is the only key that could be sending those events.
The code would look something like this:
addKeyListener(new KeyAdapter() {
#Override
public void keyPressed(KeyEvent e) {
int keyCode = e.getKeyCode();
if (keyCode != lastKeyPressed && keyCode != KeyEvent.VK_UNDEFINED) {
// do some action
lastKeyPressed = keyCode;
}
}
#Override
public void keyReleased(KeyEvent e) {
// do some action
lastKeyPressed = -1; // indicates that it is not possible for any key
// to send auto-repeat events currently
}
});
This solution of course looses some of the flexibility provided by swing's key binding system, but that has an easier workaround. You can create your own map of int to Action (or really any other type that is convenient to describe what you want to do), and instead of adding key bindings to InputMaps and ActionMaps, you put them in there. Next, instead of putting the direct code for the action you want to do inside of the KeyAdapter, put something like myMap.get(e.getKeyCode()).actionPerformed();. This allows you to add, remove, and change key bindings by performing the corresponding operation on the map.
I am trying to make a hotkey program that runs in the background, and I can not figure out how to make it check when a certain key is pressed (In this case the minus key). I need the program to wait for a key to be pressed and then the program checks if that key is the minus key.
An example of my program as is now in pseudo code is somewhere along these lines.
while(true)
{
int pressedKey = userInputedKey;
if(pressedKey == KeyEvent.VK_MINUS)
{
//action to be executed
}
}
I have found a lot of suggestions on other threads to use KeyListener but I might not be using it properly, so if your response is to use KeyListener please explain how to use it thoroughly (to be Exact I don't understand how when a KeyEvent is instantiated it already has the input of whatever key was pressed as the keyCode in that KeyEvent, so if I use it in the call to the KeyListener I will not get the proper results). Granted I do not know much about KeyEvent or KeyListener, so I might have not been attempting at the right way around my problem by using a KeyListener.
You can use the KeyEvent to get the key code, and then check it.
E.g:
someCompunent.addKeyListener(new KeyAdapter() {
public void keyPressed(KeyEvent e) {
if(e.getKeyCode() == KeyEvent.VK_MINUS) {
// do the stuff
}
}
});
If you want to use global hotkeys, when program catch keypress event in any application - try use JKeyMaster library (supports global hotkweys in Windows, Linux and MAC OS)
So for reference, I am following Trystan's roguelike tutorial for java.
The tutorial above uses Key Listeners, which does not work for Mac OS X (and appears to be discouraged by the community), so I am trying to change the code to use Key Bindings instead.
Here's the question. In his "roguelike game", the game is divided into screens, where each screen has separate actions for a particular key. For instance, the "Start Screen" sends the player to the "Play screen" upon pressing the enter key:
public class StartScreen implements Screen {
public void displayOutput(AsciiPanel terminal) {
terminal.write("rl tutorial", 1, 1);
terminal.writeCenter("-- press [enter] to start --", 22);
}
public Screen respondToUserInput(KeyEvent key) {
return key.getKeyCode() == KeyEvent.VK_ENTER ? new PlayScreen() : this;
}
}
While the "Play Screen" sends the player to the "Win screen" upon pressing the enter key:
public class PlayScreen implements Screen {
public void displayOutput(AsciiPanel terminal) {
terminal.write("You are having fun.", 1, 1);
terminal.writeCenter("-- press [escape] to lose or [enter] to win --", 22);
}
public Screen respondToUserInput(KeyEvent key) {
switch (key.getKeyCode()){
case KeyEvent.VK_ESCAPE: return new LoseScreen();
case KeyEvent.VK_ENTER: return new WinScreen();
}
return this;
}
}
Now, from what I can see from Key Bindings examples such as this and this, all the bindings and actions seem to be "dumped" into one class. This is unsatisfactory, because:
One keystroke leads to only one action (I suppose I could have the action do a switch case, and check what screen I am in, and then execute some code specific to said screen, but perhaps there is a better way.)
It becomes more difficult to determine what action is from/for what screen.
Would there be a way to get Key Bindings to have one keystroke execute different actions depending on the "Screen"? Would I just use conditionals? Can I put Key Binding logic in other classes (Screens) and then call them in the main class, like what is happening with Key Listeners above?
Key bindings are not necessarily dumped into one class. They are associated with input maps, which in turn are associated with specific JComponents. If you register screen-specific bindings in the WHEN_ANCESTOR_OF_FOCUSED_COMPONENT input map of the JComponent representing that screen, then they will be recognized when any component in that screen has the keyboard focus (supposing a component in between has not overridden the binding with its own), but not when a component in a different screen has the focus.
Overall, the system is very flexible. You might consider reading the Java Tutorial section on this topic to bring yourself up to speed.
I am using JNativeHook to listen for key presses. When the key is hold down, say the computer is supposed to type the key 4 times, each JNativeHook method will register a lot more than that.
I tried:
public void nativeKeyReleased(NativeKeyEvent e) {
System.out.println("registered key release");
}
public void nativeKeyPressed(NativeKeyEvent e) {
System.out.println("registered key press");
}
public void nativeKeyTyped(NativeKeyEvent e) {
System.out.println("registered key type");
}
All of these methods will register the wrong amount of key presses while the key is being held down. I don't think this is a glitch of the JNativeHook library, since it is almost the same as Java's KeyListener, except it's global.
Thanks in advance.
When the user presses a key, keyPressed will be called and then keyTyped will be called, and then finally when the user releases his/her finger from the key, keyReleased is called. Try printing out keyPressed, keyTyped and keyReleased messages rather than typing "registered" everywhere, in order to understand as to what is happening.
The code I put in is just a sample. In my actual program, I can confirm that all three are registered more times than needed.
If that is the case, and you are still experiencing a problem, please report a bug. The detectable auto repeat failed to enable on your platform for some reason.
I have written a calculator in NetBeans and it functions perfectly. However, I have to actually click the buttons to insert numbers and am trying to remedy that with a KeyListener. I have all my numbers and function buttons set inside a JPanel named buttons. I have my display label in a JPanel named display.
I set my class to implement KeyListener and it inserted the KeyPressed, -Typed, and -Released methods; however I stuck from there. I'm not sure how to make my buttons actually listen for the KeyPressed event, and when it hears the event - activate the button. Also, my buttons are named by their number (e.g. the Zero Button is named zero, One button is one, etc.).
I've read that you actually have to implement a KeyListener somewhere by using: something.addKeyListener(something);
but I cannot seem to figure this out.
Can I get some help here? I'm new to Java and this is my first solo project. And let me know if I didn't provide enough information.
EDIT: Most of my code is NetBeans Generated and I cannot edit the initialization of the components which seems to be my problem I think?
My class declaration:
public class Calculator extends javax.swing.JFrame implements KeyListener {
//Creates new form Calculator
public Calculator() {
initComponents();
}
One of my buttonPressed actions (all identical with changes for actual number):
private void zeroActionPerformed(java.awt.event.ActionEvent evt) {
if (display.getText().length() >= 16)
{
JOptionPane.showMessageDialog(null, "Cannot Handle > 16 digits");
return;
}
else if (display.getText().equals("0"))
{
return;
}
display.setText(display.getText().concat("0"));
Main method supplied by NetBeans:
public static void main(String args[]) {
/* Create and display the form */
java.awt.EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new Calculator().setVisible(true);
}
});
}
The initComponents() netbeans generated is absolutely massive (about 500 lines of code) and I cannot edit any of it. Let me know if I can supply any more helpful information.
Could there be an issue of Focus, and if so how can I resolve the issue?
Yes there is probably an issue with focus. That is why you should NOT be using a KeyListener.
Swing was designed to be used with Key Bindings. That is you create an Action that does what you want. Then this Action can be added to your JButton. It can also be bound to a KeyStroke. So you have nice reusable code.
Read the Swing tutorial on How to Use Key Bindings for more information. Key Bindings don't have the focus issue that you currently have.
I'm not sure I completely understand your question, and some code would help, but I'll take a crack, since it sounds like a problem I used to have a lot.
It sounds like the reason that your key presses aren't being recognized is that the focus is on one of the buttons. If you add keylisteners to the buttons, then you shouldn't have any problem.
In netbeans you can add keylisteners through the design screen really easily.
Here's a picture showing you how to add a keyPressed listener to a button in a jPanel.
private void jButton1KeyPressed(java.awt.event.KeyEvent evt) {
//Check which key is pressed
//do whatever you need to do with the keypressed information
}
It is nice to be able to write out the listeners yourself, but if you are just learning, then it is also nice to get as much help as possible.
This might not be the best solution, since you would have to add the listener for each of your buttons.