[英]Java swing - Processing simultaneous key presses with key bindings
閱讀了幾個相關問題后,我覺得我這里的情況有點獨特。
我正在構建一個 Java swing 應用程序來幫助鼓手制作簡單的速記歌曲圖表。 有一個對話框,用戶可以在其中“鍵入”節奏,將其錄制到 MIDI 序列,然后處理成樂譜或樂譜。 這旨在與歌曲的短部分一起使用。
這個想法是當綁定的 JButtons 在記錄序列時觸發它們的動作時,它們將生成一個帶有計時信息的 MidiMessage。 我還希望按鈕能夠直觀地表明它們已被激活。
綁定鍵當前使用我已經實現的鍵綁定正確觸發(同時按鍵除外)...
重要的是將同時按鍵記錄為單個事件——這里的時間很重要。
因此,例如,如果用戶同時按下H (踩鑔)和S (軍鼓),它將在小節的同一位置注冊為齊奏。
我曾嘗試使用類似於此的 KeyListener 實現: https://stackoverflow.com/a/13529058/13113770 ,但使用該設置我遇到了焦點問題,雖然它可以檢測到同時按鍵,但它也會處理它們個別地。
任何人都可以為我闡明這一點嗎?
// code omitted
public PunchesDialog(Frame owner, Song partOwner, Part relevantPart)
{
super(owner, ModalityType.APPLICATION_MODAL);
this.partOwner = partOwner;
this.relevantPart = relevantPart;
// code omitted
/*
* Voices Panel
*/
voices = new LinkedHashMap<>() {{
put("crash", new VoiceButton("CRASH (C)", crashHitAction));
put("ride", new VoiceButton("RIDE (R)", rideHitAction));
put("hihat", new VoiceButton("HI-HAT (H)", hihatHitAction));
put("racktom", new VoiceButton("RACK TOM (T)", racktomHitAction));
put("snare", new VoiceButton("SNARE (S)", snareHitAction));
put("floortom", new VoiceButton("FLOOR TOM (F)", floortomHitAction));
put("kickdrum", new VoiceButton("KICK DRUM (SPACE)", kickdrumHitAction));
}};
Action crashHitAction = new CrashHitAction();
Action rideHitAction = new RideHitAction();
Action hihatHitAction = new HihatHitAction();
Action racktomHitAction = new RacktomHitAction();
Action snareHitAction = new SnareHitAction();
Action floortomHitAction = new FloortomHitAction();
Action kickdrumHitAction = new KickdrumHitAction();
KeyStroke key;
InputMap inputMap = ((JPanel) getContentPane()).
getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
ActionMap actionMap = ((JPanel) getContentPane()).getActionMap();
key = KeyStroke.getKeyStroke(KeyEvent.VK_C, 0);
inputMap.put(key, "crashHit");
actionMap.put("crashHit", crashHitAction);
key = KeyStroke.getKeyStroke(KeyEvent.VK_R, 0);
inputMap.put(key, "rideHit");
actionMap.put("rideHit", rideHitAction);
key = KeyStroke.getKeyStroke(KeyEvent.VK_H, 0);
inputMap.put(key, "hihatHit");
actionMap.put("hihatHit", hihatHitAction);
key = KeyStroke.getKeyStroke(KeyEvent.VK_T, 0);
inputMap.put(key, "racktomHit");
actionMap.put("racktomHit", racktomHitAction);
key = KeyStroke.getKeyStroke(KeyEvent.VK_S, 0);
inputMap.put(key, "snareHit");
actionMap.put("snareHit", snareHitAction);
key = KeyStroke.getKeyStroke(KeyEvent.VK_F, 0);
inputMap.put(key, "floortomHit");
actionMap.put("floortomHit", floortomHitAction);
key = KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0);
inputMap.put(key, "kickdrumHit");
actionMap.put("kickdrumHit", kickdrumHitAction);
final JPanel pnlVoices = new JPanel(new MigLayout(
"Insets 0, gap 0, wrap 2", "[fill][fill]", "fill"));
pnlVoices.add(voices.get("crash"), "w 100%, h 100%, grow");
pnlVoices.add(voices.get("ride"), "w 100%");
pnlVoices.add(voices.get("hihat"), "w 100%");
pnlVoices.add(voices.get("racktom"), "w 100%");
pnlVoices.add(voices.get("snare"), "w 100%");
pnlVoices.add(voices.get("floortom"), "w 100%");
pnlVoices.add(voices.get("kickdrum"), "span");
// code omitted
}
private class CrashHitAction extends AbstractAction
{
@Override
public void actionPerformed(ActionEvent e) {
// voices.get("crash").doClick(100);
kfMgr.clearFocusOwner();
logger.debug("hit crash");
}
}
private class RideHitAction extends AbstractAction
{
@Override
public void actionPerformed(ActionEvent e) {
// voices.get("ride").doClick(100);
kfMgr.clearFocusOwner();
logger.debug("hit ride");
}
}
private class HihatHitAction extends AbstractAction
{
@Override
public void actionPerformed(ActionEvent e) {
// voices.get("hihat").doClick(100);
kfMgr.clearFocusOwner();
logger.debug("hit hihat");
}
}
private class RacktomHitAction extends AbstractAction
{
@Override
public void actionPerformed(ActionEvent e) {
// voices.get("racktom").doClick(100);
kfMgr.clearFocusOwner();
logger.debug("hit racktom");
}
}
private class FloortomHitAction extends AbstractAction
{
@Override
public void actionPerformed(ActionEvent e) {
// voices.get("floortom").doClick(100);
kfMgr.clearFocusOwner();
logger.debug("hit floortom");
}
}
private class SnareHitAction extends AbstractAction
{
@Override
public void actionPerformed(ActionEvent e) {
// voices.get("snare").doClick(100);
kfMgr.clearFocusOwner();
logger.debug("hit snare");
}
}
private class KickdrumHitAction extends AbstractAction
{
@Override
public void actionPerformed(ActionEvent e) {
// voices.get("kickdrum").doClick(100);
kfMgr.clearFocusOwner();
logger.debug("hit kickdrum");
}
}
此處對話框的屏幕截圖: https://i.stack.imgur.com/n4RzY.png
您需要更多地解耦一些概念。 例如, Action
API 允許您在按鈕(所有按鈕)以及鍵綁定上使用相同的Action
(相同Action
的多個實例的相同實例)。
在這種情況下,您想找到Action
與可能的觸發器分離的地方(即,如果可能,不要假設它是按鈕或鍵綁定)
對我來說,當鍵綁定被觸發時,我想通知某種觀察者或管理者操作已經發生。 一個可能的考慮是,當它被按下時和它被釋放時會發生什么,有區別嗎?
KeyStroke
允許您定義“按下”和“釋放”觸發器。 然后我會使用某種監視器來管理 state,即一系列boolean
s,它們是true
還是false
取決於操作的 state,但是,這不能很好地擴展,所以,相反,我會考慮改用enum
和Set
。
以下示例將僅在按住關聯操作的鍵時突出顯示標簽。
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.border.CompoundBorder;
import javax.swing.border.EmptyBorder;
import javax.swing.border.LineBorder;
public class Main {
public static void main(String[] args) {
new Main();
}
public Main() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
enum UserAction {
CRASH_HIT, RIDE_HIT, HI_HAT_HIT, RACK_TOM_HIT, SNARE_HIT, FLOOR_TOM_HIT, KICK_DRUM_HIT;
}
public interface Observer {
public void didActivateAction(UserAction action);
public void didDeactivateAction(UserAction action);
}
private Map<UserAction, JLabel> labels;
private Set<UserAction> activeActions = new TreeSet<>();
private final Set<UserAction> allActions = new TreeSet<>(Arrays.asList(UserAction.values()));
public TestPane() {
labels = new HashMap<>();
for (UserAction action : UserAction.values()) {
JLabel label = new JLabel(action.name());
label.setBorder(new CompoundBorder(new LineBorder(Color.DARK_GRAY), new EmptyBorder(8, 8, 8, 8)));
label.setOpaque(true);
add(label);
labels.put(action, label);
}
InputMap inputMap = getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
ActionMap actionMap = getActionMap();
Observer observer = new Observer() {
@Override
public void didActivateAction(UserAction action) {
if (activeActions.contains(action)) {
// We don't want to deal with "repeated" key events
return;
}
activeActions.add(action);
// I could update the labels here, but this is a deliberate
// example of how to decouple the action from the state
// so the actions can be dealt with in as a single unit
// of work, you can also take into consideratoin any
// relationships which different inputs might have as well
updateUIState();
}
@Override
public void didDeactivateAction(UserAction action) {
activeActions.remove(action);
updateUIState();
}
};
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_C, 0, false), "pressed-crashHit");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_C, 0, true), "released-crashHit");
actionMap.put("pressed-crashHit", new InputAction(UserAction.CRASH_HIT, true, observer));
actionMap.put("released-crashHit", new InputAction(UserAction.CRASH_HIT, false, observer));
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_R, 0, false), "pressed-rideHit");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_R, 0, true), "released-rideHit");
actionMap.put("pressed-rideHit", new InputAction(UserAction.RIDE_HIT, true, observer));
actionMap.put("released-rideHit", new InputAction(UserAction.RIDE_HIT, false, observer));
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_H, 0, false), "pressed-hihatHit");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_H, 0, true), "released-hihatHit");
actionMap.put("pressed-hihatHit", new InputAction(UserAction.HI_HAT_HIT, true, observer));
actionMap.put("released-hihatHit", new InputAction(UserAction.HI_HAT_HIT, false, observer));
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_T, 0, false), "pressed-racktomHit");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_T, 0, true), "released-racktomHit");
actionMap.put("pressed-racktomHit", new InputAction(UserAction.RACK_TOM_HIT, true, observer));
actionMap.put("released-racktomHit", new InputAction(UserAction.RACK_TOM_HIT, false, observer));
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0, false), "pressed-snareHit");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0, true), "released-snareHit");
actionMap.put("pressed-snareHit", new InputAction(UserAction.SNARE_HIT, true, observer));
actionMap.put("released-snareHit", new InputAction(UserAction.SNARE_HIT, false, observer));
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_F, 0, false), "pressed-floortomHit");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_F, 0, true), "released-floortomHit");
actionMap.put("pressed-floortomHit", new InputAction(UserAction.FLOOR_TOM_HIT, true, observer));
actionMap.put("released-floortomHit", new InputAction(UserAction.FLOOR_TOM_HIT, false, observer));
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0, false), "pressed-kickdrumHit");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0, true), "released-kickdrumHit");
actionMap.put("pressed-kickdrumHit", new InputAction(UserAction.KICK_DRUM_HIT, true, observer));
actionMap.put("released-kickdrumHit", new InputAction(UserAction.KICK_DRUM_HIT, false, observer));
}
protected void updateUIState() {
Set<UserAction> inactiveActions = new TreeSet<>(allActions);
inactiveActions.removeAll(activeActions);
for (UserAction action : inactiveActions) {
JLabel label = labels.get(action);
label.setBackground(null);
label.setForeground(Color.BLACK);
}
for (UserAction action : activeActions) {
JLabel label = labels.get(action);
label.setBackground(Color.BLUE);
label.setForeground(Color.WHITE);
}
}
// This could act as a base class, from which other, more dedicated
// implementations could be built, which did focused jobs, for example
// protected class ActivateCrashHit extends InputAction {
// public ActivateCrashHit(Observer observer) {
// super(UserAction.CRASH_HIT, true, observer);
// }
// // Override actionPerformed
// }
protected class InputAction extends AbstractAction {
private UserAction action;
private boolean activated;
private Observer observer;
public InputAction(UserAction action, boolean activated, Observer observer) {
this.action = action;
this.activated = activated;
this.observer = observer;
}
@Override
public void actionPerformed(ActionEvent e) {
// This could perform other actions, but the intention of the
// observer is provide an oppurunity for the interested party
// to also make some kind of update, to allow the user to
// see that that action occured
if (activated) {
observer.didActivateAction(action);
} else {
observer.didDeactivateAction(action);
}
}
}
}
}
您還應該注意,某些鍵盤存在硬件限制,這限制了可以同時按下的鍵的數量,盡管老實說,對於這個例子,我發現很難一次按下所有鍵方法
我個人會使用 KeyListener 接口來跟蹤用戶鍵入的內容,然后在列表中注冊已按下的鍵(不釋放),然后在釋放任何鍵后收集它們。
請確保僅將不存在的鍵添加到列表中,因為當用戶按住某個鍵時會多次觸發 keyPressed 事件。
我還創建了一個小樣本來給你這個想法。
public class MyClass extends JFrame implements KeyListener {
private JTextArea textArea;
private List<Character> listKeys;
public MyClass() {
setTitle("test");
listKeys = new ArrayList<>();
textArea = new JTextArea();
textArea.addKeyListener(this);
setLayout(new BorderLayout());
add(textArea, BorderLayout.CENTER);
setLocation(50, 50);
setSize(500, 500);
setVisible(true);
}
@Override
public void keyTyped(KeyEvent e) {
}
@Override
public void keyPressed(KeyEvent e) {
if (!listKeys.contains(e.getKeyChar())) {
listKeys.add(e.getKeyChar());
}
}
@Override
public void keyReleased(KeyEvent e) {
if (listKeys.isEmpty()) {
return;
}
if (listKeys.size() > 1) {
System.out.print("The key combination ");
} else {
System.out.print("The key ");
}
for (Character c : listKeys) {
System.out.print(c + " ");
}
System.out.println("has been entered");
listKeys.clear();
}
public static void main(String[] args) {
new MyClass();
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.