Java Swing键绑定仅在Mac上停止工作

13

我正在使用Swing制作一款游戏,并使用KeyBindings从键盘获取输入。但我遇到了KeyBindings停止响应的问题。每次运行应用程序时都会发生这种情况,但据我所知,它并不是在某个特定事件链发生时发生的。KeyBindings只是停止接收来自键盘的输入。我还使用鼠标输入,它继续工作,因此我知道这与一般输入无关。

我尝试过以下一些方法:

  • 确保我的对象未被垃圾回收
  • 查找某些原因导致问题发生(例如:在按下一定数量的按键组合后,一段时间后),但我找不到任何答案。
  • 尝试使用KeyListener

但这些方法都没有起作用。

然后,我将项目复制到Windows机器上(未更改任何代码),经过测试,我无法再次出现该问题。我没有在此处粘贴代码,因为我有99%的把握认为这与我的代码无关,而与运行它的操作系统有关。

这是macOS的问题、还是Mac的JDK的问题,或者是其他我不知道的问题?

我正在使用JDK版本8更新112,计算机正在运行macOS Sierra版本10.12.2。

似乎有人曾经遇到同样的问题,但他们没有得到答案。

编辑

这里有一个代码示例,其中出现了问题:

import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.Timer;

//an example of the problem where the keyboard stops receiving input randomly
public class ProblemExample extends JPanel {
    private static final long serialVersionUID = 1L;

    private int xPos = 200, yPos = 200;
    private boolean wKey, aKey, sKey, dKey;
    private BufferedImage image; //sprite

    public ProblemExample() {
        JFrame frame = new JFrame();
        frame.add(this);
        frame.pack();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setResizable(false);
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);

        //makes the sprite a red square
        image = new BufferedImage(50, 50, BufferedImage.TYPE_INT_ARGB);
        int[] redPixels = new int[50 * 50];
        for (int i = 0; i < redPixels.length; i++) {
            redPixels[i] = 0xffff0000;
        }
        image.setRGB(0, 0, 50, 50, redPixels, 0, 50);
        initializeKeys();
    }

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

    //sets up Key Bindings
    private void initializeKeys() {
        final String W = "W",
                     A = "A", 
                     S = "S", 
                     D = "D",
                     PRESSED = "PRESSED",
                     RELEASED = "RELEASED";

        InputMap inputMap = this.getInputMap(JPanel.WHEN_IN_FOCUSED_WINDOW);
        ActionMap actionMap = this.getActionMap();

        inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, false), W + PRESSED);
        inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0, false), A + PRESSED);
        inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0, false), S + PRESSED);
        inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0, false), D + PRESSED);
        inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, true), W + RELEASED);
        inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0, true), A + RELEASED);
        inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0, true), S + RELEASED);
        inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0, true), D + RELEASED);

        Action wActionPressed = new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                wKey = true;
            }
        };
        Action aActionPressed = new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                aKey = true;
            }
        };
        Action sActionPressed = new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                sKey = true;
            }
        };
        Action dActionPressed = new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                dKey = true;
            }
        };
        Action wActionReleased = new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                wKey = false;
            }
        };
        Action aActionReleased = new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                aKey = false;
            }
        };
        Action sActionReleased = new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                sKey = false;
            }
        };
        Action dActionReleased = new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                dKey = false;
            }
        };

        actionMap.put(W + PRESSED, wActionPressed);
        actionMap.put(A + PRESSED, aActionPressed);
        actionMap.put(S + PRESSED, sActionPressed);
        actionMap.put(D + PRESSED, dActionPressed);
        actionMap.put(W + RELEASED, wActionReleased);
        actionMap.put(A + RELEASED, aActionReleased);
        actionMap.put(S + RELEASED, sActionReleased);
        actionMap.put(D + RELEASED, dActionReleased);
    }

    public void loop() {
        if (wKey) yPos -= 5;
        if (aKey) xPos -= 5;
        if (sKey) yPos += 5;
        if (dKey) xPos += 5;
        repaint();
    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        g.drawImage(image, xPos, yPos, null);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                ProblemExample example = new ProblemExample();
                Timer timer = new Timer(60, new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        example.loop();
                    }
                });
                timer.start();
            }
        });
    }

}

1
看起来用户说问题出在了他的Mac上 - “这与我的Mac有关,不管怎样还是谢谢。”- 用户5637682于12月13日12:21 - anacron
没有你的 [mcve],别人怎么能够重现你的结果呢? - trashgod
你使用的是哪种外观?你可以尝试更改它,看看是否解决了你的问题。 - Jayfray
我没有改变LAF,所以我想我正在使用默认设置。 - kneedhelp
1
使用“defaults write -g ApplePressAndHoldEnabled -bool false”命令可以解决这个问题。看起来非常像是操作系统相关的错误。 - teppic
显示剩余5条评论
2个回答

6
您可能认为这是MAC的一个错误,但实际上不是。因为MAC有二次键功能。
作为MAC的用户,我希望您知道这个功能。这个功能可能是您问题的原因。
MAC的二次键功能:按住一个字母键将显示该字母的变体,例如按住“u”以获取“ü”。在拼写非英语单词时很方便。

有一种简单的方法可以控制并更改长按键的行为以适应您的需求。
打开终端应用程序并输入:
defaults write NSGlobalDomain ApplePressAndHoldEnabled -bool false

然后重新启动您希望激活此设置的任何打开的应用程序。 恢复原状:只需将false替换为true,并将其添加到先前的命令中,如下所示:
defaults write NSGlobalDomain ApplePressAndHoldEnabled -bool true

更新:

提示:
您可以在“系统偏好设置”中,在键盘选项下进行更改,以加快按键重复速率或减少按住键后开始重复的延迟时间。


如果我发布此应用程序并且一些用户在他们的 Mac 上下载了它,这对他们会有问题吗?如果有问题,我能为他们禁用此功能吗? - kneedhelp
1
@kneedhelp 是的,你可以这样做。使用java.lang.Runtime.exec()将设置更改为false,然后当您的应用程序退出时,将设置更改回原始值(true)。如果您不知道如何使用它,请参考我的答案https://dev59.com/VFgR5IYBdhLWcg3ww_ze#41140860。 - Tahir Hussain Mir

1
除了Tahir Hussain Mir建议更改Mac键设置之外,我发现问题在于循环,以事件驱动的方式实现会更有效率,我稍微修改了您的代码。结合Hussain Mir的解决方案,它应该能解决您的问题。
您也可以自己处理按键重复,例如,在按键按下时启动计时器,在按键释放时停止计时器,但是这样做会导致Windows和Mac之间的按键按下行为不同,这并不是您想要的方式。
package swing;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;

//an example of the problem where the keyboard stops receiving input randomly
public class SOMacKeyBindings extends JPanel
{
    private BufferedImage image; //sprite
    private Point point = new Point(200, 200);
    private int steps = 5;

    private class KeyAction extends AbstractAction
    {
        private Runnable runnable;

        public KeyAction(Runnable runnable)
        {
            this.runnable = runnable;
        }

        @Override
        public void actionPerformed(ActionEvent e)
        {
            runnable.run();
        }
    }

    public SOMacKeyBindings()
    {
        JFrame frame = new JFrame();
        frame.add(this);
        frame.pack();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setResizable(false);
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);

        //makes the sprite a red square
        image = new BufferedImage(50, 50, BufferedImage.TYPE_INT_ARGB);
        int[] redPixels = new int[50 * 50];
        for (int i = 0; i < redPixels.length; i++)
        {
            redPixels[i] = 0xffff0000;
        }
        image.setRGB(0, 0, 50, 50, redPixels, 0, 50);
        initializeKeys();
    }

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

    //sets up Key Bindings
    private void initializeKeys()
    {
        final String W = "W",
                A = "A",
                S = "S",
                D = "D",
                PRESSED = "PRESSED";

        InputMap inputMap = this.getInputMap(JPanel.WHEN_IN_FOCUSED_WINDOW);
        ActionMap actionMap = this.getActionMap();

        inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, false), W + PRESSED);
        inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0, false), A + PRESSED);
        inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0, false), S + PRESSED);
        inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0, false), D + PRESSED);

        actionMap.put(W + PRESSED, new KeyAction(() -> { point.y -= steps; repaint(); }));
        actionMap.put(A + PRESSED, new KeyAction(() -> { point.x -= steps; repaint(); }));
        actionMap.put(S + PRESSED, new KeyAction(() -> { point.y += steps; repaint(); }));
        actionMap.put(D + PRESSED, new KeyAction(() -> { point.x += steps; repaint(); }));
    }

    @Override
    public void paintComponent(Graphics g)
    {
        super.paintComponent(g);
        g.drawImage(image, point.x, point.y, null);
    }

    public static void main(String[] args)
    {
        SwingUtilities.invokeLater(() -> new SOMacKeyBindings());
    }
}

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