- = 更新 = -
事实证明,问题不在于 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/