Java Swing Sending Navigation Commands Using Arrow Buttons - java

I need to send navigation commands using JButtons and the keyboard. If the "Up" button/up key is pressed I need to send "Move North" command, if up and left buttons are pressed(from keyboard), I need to send "Move North West" command etc.. The commands should be sent periodically(every 1 second). The following is the code I have.
To explain more,
There are three JButtons on the view. Let's call them jUp, jLeft and jRight. When the user presses the jUp button on the view, the program should send moveNorth commands periodically until the user releases the jUp button. When the user presses the up button on the keyboard, the same thing should happen, and the jUp button should look pressed until the user releases the keyboard up button. When the user presses the keyboard up and left buttons together, the jUp and jLeft buttons should appear pressed until user releases the keyboard buttons. And until user releases the keyboard buttons, a move northWest command should be sent periodically. In the code I have just printed the command using a 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();
}
}
}
Now for the questions.
a) Even though I call the setSelected method, the button state does not change to pressed. (Visually it doesn't change to pressed state). How can I achieve this?
b) Is there a better/more standard way of achieving this functionality? Using mnemonics/ExecutorService etc..? Am I correct in adding the actions to the "buttons" element's input map. (Is buttons.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT) correct?) The panel will be in a tab and the buttons should work when that tab is selected.

JButton doesn't have a selected state, it has an armed. In order to have a button which maintain a "pressed" state when the mouse or key is released, you must use a JToggleButton.
I would, personally, move away from monitoring the button states, personally, and instead, use some kind enum or other constants which can be added and removed from a Set. This decouples the means by which the state is achieved from the process that is acting upon the state.
From there, you can use a single Action which would be used to notify some kind of observer that the state has changed
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);
}
}
}
Now, this example doesn't care, but you could use the Monitor to control which key/buttons where triggered at any one time, probably by returning a boolean value from pressed for example...

Related

Progress Bar value is not updating [duplicate]

I have made a very simple code to show it here, i have a button that should show a JDialog to check the progress status, i am using the invoke late to go through EDT and my loop isn't in the run method, so why isn't my bar updating ?
here is the code
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class JBarEx extends JFrame {
private JTextField progStatus = new JTextField("Undefined");
private JButton dialogBtn = new JButton("Show Progression dialog");
final JDialog dlg = new JDialog((JFrame) null, "prog Title", false);
final JProgressBar dpb = new JProgressBar(0, 100);
public JBarEx() {
JPanel pan = new JPanel();
dialogBtn.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
showProgress();
}
});
progStatus.setEditable(false);
pan.add(progStatus);
pan.add(dialogBtn);
setContentPane(pan);
this.setSize(200, 100);
setVisible(true);
}
public void showProgress() {
dlg.add(BorderLayout.CENTER, dpb);
dlg.add(BorderLayout.NORTH, new JLabel("prog message"));
dlg.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
dlg.setSize(300, 75);
dlg.setLocationRelativeTo(null);
dlg.setVisible(true);
for (int i = 0; i < 100; i++) {
final int ii = i;
try {
Thread.sleep(25);
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
updateBar(ii);
}
});
}
catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public void updateBar(int newValue) {
dpb.setValue(newValue);
}
public static void main(String[] args) {
JBarEx jbx = new JBarEx();
}
}
Your showProgress method is being executed within the context of the Event Dispatching Thread. The EDT is responsible for, amongst other things, processing paint requests. This means that so long as your for-loop is executing, the EDT can not process any new paint requests (or handle the invokeLater events either) as it is blocking the EDT.
While there are any number of possible ways to solve the problem, based on your code example, the simplest would be to use a SwingWorker.
It has the capacity to allow your to execute the long running task the a background thread (freeing up the EDT), but also allows you means for publishing updates (if required) so that they can be processed in the EDT and also provides handy progress notification.
For example...
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.SwingWorker;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.border.EmptyBorder;
public class SwingWorkerProgress {
public static void main(String[] args) {
new SwingWorkerProgress();
}
public SwingWorkerProgress() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private JProgressBar pbProgress;
private JButton start;
public TestPane() {
setBorder(new EmptyBorder(10, 10, 10, 10));
pbProgress = new JProgressBar();
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(4, 4, 4, 4);
gbc.gridx = 0;
gbc.gridy = 0;
add(pbProgress, gbc);
start = new JButton("Start");
gbc.gridy++;
add(start, gbc);
start.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
start.setEnabled(false);
ProgressWorker pw = new ProgressWorker();
pw.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
String name = evt.getPropertyName();
if (name.equals("progress")) {
int progress = (int) evt.getNewValue();
pbProgress.setValue(progress);
repaint();
} else if (name.equals("state")) {
SwingWorker.StateValue state = (SwingWorker.StateValue) evt.getNewValue();
switch (state) {
case DONE:
start.setEnabled(true);
break;
}
}
}
});
pw.execute();
}
});
}
}
public class ProgressWorker extends SwingWorker<Object, Object> {
#Override
protected Object doInBackground() throws Exception {
for (int i = 0; i < 100; i++) {
setProgress(i);
try {
Thread.sleep(25);
} catch (Exception e) {
e.printStackTrace();
}
}
return null;
}
}
}
Check out Concurrency in Swing for more details
Even if you fix the loop as others have pointed out, you'd still block the event dispatch thread. The for loop is run in showProgress() which is called from an event listener. The updates are pushed to the event queue, but that does not get processed until the loop has completed.
Use a Swing Timer instead. Something like this:
Timer timer = new Timer(25, new ActionListener() {
private int position;
#Override
public void actionPerformed(ActionEvent e) {
position++;
if (position < lastPosition) {
updateBar(position);
} else {
((Timer) e.getSource).stop();
}
}
});
timer.start();
where lastPosition would be the state where you want the progress bar to stop.
Unrelated to that bug, but a bug still, you should not create swing components outside the event dispatch thread. It's best to do it right from the start:
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JBarEx jbx = new JBarEx();
}
});
}
for (int i = 0; i < 0; i++) {
You will never enter this code so will never call the updateBar(..) method
i needs to be greater than 0 in this case. If it is 1 then updateBar will be called once, if 2 then updateBar will be called twice etc
Also rather than doing
Thread.sleep(25);
take a look at java executors as these will help with your scheduling and remove the need for the sleep

How to make jButton.setVisible() work instantaneously?

I've written a test program with making the jButton invisible and visible:
import java.awt.Dimension;
import javax.swing.*;
import javax.swing.event.*;
import java.awt.event.*;
public class Blink
{
private JButton btn;
private static JFrame f;
public static void delay(int ms)
{
try
{
Thread.sleep(ms);
}
catch(InterruptedException ex)
{
Thread.currentThread().interrupt();
}
}
public Blink()
{
f = new JFrame("Blink");
f.setPreferredSize(new Dimension(500, 500));
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
btn = new JButton("Click me and I'll blink!");
f.add(btn);
btn.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent event)
{
buttonClicked();
}
});
f.pack();
f.setVisible(true);
}
private void buttonClicked()
{
for (int i = 0; i < 5; i++)
{
delay(300);
btn.setVisible(false);
delay(300);
btn.setVisible(true);
}
}
public static void main(String[] args)
{
new Blink();
}
}
Unfortunately, the jButton does not blink. And when the buttonClicked() function is changed, so that the jButton is set invisible 5 times and is not set visible back, the jButton disappears only when the for-loop finishes. How to make the jButton disappear an reappear instantaneously?
You cannot use Thread.sleep method in Swing Thread (all listeners are called in Event Dispatcher Thread - EDT). To achieve blinking you must use javax.swing.Timer class. For more information look here and here
Here is your reworked example:
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class Blink {
private JButton btn;
private JFrame f;
public void delay(int ms, boolean show) {
Timer timer = new Timer(ms, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
btn.setVisible(show);
btn.getParent().revalidate();
btn.getParent().repaint();
}
});
timer.setRepeats(false);
timer.start();
}
public Blink() {
f = new JFrame("Blink");
f.setPreferredSize(new Dimension(500, 500));
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
btn = new JButton("Click me and I'll blink!");
f.add(btn);
btn.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent event) {
buttonClicked();
}
});
f.pack();
f.setVisible(true);
}
private void buttonClicked() {
for (int i = 1; i <= 10; i += 2) {
delay(300 * i, false);
delay(300 * (i + 1), true);
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new Blink();
}
});
}
}
For some complicated layouts, call setVisible(false) may have side-effects. In this case the CardLayout with your component and an empty panel should be used.
Here is the variant with CardLayout
import java.awt.CardLayout;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class Blink {
private static final String BUTTON_CARD = "button";
private static final String EMPTY_CARD = "empty";
private JButton btn;
private JFrame f;
private final CardLayout cardLayout = new CardLayout();
public void delay(int ms, boolean show) {
Timer timer = new Timer(ms, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
cardLayout.show(btn.getParent(), show ? BUTTON_CARD : EMPTY_CARD);
btn.getParent().revalidate();
btn.getParent().repaint();
}
});
timer.setRepeats(false);
timer.start();
}
public Blink() {
f = new JFrame("Blink");
f.setPreferredSize(new Dimension(500, 500));
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setLayout(cardLayout);
btn = new JButton("Click me and I'll blink!");
f.add(btn, BUTTON_CARD);
f.add(new JPanel(), EMPTY_CARD);
btn.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent event) {
buttonClicked();
}
});
f.pack();
f.setVisible(true);
}
private void buttonClicked() {
for (int i = 1; i <= 10; i += 2) {
delay(300 * i, false);
delay(300 * (i + 1), true);
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new Blink();
}
});
}
}
As #Sergiy points out - make sure you're running from the EDT, and don't sleep on the EDT, use a swing timer instead.
To make your jButton appear "invisbile", you can do something like this:
public void setInvisible(jButton jb) {
jb.setOpaque(false);
jb.setContentAreaFilled(false);
jb.setBorderPainted(false);
jb.setText("");
}
// Assuming you have the original text saved in a variable
public void setRevisible(jButton jb) {
jb.setOpaque(true);
jb.setContentAreaFilled(true);
jb.setBorderPainted(true);
jb.setText(originalString);
}
Depending on if you want the button to be clickable when it's invisible, you can also add btn.setEnabled(bool);

How to set a shortcut key for JRadioButton without modifiers

I'm working in a project where I need to add a key shortcut for each JRadioButton, while looking on another similar question and as I'm using some other custom Actions I decided to use the method setAction on each of my JRadioButtons, however it requires me to press ALT + 1 - ALT + 5 to "trigger" the actionPerformed method of my CustomAction class.
How can I modify this class in order to just press 1 - 5 and get the same behaviour?
This is the code I made that demonstrates this issue:
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import javax.swing.AbstractAction;
import javax.swing.BoxLayout;
import javax.swing.ButtonGroup;
import javax.swing.JFrame;
import javax.swing.JRadioButton;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
public class RadioButtonSelectableByNumbers {
private JFrame frame;
private JRadioButton buttons[];
private ButtonGroup group;
public RadioButtonSelectableByNumbers() {
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new RadioButtonSelectableByNumbers().createAndShowGui();
}
});
}
public void createAndShowGui() {
frame = new JFrame("frame");
buttons = new JRadioButton[5];
group = new ButtonGroup();
frame.getContentPane().setLayout(new BoxLayout(frame.getContentPane(), BoxLayout.PAGE_AXIS));
for (int i = 0; i < buttons.length; i++) {
buttons[i] = new JRadioButton();
switch (i) {
case 0:
buttons[i].setAction(new CustomAction(String.valueOf(i + 1), KeyEvent.VK_1));
break;
case 1:
buttons[i].setAction(new CustomAction(String.valueOf(i + 1), KeyEvent.VK_2));
break;
case 2:
buttons[i].setAction(new CustomAction(String.valueOf(i + 1), KeyEvent.VK_3));
break;
case 3:
buttons[i].setAction(new CustomAction(String.valueOf(i + 1), KeyEvent.VK_4));
break;
default:
buttons[i].setAction(new CustomAction(String.valueOf(i + 1), KeyEvent.VK_5));
break;
}
group.add(buttons[i]);
frame.getContentPane().add(buttons[i]);
}
frame.pack();
frame.setVisible(true);
}
class CustomAction extends AbstractAction {
public CustomAction(String name, Integer mnemonic, Integer modifier) {
super(name);
putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(mnemonic, modifier));
}
public CustomAction(String name, Integer mnemonic) {
super(name);
putValue(MNEMONIC_KEY, mnemonic);
}
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("radio clicked");
}
}
}
How do you tie any key to a component in Swing? Key Bindings: Key Bindings Tutorial.
Hang on while I look at your code...
For example
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.BoxLayout;
import javax.swing.ButtonGroup;
import javax.swing.InputMap;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JRadioButton;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
public class RadioButtonSelectableByNumbers {
private JFrame frame;
private JRadioButton buttons[];
private ButtonGroup group;
public RadioButtonSelectableByNumbers() {
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new RadioButtonSelectableByNumbers().createAndShowGui();
}
});
}
public void createAndShowGui() {
frame = new JFrame("frame");
frame.setDefaultCloseOperation(JFrame);
buttons = new JRadioButton[5];
group = new ButtonGroup();
frame.getContentPane().setLayout(new BoxLayout(frame.getContentPane(), BoxLayout.PAGE_AXIS));
for (int i = 0; i < buttons.length; i++) {
JRadioButton rbtn = createButton(i);
buttons[i] = rbtn;
frame.getContentPane().add(rbtn);
}
frame.pack();
frame.setVisible(true);
}
private JRadioButton createButton(int i) {
String name = String.valueOf(i + 1);
int stdMnemonic = KeyEvent.VK_1 + i; // for standard number keys
int numpadMnemonic = KeyEvent.VK_NUMPAD1 + i; // for numpad number keys
Action action = new CustomAction(name, stdMnemonic);
JRadioButton rBtn = new JRadioButton(action);
group.add(rBtn);
// bindings active if window is focused. Component doesn't have to be focused
int condition = JComponent.WHEN_IN_FOCUSED_WINDOW;
InputMap inputMap = rBtn.getInputMap(condition);
ActionMap actionMap = rBtn.getActionMap();
KeyStroke keyStroke = KeyStroke.getKeyStroke(stdMnemonic, 0);
KeyStroke keyStroke2 = KeyStroke.getKeyStroke(numpadMnemonic, 0);
inputMap.put(keyStroke, keyStroke.toString());
actionMap.put(keyStroke.toString(), action);
inputMap.put(keyStroke2, keyStroke2.toString());
actionMap.put(keyStroke2.toString(), action);
return rBtn;
}
class CustomAction extends AbstractAction {
public CustomAction(String name, Integer mnemonic, Integer modifier) {
super(name);
putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(mnemonic, modifier));
}
public CustomAction(String name, Integer mnemonic) {
super(name);
putValue(MNEMONIC_KEY, mnemonic);
}
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("radio clicked: " + e.getActionCommand());
}
}
}
Another solution, that may be better, since usually JRadioButtons don't use ActionListeners, is to create an AbstractAction that simply clicks the button. In the example below, MyAction does this, as well as gives the active JRadioButton the focus:
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.KeyEvent;
import javax.swing.*;
#SuppressWarnings("serial")
public class NumberActions extends JPanel {
private ButtonGroup buttonGroup = new ButtonGroup();
public NumberActions() {
ItemListener itemListener = new MyItemListener();
setLayout(new GridLayout(1, 0));
for (int i = 0; i < 10; i++) {
JRadioButton rBtn = createRadioBtn(i);
rBtn.addItemListener(itemListener);
buttonGroup.add(rBtn);
add(rBtn);
}
}
private JRadioButton createRadioBtn(int i) {
String text = String.valueOf(i);
JRadioButton rBtn = new JRadioButton(text);
rBtn.setActionCommand(text);
int condition = JComponent.WHEN_IN_FOCUSED_WINDOW;
InputMap inputMap = rBtn.getInputMap(condition);
ActionMap actionMap = rBtn.getActionMap();
Action action = new MyAction(rBtn);
bindAction(inputMap, actionMap, action, KeyEvent.VK_0 + i);
bindAction(inputMap, actionMap, action, KeyEvent.VK_NUMPAD0 + i);
return rBtn;
}
private void bindAction(InputMap inputMap, ActionMap actionMap, Action action, int mnemonic) {
KeyStroke keyStroke = KeyStroke.getKeyStroke(mnemonic, 0);
inputMap.put(keyStroke, keyStroke.toString());
actionMap.put(keyStroke.toString(), action);
}
private class MyItemListener implements ItemListener {
#Override
public void itemStateChanged(ItemEvent iEvt) {
if (iEvt.getStateChange() == ItemEvent.SELECTED) {
AbstractButton btn = (AbstractButton) iEvt.getSource();
System.out.println("Button: " + btn.getActionCommand());
}
}
}
private class MyAction extends AbstractAction {
private AbstractButton btn;
public MyAction(AbstractButton btn) {
this.btn = btn;
}
#Override
public void actionPerformed(ActionEvent e) {
btn.requestFocusInWindow();
btn.doClick();
}
}
private static void createAndShowGui() {
NumberActions mainPanel = new NumberActions();
JFrame frame = new JFrame("NumberActions");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
}
edit: code fixed

Which objects do I attach the Timer class event listener to, in Java?

I am trying to create a whack a mole game. I have used swing to create background and add mole images with event listeners which increment a score each time they are clicked, but I am having problems setting whether they should be visible or not. I thought the best way to do this would be to use a timer to set/reset a boolean (vis). Randomizing the period for which the images are visible would be ideal. I have tried using a swing timer several times but doesn't seem to be working. Where do I instantiate the timer, and to what do I attach the event listener which executes the code after the timer has counted down?
package whackmole;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.Timer;
public class WhackAMole extends JFrame {
public WhackAMole() {
createAndShowGUI();
}
static int score = 0;
public static JLabel scoreDisplay;
boolean vis;
public static void main(String[] args) throws Exception {
// run asynchronously
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
private static void createAndShowGUI() {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setMinimumSize(new Dimension(600, 600));
Holes holes = new Holes(frame);
frame.getContentPane().add(holes);
holes.setLayout(null);
frame.pack();
frame.setVisible(true);
scoreDisplay = new JLabel("Score: " + score);
scoreDisplay.setBounds(239, 11, 84, 38);
holes.add(scoreDisplay);
Mole mole = new Mole(68, 92, true);
mole.addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
score++;
scoreDisplay.setText("Score: " + score);
}
});
holes.add(mole);
Mole mole2 = new Mole(181, 320, false);
mole2.addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
score++;
scoreDisplay.setText("Score: " + score);
}
});
holes.add(mole2);
Mole mole3 = new Mole(414, 439, true);
mole3.addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
score++;
scoreDisplay.setText("Score: " + score);
}
});
holes.add(mole3);
Mole mole4 = new Mole(297, 203, false);
mole4.addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
score++;
scoreDisplay.setText("Score: " + score);
}
});
holes.add(mole4);
}
}
In this context, you can instantiate your Timer with a fixed rate and a class that implements ActionListener.
public class Example extends JPanel implements ActionListener {
private static final int RATE = 1000 / 8; // ~8 Hz
private final Timer timer = new Timer(RATE, this);
}
In this complete example, GameButton is a subclass of JToggleButton, and the implementation of ActionListener simply toggles the state of a randomly selected GameButton.
private final List<GameButton> buttons = new ArrayList<GameButton>(MAX);
public void actionPerformed(ActionEvent e) {
Object src = e.getSource();
if (src == timer) {
int index = random.nextInt(game.max());
GameButton gb = buttons.get(index);
gb.setSelected(!gb.isSelected());
}
...
}
To distinguish states, the example uses Unicode glyphs, but you can use setIcon() and setSelectedIcon().

JButton does not change color when button is typed

I have a very small question. Now I wrote the code for creating a virtual keyboard. I want the color of the button to change when it is typed. Here is my code:
public class ButtonColor implements KeyListener {
#Override
public void keyPressed(KeyEvent e) {
// TODO Auto-generated method stub
}
#Override
public void keyReleased(KeyEvent e) {
// TODO Auto-generated method stub
}
#Override
public void keyTyped(KeyEvent e) {
if (e.getKeyChar()=='a') {
A.setBackground(Color.red);
}
}
}
]
Whenever I press A, nothing happens. When I add this line:
JOptionPane.showMessageDialog(null, "A was typed");
then type a, the message appears and after I click OK the button changes color. Why does that happen? How can I fix this problem?
There could be any number of reasons why this doesn't work for you, for starters, the button may be transparent (opaque == false)
I would strongly recommend against KeyListener in favour of Key Bindings as KeyListener has issues with focus...
For example...
The following uses the key bindings API in order to respond to a given key stroke, depending on if it's a key press or release event, it will set the background color and opacity state accordingly and even sets the buttons pressed state...
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class KeyboardTest {
public static void main(String[] args) {
new KeyboardTest();
}
public KeyboardTest() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
public TestPane() {
JButton btnA = createButton("A");
JButton btnB = createButton("B");
JButton btnC = createButton("C");
JButton btnD = createButton("D");
JButton btnE = createButton("E");
add(btnA);
add(btnB);
add(btnC);
add(btnD);
add(btnE);
addKeyBinding(btnA, "A", KeyEvent.VK_A);
addKeyBinding(btnB, "B", KeyEvent.VK_B);
addKeyBinding(btnC, "C", KeyEvent.VK_C);
addKeyBinding(btnD, "D", KeyEvent.VK_D);
addKeyBinding(btnE, "E", KeyEvent.VK_E);
}
protected JButton createButton(String text) {
JButton btn = new JButton(text);
btn.setFocusable(false);
return btn;
}
protected void addKeyBinding(JButton btn, String name, int virtualKey) {
ActionMap am = getActionMap();
InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW);
im.put(KeyStroke.getKeyStroke(virtualKey, 0, false), name + ".pressed");
im.put(KeyStroke.getKeyStroke(virtualKey, 0, true), name + ".released");
am.put(name + ".pressed", new KeyAction(btn, true));
am.put(name + ".released", new KeyAction(btn, false));
}
}
public class KeyAction extends AbstractAction {
private JButton btn;
private boolean highlight;
public KeyAction(JButton btn, boolean highlight) {
this.btn = btn;
this.highlight = highlight;
}
#Override
public void actionPerformed(ActionEvent e) {
if (highlight) {
btn.getModel().setPressed(true);
btn.setBackground(Color.RED);
btn.setOpaque(true);
} else {
btn.getModel().setPressed(false);
btn.setBackground(null);
btn.setOpaque(false);
}
}
}
}
Updated
If you also use btn.getModel().setArmed(...); you will get a much more "bolded" response, which produces better visual feedback...IMHO

Categories