Cory Smith
Cory Smith

Reputation: 27

Java swing timer only works once then keyEvents fire in rapid succession - holding key down

So I've set this up as a test for KeyEvents and timers. The first time the right arrow key is pressed the event will wait 5 seconds like the timer is setup to, then print KeyPressed. However, after the first println, KeyPressed will be printed in rapid succession like a long queue of KeyEvents it was collecting up while I held the key down.I don't want all the extra key presses that holding the right arrow key causes. I want to hold the right arrow key down and only receive a println every 5 seconds. Any help is greatly appreciated.

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;

public class GameBoard extends JPanel
{

public Ninja ninja;


public GameBoard()
{
    addKeyListener(new TAdapter());
    setFocusable(true);
    setBackground(Color.BLACK);
    setDoubleBuffered(true); 
    ninja = new Ninja();
}

public void paint(Graphics g)
{
    Graphics2D g2 = (Graphics2D) g;

    g2.drawImage(ninja.getImage(), 20,20,null);
}

private class TAdapter extends KeyAdapter
{

    private Timer timer;


    @Override
    public void keyPressed(KeyEvent e)
    {
        timer = new Timer(5000, new ActionListener(){

            public void actionPerformed(ActionEvent ae)
            {
                System.out.println("KeyPressed");

            }

        });

        timer.start();

    }


    @Override
    public void keyReleased(KeyEvent e)
    {

        ninja.keyReleased(e);
        repaint();
    }

}
}

Upvotes: 0

Views: 1127

Answers (3)

MadProgrammer
MadProgrammer

Reputation: 347194

When the key is held down, the OS will generate a repeating event for the stroke.

Normally, you would need some kind of flag that would indicate that the keyPressed event has already been handled or not.

Based on your example, you could use the Timer. For example, when keyPressed is triggered, you would check to see of the Timer is null or is running...

if (timer == null || !timer.isRunning()) {...

Now, in your keyReleased event, you could need to stop the timer, so that the next time keyPressed is triggered, you can restart the timer.

This assumes that you only want the timer to run only while the key is pressed.

As a general suggestion, you should be using Key Bindings instead of KeyListener as it will provide you better control over the focus level which triggers the key events

Updated with Key Bindings Example

This is based on what your code appears to be doing...

Walkies

import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.imageio.ImageIO;
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 WalkCycle {

    public static void main(String[] args) {
        new WalkCycle();
    }

    public WalkCycle() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        private List<BufferedImage> walkCycle;

        private int frame;

        private Timer timer;

        public TestPane() {
            setBackground(Color.WHITE);
            walkCycle = new ArrayList<>(10);
            try {
                walkCycle.add(ImageIO.read(getClass().getResource("/Walk01.png")));
                walkCycle.add(ImageIO.read(getClass().getResource("/Walk02.png")));
                walkCycle.add(ImageIO.read(getClass().getResource("/Walk03.png")));
                walkCycle.add(ImageIO.read(getClass().getResource("/Walk04.png")));
                walkCycle.add(ImageIO.read(getClass().getResource("/Walk05.png")));
                walkCycle.add(ImageIO.read(getClass().getResource("/Walk06.png")));
                walkCycle.add(ImageIO.read(getClass().getResource("/Walk07.png")));
                walkCycle.add(ImageIO.read(getClass().getResource("/Walk08.png")));
                walkCycle.add(ImageIO.read(getClass().getResource("/Walk09.png")));
                walkCycle.add(ImageIO.read(getClass().getResource("/Walk10.png")));

                Timer timer = new Timer(80, new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        frame++;
                        if (frame >= walkCycle.size()) {
                            frame = 0;
                        }
                        System.out.println(frame);
                        repaint();
                    }
                });

                InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW);
                ActionMap am = getActionMap();
                im.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, false), "right-down");
                im.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, true), "right-up");

                am.put("right-down", new TimerAction(timer, true));
                am.put("right-up", new TimerAction(timer, false));
            } catch (IOException exp) {
                exp.printStackTrace();
            }
        }


        @Override
        public Dimension getPreferredSize() {
            return new Dimension(300, 300);
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics g2d = (Graphics2D) g.create();
            BufferedImage img = walkCycle.get(frame);
            int x = (getWidth() - img.getWidth()) / 2;
            int y = (getHeight() - img.getHeight()) / 2;
            g2d.drawImage(img, x, y, this);
            g2d.dispose();
        }

    }

    public class TimerAction extends AbstractAction {

        private Timer timer;
        private boolean start;

        public TimerAction(Timer timer, boolean start) {
            this.timer = timer;
            this.start = start;
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            if (start && !timer.isRunning()) {
                System.out.println("Start");
                timer.start();
            } else if (!start && timer.isRunning()) {
                System.out.println("stop");
                timer.stop();
            }
        }

    }

}

Personally, I would have a single Timer which was always ticking, which updated the view. The view would then check with the model about what should be updated and rendered and the key bindings would update the state of the model, but that's just me.

Upvotes: 3

Braj
Braj

Reputation: 46841

This will solve your problem.

No timer is required

Its a simple use to system current timing...

private long startTime;

private class TAdapter extends KeyAdapter {
    public void keyPressed(final KeyEvent e) {

        if (System.currentTimeMillis() - startTime > 2000) {
            startTime = System.currentTimeMillis();
            ninja.keyPressed(e, 1);
            repaint();
        } else if (System.currentTimeMillis() - startTime > 1000) {
            ninja.keyPressed(e, 2);
            repaint();
        }
    }

    public void keyReleased(KeyEvent e) {
        ninja.keyReleased(e);
        repaint();

        startTime = 0;
    }
}

Upvotes: 0

Kyle Spencer
Kyle Spencer

Reputation: 323

When you hold down a key, keyPressed events will happen in rapid succession. What you have done is added a 5 second delay to this wave of events.

To fix this depends on what you want to do. If you want to only allow an event to happen every 5 seconds, you can move the timer outside of the event, then when the event gets called, check if 5 seconds has passed based on a boolean toggled by the timer every 5 seconds.

Upvotes: 0

Related Questions