我有一个程序,用户可以按一个键来执行操作。那个事件花了很少的时间。用户还可以按住该键并连续多次执行操作。问题是keyPress()事件排队的速度比处理事件的速度快。这意味着在用户释放密钥后,事件将继续处理,这些事件是从先前按住密钥的用户排队的。我还注意到,直到最后的keyPress事件被处理之后才会发生keyRelease事件,无论密钥何时被实际释放。我希望能够 1.检测密钥释放事件并忽略将来的keyPress事件,直到用户实际再次按下该密钥。 2.在第一个完成后,不执行后续的keyPress事件,然后检测何时未按下该键,然后停止。
有谁知道怎么做?
答案 0 :(得分: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组件。任何不在Component上调用方法的计算,处理等都可以在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);
}
}
答案 1 :(得分:1)
我还注意到,直到最后的keyPress事件被处理之后才会发生keyRelease事件,无论密钥何时被实际释放
这取决于您使用的操作系统。这是Windows上的行为(这对我来说很有意义)。在Unix或Mac上,我相信你会得到多个keyPressed,keyReleased事件。所以你的解决方案不应该基于keyReleased事件。
我有一个程序,用户可以按一个键来执行操作。
然后你应该使用Key Binding,而不是KeyListener。有关详细信息,请阅读How to Use Key Bindings上的Swing教程中的部分。
当调用Action时,您可以禁用它。我不确定这是否会阻止KeyStroke再次运行,或者您是否仍需要检查Action的启用状态。然后,当Action代码执行完毕后,您可以重新启用Action。
此外,这个长时间运行的代码不应该在EDT上执行。阅读Swing教程关于并发的部分,了解有关此内容和解决方案的更多信息。
答案 2 :(得分:0)
您必须使用选项1.一旦启动较长的进程,请设置一段时间的布尔值以指示您正在处理它并丢弃其他传入的相同请求。完成该过程后,将布尔值设置回来并允许其他事件。