JPanel的Keylistener无法正常工作

11

我想在我的JPanel类中,使用KeyListener来检测当用户按下箭头键时执行某些操作。以下是我的代码:

public class TestPanel extends JPanel implements KeyListener{

    public TestPanel(){
        this.addKeyListener(this);
        this.setFocusable(true);
        this.requestFocusInWindow();
    }

    public void keyPressed(KeyEvent e) {
        if (e.getKeyCode() == KeyEvent.VK_RIGHT) {
            System.out.println("Right");

        }

        if (e.getKeyCode() == KeyEvent.VK_LEFT) {
            System.out.println("Left");
        }

    }

    public void keyTyped(KeyEvent e) {}
    public void keyReleased(KeyEvent e) {}
}

我的主要方法是将此面板的一个新实例添加到框架并显示它。我需要在JFrame中添加键盘监听器吗?在我的情况下,这样做会很困难和低效,所以如果可能的话,我想让它与此JPanel一起工作。有人知道我错在哪里吗?

编辑:不起作用的键绑定代码:

public class GamePanel extends JPanel implements ActionListener{

//Constructor
public GamePanel(){

    setupKeyBinding();
    this.setFocusable(true);
    this.requestFocusInWindow();


}

private void setupKeyBinding() {
    int condition = JComponent.WHEN_IN_FOCUSED_WINDOW;
    InputMap inMap = getInputMap(condition);
    ActionMap actMap = getActionMap();

    inMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), "Left");
    actMap.put("Left", new leftAction());
}

private class leftAction extends AbstractAction {

       public void actionPerformed(ActionEvent e) {
          System.out.println("test");
       }
}

public void actionPerformed(ActionEvent e) {
    //some other game info
}
} 

有人可以告诉我为什么这也不起作用吗?(我的第二个操作侦听器是为了我的游戏需要的其他东西)


另一个想法可能是创建一个内部类并使用类似于以下内容的语句:"this.addKeyListener(内部类/匿名内部类);" - ObedMarsh
可能是 java keylistener not called 的重复问题。 - Hovercraft Full Of Eels
4个回答

13
如果您搜索这个问题,您会发现它已经被问过并解决了很多次。
KeyListener需要在焦点组件上工作。一个解决方案是在首次使它成为可聚焦的后,将您的组件设置为焦点组件。
然而,更好的解决方法是使用Key Bindings。请谷歌一下相关的教程。
请查看我的回答以获取更多信息,包括许多详细信息:此问题

谢谢,我会研究一下键绑定,但是对于这个问题,我已经看了各种解决方案,也没有发现我做错了什么。我该如何让我的组件获得焦点?我认为使用requestFocusInWindow()可以实现。 - user2373733
3
不同的平台会有不同的焦点行为;此外,如果存在任何其他可获取焦点的组件,一个KeyListener将在这方面变得脆弱。 - trashgod
我阅读了Oracle文档,查看了您的示例并尝试使用键绑定,但它没有起作用,所以我复制了您的键绑定代码并尝试了一下,但仍然无法正常工作。目前不知道该怎么办。 - user2373733
1
@user2373733:你需要做的是创建并发布一个sscce。如果我们要帮助您解决这个错误,您或我们必须隔离和识别错误,而没有sscce,这对我们来说是很困难的。 - Hovercraft Full Of Eels
啊,糟糕,这意味着我在那个情境下做的是正确的吗? - user2373733
显示剩余2条评论

8

为了参考,我使用您的方法创建了一个示例;虽然它有效,但也表明了您代码中其他地方存在的焦点问题。使用键绑定可以避免这种情况,如此处所示。

附加说明:下面是我的工作键绑定。

private static class TestPanel extends JPanel {

    private static final String LEFT = "Left";
    private Action left = new AbstractAction(LEFT) {
        @Override
        public void actionPerformed(ActionEvent e) {
            System.out.println(LEFT);
        }
    };
    private static final String RIGHT = "Right";
    private Action right = new AbstractAction(RIGHT) {
        @Override
        public void actionPerformed(ActionEvent e) {
            System.out.println(RIGHT);
        }
    };

    public TestPanel() {
        this.getInputMap().put(
            KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), LEFT);
        this.getActionMap().put(LEFT, left);
        this.getInputMap().put(
            KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), RIGHT);
        this.getActionMap().put(RIGHT, right);
    }
}

原始的SSCCE:

import java.awt.EventQueue;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.JFrame;
import javax.swing.JPanel;

/**
 * @see https://dev59.com/A2Qn5IYBdhLWcg3w1qEc#16531380
 */
public class Test {

    private void display() {
        JFrame f = new JFrame("Test");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.add(new TestPanel());
        f.pack();
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }

    private static class TestPanel extends JPanel implements KeyListener {

        public TestPanel() {

            this.addKeyListener(this);
            this.setFocusable(true);
            this.requestFocusInWindow();
        }

        @Override
        public void keyPressed(KeyEvent e) {
            if (e.getKeyCode() == KeyEvent.VK_RIGHT) {
                System.out.println("Right");
            }

            if (e.getKeyCode() == KeyEvent.VK_LEFT) {
                System.out.println("Left");
            }
        }

        @Override
        public void keyTyped(KeyEvent e) {
        }

        @Override
        public void keyReleased(KeyEvent e) {
        }
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                new Test().display();
            }
        });
    }
}

1
啊,谢谢。我不知道是什么在占用焦点。在测试时,我将所有其他添加到我的框架中的面板设置为无法聚焦,以确保没有任何干扰。现在我正在尝试使用键绑定,但由于对这方面的许多内容都很陌生,所以遇到了一些困难。 - user2373733
1
我已经将那段代码添加到我的帖子中,如果有人能告诉我为什么它还是不起作用,我会非常感激! - user2373733
我已经在上面添加了一个可工作的示例;如果您发布新问题,请告诉我们。 - trashgod
谢谢,我打算在此之前再做些研究。我已经向我的老师和其他多个人询问了我的问题,但是没有人能够解决它。基本上,只有当我的面板获得焦点时它才起作用。我认为这只适用于键盘监听器。 - user2373733
缺乏更多的上下文,很难说;你尝试过这三个输入映射中的每一个吗? - trashgod

3

如果要在 JPanel 上接收按键事件,必须设置焦点:

setFocusable(true);
requestFocus(); 

JPanel 现在拥有焦点,因此它会接收键盘事件。


0
我必须做两件事情:将comp.setFocusable(true);添加到监听键事件的组件comp中,并在每个导致comp失去焦点的操作中添加comp.requestFocus();。

这种方法在大多数情况下都是一种欺骗,一旦窗口失去焦点或其他窃取焦点的组件出现,它就很容易被破坏 - 正确的答案是使用键绑定API。 - MadProgrammer
@MadProgrammer - 如果“正确”的方法是使用键绑定,那么Keylistener API为什么还会存在? - John Calcote
@JohnCalcote请查阅JavaDocs。KeyListener在1.1版本中添加,键绑定在1.3版本中添加 - 键绑定的添加是为了解决 KeyListener 所创建的问题(以及其他一些问题)。甚至在JComponent的JavaDocs中都明确指出:“全面的按键处理。有关更多信息,请参阅Java教程中的文章《如何使用键绑定》。”这是否意味着您永远不应该使用 KeyListener?不是的,这意味着您首先需要了解自己的需求,并选择最佳API来解决问题。键绑定可以解决“需要焦点”的问题。 - MadProgrammer
@JohnCalcote 而且,就这个问题而言,键绑定是更好的API,因为它可以避免不断地围绕焦点问题进行修改,因为在多个平台上,焦点的处理方式并不相同。 - MadProgrammer
@JohnCalcote 这有点像问“为什么我们有java.util.Date和相关API时还需要java.time” - 开发不会停滞不前。最初实现的想法并不总是经得起时间的考验,有时候会出现更好的方法。在这方面,键绑定通常提供了更好的解决方案,但并非所有情况都是如此,因此在那些键绑定无法满足问题要求的情况下,您仍然可以使用KeyListener,只需注意了解两个API的优缺点即可。 - MadProgrammer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接