javaswingawtkeylistener

How to eliminate delay in keyPress?


So I have seen a few threads about this already and I need some help about how to fix it specifically. When you hold down a key, Java will read the first key press, then there will be a small delay, and then it will continuously read the key press until you release the key.

public void keyPressed(KeyEvent key) {
    int code = key.getKeyCode();
    if (code == KeyEvent.VK_DOWN) {
        //Do stuff
    }
    if (code == KeyEvent.VK_LEFT) {
        //Do stuff
    }
    if (code == KeyEvent.VK_RIGHT) {
        //Do stuff
    }
    if (code == KeyEvent.VK_UP) {
        //Do stuff
    }
}

That is my current code. I heard that to fix this you can create a timer which rapidly checks for key presses, but I'm not really sure how to do that. Would appreciate some help here or if there is a better solution.


Solution

  • The basic answer to your question is, you can't, the delay is OS specific.

    The longer answer is, you should be ignoring the individual events themselves and monitor a change in state (between press and release) through the use of appropriate flags.

    This means, that when a key is pressed, you set some flag which you program can use to change the state of the program and when released, you reset it.

    This disassociates the event from state change and provides you with much more flexibility, as your program doesn't care what caused the state change, only that the state has changed and it should react to it.

    This will require you to have some kind of "loop" whose responsibility it is, is to monitor this change and react to it accordingly. In gaming, this is commonly known as a "game-loop", but can also been known as "main-loop".

    It's this "loops" responsibility to update the state of the program and get it painted.

    Below is a very simple example which uses the key bindings API and a javax.swing.Timer to demonstrate the basic concepts

    import com.sun.glass.events.KeyEvent;
    import java.awt.BorderLayout;
    import java.awt.Color;
    import java.awt.Dimension;
    import java.awt.EventQueue;
    import java.awt.Graphics;
    import java.awt.Graphics2D;
    import java.awt.Rectangle;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import javax.swing.AbstractAction;
    import javax.swing.ActionMap;
    import javax.swing.InputMap;
    import javax.swing.JFrame;
    import javax.swing.JPanel;
    import javax.swing.KeyStroke;
    import javax.swing.Timer;
    import javax.swing.UIManager;
    import javax.swing.UnsupportedLookAndFeelException;
    
    public class MoveMe {
    
        public static void main(String[] args) {
            new MoveMe();
        }
    
        public MoveMe() {
            EventQueue.invokeLater(new Runnable() {
                @Override
                public void run() {
                    try {
                        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                    } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    }
    
                    JFrame frame = new JFrame("Testing");
                    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                    frame.setLayout(new BorderLayout());
                    frame.add(new TestPane());
                    frame.pack();
                    frame.setLocationRelativeTo(null);
                    frame.setVisible(true);
                }
            });
        }
    
        public class MovementState {
    
            public int xDirection;
            public int yDirection;
    
        }
    
        public class TestPane extends JPanel {
    
            private MovementState movementState;
            private Rectangle box;
    
            public TestPane() {
                movementState = new MovementState();
                InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW);
                ActionMap am = getActionMap();
    
                im.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, false), "down-pressed");
                im.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, true), "down-released");
                im.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, false), "up-pressed");
                im.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, true), "up-released");
                im.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, false), "left-pressed");
                im.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, true), "left-released");
                im.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, false), "right-pressed");
                im.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, true), "right-released");
    
                am.put("down-pressed", new YDirectionAction(movementState, 2));
                am.put("down-released", new YDirectionAction(movementState, 0));
                am.put("up-pressed", new YDirectionAction(movementState, -2));
                am.put("up-released", new YDirectionAction(movementState, 0));
                am.put("left-pressed", new XDirectionAction(movementState, -2));
                am.put("left-released", new XDirectionAction(movementState, 0));
                am.put("right-pressed", new XDirectionAction(movementState, 2));
                am.put("right-released", new XDirectionAction(movementState, 0));
    
                box = new Rectangle(90, 90, 20, 20);
    
                Timer timer = new Timer(40, new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        box.x += movementState.xDirection;
                        box.y += movementState.yDirection;
                        if (box.x < 0) {
                            box.x = 0;
                        } else if (box.x + box.width > getWidth()) {
                            box.x = getWidth() - box.width;
                        }
                        if (box.y < 0) {
                            box.y = 0;
                        } else if (box.y + box.height > getHeight()) {
                            box.y = getHeight() - box.height;
                        }
                        repaint();
                    }
                });
                timer.start();
            }
    
            @Override
            public Dimension getPreferredSize() {
                return new Dimension(200, 200);
            }
    
            @Override
            protected void paintComponent(Graphics g) {
                super.paintComponent(g);
                Graphics2D g2d = (Graphics2D) g.create();
                g2d.setColor(Color.RED);
                g2d.fill(box);
                g2d.dispose();
            }
        }
    
        public abstract class AbstractDirectionAction extends AbstractAction {
    
            private final MovementState movementState;
            private final int value;
    
            public AbstractDirectionAction(MovementState movementState, int value) {
                this.movementState = movementState;
                this.value = value;
            }
    
            public MovementState getMovementState() {
                return movementState;
            }
    
            public int getValue() {
                return value;
            }
    
        }
    
        public class YDirectionAction extends AbstractDirectionAction {
    
            public YDirectionAction(MovementState movementState, int value) {
                super(movementState, value);
            }
    
            @Override
            public void actionPerformed(ActionEvent e) {
                getMovementState().yDirection = getValue();
            }
    
        }
    
        public class XDirectionAction extends AbstractDirectionAction {
    
            public XDirectionAction(MovementState movementState, int value) {
                super(movementState, value);
            }
    
            @Override
            public void actionPerformed(ActionEvent e) {
                getMovementState().xDirection = getValue();
            }
    
        }
    
    }