Pausing a timerTask - java

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);
}
}

Related

Slowing Down Program's execution to show steps

I have a program that runs a basic algorithm for solving a puzzle. I am trying to slow it down so that the user can see the algorithm in action. I want the program to show the steps that it takes to solve the puzzle. I have tried to use a timer and even thread.sleep to try and slow it down. However, my GUI (JPanel) is not repainting before or after the delay statement. What is happening is the delay is happening and the algorithm is solving step by step, but the program GUI is not updating/repainting to show the steps. Here is the code for that section:
delay(System.currentTimeMillis(), 500);
repaint();
The delay part is all good. Can anybody tell me why the JPanel is not repainting?
Here is the delay method that I made:
public static void delay (long a, int x) //Simple Delay Method
{
while((System.currentTimeMillis()-a) < (x/10)){}
}
You can't just sleep the UI thread and wait for repaint to happen, that will stop all user and os interaction for that time (including repaint events). From the repaint documentation:
If this component is a lightweight component, this method causes a call to this component's paint method as soon as possible.
As soon as possible does not mean immediately, it will most likely schedule a repaint, which will run when the ui thread is released.
You have to change your algorithm tohold it's state in an object, and make it possible to execute step-by-step, and use a timer like in this answer.
Example:
public class SwingMain extends JFrame {
public class MyPanel extends JPanel {
public String state = "Initial";
private JButton btn_start;
public MyPanel() {
super();
btn_start = new JButton("Start");
add(btn_start);
btn_start.addActionListener(e -> doSolve());
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setFont(getFont());
g.drawString(state, 10, 20);
};
#Override
public Dimension getPreferredSize() {
return new Dimension(640, 480);
}
}
private List<String> states;
private int stateIndex;
protected Task token;
private MyPanel pnl_main;
public SwingMain() {
super("Step by step");
setupUi();
}
protected void setupUi() {
pnl_main = new MyPanel();
getContentPane().add(pnl_main);
}
private List<String> modelSolve() {
return Arrays.asList("First", "Second", "Third", "Fourth", "Fifth");
}
public void doSolve() {
this.stateIndex = 0;
this.states = modelSolve();
if (token != null)
token.cancel();
token = GuiTimer.scheduleAtFixedRate(() -> {
if (stateIndex >= states.size()) {
token.cancel();
} else {
pnl_main.state = states.get(stateIndex);
stateIndex++;
pnl_main.repaint();
}
}, 0, 500, TimeUnit.MILLISECONDS);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
SwingMain frame = new SwingMain();
frame.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
System.out.println(frame.pnl_main.getWidth());
System.out.println(frame.pnl_main.getHeight());
});
}
}

Why does SwingUtilities.invokeLater() cause JButton to freeze?

Consider this basic Swing program, consisting out of two buttons:
public class main {
public static void main(String[] args) {
JFrame jf = new JFrame("hi!");
JPanel mainPanel = new JPanel(new GridLayout());
JButton longAction = new JButton("long action");
longAction.addActionListener(event -> doLongAction());
JButton testSystemOut = new JButton("test System.out");
testSystemOut.addActionListener(event -> System.out.println("this is a test"));
mainPanel.add(longAction);
mainPanel.add(testSystemOut);
jf.add(mainPanel);
jf.pack();
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.setVisible(true);
}
public static void doLongAction() {
SwingUtilities.invokeLater(() -> {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
System.out.println("Interrupted!");
}
System.out.println("Finished long action");
});
}
}
I want my second button testSystemOut to be usable while the first one is working on its long action (here, I put a 3 second sleep in it). I can do that by manually putting doLongAction() in a Thread and call start(). But I've read I should use SwingUtilities instead, which works exactly like EventQueue here. However, if I do so, my Button freezes for the duration of its action.
Why?
By using SwingUtilities.invokeLater, you are calling the enclosed code, including the Thread.sleep(...) call, on the Swing event thread, which is something you should never do since it puts the entire event thread, the thread responsible for drawing your GUI's and responding to user input, to sleep -- i.e., it freezes your application. Solution: use a Swing Timer instead or do your sleeping in a background thread. If you are calling long-running code and using a Thread.sleep(...) to simulate it, then use a SwingWorker to do your background work for you. Please read Concurrency in Swing for the details on this. Note that there is no reason for the SwingUtilities.invokeLater where you have it since the ActionListener code will be called on the EDT (the Swing event thread) regardless. I would however use SwingUtilities.invokeLater where you create your GUI.
e.g.,
import java.awt.*;
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.concurrent.ExecutionException;
import javax.swing.*;
public class Main {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFrame jf = new JFrame("hi!");
JPanel mainPanel = new JPanel(new GridLayout());
JButton testSystemOut = new JButton("test System.out");
testSystemOut.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("this is a test");
}
});
mainPanel.add(new JButton(new LongAction("Long Action")));
mainPanel.add(new JButton(new TimerAction("Timer Action")));
mainPanel.add(testSystemOut);
jf.add(mainPanel);
jf.pack();
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.setVisible(true);
}
});
}
#SuppressWarnings("serial")
public static class LongAction extends AbstractAction {
private LongWorker longWorker = null;
public LongAction(String name) {
super(name);
int mnemonic = (int) name.charAt(0);
putValue(MNEMONIC_KEY, mnemonic);
}
#Override
public void actionPerformed(ActionEvent e) {
setEnabled(false);
longWorker = new LongWorker(); // create a new SwingWorker
// add listener to respond to completion of the worker's work
longWorker.addPropertyChangeListener(new LongWorkerListener(this));
// run the worker
longWorker.execute();
}
}
public static class LongWorker extends SwingWorker<Void, Void> {
private static final long SLEEP_TIME = 3 * 1000;
#Override
protected Void doInBackground() throws Exception {
Thread.sleep(SLEEP_TIME);
System.out.println("Finished with long action!");
return null;
}
}
public static class LongWorkerListener implements PropertyChangeListener {
private LongAction longAction;
public LongWorkerListener(LongAction longAction) {
this.longAction = longAction;
}
#Override
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getNewValue() == SwingWorker.StateValue.DONE) {
// if the worker is done, re-enable the Action and thus the JButton
longAction.setEnabled(true);
LongWorker worker = (LongWorker) evt.getSource();
try {
// call get to trap any exceptions that might have happened during worker's run
worker.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
}
#SuppressWarnings("serial")
public static class TimerAction extends AbstractAction {
private static final int TIMER_DELAY = 3 * 1000;
public TimerAction(String name) {
super(name);
int mnemonic = (int) name.charAt(0);
putValue(MNEMONIC_KEY, mnemonic);
}
#Override
public void actionPerformed(ActionEvent e) {
setEnabled(false);
new Timer(TIMER_DELAY, new TimerListener(this)).start();
}
}
public static class TimerListener implements ActionListener {
private TimerAction timerAction;
public TimerListener(TimerAction timerAction) {
this.timerAction = timerAction;
}
#Override
public void actionPerformed(ActionEvent e) {
timerAction.setEnabled(true);
System.out.println("Finished Timer Action!");
((Timer) e.getSource()).stop();
}
}
}
Don't use SwingUtilities.invokeLater(...) when you want to execute some long-running code. Do that in a separate normal thread.
Swing is not multi-threaded, it's event-driven. Because of that there are methods like SwingUtilities.invokeLater(...). You have to use those methods if you want to alter Swing-Components from a different thread (since Swing is not thread-safe), for example if you want to change a Button's text.
Everything thats GUI-Related runs in that Swing-Thread, e.g. Cursor-Blinks, Messages from the OS, User Commands, etc.
Since its a single thread, every long running Code in this thread it will block your GUI.
If you just do some long-running code that isn't GUI-related, it shouldn't run in the Swing-Event-Thread, but in its own separated thread.
See
https://weblogs.java.net/blog/kgh/archive/2004/10/multithreaded_t.html
for why Swing is not Multi-Threaded.

make jbutton to turn on and off

My goal is creating a function that waits half second, set the jbutton's background to red for one second, and after this second the jbutton will return to normal. Cant make this work..
This is my Function
private void paint(final int num){
Timer timer = new Timer(500, new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (num == 1){
btn.setBackground(Color.black);
}
}
});
timer.start();
}
Start a 500ms timer that will do two things when it goes off:
- change the color to red
- start a 1s timer that will change the color to normal, when it goes off
Well, this would do it (note, you would probably want to put the code from the first sleep onwards into a timer or it's own thread in a real application to avoid blocking the thread running the code):
public static void main(String[] args) throws Exception {
final JFrame jf = new JFrame();
final JButton jButton = new JButton("Hello");
final Color originalBackground = jButton.getBackground();
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
jf.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
jf.getContentPane().add(jButton);
jf.pack();
jf.setVisible(true);
}
});
Thread.sleep(500);
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
jButton.setBackground(Color.RED);
}
});
Thread.sleep(1000);
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
jButton.setBackground(originalBackground);
}
});

Display a countdown

I need a timer implemented in my application, which will do a countdown from 10 sec - 0 sec.
and, display the countdown in a JLabel.
Here's my implementation;
...
Timer t = new Timer(1000, new List());
t.start();
}
class List implements ActionListener{
#Override
public void actionPerformed(ActionEvent e) {
int sec = 0;
label.setText(""+sec);
// Do a if- condition check to see if the clock has reached to, and then stop
}
}
I was expecting the JLabel to start counting from 0 - 10 and then stop. But it doesn't. The JLabel set the value 0 and it doesn't get incremented.
UPDATE 1
t = new Timer(1000, new Listner());
t.start();
}
class Listner implements ActionListener{
private int counter = 0;
#Override
public void actionPerformed(ActionEvent e) {
lable.setText(""+ (counter++));
if (counter == 10)
t.removeActionListener(this);
}
}
You are not storing nor incrementing secs anywhere so I don't see how it should get updated, try with
Timer timer;
void start() {
timer = new Timer(1000,new List());
}
class List implements ActionListener {
private counter = 0;
#Override
public void actionPerformed(ActionEvent e) {
label.setText(""+counter++);
if (counter == 10)
timer.removeActionListener(this);
}
}
Mind that you need to store a reference to the timer somewhere to be able to remove the listener from it once countdown finished.
Well each time the timer is called it declares the int variable sec to 0. Hence the Label doesnt get updated.
You should declare the sec variable as a global variable and then in the actionPerformed method increment its value each time it is called.
public int sec = 0;
class List implements ActionListener{
#Override
public void actionPerformed(ActionEvent e) {
sec++;
label.setText(""+sec);
// Do a if- condition check to see if the clock has reached to, and then stop
}
}
A complete example
public class ATimerExample {
Timer timer;
int counter = 0;
public ATimerExample() {
final JFrame frame = new JFrame("somethgi");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final JLabel label = new JLabel("0");
JPanel panel = new JPanel();
panel.add(label, BorderLayout.SOUTH);
frame.getContentPane().add(panel);
frame.pack();
frame.setVisible(true);
timer = new Timer(1000, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
label.setText(String.valueOf(counter));
counter++;
if (counter == 10) {
//timer.removeActionListener(this);
timer.stop();
}
}
});
timer.start();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new ATimerExample();
}
});
}
}
Since java reads the time in milliseconds, it should be 10000 instead of 1000. try your code and see if that works. I had the same problem when I wanted 30 seconds. And instead of writing Timer t = new Timer(30000, new List()); t.start();
I wrote Timer t = new Timer(3000, new List());
t.start();
That made my program to stop every after 3 seconds. I would suggest, you use 10000 instead of 1000.
Remember to do: t.stop() in your List class. Thanks

How can I repaint a label while doing some processing, in Swing?

I'm new to Swing and I was trying to do this:
On pressing a JButton, the program will start iterating over hundreds of items, taking 1 second to process each one, and after finishing each one he should update a label to show the number of items already processed.
The problem is, the label's text is not updated until the cycle finishes iterating over all the items.
I searched online and apparently it's because this is running in the same thread, so I created a new thread to process the data and to update the variable to be used in the label (number of processed files).
But it didn't work. Then I even made another thread, which I start after the previous one, that just repaints the label. Still nothing works.
The code is like this:
btnNewButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
try { SwingUtilities.invokeLater(validateFiles); }
}); }
Runnable validateFiles = new Runnable() {
#Override
public void run() {
while(x_is_not_100) {
processLoadsOfStuff();
label.setText(x); }
}
};
Can you help me with this?
Simple - use a SwingWorker. For more information, read the Tasks that Have Interim Results tutorial.
Here's a pretty generic example that will use a JLabel to display counting from 0 to 30 -
public final class SwingWorkerDemo {
private static JLabel label =
new JLabel(String.valueOf(0), SwingConstants.CENTER);
public static void main(String[] args){
SwingUtilities.invokeLater(new Runnable(){
#Override
public void run() {
createAndShowGUI();
}
});
JLabelSwingWorker workerThread = new JLabelSwingWorker();
workerThread.run();
}
private static void createAndShowGUI(){
final JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(label);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
private static class JLabelSwingWorker extends SwingWorker<Void, Integer>{
#Override
protected Void doInBackground() throws Exception {
for(int i = 1; i < 31; i++){
Thread.sleep(1000);
publish(i);
}
return null;
}
#Override
protected void process(List<Integer> integers) {
Integer i = integers.get(integers.size() - 1);
label.setText(i.toString());
}
}
}
The background processing must be done in a separate thread. But the label update must be done in the event dispatch thread.
So your code should look like this:
btnNewButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
// start a new thread for the background task
new Thread(validateFiles).start();
});
}
Runnable validateFiles = new Runnable() {
#Override
public void run() {
while(x_is_not_100) {
processLoadsOfStuff();
// use SwingUtilities.invokeLater so that the label update is done in the EDT:
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
label.setText(x);
}
});
}
};
But you might want to use the SwingWorker class, which is designed to do that in a simpler way. Its documentation is very well done and contains examples.

Categories