Java Swing 使用箭头按钮发送导航命令

Java Swing Sending Navigation Commands Using Arrow Buttons

我需要使用 JButton 和键盘发送导航命令。如果按下 "Up" button/up 键我需要发送 "Move North" 命令,如果按下向上和向左按钮(从键盘),我需要发送 "Move North West" 命令等。 . 命令应定期发送(每 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) 是否有 better/more 实现此功能的标准方法?使用 mnemonics/ExecutorService 等..?我将操作添加到 "buttons" 元素的输入映射是否正确。 (buttons.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)正确吗?)面板将在一个选项卡中,并且选择该选项卡时应工作。

JButton 没有 selected 状态,它有 armed。为了让按钮在释放鼠标或按键时保持 "pressed" 状态,您必须使用 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 来控制在任何时候触发哪个 key/buttons,可能通过返回一个 booleanpressed 例如...