代码之家  ›  专栏  ›  技术社区  ›  Poehli

linux上的java keylistener

  •  0
  • Poehli  · 技术社区  · 10 年前

    我正在尝试在Linux上用java3d编写游戏,为此我需要一个合适的KeyListener。 你们中有人知道怎么做吗?我目前正在使用以下代码,我在网上找到了。它工作得很好,只需按下一个键,但只要我按下多个键(如空格和w),它就会发生意想不到的事情。。。

    public class RepeatingReleasedEventsFixer implements AWTEventListener {
    
        private final HashMap<Integer, ReleasedAction> _map = new HashMap<Integer, ReleasedAction>();
    
        public void install() {
            Toolkit.getDefaultToolkit().addAWTEventListener(this, AWTEvent.KEY_EVENT_MASK);
        }
    
        public void remove() {
            Toolkit.getDefaultToolkit().removeAWTEventListener(this);
        }
    
        @Override
        public void eventDispatched(AWTEvent event) {
            assert event instanceof KeyEvent : "Shall only listen to KeyEvents, so no other events shall come here";
            assert assertEDT(); // REMEMBER THAT THIS IS SINGLE THREADED, so no need for synch.
    
            // ?: Is this one of our synthetic RELEASED events?
            if (event instanceof Reposted) {
                // -> Yes, so we shalln't process it again.
                return;
            }
    
            // ?: KEY_TYPED event? (We're only interested in KEY_PRESSED and KEY_RELEASED).
            if (event.getID() == KeyEvent.KEY_TYPED) {
                // -> Yes, TYPED, don't process.
                return;
            }
    
            final KeyEvent keyEvent = (KeyEvent) event;
    
            // ?: Is this already consumed?
            // (Note how events are passed on to all AWTEventListeners even though a previous one consumed it)
            if (keyEvent.isConsumed()) {
                return;
            }
    
            // ?: Is this RELEASED? (the problem we're trying to fix!)
            if (keyEvent.getID() == KeyEvent.KEY_RELEASED) {
                // -> Yes, so stick in wait
                /**
                 * Really just wait until "immediately", as the point is that the subsequent PRESSED shall already have been
                 * posted on the event queue, and shall thus be the direct next event no matter which events are posted
                 * afterwards. The code with the ReleasedAction handles if the Timer thread actually fires the action due to
                 * lags, by cancelling the action itself upon the PRESSED.
                 */
                final Timer timer = new Timer(2, null);
                ReleasedAction action = new ReleasedAction(keyEvent, timer);
                timer.addActionListener(action);
                timer.start();
    
                _map.put(Integer.valueOf(keyEvent.getKeyCode()), action);
    
                // Consume the original
                keyEvent.consume();
            }
            else if (keyEvent.getID() == KeyEvent.KEY_PRESSED) {
                // Remember that this is single threaded (EDT), so we can't have races.
                ReleasedAction action = _map.remove(Integer.valueOf(keyEvent.getKeyCode()));
                // ?: Do we have a corresponding RELEASED waiting?
                if (action != null) {
                    // -> Yes, so dump it
                    action.cancel();
                }
                // System.out.println("PRESSED: [" + keyEvent + "]");
            }
            else {
                throw new AssertionError("All IDs should be covered.");
            }
        }
    
        /**
         * The ActionListener that posts the RELEASED {@link RepostedKeyEvent} if the {@link Timer} times out (and hence the
         * repeat-action was over).
         */
        private class ReleasedAction implements ActionListener {
    
            private final KeyEvent _originalKeyEvent;
            private Timer _timer;
    
            ReleasedAction(KeyEvent originalReleased, Timer timer) {
                _timer = timer;
                _originalKeyEvent = originalReleased;
            }
    
            void cancel() {
                assert assertEDT();
                _timer.stop();
                _timer = null;
                _map.remove(Integer.valueOf(_originalKeyEvent.getKeyCode()));
            }
    
            @Override
            public void actionPerformed(@SuppressWarnings ("unused") ActionEvent e) {
                assert assertEDT();
                // ?: Are we already cancelled?
                // (Judging by Timer and TimerQueue code, we can theoretically be raced to be posted onto EDT by TimerQueue,
                // due to some lag, unfair scheduling)
                if (_timer == null) {
                    // -> Yes, so don't post the new RELEASED event.
                    return;
                }
                // Stop Timer and clean.
                cancel();
                // Creating new KeyEvent (we've consumed the original).
                KeyEvent newEvent = new RepostedKeyEvent((Component) _originalKeyEvent.getSource(),
                        _originalKeyEvent.getID(), _originalKeyEvent.getWhen(), _originalKeyEvent.getModifiers(),
                        _originalKeyEvent.getKeyCode(), _originalKeyEvent.getKeyChar(), _originalKeyEvent.getKeyLocation());
                // Posting to EventQueue.
                Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(newEvent);
                // System.out.println("Posted synthetic RELEASED [" + newEvent + "].");
            }
        }
    
        /**
         * Marker interface that denotes that the {@link KeyEvent} in question is reposted from some
         * {@link AWTEventListener}, including this. It denotes that the event shall not be "hack processed" by this class
         * again. (The problem is that it is not possible to state "inject this event from this point in the pipeline" - one
         * have to inject it to the event queue directly, thus it will come through this {@link AWTEventListener} too.
         */
        public interface Reposted {
            // marker
        }
    
        /**
         * Dead simple extension of {@link KeyEvent} that implements {@link Reposted}.
         */
        public static class RepostedKeyEvent extends KeyEvent implements Reposted {
            public RepostedKeyEvent(@SuppressWarnings ("hiding") Component source, @SuppressWarnings ("hiding") int id,
                    long when, int modifiers, int keyCode, char keyChar, int keyLocation) {
                super(source, id, when, modifiers, keyCode, keyChar, keyLocation);
            }
        }
    
        private static boolean assertEDT() {
            if (!EventQueue.isDispatchThread()) {
                throw new AssertionError("Not EDT, but [" + Thread.currentThread() + "].");
            }
            return true;
        }
    }
    

    我不可能是唯一一个仍然遇到这个问题的人——同时也是15岁——不想使用计时器。。。

    编辑:这段代码所做的是修复任何Linux发行版上的已知问题,其中添加了一个简单的KeyListener,它处理keyDowns,但重复调用keyReleasedEvent。为了澄清我的问题,这里有一个简单的例子

    import java.awt.event.KeyEvent;
    import java.awt.event.KeyListener;
    
    import javax.swing.JFrame;
    
    public class Test5 extends JFrame{
    
        public Test5() {
            addKeyListener(new KeyListener() {
                boolean keydown = false;
                @Override
                public void keyTyped(KeyEvent arg0) {
                    // TODO Auto-generated method stub
    
                }
    
                @Override
                public void keyReleased(KeyEvent arg0) {
                    keydown = false;
                    System.out.println("keyup");
                }
    
                @Override
                public void keyPressed(KeyEvent arg0) {
                    if (keydown){
                        System.out.println("key is down");
                    } else {
                        System.out.println("key not down");
                    }
                    keydown = true;
                }
            });
    
            setDefaultCloseOperation(EXIT_ON_CLOSE);
            setSize(400, 400);
            setVisible(true);
            //new RepeatingReleasedEventsFixer().install(); // This line will fix it for one key pressed
        }
        public static void main(String[] args) {
            new Test5();
        }
    
    }
    

    未注释行的输出:

    key not down
    keyup
    key not down
    keyup
    key not down
    keyup
    key not down
    keyup
    key not down
    keyup
    

    否则:

    key not down
    key is down
    key is down
    key is down
    key is down
    key is down
    key is down
    key is down
    key is down
    key is down
    keyup
    

    顺便问一下,为什么现在还没有修好?

    编辑: 我按照建议尝试了KeyBindings,以解决这些问题:

    public class Test5 extends JFrame{
        long timestamp = 0;
        public Test5() {
            ((JComponent)getComponent(0)).getInputMap().put(KeyStroke.getKeyStroke('a'), "a");
            ((JComponent)getComponent(0)).getActionMap().put("a", new AbstractAction() {
    
                @Override
                public void actionPerformed(ActionEvent e) {
                    System.out.println("time: "+(System.currentTimeMillis()-timestamp));
                    timestamp = System.currentTimeMillis();
                }
            });
    
            ((JComponent)getComponent(0)).getInputMap().put(KeyStroke.getKeyStroke('s'), "s");
            ((JComponent)getComponent(0)).getActionMap().put("s", new AbstractAction() {
    
                @Override
                public void actionPerformed(ActionEvent arg0) {
                    System.out.println("s");
                }
            });
    
            ((JComponent)getComponent(0)).getInputMap().put(KeyStroke.getKeyStroke('d'), "d");
            ((JComponent)getComponent(0)).getActionMap().put("d", new AbstractAction() {
    
                @Override
                public void actionPerformed(ActionEvent arg0) {
                    System.out.println("d");
                }
            });
            setDefaultCloseOperation(EXIT_ON_CLOSE);
            setSize(400, 400);
            setVisible(true);
            new RepeatingReleasedEventsFixer().install(); // This line will fix it for one key pressed
        }
    
        /**
         * @param args
         */
        public static void main(String[] args) {
            new Test5();
        }
    

    按住“a”将给出以下输出:

    time: 4171
    time: 501
    time: 30
    time: 30
    time: 30
    

    第二次是真正的问题。这需要大约470毫秒的时间。
    按住“s”,然后按下“d”将给出输出:

    s
    s
    s
    s
    d
    d
    d
    d
    d
    

    所以我不能同时处理两个操作,所以我不能使用KeyBindings

    2 回复  |  直到 10 年前
        1
  •  1
  •   Community CDub    7 年前

    这不是一个答案,而是一个带图片和一些解释的长评论。

    我用了你的 Test5 (无 RepeatingReleasedEventsFixer )压制 并测量时间响应。输出的形式为

    time: t1
    time: t2
    time: t3
    time: t3
    time: t3
    ...
    

    时间t1 因为它取决于当前时间,与响应时间无关(您似乎也忽略了它)。

    时间2 是操作系统意识到您在为重复输入而按键所需的时间。

    第三天 是保持键的“采样时间”,或输入的离散化。

    我使用的Windows具有以下控制面板选项:

    enter image description here

    重复延迟 允许我设置 时间2 介于约257(短)和约1050(长)之间。

    重复率 允许我设置 第三天 介于约407(慢)和约37(快)之间。

    对于Linux,如果您还不知道如何更改这些值,则必须咨询某人/某处。

    关于使用多个键,请参见 this question and answer 以及其中的优秀链接(尤其是“按下多个键的运动”部分)。这是一个简短的教程,对密钥绑定和密钥侦听器进行了分析,类似于我在本网站上向您发送的教程。

    键绑定总是优先于键侦听器,除非您想做一些非常低级的事情。

        2
  •  0
  •   Poehli    10 年前

    经过几天的研究和整理,我最终编写了自己的Listener,并结合了KeyEventDispatcher,下面是遇到同样问题的人的代码。它可以也应该被优化,但目前正在发挥作用:

    Klass测试是否按下了特定键:

    import java.awt.KeyEventDispatcher;
    import java.awt.KeyboardFocusManager;
    import java.awt.event.KeyEvent;
    import java.util.HashMap;
    
    
    public class IsKeyPressed {
        private static boolean wPressed = false;
        private HashMap<Integer, Boolean> keys = new HashMap<Integer, Boolean>();
        public IsKeyPressed() {
            KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(new KeyEventDispatcher() {
    
                @Override
                public boolean dispatchKeyEvent(KeyEvent ke) {
                    synchronized (IsKeyPressed.class) {
                        switch (ke.getID()) {
                        case KeyEvent.KEY_PRESSED:
                            keys.put(ke.getKeyCode(), true);
                            break;
    
                        case KeyEvent.KEY_RELEASED:
                            keys.put(ke.getKeyCode(), false);
                            break;
                        }
                        return false;
                    }
                }
            });
        }
        public static boolean isWPressed() {
            synchronized (IsKeyPressed.class) {
                return wPressed;
            }
        }
    
        public boolean isPressed(int keyCode){
            synchronized (IsKeyPressed.class) {
                if (keys == null)
                    return false;
                if (keys.get(keyCode) == null)
                    return false;
                return keys.get(keyCode);
            }
        }
    
    }
    

    抽象类,用于动作。

    public abstract class KeyActionListener {
        protected int keyCode;
        public KeyActionListener(int keyCode) {
            this.keyCode = keyCode;
        }
        public void setKeyCode(int keyCode){
            this.keyCode = keyCode;
        }
        public int getKeyCode(){
            return this.keyCode;
        }
        public abstract void onKeyDown();
        public abstract void onKeyUp();
        public abstract void onKeyHolding();
    }
    

    开始聆听按键并执行操作。

    import java.util.ArrayList;
    import java.util.HashMap;
    
    public class KeyThread extends Thread{
        private int sleep = 3;
        ArrayList<KeyActionListener> listener = new ArrayList<KeyActionListener>();
        IsKeyPressed isPressed = new IsKeyPressed();
        HashMap<KeyActionListener, Boolean> pressed = new HashMap<KeyActionListener, Boolean>();
        public KeyThread() {
            this.start();
        }
        public void run() {
            while (true){
                for (int i = 0; i < listener.size(); i++) {
                    KeyActionListener curListener = listener.get(i);
                    if (isPressed.isPressed(curListener.getKeyCode()) && !pressed.get(curListener)){
                        curListener.onKeyDown();
                        pressed.put(curListener, true);
                    } else if(!isPressed.isPressed(curListener.getKeyCode()) && pressed.get(curListener)) {
                        curListener.onKeyUp();
                        pressed.put(curListener, false);
                    } 
    
                    if(isPressed.isPressed(curListener.getKeyCode())){
                        curListener.onKeyHolding();
                    }
                    try{
                        Thread.sleep(sleep);
                    } catch(InterruptedException e){
    
                    }
                }
            }
        }
    
        public void addKeyActionListener(KeyActionListener l){
            listener.add(l);
            pressed.put(l, false);
        }
    
    }