无法在 JComponent 中实现 keylistener

Unable to implement keylistener in JComponent

我已经在下面的代码上工作了很长一段时间,但我似乎无法让 keyListeners 工作。我试过移动 setFocusable(true)、requestFocus() 和 addKeyListener(this),但没有任何区别。

而且,在任何人提到它之前,是的,如果到目前为止我在所有阅读中都学到了一件事,那么互联网似乎一致认为按键绑定更优越。问题是,这是学校作业,所以我必须照章办事。我应该做些什么来激活 KeyListener?

public class SnakeGUI extends JComponent implements KeyListener {
    private static JTextField timeKeeper;
    private static JTextField scoreKeeper;

    private static int time;
    private static int score;
    private static boolean playing;
    private static SnakeSettings settings;
    private static Snake snake;
    private static SnakePanel snakePanel;

    private static Timer gameTimer;
    private static Timer moveTimer;

    public static void main(String[] args) {

        // @author Every second, the displayed time ticks up
        TimerTask uptick = new TimerTask() {
            public void run() {
                time += 1;
                scoreKeeper.setText(Integer.toString(score));
                timeKeeper.setText(Integer.toString(time));
            }
        };

        // @author Depending on difficulty, the snake moves at different speeds.
        TimerTask move = new TimerTask() {
            public void run() {
                snake.move();
                playing = ! snake.isGameOver(settings.getWidth(), settings.getHeight());
                if (! playing) {
                    // @author Clear timers until next game.
                    gameTimer.cancel();
                    moveTimer.cancel();
                }
                else {
                    snakePanel.setDisplay(snake);
                }
            }
        };

        //@author Use defaults settings first time around.
        settings = new SnakeSettings();

        SnakeSettingsPanel settingsPanel;
        JFrame jf;
        SnakeGUI gui;
        
        // @author Alternate between game/settings elements till they quit.
        while (true) {
            settingsPanel = new SnakeSettingsPanel(settings);
            jf = new JFrame();
            jf.add(settingsPanel);
            jf.pack();
            jf.setTitle("Snake");
            jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            jf.setVisible(true);

            // @author Wait until they press play...
            while (! settingsPanel.getPlay()) {
                try {
                            Thread.sleep(25);
                    } catch (Exception e) {
                            System.out.println(e);
                    }
            }
            
            // @author Then, update settings accordingly.
            settings = settingsPanel.getSettings();
            gui = new SnakeGUI(settings);

            // @author Remove settingspanel, add game gui.
            jf.dispose();
            jf = new JFrame();
            jf.add(gui);
            jf.pack();
            jf.setTitle("Snake");
            jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            jf.setVisible(true);

            // @author Begin a new game.
            playing = true;
            time = 0;
            score = 0;
            snake = new Snake(new Point(settings.getWidth()/2, settings.getHeight()/2));

            gameTimer = new Timer();
            moveTimer = new Timer();
            gameTimer.schedule(uptick, 1000, 1000);

            // @author Set the delay based on the game speed.
            if (Speed.SLOW == settings.getSpeed()) {
                moveTimer.schedule(move, 750, 750);
            }
            else if (Speed.MEDIUM == settings.getSpeed()) {
                moveTimer.schedule(move, 500, 500);
            }
            else {
                moveTimer.schedule(move, 333, 333);
            }
            
            // @author Wait until the game ends.
            while (playing) {
                try {
                            Thread.sleep(25);
                    } catch (Exception e) {
                            System.out.println(e);
                    }
            }
            jf.dispose();
        }

    }

    public SnakeGUI(SnakeSettings set) {
        addKeyListener(this);
        setFocusable(true);
        requestFocus();

        setLayout(new GridBagLayout());
        
        JLabel tm = new JLabel("Time:");
        JLabel sc = new JLabel("Score:");
        timeKeeper = new JTextField(4);
        scoreKeeper = new JTextField(4);
        timeKeeper.setText("0");
        scoreKeeper.setText("0");
        timeKeeper.setEditable(false);
        scoreKeeper.setEditable(false);

        JPanel jp = new JPanel();
        jp.setLayout(new FlowLayout());

        jp.add(tm);
        jp.add(timeKeeper);
        jp.add(sc);
        jp.add(scoreKeeper);

        snakePanel = new SnakePanel(set);
        
        GridBagConstraints p = new GridBagConstraints();
            p.gridx = 0;
            p.gridy = 0;
        
        add(jp, p);
        p.gridy = 1;
        add(snakePanel, p);
        
        setVisible(true);
    }

    public void keyReleased(KeyEvent e) {}
    public void keyTyped(KeyEvent e) {}
    public void keyPressed(KeyEvent e) {
        System.out.println(1); // For testing purposes
        if (e.getKeyCode() == KeyEvent.VK_LEFT) {
            System.out.println(1);
                snake.changeDirection(SnakeInterface.Direction.Left);
            }
        if (e.getKeyCode() == KeyEvent.VK_RIGHT) {
            System.out.println(2); // For testing purposes
                snake.changeDirection(SnakeInterface.Direction.Right);
            }
        if (e.getKeyCode() == KeyEvent.VK_DOWN) {
                snake.changeDirection(SnakeInterface.Direction.Down);
            }
        if (e.getKeyCode() == KeyEvent.VK_UP) {
                snake.changeDirection(SnakeInterface.Direction.Up);
            }
    }
}

好的,这个...

// @author Alternate between game/settings elements till they quit.
while (true) {
    settingsPanel = new SnakeSettingsPanel(settings);
    jf = new JFrame();
    jf.add(settingsPanel);
    jf.pack();
    jf.setTitle("Snake");
    jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    jf.setVisible(true);

    // @author Wait until they press play...
    while (! settingsPanel.getPlay()) {
        try {
                    Thread.sleep(25);
            } catch (Exception e) {
                    System.out.println(e);
            }
    }
    
    // @author Then, update settings accordingly.
    settings = settingsPanel.getSettings();
    gui = new SnakeGUI(settings);

    // @author Remove settingspanel, add game gui.
    jf.dispose();
    jf = new JFrame();
    jf.add(gui);
    jf.pack();
    jf.setTitle("Snake");
    jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    jf.setVisible(true);

    // @author Begin a new game.
    playing = true;
    time = 0;
    score = 0;
    snake = new Snake(new Point(settings.getWidth()/2, settings.getHeight()/2));

    gameTimer = new Timer();
    moveTimer = new Timer();
    gameTimer.schedule(uptick, 1000, 1000);

    // @author Set the delay based on the game speed.
    if (Speed.SLOW == settings.getSpeed()) {
        moveTimer.schedule(move, 750, 750);
    }
    else if (Speed.MEDIUM == settings.getSpeed()) {
        moveTimer.schedule(move, 500, 500);
    }
    else {
        moveTimer.schedule(move, 333, 333);
    }
    
    // @author Wait until the game ends.
    while (playing) {
        try {
                    Thread.sleep(25);
            } catch (Exception e) {
                    System.out.println(e);
            }
    }
    jf.dispose();
}

太疯狂了,再加上 static 的使用,您很快就会 运行 陷入很多麻烦的领域,其中任何一个都可能很容易在您的脸上爆炸。

Swing 不是线程安全的,因此部分原因是线程之间存在脏状态风险(脏 read/writes),您冒着 UI 尝试在更新数据时执行绘制过程的风险 -尝试调试它。

Swing 是一个事件驱动的环境,也就是说,当某事发生并且您对其做出响应时,这与过程驱动的工作流不同,例如控制台应用程序,其中每个语句都紧随其后。

以下是一个过于简化的版本,它有一个“菜单”和“游戏”窗格,您可以使用 CardLayout 和一系列“观察者”

简单地在它们之间切换

一个“技巧”……实际上,这不是一个“技巧”,而是一个彻头彻尾的肮脏技巧,就是当“移动”计时器滴答作响时,我们再次请求焦点。同样,这是 hack,如果您在 UI 上有其他控件,您希望用户与之交互,那么这将给您带来无穷无尽的问题,正如我已经说过的,KeyListener不是解决问题的合适方法。

您应该使用:

为了稍微不那么糟糕的做事方式...

import java.awt.CardLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.time.Duration;
import java.time.Instant;
import java.util.Set;
import java.util.TreeSet;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;

public class Test {

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

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame();
                frame.add(new MainPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class MainPane extends JPanel {

        private CardLayout cardLayout;
        private MenuPane menuPane;
        private GamePane gamePane;

        public MainPane() {
            cardLayout = new CardLayout();
            setLayout(cardLayout);

            menuPane = new MenuPane(new MenuPane.Observer() {
                @Override
                public void didStartGame(MenuPane source) {
                    gamePane.start();
                    cardLayout.show(MainPane.this, "game");
                }
            });
            gamePane = new GamePane(new GamePane.Observer() {
                @Override
                public void gameDidEnd(GamePane source, int score) {
                    source.stop();
                    cardLayout.show(MainPane.this, "menu");
                }
            });

            add(menuPane, "menu");
            add(gamePane, "game");
        }

    }

    public class MenuPane extends JPanel {

        public interface Observer {

            public void didStartGame(MenuPane source);
        }

        public MenuPane(Observer observer) {
            setLayout(new GridBagLayout());
            JPanel contentPane = new JPanel(new GridLayout(-1, 1));
            JButton startButton = new JButton("Start");
            startButton.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    observer.didStartGame(MenuPane.this);
                }
            });

            contentPane.add(startButton);
            add(contentPane);
        }

    }

    public class GamePane extends JPanel {

        public interface Observer {

            public void gameDidEnd(GamePane source, int score);
        }

        public enum Direction {
            UP, DOWN, LEFT, RIGHT;
        }

        private Set<Direction> keyManager = new TreeSet<>();

        private Timer gameTimer;

        private int score;
        private Instant startedAt;

        private Rectangle player = new Rectangle(195, 195, 10, 10);

        public GamePane(Observer observer) {
            addKeyListener(new KeyAdapter() {
                @Override
                public void keyPressed(KeyEvent e) {
                    if (e.getKeyCode() == KeyEvent.VK_UP) {
                        keyManager.add(Direction.UP);
                    } else if (e.getKeyCode() == KeyEvent.VK_DOWN) {
                        keyManager.add(Direction.DOWN);
                    } else if (e.getKeyCode() == KeyEvent.VK_LEFT) {
                        keyManager.add(Direction.LEFT);
                    } else if (e.getKeyCode() == KeyEvent.VK_RIGHT) {
                        keyManager.add(Direction.RIGHT);
                    }
                }

                @Override
                public void keyReleased(KeyEvent e) {
                    if (e.getKeyCode() == KeyEvent.VK_UP) {
                        keyManager.remove(Direction.UP);
                    } else if (e.getKeyCode() == KeyEvent.VK_DOWN) {
                        keyManager.remove(Direction.DOWN);
                    } else if (e.getKeyCode() == KeyEvent.VK_LEFT) {
                        keyManager.remove(Direction.LEFT);
                    } else if (e.getKeyCode() == KeyEvent.VK_RIGHT) {
                        keyManager.remove(Direction.RIGHT);
                    }
                }
            });
        }

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

        public void start() {
            stop();

            keyManager.clear();

            startedAt = Instant.now();
            score = 0;

            gameTimer = new Timer(5, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    //scoreKeeper.setText(Integer.toString(score));
                    //timeKeeper.setText(Integer.toString(time));
                    move();
                }
            });
            gameTimer.start();
        }

        public void stop() {
            if (gameTimer != null) {
                gameTimer.stop();
            }
        }

        protected void move() {
            requestFocusInWindow();
            int delta = 1;
            if (keyManager.contains(Direction.UP)) {
                player.y -= delta;
            }
            if (keyManager.contains(Direction.DOWN)) {
                player.y += delta;
            }
            if (keyManager.contains(Direction.LEFT)) {
                player.x -= delta;
            }
            if (keyManager.contains(Direction.RIGHT)) {
                player.x += delta;
            }

            if (player.y < 0) {
                player.y = 0;
            } else if (player.y + player.height >= getHeight()) {
                player.y = getHeight() - player.height;
            }
            if (player.x < 0) {
                player.x = 0;
            } else if (player.x + player.width >= getWidth()) {
                player.x = getWidth() - player.width;
            }
            repaint();
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();

            g2d.setColor(Color.RED);
            g2d.fill(player);

            g2d.setColor(Color.BLACK);

            FontMetrics fm = g2d.getFontMetrics();
            String text = Integer.toString(score);
            g2d.drawString(text, 16, fm.getAscent());

            text = "---";
            if (startedAt != null) {
                text = Long.toString(Duration.between(startedAt, Instant.now()).toSeconds());
            }
            g2d.drawString(text, getWidth() - 16 - fm.stringWidth(text), fm.getAscent());

            g2d.dispose();
        }

    }
}

哦,关于static的话题,不要这样用,太容易出问题了。相反,使用依赖注入(即 Passing Information to a Method or a Constructor