簡體   English   中英

Java swing - 使用鍵綁定處理同時按鍵

[英]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,但是,這不能很好地擴展,所以,相反,我會考慮改用enumSet

以下示例將僅在按住關聯操作的鍵時突出顯示標簽。

在此處輸入圖像描述

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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM