Java KeyListener 停止工作

标签 java swing canvas keylistener

- = 更新 = -

事实证明,问题不在于 Java,而在于我的 Apple 键盘。按住字母键会弹出一个菜单,该菜单会破坏我的 Java 程序。通过禁用该菜单弹出窗口,我的 KeyListener 和我的键绑定(bind)都可以正常工作。谢谢大家的回答。

问题

我在 Google 和 StackOverflow 上搜索了我的问题的答案,但没有结果。我发现的所有问题都有扩展 JComponent、JFrame、JPanel 等的主类,而不是 Canvas。

现在回答我的问题:

我在程序运行时无法让 Java KeyListener 配合。当我启动程序时,一切都正常。然而,当我开始按键并移动物体(用所述按键)时,程序开始延迟并需要更多时间来注册按键。突然间,它们的 KeyListener 完全中断,我没有任何输入(甚至 keyPressed 方法中的 System.out.println 语句也没有显示任何 Activity )。我有三个类与我的 KeyListener 以任何方式相关。

如果有帮助的话,该程序的目标是使用 BufferedImage 类来绘制来自不同数学函数的点,例如正弦波。我已尽我所能进行评论,但我可以尽我所能澄清任何代码的目的。

首先,我的 Screen 类(使用 BufferStrategy 在 JFrame 上绘制内容):

package com.elek.waves.graphics;

import java.awt.Canvas;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;

import javax.swing.JFrame;

import com.elek.waves.graphics.math.Controller;
import com.elek.waves.graphics.math.Graph;
import com.elek.waves.input.Keyboard;

/**
 * The engine of the entire Waves project. Processes the BufferedImage and puts the JFrame
 * on the screen. Uses other classes to calculate where to put pixels (what color each pixel
 * in the array should be) and get keyboard input.
 * 
 * @author my name
 * @version 1.0
 */
public class Screen extends Canvas {
    /**
     * Holds some *important* number that makes Java happy.
     */
    private static final long serialVersionUID = 1L;

    /**
     * Constant (and static) dimensions of the window.
     */
    public static final int WIDTH = 800, HEIGHT = 800;

    /**
     * Frame that will contain the BufferedImage and all its glory.
     */
    private JFrame frame;

    /**
     * BufferedImage processes the pixel array and translates it into fancy screen magic.
     */
    private BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);

    /**
     * Holds color data for each pixel on the screen. Each pixel has an integer value equivalent
     * to the hexadecimal RGB value for whatever color this pixel is supposed to be.
     */
    private int[] pixels = ((DataBufferInt)image.getRaster().getDataBuffer()).getData();

    /**
     * Graph object to draw the lines on.
     */
    private Graph graph;

    /**
     * Controller object to control the graph.
     */
    private Controller controller;

    /**
     * Keybaord object to use as a key-listener.
     */
    private Keyboard key;

    /* -- Constructor -- */

    /**
     * Creates a new Screen object. Initializes the JFrame object.
     */
    public Screen() {
        frame = new JFrame("Magic!");

        graph = new Graph(pixels);
        key = new Keyboard();

        controller = new Controller(key, graph);

        addKeyListener(key);
    }

    /* -- Methods -- */

    /**
     * Called once and only once by the main method. Repeatedly calls the update and render methods
     * until the program stops running.
     */
    private void start() {
        this.requestFocus();
        this.requestFocusInWindow();

        while (true) {
            update();
            render();
        }
    }

    /**
     * Called by the start method repeatedly. First, clears the screen of the previous image in
     * order to prevent ghost-imaging or blurring. Then, updates the pixel array to whatever it
     * needs to be for the next iteration of the render method.
     */
    private void update() {
        // Update the keyboard input
        key.update();

        // Update the controller
        controller.update();

        // Clean up the screen and then graph the line
        clearScreen();
        graph.drawWave();
    }

    /**
     * Called by the start method repeatedly. Draws the pixel array onto the JFrame using the
     * BufferedImage magic.
     */
    private void render() {
        // Initialize buffer strategies
        BufferStrategy bs = getBufferStrategy();

        if (bs == null) {
            createBufferStrategy(2);
            return;
        }

        // Physically update the actual pixels on the image
        Graphics g = (Graphics2D) bs.getDrawGraphics();
        g.drawImage(image, 0, 0, getWidth(), getHeight(), null);
        g.dispose();
        bs.show();
    }

    /**
     * Clears the screen by setting every pixel in the pixel array to black. Used to prevent
     * ghost-images or blurring.
     */
    public void clearScreen() {
        for (int i = 0; i < pixels.length; i++)
            pixels[i] = 0;
    }

    /**
     * Main method to run the program. Creates a Screen object with a BufferedImage to display
     * pixels however the other classes decide to. All this does is set up the JFrame with the
     * proper parameters and properties to get it up and running.
     * 
     * @param   args    A String array of random arguments that Java requires or it gets fussy
     */
    public static void main(String[] args) {
        // Create Screen object
        Screen screen = new Screen();

        screen.frame.add(screen);
        screen.frame.pack();
        screen.frame.setSize(WIDTH, HEIGHT);
        screen.frame.setLocationRelativeTo(null);
        screen.frame.setResizable(false);
        screen.frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        screen.frame.setVisible(true);

        screen.start();
    }
}

第二,我的键盘类(损坏的 KeyListener):

package com.elek.waves.input;

import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;

/**
 * Gets the user's key strokes and determines which keys are down at a given time.
 * 
 * @author my name
 * @version 1.0
 */
public class Keyboard implements KeyListener {
    /**
     * Holds the state of 120 keys (true if they're down, false if they're not).
     */
    private boolean[] keys = new boolean[120];

    /**
     * Holds the state of the "useful" keys (true if down, false if not).
     */
    public boolean w, a, s, d, up, down, left, right;

    /**
     * Determines if the "useful" keys are down or not. Sets the variables to true if they're down and
     * false if they're up.
     */
    public void update() {
        w = keys[KeyEvent.VK_W];
        a = keys[KeyEvent.VK_A];
        s = keys[KeyEvent.VK_S];
        d = keys[KeyEvent.VK_D];

        up = keys[KeyEvent.VK_UP];
        down = keys[KeyEvent.VK_DOWN];
        left = keys[KeyEvent.VK_LEFT];
        right = keys[KeyEvent.VK_RIGHT];
    }

    /**
     * Changes the state of the pressed key's corresponding boolean in the array to true.
     */
    public void keyPressed(KeyEvent e) {
        keys[e.getKeyCode()] = true;
    }

    /**
     * Changes the state of the pressed key's corresponding boolean in the array to false.
     */
    public void keyReleased(KeyEvent e) {
        keys[e.getKeyCode()] = false;
    }

    public void keyTyped(KeyEvent e) {
    }
}

三,我的Controller类(使用KeyListener来控制程序):

package com.elek.waves.graphics.math;

import com.elek.waves.input.Keyboard;

/**
 * Controls the graph's transformation properties (stretching and shifting). Directly changes the
 * transformation variables in the Graph class to achieve this.
 * 
 * @author my name
 * @version 1.0
 */
public class Controller {
    /**
     * Keyboard object to get the user's key-inputs.
     */
    private Keyboard input;

    /**
     * Graph object that this Controller will control.
     */
    private Graph graph;

    /* -- Constructor -- */

    /**
     * Create a new Controller object with the specific keyboard input parameter. 
     * <pre>Sets the starting parameters as the following:
     * Vertical Scale: 1
     * Horizontal Scale: 1
     * Vertical Shift = 0
     * Horizontal Shift = 0</pre>
     * 
     * @param   input   The Keybaord object from which the controller will get input
     */
    public Controller(Keyboard input, Graph parent) {
        // Initialize keybaord input and graph parent
        this.input = input;
        graph = parent;

        // Initialize transformation variables
        graph.vScale = 50;
        graph.hScale = 0.05;
        graph.vShift = 0;
        graph.hShift = 0;
    }

    /* -- Methods -- */

    /**
     * Updates the shifting of the graph (moving around) and the scaling of the graph (stretching)
     * from the keyboard input. <strong>WASD</strong> keys control shifting, and <strong>up, down, 
     * left, and right</strong> keys control stretching.
     */
    public void update() {
        // Update shifting
        if (input.w)        graph.vShift += 0.5;
        if (input.s)        graph.vShift -= 0.5;
        if (input.a)        graph.hShift -= 0.04;
        if (input.d)        graph.hShift += 0.04;

        // Update scaling
        if (input.up)       graph.vScale += 0.5;
        if (input.down)     graph.vScale -= 0.5;
        if (input.left)     graph.hScale += 0.0001;
        if (input.right)    graph.hScale -= 0.0001;
    }
}

我发现一些有用的人说使用 KeyBindings 而不是 KeyListener。不过,我过去曾成功使用过 KeyListener,如果可能的话,我想让它再次工作。如果 KeyBindings 是绝对必要的,我想我可以进行切换,但如果不是必须如此,我更愿意。

先谢谢大家了!

最佳答案

Canvas 会遇到与所有其他组件相同的问题,即失去键盘焦点,这就是为什么我们通常不推荐 KeyListener

首先,您需要使 Canvas 可聚焦,请参阅 Canvas#setFocusable

下一个更困难的问题是请求键盘焦点,您可以使用 Canvas#requestFocusInWindow但任何需要键盘焦点的组件都会窃取它。

根据您正在执行的操作,您也许可以简单地将调用放入更新循环中,但您需要注意,如果您想在同一窗口内询问用户输入,则会遇到问题( Canvas 抢走了焦点)

更新

由于在键盘 Controller 中使用数组,我在边界索引方面遇到了一些问题,我将其切换到Set...

public class Keyboard implements KeyListener {

    /**
     * Holds the state of 120 keys (true if they're down, false if they're
     * not).
     */

//private boolean[] keys = new boolean[120];

    /**
     * Holds the state of the "useful" keys (true if down, false if not).
     */
    private Set<Integer> keys;

    /**
     * Determines if the "useful" keys are down or not. Sets the variables
     * to true if they're down and false if they're up.
     */
    public void update() {
        
        keys = new HashSet<>(8);
    }
    
    public boolean isKeyPressed(int key) {
        return keys.contains(key);
    }
    
    public boolean isWPressed() {
        return isKeyPressed(KeyEvent.VK_W);
    }
    
    public boolean isAPressed() {
        return isKeyPressed(KeyEvent.VK_A);
    }
    
    public boolean isSPressed() {
        return isKeyPressed(KeyEvent.VK_S);
    }
    
    public boolean isDPressed() {
        return isKeyPressed(KeyEvent.VK_D);
    }

    public boolean isUpPressed() {
        return isKeyPressed(KeyEvent.VK_UP);
    }
    
    public boolean isDownPressed() {
        return isKeyPressed(KeyEvent.VK_DOWN);
    }
    
    public boolean isLeftPressed() {
        return isKeyPressed(KeyEvent.VK_LEFT);
    }
    
    public boolean isRightPressed() {
        return isKeyPressed(KeyEvent.VK_RIGHT);
    }
    /**
     * Changes the state of the pressed key's corresponding boolean in the
     * array to true.
     */
    public void keyPressed(KeyEvent e) {
        System.out.println("Pressed = " + e.getKeyCode());
        keys.add(e.getKeyCode());
    }

    /**
     * Changes the state of the pressed key's corresponding boolean in the
     * array to false.
     */
    public void keyReleased(KeyEvent e) {
        System.out.println("Released = " + e.getKeyCode());
        keys.remove(e.getKeyCode());
    }

    public void keyTyped(KeyEvent e) {
    }
}

我还在渲染循环中添加了一个小的延迟,这样就不会阻塞系统

private void start() {
    setFocusable(true);
    while (true) {
        this.requestFocusInWindow();
        update();
        render();
        try {
            Thread.sleep(16);
        } catch (InterruptedException ex) {
        }
    }
}

关于Java KeyListener 停止工作,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42660896/

相关文章:

Java 转义字符替换为空白

java - 从 JOptionPane 创建对话框并处理 OK_CANCEL_OPTION

jquery - 在 Canvas 中上传的图像拖动,可触摸和可在 html5 中旋转

javascript - 如何预览线条工具?

javascript - 分别绘制每个像素 WebGL

java - 通过ajax发送JSON并在JSP中通过请求获取参数

java - 一般来说,我应该编写节省内存的代码还是节省智能手机应用程序简单对象处理时间的代码?

java - 子类中的 equals 方法

java - BorderLayout 显示边框线

java - JComboBox 在 JTable TableHeader 中展开失败