3

The arrow keys for this application need to cause press and release events independent of focus. Setting onKeyRelease true causes a release event as expected but setting onKeyRelease false (code below) doesn't seem to stop auto-repeat. Is there a way to implement the key binding to trigger once when the arrow key is pressed and held?

Action right = new AbstractAction() {
    public void actionPerformed(ActionEvent e) {
        ...
    }
};
mainPanel.getInputMap(JPanel.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)
        .put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, false), "right");
mainPanel.getActionMap().put("right", right);
skaffman
  • 398,947
  • 96
  • 818
  • 769
jacknad
  • 13,483
  • 40
  • 124
  • 194
  • (1): are you trying to block the auto-repeat when a key is pressed for a long time? - (2): are you looking for a cross-platform resolution ? – ring bearer Mar 19 '12 at 15:31
  • @ring bearer: I am not trying to block auto-repeat, I just want do trigger an event once when the arrow key is pressed or released. And yes, this needs to work on various Windows and Linux platforms. – jacknad Mar 19 '12 at 15:48

1 Answers1

5

... could be good question, but please not clear everything, can you please elaborate ...

import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.*;
import java.util.HashMap;
import java.util.Map;
import javax.imageio.ImageIO;
import javax.swing.*;

public class MoveIcon extends JPanel {

    private static final long serialVersionUID = 1L;
    private static final String IMAGE_PATH = "http://duke.kenai.com/misc/Bullfight.jpg";
    private static final String IMAGE_PATH_PLAYER = "http://duke.kenai.com/iconSized/duke4.gif";
    public static final int STEP = 3;
    private static final int TIMER_DELAY = STEP * 8;
    private BufferedImage bkgrndImage = null;
    private BufferedImage playerImage = null;
    private Map<Direction, Boolean> directionMap = new HashMap<Direction, Boolean>();
    private int playerX = 0;
    private int playerY = 0;

    enum Direction {

        UP(KeyEvent.VK_UP, 0, -1), DOWN(KeyEvent.VK_DOWN, 0, 1),
        LEFT(KeyEvent.VK_LEFT, -1, 0), RIGHT(KeyEvent.VK_RIGHT, 1, 0);
        private int keyCode;
        private int xDirection;
        private int yDirection;

        private Direction(int keyCode, int xDirection, int yDirection) {
            this.keyCode = keyCode;
            this.xDirection = xDirection;
            this.yDirection = yDirection;
        }

        public int getKeyCode() {
            return keyCode;
        }

        public int getXDirection() {
            return xDirection;
        }

        public int getYDirection() {
            return yDirection;
        }
    }

    public MoveIcon() {
        try {
            URL bkgrdImageURL = new URL(IMAGE_PATH);
            URL playerImageURL = new URL(IMAGE_PATH_PLAYER);
            bkgrndImage = ImageIO.read(bkgrdImageURL);
            playerImage = ImageIO.read(playerImageURL);
            setPreferredSize(new Dimension(bkgrndImage.getWidth(), bkgrndImage.getHeight()));
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        for (Direction direction : Direction.values()) {
            directionMap.put(direction, false);
        }
        setKeyBindings();
        Timer timer = new Timer(TIMER_DELAY, new TimerListener());
        timer.start();
    }

    private void setKeyBindings() {
        InputMap inMap = getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
        ActionMap actMap = getActionMap();
        for (final Direction direction : Direction.values()) {
            KeyStroke pressed = KeyStroke.getKeyStroke(direction.getKeyCode(), 0, false);
            KeyStroke released = KeyStroke.getKeyStroke(direction.getKeyCode(), 0, true);
            inMap.put(pressed, direction.toString() + "pressed");
            inMap.put(released, direction.toString() + "released");
            actMap.put(direction.toString() + "pressed", new AbstractAction() {

                private static final long serialVersionUID = 1L;

                @Override
                public void actionPerformed(ActionEvent e) {
                    directionMap.put(direction, true);
                }
            });
            actMap.put(direction.toString() + "released", new AbstractAction() {

                private static final long serialVersionUID = 1L;

                @Override
                public void actionPerformed(ActionEvent e) {
                    directionMap.put(direction, false);
                }
            });
        }

    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        if (bkgrndImage != null) {
            g.drawImage(bkgrndImage, 0, 0, null);
        }
        if (playerImage != null) {
            g.drawImage(playerImage, playerX, playerY, null);
        }
    }

    private class TimerListener implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent e) {
            boolean moved = false;
            for (Direction direction : Direction.values()) {
                if (directionMap.get(direction)) {
                    playerX += STEP * direction.getXDirection();
                    playerY += STEP * direction.getYDirection();
                    moved = true;
                }
            }
            if (moved) {
                int x = playerX - 2 * STEP;
                int y = playerY - 2 * STEP;
                int w = playerImage.getWidth() + 4 * STEP;
                int h = playerImage.getHeight() + 4 * STEP;
                MoveIcon.this.repaint(x, y, w, h); // !! repaint just the player
            }
        }
    }

    private static void createAndShowUI() {
        JFrame frame = new JFrame("MoveIcon");
        frame.getContentPane().add(new MoveIcon());
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        java.awt.EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                createAndShowUI();
            }
        });
    }
}
mKorbel
  • 109,525
  • 20
  • 134
  • 319
  • What you have here is basically the same as what I did. The problem is "KeyStroke pressed" triggers continuous events if the arrow key is pressed and held. In my system this crashes the firmware which is the eventual target of the event, and is a problem that needs to be fixed in the firmware, but the GUI should only send the event once when the key is pressed, and a different event when the key is released (the key release works as expected). I suppose I could add a bool that sets on the press event and resets on the release event. But there should be a proper way to do this. – jacknad Mar 19 '12 at 15:45
  • @jacknad well, right, then there are three ways by assuming that you can re_use output from KeyBindings to the Swing Action or ActionLitener 1) remove Action from ActionMap (wrong way) 2) disable output from Swing Action by using Boolean 3) use Swing Timer to disable Swing Action by using Boolean for defined delay – mKorbel Mar 19 '12 at 16:07
  • Used Boolean to disable auto-repeat action. – jacknad Mar 19 '12 at 17:28
  • @mKorbel - I don't understand how you solved that problem? "disable output from Swing Action by using Boolean" - what the heck is that supposed to mean? Thanks – 0__ Oct 07 '14 at 21:42
  • @0__ very interesting question, Swing Action is settable, scalable, shareable, I'm sure that rest in official Oracle tutorial and API, too close to ...., to the.... how is able to do by using (java.beans.)EventHandler – mKorbel Oct 08 '14 at 04:46