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,可能通过返回一个 boolean
值pressed
例如...
我需要使用 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,可能通过返回一个 boolean
值pressed
例如...