Я нашел по крайней мере частичный ответ.
Для проблемы, когда мне приходилось удерживать обе клавиши Shift для генерации события нажатия клавиши при использовании привязки клавиш, есть простое исправление. Все, что нужно сделать, это изменить то, что добавлено к InputMap
, с:
getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_SHIFT, 0), "pressed");
на
getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_SHIFT, KeyEvent.SHIFT_DOWN_MASK), "pressed");
Я не совсем уверен, почему карта ввода учитывает нажатие одинарная клавиша Shift как KeyEvent
с кодом клавиши VK_SHIFT
И SHIFT_DOWN_MASK
, но, похоже, это именно то, что он делает. Для меня было бы более интуитивно понятным, если бы маска применялась только в том случае, если уже была нажата одна клавиша Shift, и пользователь пытается нажать другую, но, что интересно, эта привязка больше не обнаруживает события, если одна клавиша Shift удерживается и другой нажимается. Странно.
Проблемы с другими ключами имеют несколько менее чистое решение. Относительно вопроса, почему shift ведет себя иначе, чем другие клавиши. Я считаю, что это намеренный дизайн, встроенный в ОС. Например, если пользователь нажимает и удерживает стрелку вправо (или многие другие клавиши, такие как клавиша каждого текстового символа), разумно предположить, что он хочет повторить действие, привязанное к этой клавише. То есть, если пользователь печатает, нажимает и удерживает «а», он, вероятно, захочет ввести несколько символов «а» в быстрой последовательности в текстовый документ. Однако автоматическое повторение клавиши Shift аналогичным образом (в большинстве случаев) бесполезно для пользователя. Следовательно, имеет смысл, что никакие повторяющиеся события для клавиши Shift не генерируются. У меня нет никаких источников, подтверждающих это, это всего лишь гипотеза, но для меня это имеет смысл.
Для того, чтобы удалить эти лишние события, похоже, нет хорошего решения. Одна вещь, которая работает, но неаккуратна, - это сохранить список всех нажатых клавиш, а затем проверить карту действий, нажата ли клавиша, перед выполнением ее действия. Другой подход - использовать таймеры и игнорировать события, которые происходят, чтобы сблизиться друг с другом (подробнее см. этот пост ). Обе эти реализации требуют большего использования памяти и кода для каждого ключа, который вы sh отслеживаете, поэтому они не идеальны.
Чуть лучшее решение (IMO) может быть достигнуто с использованием KeyAdapter
вместо ключа Привязки. Ключ к этому решению заключается в том, что нажатие одной клавиши при удерживании другой прервет поток событий автоповтора, и он больше не возобновится для исходной клавиши (даже если вторая клавиша будет отпущена). Из-за этого нам действительно нужно отслеживать только последнюю нажатую клавишу, чтобы точно отфильтровать все события автоповтора, потому что это единственный ключ, который может отправлять эти события.
Код будет выглядеть примерно так вот так:
addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
int keyCode = e.getKeyCode();
if (keyCode != lastKeyPressed && keyCode != KeyEvent.VK_UNDEFINED) {
// do some action
lastKeyPressed = keyCode;
}
}
@Override
public void keyReleased(KeyEvent e) {
// do some action
lastKeyPressed = -1; // indicates that it is not possible for any key
// to send auto-repeat events currently
}
});
Это решение, конечно, теряет некоторую гибкость, обеспечиваемую системой привязки клавиш Swing, но здесь есть более простой обходной путь. Вы можете создать свою собственную карту от int
до Action
(или действительно любого другого типа, который удобен для описания того, что вы хотите сделать), и вместо добавления привязок клавиш к InputMap
s и ActionMap
s вы положи их туда. Затем вместо того, чтобы помещать прямой код действия, которое вы хотите выполнить, внутри KeyAdapter
, введите что-то вроде myMap.get(e.getKeyCode()).actionPerformed();
. Это позволяет добавлять, удалять и изменять привязки клавиш, выполнив соответствующую операцию на карте.