Swing的KeyListener和同时按下多个键

15

在Swing中,有没有一种传统的方法来跟踪两个键同时按下时的事件?我有几个想法,例如记住键和事件生成时间,以便在连续的事件处理程序调用中检查这两个事件之间的时间差,并决定是否为两个按钮事件。但看起来这很不完美。

3个回答

22

使用一个集合来记住当前按下的键,每次按键时检查是否有多个键被按下。

class MultiKeyPressListener implements KeyListener {
    // Set of currently pressed keys
    private final Set<Integer> pressedKeys = new HashSet<>();
        
    @Override
    public synchronized void keyPressed(KeyEvent e) {
        pressedKeys.add(e.getKeyCode());
        Point offset = new Point();
        if (!pressedKeys.isEmpty()) {
            for (Iterator<Integer> it = pressedKeys.iterator(); it.hasNext();) {
                switch (it.next()) {
                    case KeyEvent.VK_W:
                    case KeyEvent.VK_UP:
                        offset.y = -1;
                        break;
                    case KeyEvent.VK_A:
                    case KeyEvent.VK_LEFT:
                        offset.x = -1;
                        break;
                    case KeyEvent.VK_S:
                    case KeyEvent.VK_DOWN:
                        offset.y = 1;
                        break;
                    case KeyEvent.VK_D:
                    case KeyEvent.VK_RIGHT:
                        offset.x = 1;
                        break;
                }
            }
        }
        System.out.println(offset); // Do something with the offset.
    }
    
    @Override
    public synchronized void keyReleased(KeyEvent e) {
        pressedKeys.remove(e.getKeyCode());
    }
    
    @Override
    public void keyTyped(KeyEvent e) { /* Event not used */ }
}

2
抱歉,我不明白如何使用它。能否再给我一个例子?如何同时处理每个键? - Chu
自Java 1.7起,HashSet必须声明为:Set<Character> pressed = new HashSet<>(); - Ernestas Gruodis
@ErnestasGruodis 不是“必须”,你的意思是“可以”。缩短代码是可选的。无论如何,我已经更新了它并添加了一个实际的使用示例。 - Mr. Polywhirl

3
KeyListener接口允许分别检测按键按下和释放。因此,您可以维护一组“活动按键”,即已按下但尚未释放的按键。

1
@K_7:Set将强制实现项目的唯一性,并且具有更快的查找速度(例如,如果您使用HashSet)。 - Eyal Schneider
我已经猜到了。感谢确认! - Kartik Chugh

2
如果我尝试这样做(只是为了看看是否可能)七年后,其他人也可能做到...
下面的代码控制8个轴方向的移动,在注释中有解释。但基本上,KeyListener只定义了可以移动的位置,然后Thread将组合可能的目的地并移动JLabel。
package tests;

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

import javax.swing.JFrame;
import javax.swing.JLabel;

public class Move8Axis extends JFrame {

    private static final long serialVersionUID = 7722803326073073681L;

    private boolean left = false;
    private boolean up = false;
    private boolean down = false;
    private boolean right = false;

    private JLabel lbl = new JLabel("#");


    public Move8Axis() {
        // Just setting up the window and objects
        setSize(400, 400);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setVisible(true);
        lbl.setBounds(100, 100, 20, 20);
        add(lbl);
        setLocationRelativeTo(null);

        // Key listener, will not move the JLabel, just set where to
        addKeyListener(new KeyListener() {
            @Override
            public void keyTyped(KeyEvent e) {}
            @Override
            public void keyReleased(KeyEvent e) {
                if (e.getKeyCode() == KeyEvent.VK_LEFT) left = false;
                if (e.getKeyCode() == KeyEvent.VK_RIGHT) right = false;
                if (e.getKeyCode() == KeyEvent.VK_UP) up = false;
                if (e.getKeyCode() == KeyEvent.VK_DOWN) down = false;
            }
            @Override
            public void keyPressed(KeyEvent e) {
                if (e.getKeyCode() == KeyEvent.VK_LEFT) left = true;
                if (e.getKeyCode() == KeyEvent.VK_RIGHT) right = true;
                if (e.getKeyCode() == KeyEvent.VK_UP) up = true;
                if (e.getKeyCode() == KeyEvent.VK_DOWN) down = true;
            }
        });

        // This thread will read the 4 variables left/right/up/down at every 30 milliseconds
        // It will check the combination of keys (left and up, right and down, just left, just up...) 
        // And move the label 3 pixels
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {

                    while (true) {  
                        if (left && up) {
                            lbl.setBounds(lbl.getX() - 3, lbl.getY() - 3, 20, 20);
                        } else if (left && down) {
                            lbl.setBounds(lbl.getX() - 3, lbl.getY() + 3, 20, 20);
                        } else if (right && up) {
                            lbl.setBounds(lbl.getX() + 3, lbl.getY() - 3, 20, 20);
                        } else if (right && down) {
                            lbl.setBounds(lbl.getX() + 3, lbl.getY() + 3, 20, 20);
                        } else if (left) {
                            lbl.setBounds(lbl.getX() - 3, lbl.getY(), 20, 20);
                        } else if (up) {
                            lbl.setBounds(lbl.getX(), lbl.getY() - 3, 20, 20);
                        } else if (right) {
                            lbl.setBounds(lbl.getX() + 3, lbl.getY(), 20, 20);
                        } else if (down) {
                            lbl.setBounds(lbl.getX(), lbl.getY() + 3, 20, 20);
                        } 

                        Thread.sleep(30);
                    }

                } catch (Exception ex) {
                    ex.printStackTrace();
                    System.exit(0);
                }
            }
        }).start();
    }

    public static void main(String[] args) {
        new Move8Axis();
    }
}

与AWT / Swing组件的交互必须仅在事件分派线程中发生。因此,构造函数调用和标签更改不能在另一个线程中发生。为了更新标签,重复使用计时器也是非常适合的。 - Marcono1234

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