【问题标题】:Java Swing Sending Navigation Commands Using Arrow ButtonsJava Swing 使用箭头按钮发送导航命令
【发布时间】:2015-02-26 14:45:48
【问题描述】:

我需要使用 JButtons 和键盘发送导航命令。如果按下“向上”按钮/向上键,我需要发送“向北移动”命令,如果按下向上和向左按钮(从键盘),我需要发送“向北移动”命令等。命令应该是定期发送(每 1 秒)。以下是我的代码。

为了解释更多,

视图上有三个 JButton。我们称它们为 jUp、jLeft 和 jRight。当用户按下视图上的 jUp 按钮时,程序应该定期发送 moveNorth 命令,直到用户释放 jUp 按钮。当用户按下键盘上的向上按钮时,应该会发生同样的事情,并且 jUp 按钮应该看起来被按下,直到用户释放键盘向上按钮。当用户同时按下键盘的向上和向左按钮时,jUp 和 jLeft 按钮应显示为按下状态,直到用户松开键盘按钮。在用户释放键盘按钮之前,应该定期发送 move northWest 命令。在代码中,我刚刚使用 System.out.println 打印了命令。

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;

import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.Timer;

public class ButtonDemo {
    private JPanel buttons;
    private Timer t;
    private JButton upButton;
    private JButton leftButton;
    private JButton rightButton;

    public static void main(String[] args) {
        new ButtonDemo().run();

    }

    public ButtonDemo() {
        buttons = new JPanel(new BorderLayout());
        this.t = new Timer(1000, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                if (upButton.isSelected() && leftButton.isSelected()) {
                    System.out.println("Move north west");
                } else if (upButton.isSelected() && rightButton.isSelected()) {
                    System.out.println("Move north east");
                } else if (upButton.isSelected()) {
                    System.out.println("Move north");
                } else {
                    t.stop();
                }
            }
        });
    }

    void run() {

        this.upButton = new JButton("Up");
        buttons.add(upButton, BorderLayout.NORTH);
        setupButton(upButton, "Up", KeyEvent.VK_UP);

        this.leftButton = new JButton("Left");
        buttons.add(leftButton, BorderLayout.WEST);
        setupButton(leftButton, "Left", KeyEvent.VK_LEFT);

        this.rightButton = new JButton("Right");
        buttons.add(rightButton, BorderLayout.EAST);
        setupButton(rightButton, "Right", KeyEvent.VK_RIGHT);

        JFrame frame = new JFrame("FrameDemo");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(buttons, BorderLayout.CENTER);
        frame.pack();
        frame.setVisible(true);
    }

    private void setupButton(JButton button, String key, int vkUp) {
        buttons.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(vkUp, 0),
                key + " pressed");
        buttons.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(vkUp, 0, true),
                key + " released");
        buttons.getActionMap().put(key + " pressed", new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                button.setSelected(true);
                pressed(key);
            }
        });

        buttons.getActionMap().put(key + " released", new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                button.setSelected(false);
            }
        });
    }

    private void pressed(String key) {
        if (!t.isRunning()) {
            t.start();
        }
    }
}

现在回答问题。

a) 即使我调用了 setSelected 方法,按钮状态也不会变为按下状态。 (视觉上它不会变为按下状态)。我怎样才能做到这一点?

b) 是否有更好/更标准的方式来实现此功能?使用助记符/ExecutorService 等..?我将动作添加到“按钮”元素的输入映射中是否正确。 ( buttons.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)正确吗?)面板会在选项卡中,并且选择该选项卡时应使用。

【问题讨论】:

  • 你是否关心按下的顺序(即如果我向上按,然后向左,然后向左松开,你是否关心首先向上的事实)?
  • 顺序无关紧要。应该根据现在按下的按钮发送消息。由于问题中的代码现在适用于键盘按钮。根据现在按下的键盘按钮,UI 按钮应该看起来被按下。

标签: java swing key-bindings


【解决方案1】:

JButton 没有selected 状态,它有一个armed。为了使按钮在释放鼠标或键时保持“按下”状态,您必须使用JToggleButton

就我个人而言,我会远离对按钮状态的监控,而是使用某种enum 或其他可以在Set 中添加和删除的常量。这将实现状态的方式与作用于状态的过程分离。

从那里,您可以使用单个 Action 来通知某种观察者状态已更改

import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.util.HashSet;
import java.util.Set;
import java.util.StringJoiner;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JToggleButton;
import javax.swing.KeyStroke;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class Test {

    public static void main(String[] args) {
        new Test();
    }

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public static class TestPane extends JPanel {

        public enum Direction {

            UP, DOWN, LEFT, RIGHT;
        }

        private JToggleButton[] buttons;
        private Set keys;

        private Timer timer;

        private JLabel direction;

        public TestPane() {
            keys = new HashSet();
            direction = new JLabel("Stopped");

            timer = new Timer(1000, new ActionListener() {

                @Override
                public void actionPerformed(ActionEvent e) {
                    if (keys.isEmpty()) {
                        ((Timer) e.getSource()).stop();
                        direction.setText("Stopped");
                    } else {
                        StringJoiner joiner = new StringJoiner("-");
                        if (keys.contains(Direction.UP)) {
                            joiner.add("North");
                        }
                        if (keys.contains(Direction.DOWN)) {
                            joiner.add("South");
                        } 
                        if (keys.contains(Direction.LEFT)) {
                            joiner.add("West");
                        } 
                        if (keys.contains(Direction.RIGHT)) {
                            joiner.add("East");
                        }
                        direction.setText(joiner.toString());
                    }
                }

            });

            Monitor monitor = new Monitor() {
                @Override
                public void pressed(Direction direction) {
                    keys.add(direction);
                    timer.restart();
                }

                @Override
                public void released(Direction direction) {
                    keys.remove(direction);
                }
            };

            MovementAction up = new MovementAction("Up", Direction.UP, monitor);
            MovementAction down = new MovementAction("Down", Direction.DOWN, monitor);
            MovementAction left = new MovementAction("Left", Direction.LEFT, monitor);
            MovementAction right = new MovementAction("Right", Direction.RIGHT, monitor);

            setLayout(new GridBagLayout());
            GridBagConstraints gbc = new GridBagConstraints();
            gbc.gridy = 0;
            gbc.gridx = 1;

            buttons = new JToggleButton[4];
            buttons[0] = new JToggleButton(up);
            buttons[1] = new JToggleButton(down);
            buttons[2] = new JToggleButton(left);
            buttons[3] = new JToggleButton(right);

            add(buttons[0], gbc);
            gbc.gridy = 2;
            add(buttons[1], gbc);

            gbc.gridy = 1;
            gbc.gridx = 0;
            add(buttons[2], gbc);
            gbc.gridx++;
            add(direction, gbc);
            gbc.gridx++;
            add(buttons[3], gbc);

            addTriggerKeyBindingTo(buttons[0], KeyEvent.VK_UP, KeyEvent.VK_W, KeyEvent.VK_NUMPAD8);
            addTriggerKeyBindingTo(buttons[1], KeyEvent.VK_DOWN, KeyEvent.VK_S, KeyEvent.VK_NUMPAD2);
            addTriggerKeyBindingTo(buttons[2], KeyEvent.VK_LEFT, KeyEvent.VK_A, KeyEvent.VK_NUMPAD6);
            addTriggerKeyBindingTo(buttons[3], KeyEvent.VK_RIGHT, KeyEvent.VK_D, KeyEvent.VK_NUMPAD4);

        }

        protected void addTriggerKeyBindingTo(JToggleButton comp, int... virtualKeys) {
            InputMap im = comp.getInputMap(WHEN_IN_FOCUSED_WINDOW);
            ActionMap am = comp.getActionMap();

            for (int key : virtualKeys) {
                im.put(KeyStroke.getKeyStroke(key, 0), "trigger");
            }
            am.put("trigger", new AbstractAction() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    JToggleButton button = (JToggleButton) e.getSource();
                    button.doClick();
                }
            });
        }

        protected class MovementAction extends AbstractAction {

            private Direction direction;
            private Monitor monitor;

            public MovementAction(String name, Direction direction, Monitor monitor) {
                putValue(NAME, name);
                this.direction = direction;
                this.monitor = monitor;
                putValue(SELECTED_KEY, false);
            }

            @Override
            public void actionPerformed(ActionEvent e) {
                boolean selected = (boolean) getValue(SELECTED_KEY);
                if (selected) {
                    monitor.pressed(direction);
                } else {
                    monitor.released(direction);
                }
            }

        }

        public interface Monitor {

            public void pressed(Direction direction);

            public void released(Direction direction);

        }

    }

}

现在,这个示例并不关心,但您可以使用Monitor 来控制在任何时候触发哪些键/按钮,例如可能通过从pressed 返回一个boolean 值...

【讨论】:

  • 感谢您的回答,但我不确定是否可以使用您的方法实现与现在相同的效果。例如,当用户释放所有按钮时,我需要停止计时器。所以我将不得不监视一些状态,如果不是 JButton#isSelected。而且我不明白队列会如何产生影响。另外,通过关联一个动作,我认为我不能分别检查按下和释放状态。而且您的答案没有提到当用户按下相应的键时如何使按钮看起来被按下。如果您可以修改我的代码以显示您的意思,那将非常有帮助。
  • 如何一次按多个按钮?
  • 现在我在 actionPerformed 方法中调用 setSelected,如代码所示。我使用 isSelected 来知道按钮是否被按下。现在代码只能使用键盘工作。我还没有尝试使用鼠标按下按钮。使用鼠标它总是一个按钮,但使用键盘它可以是多个。
  • 您真正想要实现的目标是什么?您如何一次按多个按钮?
  • 是的,感谢您的回答 +1。在接受你的回答之前,我会等着看是否有人给出了基于 JButton 的解决方案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-10-21
相关资源
最近更新 更多