привязки клавиш Java Swing - отсутствует действие для выпущенной клавиши

Зарегистрировав привязки клавиш для «ПРОБЕЛ» и «отпущенный ПРОБЕЛ», которые работают, как рекламируется, когда пробел является единственной нажатой/отпущенной клавишей, я замечаю, что нажатие пробела, затем нажатие ctrl (или любой другой клавиши-модификатора), затем отпускание пробела и, наконец, отпускание ctrl приведет к выполнению действия, связанного с «ПРОБЕЛ», но не действия, связанного с «выпущенным ПРОБЕЛОМ».

Каков предпочтительный способ выполнения действия после того, как пробел больше не нажат (или одновременно нажата клавиша-модификатор)? Я пробовал это только на Windows 7, 64-битной версии.

import javax.swing.SwingUtilities;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.AbstractAction;
import javax.swing.KeyStroke;
import java.awt.event.ActionEvent;
import java.awt.Cursor;

class Bind extends JPanel {
  {
    getInputMap().put(KeyStroke.getKeyStroke("SPACE"), "pressed");
    getInputMap().put(KeyStroke.getKeyStroke("released SPACE"), "released");
    getActionMap().put("pressed", new AbstractAction() {
      @Override public void actionPerformed(ActionEvent e) {
        System.out.println("pressed");
        setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR)); 
      }
    });
    getActionMap().put("released", new AbstractAction() {
      @Override public void actionPerformed(ActionEvent e) {
        System.out.println("released");
        setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); 
      }
    });
  }
  public static void main(String[] args) {
    SwingUtilities.invokeLater(new Runnable() {
      @Override public void run() {
        JFrame f = new JFrame("Key Bindings");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
        f.add(new Bind());
        f.setSize(640, 480);
        f.setVisible(true);
      }
    });
  }
}

ОБНОВЛЕНИЕ: это способ избежать залипания пробела при случайном нажатии ctrl, alt или Shift перед отпусканием пробела:

import javax.swing.SwingUtilities;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.AbstractAction;
import javax.swing.KeyStroke;
import java.awt.event.ActionEvent;
import java.awt.Cursor;

class Bind extends JPanel {
  {
    getInputMap().put(KeyStroke.getKeyStroke("SPACE"), "pressed");
    getInputMap().put(KeyStroke.getKeyStroke("released SPACE"), "released");
    getInputMap().put(KeyStroke.getKeyStroke("ctrl released SPACE"), "released");
    getInputMap().put(KeyStroke.getKeyStroke("shift released SPACE"), "released");
    getInputMap().put(KeyStroke.getKeyStroke("shift ctrl released SPACE"), "released");
    getInputMap().put(KeyStroke.getKeyStroke("alt released SPACE"), "released");
    getInputMap().put(KeyStroke.getKeyStroke("alt ctrl released SPACE"), "released");
    getInputMap().put(KeyStroke.getKeyStroke("alt shift released SPACE"), "released");
    getInputMap().put(KeyStroke.getKeyStroke("alt shift ctrl released SPACE"), "released");
    getActionMap().put("pressed", new AbstractAction() {
      @Override public void actionPerformed(ActionEvent e) {
        System.out.println("pressed");
        setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR)); 
      }
    });
    getActionMap().put("released", new AbstractAction() {
      @Override public void actionPerformed(ActionEvent e) {
        System.out.println("released");
        setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); 
      }
    });
  }
  public static void main(String[] args) {
    SwingUtilities.invokeLater(new Runnable() {
      @Override public void run() {
        JFrame f = new JFrame("Key Bindings");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
        f.add(new Bind());
        f.setSize(640, 480);
        f.setVisible(true);
      }
    });
  }
}

person Aksel    schedule 15.12.2011    source источник
comment
Чтобы быстрее получить помощь, опубликуйте SSCCE.   -  person Andrew Thompson    schedule 15.12.2011
comment
SSCCE добавлен для более быстрой помощи :-)   -  person Aksel    schedule 15.12.2011
comment
Хорошо.. Это очень хороший вопрос. Я вижу описанное вами поведение (в 32-битной Win 7), но понятия не имею, как это исправить. Будем надеяться, что один из гуру связывания ключей появится .. в ближайшее время. :)   -  person Andrew Thompson    schedule 15.12.2011


Ответы (2)


Имеет смысл, что событие released SPACE не запускается, когда клавиша Control все еще удерживается нажатой. Я ожидаю, что будет запущено событие control released SPACE.

Добавьте в свой код следующее:

getInputMap().put(KeyStroke.getKeyStroke("control released SPACE"), "released");

По той же причине событие SPACE не сработает, если вы сначала удерживаете клавишу Control. Поэтому вам также нужно будет добавить привязки для control SPACE.

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

person camickr    schedule 15.12.2011
comment
Я принимаю ваш ответ как ответ, который я бы сделал сам (добавьте привязки для ВСЕХ комбинаций пробела + модификатора), но я не принимаю его логику (я не согласен с тем, что это имеет смысл). Когда выполняется действие для пространства, я ожидаю, что действие для освобожденного пространства будет выполнено в какой-то момент после того, как пространство было освобождено, с модификаторами или без. Как вы заметили, я должен добавить привязки для всех комбинаций клавиш пробела + модификатора и всех комбинаций клавиш модификатора (также ctrl + alt + shift + пробел), чтобы предотвратить такое поведение. - person Aksel; 16.12.2011
comment
Это имеет смысл (согласны вы с этим или нет). Если событие Space генерируется каждый раз, когда нажимается клавиша Space, то как вы сможете отличить его от Space и Control/Space? С вашей логикой вы всегда будете получать 2 события: одно для Control/Space и одно для Space. Таким образом, даже если пользователь только попытается выполнить действие Control/Space, действие Space будет добавлено бесплатно. - person camickr; 16.12.2011
comment
Я не говорю, что получу космическое событие для ctrl+space, я говорю, что хочу, чтобы космическое событие было выпущено после космического события. То, что я получаю сейчас, — это выпущенное событие ctrl+space при космическом событии, и хотя это может иметь смысл для вас, и даже если вы заставили себя судить о том, что имеет смысл, а что нет, для меня это не очень полезно. - person Aksel; 16.12.2011
comment
Вы должны согласиться, что это немного раздражает, если режим панорамирования, активируемый нажатием пробела, становится полупостоянным из-за случайного нажатия ctrl перед отпусканием пробела? - person Aksel; 16.12.2011
comment
Это может быть я слишком зациклен на деталях, я чувствую, что привязки клавиш - правильный инструмент для поставленной задачи, я просто не думаю, что они работают совсем так, как мне хотелось бы. Я живу с зависанием режима панорамирования, в конце концов, это не шоустоппер. - person Aksel; 16.12.2011
comment
Вы тот, кто судит, что имеет смысл, а что нет, основываясь на том, как вы хотите, чтобы все работало, и на том, что вы считаете полезным. Я просто заявляю, почему он делает то, что делает. Компьютер не может определить, чего вы хотите или ожидаете. Он может делать только то, что ему говорят. Только одно событие генерируется каждый раз, когда изменяется состояние (нажатие/отпускание) клавиши. Поэтому всякий раз, когда состояние клавиши изменяется, событие отмечает, какая клавиша была изменена, а также отмечает, были ли нажаты какие-либо модификаторы в то время. Это так просто. Компьютер не может прочитать намерение пользователя. - person camickr; 16.12.2011
comment
Возможно, проблема в вашем дизайне. Обычно, когда я вижу приложения, которые имеют какую-то функцию масштабирования. Для обработки действий используются два нажатия клавиш. Что-то вроде Control/Plus для масштабирования и Control/Minus для уменьшения масштаба. Может быть, вам нужно то же самое, Плюс для панорамирования Минус для распаковки. Большинство программ, которые я использую, выполняют действия с нажатым событием, а не с выпущенными событиями. Может быть, это одна из причин, почему. Взгляните на ускорители и то, как они используются в пунктах меню. - person camickr; 16.12.2011
comment
Одной из программ, которая использует пробел для панорамирования, является Photoshop. Вместо этого я мог бы сделать его переключателем. В этом есть смысл... - person Aksel; 16.12.2011

Возможно, ваша ОС не запускает события keyReleased, а только события keyPressed и keyTyped или какую-либо другую комбинацию, поэтому сначала проверьте это. Возможно, вам просто нужно проверить keyTyped событий вместо keyReleased, и все будет готово.

Короткий ответ:

Используйте битовую маску или массив, чтобы отслеживать, какие ключи в данный момент находятся в " нажато», затем используйте значения для запуска событий. То есть не используйте события Swing напрямую для запуска ответов в вашем приложении — вам нужен дополнительный уровень, который по существу хранит состояние клавиатуры и из этого состояния выполняет соответствующие действия.

Также доступны методы (см. в конце этого руководства - "isAltDown", "isCtrlDown" и т. д.), чтобы проверить, нажаты ли клавиши-модификаторы, когда вы получаете событие, такое как нажатие клавиши "Пробел".

Длинный ответ:

Вы правы в том, что события запускаются при нажатии и отпускании клавиш. Это должно работать таким образом, чтобы вы могли поддерживать приложения, которые должны обрабатывать эти события по отдельности, а не вместе. Одним из примеров (хотя и не единственным) являются видеоигры на ПК, в которых вы можете одновременно нажимать несколько клавиш-букв/модификаторов (например, A для перехода влево и W для перехода вперед), и игра должна обрабатывать эти два события представляют собой отдельные входные данные, а не составные входные данные, что приводит к вашему движению вперед-влево.

Итак, что вы в основном хотите сделать, если вам нужно иметь дело с составными входными данными, это просто массив действий, на которые должно реагировать ваше приложение, и связанные с ними привязки клавиш (будь то одиночные или многоклавишные, на самом деле не имеет значения ). Когда клавиша нажата, вы в основном включаете флаг для этой клавиши, который говорит, что она в данный момент «нажата», и очищаете флаг, когда она отпущена.

Затем, чтобы инициировать ваши события, вы просто проверяете все нажатые клавиши (путем проверки того, какие «флаги» клавиш активны), и если нажата комбинация клавиш определенного события, то событие запускается.

Если у вас менее 32 ключей, запускающих события, вы можете сделать это с помощью битовой маски и 32-битное значение int, а не массив. На самом деле, если есть возможность, сделать это гораздо проще. Если вам нужно до 64 ключей, сделайте то же самое с long. Если у вас очень мало клавиш, которые запускают события (например, 8 или меньше), вы можете использовать 8-битный тип short.

person jefflunt    schedule 15.12.2011
comment
Итак, еще более короткий ответ: не используйте привязки клавиш. Я так и подозревал. Мне грустно от этого. :-) - person Aksel; 15.12.2011
comment
На самом деле я только что изменил свой ответ и добавил абзац в самое начало. Возможно, ваша ОС не запускает все три события. Это особенность Swing применительно к нескольким платформам. Это может быть ваш ответ. - person jefflunt; 15.12.2011
comment
Чтение всех KeyEvent документов (по крайней мере, верхнего раздела/обзора) или может не помочь: docs.oracle.com/javase/6/docs/api/java/awt/event/KeyEvent.html - person jefflunt; 15.12.2011