Пропуск Java KeyEvents

У меня есть программа, в которой пользователь может нажать клавишу для выполнения действия. Это одно событие занимает небольшое количество времени. Пользователь также может удерживать эту клавишу и выполнять действие много раз подряд. Проблема в том, что события keyPress() ставятся в очередь быстрее, чем события могут быть обработаны. Это означает, что после того, как пользователь отпускает клавишу, продолжают обрабатываться события, которые были поставлены в очередь от пользователя, ранее удерживавшего клавишу. Я также заметил, что событие keyRelease не происходит до тех пор, пока не будет обработано последнее событие keyPress, независимо от того, когда клавиша была фактически выпущена. Я хотел бы иметь возможность: 1. Обнаружить событие отпускания клавиши и игнорировать будущие события keyPress до тех пор, пока пользователь снова не нажмет клавишу. 2. Не выполнять последующее событие keyPress до тех пор, пока первое не будет завершено, а затем определить, когда клавиша не нажата, и просто остановить.

Кто-нибудь знает как это сделать?


person Jon    schedule 04.01.2011    source источник


Ответы (3)


Отказ от ответственности: я плохо себя чувствую, поэтому этот код ужасен, как будто... он тоже болен.

Что я хочу сделать: получить доступ к DirectInput для получения состояния клавиатуры, а не событий. Хотя это далеко выходит за рамки этого вопроса. Таким образом, мы будем поддерживать наше собственное состояние действия.

Проблема, с которой вы столкнулись, заключается в том, что вы выполняете свое действие в потоке пользовательского интерфейса. Вам нужно создать рабочий поток и игнорировать последующие события, пока ваше действие не будет завершено.

В приведенном мной примере я начинаю новое действие, когда буква «а» нажата или удерживается нажатой. Это не вызовет другое действие, пока первое действие не будет завершено. Действие обновляет метку в форме, отображая, сколько «циклов» осталось до его завершения.

Существует также еще одна метка, которая показывает, сколько действий было выполнено на данный момент.

Создание нового действия

Важно, чтобы все ключевые события пользовательского интерфейса происходили, а не блокировались в потоке пользовательского интерфейса, заставляя их стоять в очереди.

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();
        }
    }
}

Обновления в потоке пользовательского интерфейса

Если ваше действие выполняет какие-либо обновления пользовательского интерфейса, оно должно будет использовать метод SwingUtilities.invokeLater. Этот метод поставит ваш код в очередь для запуска в потоке пользовательского интерфейса. Вы не можете изменить пользовательский интерфейс в потоке, отличном от потока пользовательского интерфейса. Кроме того, используйте SwingUtilities только для обновления компонентов пользовательского интерфейса. Любые вычисления, обработка и т. д., которые не вызывают методы для Компонента, могут выполняться за пределами 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);
    }
}
person Andrew T Finnell    schedule 04.01.2011

Я также заметил, что событие keyRelease не происходит до тех пор, пока не будет обработано последнее событие keyPress, независимо от того, когда ключ был фактически выпущен.

Это зависит от используемой ОС. Это поведение в Windows (что имеет смысл для меня). Я полагаю, что в Unix или Mac вы получаете несколько событий keyPressed, keyReleased. Таким образом, ваше решение не должно основываться на событиях keyReleased.

У меня есть программа, в которой пользователь может нажать клавишу для выполнения действия.

Тогда вы должны использовать Key Binding, а не KeyListener. Прочтите раздел руководства по Swing, посвященный Как использовать привязки клавиш. Чтобы получить больше информации.

Когда действие вызывается, вы можете отключить его. Я не уверен, что это предотвратит повторную работу KeyStroke или вам все равно нужно будет проверить включенное состояние действия. Затем, когда код действия завершится, вы можете повторно включить действие.

Кроме того, этот длинный код не должен выполняться в EDT. Прочтите раздел учебного пособия по Swing, посвященный параллелизму, для получения дополнительной информации об этом и решениях.

person camickr    schedule 04.01.2011
comment
Насколько нам известно, он может разрабатывать простую видеоигру и хочет переместить персонажа на определенное расстояние, что требует некоторого времени. Насколько я понимаю, он хочет, чтобы действие происходило, пока он держит нажатой клавишу. Хотя я мог неправильно понять, что он сказал. - person Andrew T Finnell; 04.01.2011
comment
Вы правы, на самом деле я делаю приложение для обработки изображений, и я хочу иметь возможность удерживать клавишу и изменять плоскость изображения, на которую смотрит пользователь, и я хочу перестать перемещать плоскость, когда пользователь отпускает клавишу. - person Jon; 04.01.2011

Вам придется выбрать вариант 1. Как только вы начнете более длительный процесс, установите логическое значение некоторого времени, чтобы указать, что вы работаете над ним, и отбрасывайте другие входящие идентичные запросы. После завершения процесса установите логическое значение обратно и разрешите дополнительные события.

person jzd    schedule 04.01.2011
comment
Я пытался сделать то, что вы сказали, и установить логическое значение, но это не сработало. Похоже, что последующие события хранятся где-то (в Java, я не знаю где) и отправляются мне только после завершения предыдущего события, поэтому события никогда не пропускаются. - person Jon; 04.01.2011
comment
Я предположил, что вы делаете длинное событие в отдельном потоке. (Что, возможно, вам нужно сделать.) - person jzd; 04.01.2011