我有一个程序,用户可以在其中按下一个键来执行一个操作。那一个事件需要很短的时间。用户还可以按住该键并连续多次执行该操作。问题是 keyPress() 事件排队的速度比处理事件的速度要快。这意味着在用户释放键后,继续处理从用户之前按住键的队列中排队的事件。我还注意到 keyRelease 事件直到最后的 keyPress 事件被处理后才会发生,而不管键的实际释放时间。我希望能够 1. 检测按键释放事件并忽略未来的按键事件,直到用户再次真正按下按键。2.
有谁知道如何做到这一点?
免责声明:我感觉不舒服,所以这段代码很可怕,好像……它也病了。
我想要发生的事情:访问 DirectInput 以获取键盘状态,而不是事件。不过,这远远超出了这个问题的范围。所以我们会保持我们自己的动作状态。
您遇到的问题是您正在 UI 线程中执行您的操作。您需要生成一个工作线程并忽略后续事件,直到您的操作完成。
在我给出的示例中,当按下或按住字母“a”时,我开始了一个新动作。在第一个动作完成之前,它不会产生另一个动作。该操作会更新表单上的标签,显示在完成之前还剩下多少个“循环”。
还有另一个标签显示到目前为止发生了多少操作。
产生一个新动作
重要的部分是让所有 UI 关键事件发生,而不是阻塞在 UI 线程中导致它们排队。
public void keyPressed(KeyEvent e) {
char keyChar = e.getKeyChar();
System.out.println("KeyChar: " + keyChar);
// Press a to start an Action
if (keyChar == 'a') {
if (!mAction.isRunning()) {
mTotalActions.setText("Ran " + (++mTotalActionsRan) + " actions.");
System.out.println("Starting new Action");
Thread thread = new Thread(new Runnable() {
public void run() {
mAction.run();
}
});
thread.start();
}
}
}
UI 线程的更新
如果您的操作对用户界面执行任何类型的更新,则需要使用该SwingUtilities.invokeLater
方法。此方法将排队您的代码以在 UI 线程中运行。您不能在 UI 线程以外的线程中修改用户界面。此外,仅使用 SwingUtilities 更新 UI 组件。任何不调用组件上的方法的计算、处理等都可以在 SwingUtilities.invokeLater 的范围之外完成。
完整代码清单
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package stackoverflow_4589538;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;
public class Main extends JFrame {
private JLabel mActionLabel;
private JLabel mTotalActions;
private int mTotalActionsRan;
private class MyAction {
private boolean mIsRunning = false;
public void run() {
// Make up a random wait cycle time
final int cycles = new Random().nextInt(100);
for (int i = 0; i < cycles; ++i) {
final int currentCycle = i;
try {
Thread.sleep(100);
} catch (InterruptedException ex) {
}
SwingUtilities.invokeLater(new Runnable() {
public void run() {
mActionLabel.setText("Cycle " + currentCycle + " of " + cycles);
}
});
}
completed();
}
public synchronized void start() {
mIsRunning = true;
}
public synchronized void completed() {
mIsRunning = false;
}
public synchronized boolean isRunning() {
return mIsRunning;
}
}
private MyAction mAction = new MyAction();
public Main() {
setLayout(null);
setBounds(40, 40, 800, 600);
setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
char keyChar = e.getKeyChar();
System.out.println("KeyChar: " + keyChar);
// Press A to start an Action
if (keyChar == 'a') {
if (!mAction.isRunning()) {
mTotalActions.setText("Ran " + (++mTotalActionsRan) + " actions.");
System.out.println("Starting new Action");
Thread thread = new Thread(new Runnable() {
public void run() {
mAction.run();
}
});
// I had this within the run() method before
// but realized that it is possible for another UI event
// to occur and spawn another Action before, start() had
// occured within the thread
mAction.start();
thread.start();
}
}
}
@Override
public void keyReleased(KeyEvent e) {
}
});
mActionLabel = new JLabel();
mActionLabel.setBounds(10, 10, 150, 40);
mTotalActions = new JLabel();
mTotalActions.setBounds(10, 50, 150, 40);
add(mActionLabel);
add(mTotalActions);
}
public static void main(String[] args) {
new Main().setVisible(true);
}
}
我还注意到 keyRelease 事件直到最后的 keyPress 事件被处理后才发生,无论何时实际释放键
这取决于您使用的操作系统。这是 Windows 上的行为(这对我来说很有意义)。在 Unix 或 Mac 上,我相信您会收到多个 keyPressed、keyReleased 事件。所以你的解决方案不应该基于 keyReleased 事件。
我有一个程序,用户可以在其中按下一个键来执行一个操作。
那么你应该使用 Key Binding,而不是 KeyListener。阅读 Swing 教程中有关如何使用键绑定的部分以获取更多信息。
调用 Action 时,您可以禁用它。我不确定这是否会阻止 KeyStroke 再次工作,或者您是否仍需要检查 Action 的启用状态。然后,当动作代码执行完毕后,您可以重新启用动作。
此外,这个长时间运行的代码不应在 EDT 上执行。阅读 Swing 教程中关于并发的部分,以获取有关此内容和解决方案的更多信息。
您将不得不选择选项 1。一旦您开始更长的流程,请设置一个布尔值一段时间以指示您正在处理它并丢弃其他传入的相同请求。完成该过程后,将布尔值设置回去并允许其他事件。