I am creating a simple console application in which I can use keyboard arrow keys as an input like a typical remote of a toy. When I press arrow up the console will print the output text "UP" or if I press arrow down it will print "down".
I want to press the arrow key only once, i.e. I am not needed to press enter afterwards to accept my input. I want the input to be accepted automatically on pressing the arrow key.
I already tried some code but this is still not happening and I still need to press enter to accept my input. If you have any idea how I can achieve this as simple as possible, I would really appreciate it.
This sample code will helps you to get the Left Arrow Key Event. You can refer this,
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import javax.swing.*;
public class Test2 extends JPanel {
private static final int PREF_W = 400;
private static final int PREF_H = PREF_W;
private static final int TIMER_DELAY = 50;
private Timer leftKeyTimer = new Timer(TIMER_DELAY , new TimerListener());
public Test2() {
int condition = JComponent.WHEN_IN_FOCUSED_WINDOW;
InputMap inputMap = getInputMap(condition );
ActionMap actionMap = getActionMap();
String leftDownKey = "Left Down";
String leftUpKey = "Left Up";
KeyStroke leftDown = KeyStroke.getKeyStroke(KeyEvent.VK_LEFT , 0, false);
KeyStroke leftUp = KeyStroke.getKeyStroke(KeyEvent.VK_LEFT , 0, true);
inputMap.put(leftDown, leftDownKey);
inputMap.put(leftUp, leftUpKey);
actionMap.put(leftDownKey, new LeftKeyAction(false));
actionMap.put(leftUpKey, new LeftKeyAction(true));
leftKeyTimer.setActionCommand("Left Key");
}
#Override
public Dimension getPreferredSize() {
return new Dimension(PREF_W, PREF_H);
}
private class LeftKeyAction extends AbstractAction {
private boolean onKeyRelease;
public LeftKeyAction(boolean onKeyRelease) {
this.onKeyRelease = onKeyRelease;
}
#Override
public void actionPerformed(ActionEvent e) {
if (onKeyRelease) {
if (leftKeyTimer != null && leftKeyTimer.isRunning()) {
leftKeyTimer.stop();
}
} else {
if (leftKeyTimer != null && !leftKeyTimer.isRunning()) {
leftKeyTimer.start();
}
}
}
}
private class TimerListener implements ActionListener {
public void actionPerformed(ActionEvent actEvt) {
System.out.println(actEvt.getActionCommand());
}
}
private static void createAndShowGui() {
Test2 mainPanel = new Test2();
JFrame frame = new JFrame("Test2");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
This is actually a surprisingly complicated problem.
If you want a true console app (no GUI elements) you have to sacrifice portability.
Most consoles are line buffered by default, so Java won't get any input until enter is pressed. Most can be switched to a character mode, but there is no OS independent way to do this. For more information see http://www.darkcoding.net/software/non-blocking-console-io-is-not-possible/
Related
So, my window detects mouse presses but not key presses.
Here some shortened code:
public class Frame {
public static final int MAX_WIDTH = (int) Toolkit.getDefaultToolkit().getScreenSize().getWidth();
public static final int MAX_HEIGHT = (int) Toolkit.getDefaultToolkit().getScreenSize().getHeight();
private static final JWindow window = new JWindow();
private static final DrawMain dm = new DrawMain();
private static final GIH gih = new GIH();
public static void init() {
window.setSize(CVar.clientSizeX, CVar.clientSizeY);
window.setLocationRelativeTo(null);
window.setAutoRequestFocus(true);
window.add(dm);
window.addMouseListener(mh);
window.addMouseWheelListener(mh);
window.addMouseMotionListener(mh);
window.setVisible(true);
}
public static void update() {
window.remove(dm);
window.removeMouseListener(mh);
window.removeMouseMotionListener(mh);
window.removeMouseWheelListener(mh);
window.setSize(MAX_WIDTH, MAX_HEIGHT);
window.setLocationRelativeTo(null);
window.add(dm);
window.setAutoRequestFocus(true);
window.setAlwaysOnTop(true);
window.addMouseListener(gih);
window.addMouseWheelListener(gih);
window.addMouseMotionListener(gih);
window.addKeyListener(gih);
window.setVisible(true);
}
}
public class GIH implements KeyListener, MouseListener, MouseMotionListener, MouseWheelListener {
#Override
public void keyReleased(KeyEvent e) {
System.out.println(e.getKeyChar());
}
#Override
public void mouseClicked(MouseEvent e) {
switch (e.getButton()) {
case MouseEvent.BUTTON1 -> {
System.out.println("Mouse 1 clicked");
}
case MouseEvent.BUTTON3 -> {
System.out.println("Mouse 3 clicked");
}
}
}
For whatever reason, i get my Mouse 1 clicked message if i click, but neither the key char if i press a key nor the output that the game would usually give on key press. Instead i write the respective character into IntelliJ (my IDE). I tried out multiple variations of window and dm.requestFocus() and window.setAutoRequestFocus(true) but none of them works. Does anyone know why? (Notice: dm is just a class with a paintComponent method)
try to add window.addKeyListener(...) into init() method.
I want to be able to increase the step size on JSpinner when a certain key is pressed. What i tried so far was to put KeyListener on my spinner and when a certain key is pressed, change the value of the step size. When the key is release it should return to it's default.
I think i don't have to directly put the KeyListener onto the JSpinner but rather on its button.
I have honnestly no idea on how to achieve that. What confuse me here is the double Listener.
Here is the code i wrote for the KeyListener :
public class SpinnerKeyIncrement implements KeyListener {
JSpinner spinner;
SpinnerNumberModel spinnerModel;
public SpinnerKeyIncrement(JSpinner s) {
this.spinner = s;
if(spinnerModel == null)
spinnerModel = (SpinnerNumberModel) spinner.getModel();
}
#Override
public void keyPressed(KeyEvent e) {
if(e.getKeyCode() == KeyEvent.VK_CONTROL) {
spinnerModel.setStepSize(.10);
}
else if(e.getKeyCode() == KeyEvent.VK_SHIFT) {
spinnerModel.setStepSize(1);
}
}
#Override
public void keyReleased(KeyEvent e) {
spinnerModel.setStepSize(0.01);
}
#Override
public void keyTyped(KeyEvent e) {}
}
}
Again, I'd use Key Bindings to solve this, and I'd use bindings bound to the JSpinner's InputMap that's active when it has focus:
InputMap inputMap = spinner.getInputMap(JComponent.WHEN_FOCUSED);
A working small example program could look like this:
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import javax.swing.*;
#SuppressWarnings("serial")
public class ChangeStepSize extends JPanel {
private static final String DOUBLE_STEP = "double step";
public static final int SMALL_STEP_SIZE = 1;
public static final int BIG_STEP_SIZE = 10;
// bind to the "d" key, but you could use any key you'd like
private static final int SPECIAL_KEY = KeyEvent.VK_D;
private SpinnerNumberModel numberModel = new SpinnerNumberModel(50, 0, 100, 1);
private JSpinner spinner = new JSpinner(numberModel);
public ChangeStepSize() {
add(spinner);
// set up key bindings. First get InputMap and ActionMap
InputMap inputMap = spinner.getInputMap(JComponent.WHEN_FOCUSED);
ActionMap actionMap = spinner.getActionMap();
// next set bindings for when key is pressed
boolean onKeyRelease = false;
KeyStroke keyStroke = KeyStroke.getKeyStroke(SPECIAL_KEY, 0, onKeyRelease);
inputMap.put(keyStroke, DOUBLE_STEP + onKeyRelease);
actionMap.put(DOUBLE_STEP + onKeyRelease, new DoubleStepAction(onKeyRelease));
// next set bindings for when key is released
onKeyRelease = true;
keyStroke = KeyStroke.getKeyStroke(SPECIAL_KEY, 0, onKeyRelease);
inputMap.put(keyStroke, DOUBLE_STEP + onKeyRelease);
actionMap.put(DOUBLE_STEP + onKeyRelease, new DoubleStepAction(onKeyRelease));
}
// our Action is passed in a parameter to indicate which state it
// gets activated in: key press or key release
private class DoubleStepAction extends AbstractAction {
private boolean onKeyRelease;
public DoubleStepAction(boolean onKeyRelease) {
this.onKeyRelease = onKeyRelease;
}
#Override
public void actionPerformed(ActionEvent e) {
if (onKeyRelease) {
numberModel.setStepSize(SMALL_STEP_SIZE);
} else {
numberModel.setStepSize(BIG_STEP_SIZE);
}
}
}
private static void createAndShowGui() {
ChangeStepSize mainPanel = new ChangeStepSize();
JFrame frame = new JFrame("ChangeStepSize");
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(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
I am using a gui with JTextFields to collect some information and then a JButton that takes that infomration and writes it to a file, sets the gui visibility to false, and then uses Runnable to create an instance of another JFrame from a different class to display a slideshow.
I would like to access some of the information for the JTextFields from the new JFrame slideshow. I have tried creating an object of the previous class with accessor methods, but the values keep coming back null (I know that I have done this correctly).
I'm worried that when the accessor methods go to check what the variables equal the JTextFields appear null to the new JFrame.
Below is the sscce that shows this problem.
package accessmain;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
public class AccessMain extends JFrame implements ActionListener
{
private static final int FRAMEWIDTH = 800;
private static final int FRAMEHEIGHT = 300;
private JPanel mainPanel;
private PrintWriter outputStream = null;
private JTextField subjectNumberText;
private String subjectNumberString;
public static void main(String[] args)
{
AccessMain gui = new AccessMain();
gui.setVisible(true);
}
public AccessMain()
{
super("Self Paced Slideshow");
setSize(FRAMEWIDTH, FRAMEHEIGHT);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLayout(new BorderLayout());
//Begin Main Content Panel
mainPanel = new JPanel();
mainPanel.setBorder(new EmptyBorder(0,10,0,10));
mainPanel.setLayout(new GridLayout(7, 2));
mainPanel.setBackground(Color.WHITE);
add(mainPanel, BorderLayout.CENTER);
mainPanel.add(new JLabel("Subject Number: "));
subjectNumberText = new JTextField(30);
mainPanel.add(subjectNumberText);
mainPanel.add(new JLabel(""));
JButton launch = new JButton("Begin Slideshow");
launch.addActionListener(this);
mainPanel.add(launch);
//End Main Content Panel
}
#Override
public void actionPerformed(ActionEvent e)
{
String actionCommand = e.getActionCommand();
if(actionCommand.equals("Begin Slideshow"))
{
subjectNumberString = subjectNumberText.getText();
if(!(subjectNumberString.equals("")))
{
System.out.println(getSubjectNumber());
this.setVisible(false);
writeFile();
outputStream.println("Subject Number:\t" + subjectNumberString);
outputStream.close();
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
AccessClass testClass = new AccessClass();
testClass.setVisible(true);
}
});
}
else
{
//Add warning dialogue here later
}
}
}
private void writeFile()
{
try
{
outputStream = new PrintWriter(new FileOutputStream(subjectNumberString + ".txt", false));
}
catch(FileNotFoundException e)
{
System.out.println("Cannot find file " + subjectNumberString + ".txt or it could not be opened.");
System.exit(0);
}
}
public String getSubjectNumber()
{
return subjectNumberString;
}
}
And then creating a barebones class to show the loss of data:
package accessmain;
import javax.swing.*;
import java.awt.*;
public class AccessClass extends JFrame
{
AccessMain experiment = new AccessMain();
String subjectNumber = experiment.getSubjectNumber();
public AccessClass()
{
System.out.println(subjectNumber);
}
}
Hardcoding the accessor method with "test" like this:
public String getSubjectNumber()
{
return "test";
}
Running this method as below in the new JFrame:
SelfPaceMain experiment = new SelfPaceMain();
private String subjectNumber = experiment.getSubjectNumber();
System.out.println(subjectNumber);
Does cause the system to print "test". So the accessor methods seem to be working. However, trying to access the values from the JTextFields doesn't seem to work.
I would read the information from the file I create, but without being able to pass the subjectNumber (which is used as the name of the file), I can't tell the new class what file to open.
Is there a good way to pass data from JTextFields to other classes?
pass the argument 'AccessMain' or 'JTextField' to the second class:
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
AccessClass testClass = new AccessClass(AccessMain.this); //fixed this
testClass.setVisible(true);
}
});
Then reading the value of 'subjectNumber'(JTextField value) from the 'AccessMain' or 'JTextField' in the second class:
public class AccessClass extends JFrame
{
final AccessMain experiment;
public AccessClass(AccessMain experiment)
{
this.experiment = experiment;
}
public String getSubjectNumber(){
return experiment.getSubjectNumber();
}
}
Also, you should try Observer pattern.
A simple demo of Observalbe and Observer
Observable and Observer Objects
I have two frames (One main and one popup). On the main frame key & mouse listeners are added. Both work just fine. On the pop up there are 50 buttons. I have one action listener for all of them. It works fine. I also have key & mouse listeners. Mouse works. Key is flaky.
The same keyListener class used in main frame is added to the pop-up frame too.
As soon as the pop up shows, key listener works, once mouse click happens (Action listener kicks in) keylistener stops working. Please help. The code attached is a simplified version
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class key {
private static class toolsAction implements ActionListener {
public void actionPerformed (ActionEvent ae) {
log("Command " + Integer.parseInt(ae.getActionCommand()));
}
}
private static void log(String s) { System.out.println(s); }
private static class keyboardHandler implements KeyListener {
public void keyPressed( KeyEvent e) { log("KB Press called "); }
public void keyReleased(KeyEvent e) { log("KB Release called "); }
public void keyTyped (KeyEvent e) { log("KB Typed called "); }
}
public static void main(String [] args) {
JFrame pFrame = new JFrame("Frame");
pFrame.addKeyListener(new keyboardHandler());
Container pane = pFrame.getContentPane();
pane.setLayout(null);
pane.setVisible(true);
pFrame.setSize(650, 300);
pFrame.setVisible(true);
JButton[] buttons = new JButton[50];
toolsAction action = new toolsAction();
for (int i = 0; i < 50; i++) {
buttons[i] = new JButton("" + i);
buttons[i].setActionCommand("" + i);
buttons[i].addActionListener(action);
pane.add(buttons[i]);
buttons[i].setBounds(((i % 10) * 60), ((i / 10) * 40), 60, 40);
}
}
}
An alternative approach uses Action and key bindings. The example below binds 10 buttons to the number keys, also using the numbers as the MNEMONIC_KEY of each.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
/** #see http://stackoverflow.com/a/14182227/230513 */
public class Key {
public static final int SIZE = 10;
private static class ToolAction extends AbstractAction {
public ToolAction(int i) {
super(String.valueOf(i));
putValue(MNEMONIC_KEY, KeyEvent.VK_0 + i);
}
#Override
public void actionPerformed(ActionEvent ae) {
System.out.println(ae.getActionCommand());
}
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame f = new JFrame("Frame");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setLayout(new GridLayout(0, SIZE / 2));
for (int i = 0; i < SIZE; i++) {
final ToolAction toolAction = new ToolAction(i);
JButton b = new JButton(toolAction);
String name = b.getText();
b.getInputMap(JButton.WHEN_IN_FOCUSED_WINDOW).put(
KeyStroke.getKeyStroke(KeyEvent.VK_0 + i, 0), name);
b.getActionMap().put(name, toolAction);
f.add(b);
}
f.pack();
f.setVisible(true);
}
});
}
}
The KeyListener will only be executed if the component, to which the listener is added, has the (keyboard) focus. If you click on another component, that component will become the focus and the keyboard events will be sent to its KeyListeners.
Not sure if that is the problem without seeing the code of the popup, but it's the problem in the posted code...
EDIT
you can add an AWTEventListener to the Toolkit to intercept all events independently of the focused component:
private static class AWTListener implements AWTEventListener {
#Override
public void eventDispatched(AWTEvent event) {
log("AWT: " + event);
}
};
...
Toolkit toolkit = Toolkit.getDefaultToolkit();
toolkit.addAWTEventListener(new AWTListener(), AWTEvent.KEY_EVENT_MASK);
I solved this problem by adding MouseMotionListener and in the mouseMoved method calling pFrame.requestFocusInWindow() & also adding focus request in the button's actionPerformed method.
Even with just one button in a frame as long as the ActionListener is added to the button, KeyListener does not work.
This is kludgy at best, but works for me. I would still love to hear from the community why my original code does not work.
I have hit another wall. After getting my key input working, I have been racking my brains for hours, i want to create a pause function, so that if the same key is pressed again the timertask stops running (i.e the game is paused)
JPanel component = (JPanel)frame.getContentPane();
component.getInputMap().put(KeyStroke.getKeyStroke("SPACE"), "space");
component.getActionMap().put("space", (new AbstractAction(){
public void actionPerformed(ActionEvent e){
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask(){
public void run(){
grid.stepGame();
}
},250, 250);
}}));
}
The problem is i cant use a global boolean isRunning var and switch it each time the key is pressed because the timerTask method in a nested class (so the boolean isRunning would have to be declared final to be accessed...). Any ideas on how to detect if the key is pressed again or if the game is already running so i can pause/cancel my timerTask.
Many Thanks Sam
Since this is a Swing game, you should be using a javax.swing.Timer or Swing Timer and not a java.util.Timer. By using a Swing Timer, you guarantee that the code being called intermittently is called on the EDT, a key issue for Swing apps, and it also has a stop method that pauses the Timer. You can also give your anonymous AbstractAction class a private boolean field to check if the key is being pressed for the first time or not.
Also, kudos and 1+ for using Key Bindings instead of a KeyListener.
e.g.,
JPanel component = (JPanel) frame.getContentPane();
component.getInputMap().put(KeyStroke.getKeyStroke("SPACE"), "space");
component.getActionMap().put("space", (new AbstractAction() {
private boolean firstPress = true;
private int timerDelay = 250;
private javax.swing.Timer keyTimer = new javax.swing.Timer(timerDelay , new ActionListener() {
// Swing Timer's actionPerformed
public void actionPerformed(ActionEvent e) {
grid.stepGame();
}
});
// key binding AbstractAction's actionPerformed
public void actionPerformed(ActionEvent e) {
if (firstPress) {
keyTimer.start();
} else {
keyTimer.stop();
}
firstPress = !firstPress;
}
}));
Another useful option is to perform a repeating task on key press and stop it on key release, and this can be done easily by getting the keystrokes for on press and on release:
KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0, true) // for key release
KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0, false) // for key press
For example:
import java.awt.event.*;
import javax.swing.*;
public class SwingTimerEg2 {
private JFrame frame;
private Grid2 grid = new Grid2(this);
private JTextArea textarea = new JTextArea(20, 20);
private int stepCount = 0;
public SwingTimerEg2() {
frame = new JFrame();
textarea.setEditable(false);
frame.add(new JScrollPane(textarea, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
JScrollPane.HORIZONTAL_SCROLLBAR_NEVER));
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
setUpKeyBinding();
}
void setUpKeyBinding() {
final int timerDelay = 250;
final Timer keyTimer = new Timer(timerDelay, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
grid.stepGame();
}
});
JPanel component = (JPanel) frame.getContentPane();
final int condition = JComponent.WHEN_IN_FOCUSED_WINDOW;
final String spaceDown = "space down";
final String spaceUp = "space up";
component.getInputMap(condition).put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0, false), spaceDown);
component.getActionMap().put(spaceDown, (new AbstractAction() {
public void actionPerformed(ActionEvent e) {
keyTimer.start();
}
}));
component.getInputMap(condition).put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0, true), spaceUp);
component.getActionMap().put(spaceUp, (new AbstractAction() {
public void actionPerformed(ActionEvent e) {
keyTimer.stop();
}
}));
}
public void doSomething() {
textarea.append(String.format("Zap %d!!!%n", stepCount));
stepCount ++;
}
private static void createAndShowGui() {
new SwingTimerEg2();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
class Grid2 {
private SwingTimerEg2 stEg;
public Grid2(SwingTimerEg2 stEg) {
this.stEg = stEg;
}
void stepGame() {
stEg.doSomething();
}
}
Easiest and dirty solution:
final boolean[] isRunning = new boolean[1];
You don't want to do that—but it works assuming proper synchronization around.
What would be better is
final AtomicBoolean isRunning = new AtomicBoolean();
What would be even better is to review the design once again: global state usually means, "global problems"
The final qualifier requirement can easily be avoided -- replace your inner method (which has the final requirement) with a call to a class method.
No you got the wrong idea about WHY you need final for anonymous classes! Final is only needed for local variables (well more exactly any variable that might have a live time shorter than the given object).
Hence a static variable in a class is perfectly fine and will work perfectly!
Edit: example:
public class Main {
interface Test {
void call();
}
public static volatile boolean running = true;
public static void main(String[] args) {
Test t = new Test() {
#Override
public void call() {
System.out.println(Main.running);
}
};
t.call();
running = false;
t.call();
}
}
Keep a reference to the Timer somewhere, say in your game class.
When the game is paused cancel the Timer.
This will cancel any currently scheduled tasks.
Then when the game is unpaused schedule the timer again as you have done above.
public class Game {
private Timer timer;
public void pause() {
if (timer != null) {
timer.pause();
}
}
public void startOrResumeGame() {
if (timer == null) {
timer = new Timer();
} else {
// Just in case the game was already running.
timer.cancel();
}
timer.scheduleAtFixedRate(new TimerTask() {
public void run() {
grid.stepGame();
}
}, 250, 250);
}
}