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

标签 java swing key-bindings

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

为了解释更多,

View 上有三个 JButton。我们称它们为 jUp、jLeft 和 jRight。当用户按下 View 上的 jUp 按钮时,程序应定期发送 moveNorth 命令,直到用户释放 jUp 按钮。当用户按下键盘上的向上按钮时,也会发生同样的事情,并且 jUp 按钮应该看起来被按下,直到用户释放键盘上的向上按钮。当用户同时按下键盘的向上和向左按钮时,jUp 和 jLeft 按钮应显示为按下状态,直到用户释放键盘按钮。在用户释放键盘按钮之前,应定期发送向西北移动命令。在代码中,我刚刚使用 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) 正确吗?)面板将位于选项卡中,并且在选择该选项卡时按钮应该可以工作。

最佳答案

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

我个人会不再监视按钮状态,而是使用某种enum或其他可以在Set中添加和删除的常量 >。这将实现状态的方式与作用于状态的过程解耦。

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

Navigation

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来控制在任何时候触发哪个键/按钮,可能通过返回一个boolean例如,来自按下的值...

关于Java Swing 使用箭头按钮发送导航命令,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28735302/

相关文章:

java - 如何将 StringBuffer 的最后一个字符传输到开头?

java - Rest webservice 生成客户端 stub

java - 根据按下的键更改标签文本

keyboard-shortcuts - 使用 Karabiner-Elements 将应用程序键映射到插入符位置的右键单击

java - 即使选择了 JTextField,箭头键 KeyBindings 仍然有效

java - 打印图形对象

java - 为什么 JNLP (Java Web Start) 不下载 jar 文件?

Java JTable - 排序

java - JTable 和 ListSelectionListener

java - 在 JPanel/JFrame 中动态显示 JLabels