How to create a delay in Swing - java

I made a blackjack game, and I want the AI player to pause between taking cards. I tried simply using Thread.sleep(x), but that makes it freeze until the AI player is done taking all of his cards. I know that Swing is not thread safe, so I looked at Timers, but I could not understand how I could use one for this. Here is my current code:
while (JB.total < 21) {
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
System.out.println("Oh noes!");
}
switch (getJBTable(JB.total, JB.aces > 0)) {
case 0:
JB.hit();
break;
case 1:
break done;
case 2:
JB.hit();
JB.bet *= 2;
break done;
}
}
BTW, the hit(); method updates the GUI.

so I looked at Timers, but I could not understand how I could use one for this
The Timer is the solution, since as you say you are updating the GUI which should be done on the EDT.
I'm not sure what your concern is. You deal a card and start the Timer. When the Timer fires you decide to take another card or hold. When you hold your stop the Timer.

Well, the following code shows a JFrame with a JTextArea and a JButton. When the buttons is clicked, the Timer send the event repeatedly (with a second delay between them) to the actionListener related to the button which appends a line with the current time.
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Calendar;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JTextArea;
import javax.swing.Timer;
public class TimerTest extends JFrame implements ActionListener{
private static final long serialVersionUID = 7416567620110237028L;
JTextArea area;
Timer timer;
int count; // Counts the number of sendings done by the timer
boolean running; // Indicates if the timer is started (true) or stopped (false)
public TimerTest() {
super("Test");
setBounds(30,30,500,500);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setLayout(null);
area = new JTextArea();
area.setBounds(0, 0, 500, 400);
add(area);
JButton button = new JButton("Click Me!");
button.addActionListener(this);
button.setBounds(200, 400, 100, 40);
add(button);
// Initialization of the timer. 1 second delay and this class as ActionListener
timer = new Timer(1000, this);
timer.setRepeats(true); // Send events until someone stops it
count = 0; // in the beginning, 0 events sended by timer
running = false;
System.out.println(timer.isRepeats());
setVisible(true); // Shows the frame
}
public void actionPerformed(ActionEvent e) {
if (! running) {
timer.start();
running = true;
}
// Writing the current time and increasing the cont times
area.append(Calendar.getInstance().getTime().toString()+"\n");
count++;
if (count == 10) {
timer.stop();
count = 0;
running = false;
}
}
public static void main(String[] args) {
// Executing the frame with its Timer
new TimerTest();
}
}
Well, this code is a sample of how to use javax.swig.Timer objects. In relation with the particular case of the question. The if statement to stop the timer must change, and, obviously, the actions of the actionPerformed. The following fragment is a skeleton of the solution actionPerformed:
public void actionPerformed(ActionEvent e) {
if (e.getComponent() == myDealerComponent()) {
// I do this if statement because the actionPerformed can treat more components
if (! running) {
timer.start();
runnig = true;
}
// Hit a card if it must be hitted
switch (getJBTable(JB.total, JB.aces > 0)) {
case 0:
JB.hit();
break;
case 1:
break done;
case 2:
JB.hit();
JB.bet *= 2;
break done;
}
if (JB.total >= 21) { // In this case we don't need count the number of times, only check the JB.total 21 reached
timer.stop()
running = false;
}
}
}
IMHO this resolves the problem, now #user920769 must think where put the actionListener and the starting/stopping conditions...
#kleopatra: Thanks for show me the existence of this timer class, I don't know nothing about it and it's amazing, make possible a lot of tasked things into a swing application :)

Well, a quick explanation about Timers.
First of all, you need a java.util.Timer variable in your class and another class in your project which extends from java.util.TimerTask (let's call it Tasker).
The initialization of the Timer variable is so easy:
Timer timer = new Timer();
Now the Tasker class:
public class Tasker extends TimerTask {
#Override
public void run() {
actionToDo(); // For example take cards
}
// More functions if they are needed
}
Finally, the installation of the timer with its related Tasker:
long delay = 0L;
long period = pauseTime;
timer.schedule(new Tasker(),delay,period);
The schedule function indicates the following:
Fisrt param: Action to do each period milliseconds (Executes the run function of a TimerTask class or its extension)
Second param: When the timer must start. In this case, it starts when the schedule function is called. The following example indicates a starting 1 second after call the schedule function: timer.schedule(new Tasker(),1000,period);
Third param: milliseconds between one call of Tasker.run() function and the following call.
I hope you understand this microtutorial :). If you have any problem, ask for more detailed information!
Kind regards!

I think that in this tutorial is clear how to use Timers in order to achieve what you want, without having to deal with Threads.

Related

How can I pause/continue scheduled task?

I'm new to JAVA and trying to learn some concurrency concepts.
I have a simple GUI class that pops-up a window with 1 button which I want to use for pause/continue.
Also, I have a class that extends TimerTask, it looks like below and start with the GUI:
public class process extends TimerTask {
public void run() {
while(true) { /*some repetitive macro commands.. */ }
}
}
Real question is, how can I pause the task onClick of the button and also continue onClick of the button if already paused?
I have taken a step to use a boolean to flag the button as it changes from pause to continue on each click.
But then I had to type a lot of while(button); for busy waiting inside the while()...
Do you think I can make like Thread.sleep() or something but from outside the task thread?
OLD ANSWER
Basically, there is no support for pause and resume on TimerTask, you can only cancel, check here
perhaps you might want to read about threading as that's the alternative I know of that has an interrupt and start features and then you can keep track of the progress of what you're doing to resume where it stopped.
So, I will suggest you go through this link, because you need to understand threading not just copy a code to use, there is a sample code there that will definitely solve your problem also.
Note that running an endless while loop will basically cause your program not to respond, unless the system crashes. At a certain point, the data becomes an overload and the program will overflow. This means it will fail.
.
NEW ANSWER
So, response to the new question, I was able to run a tiny little program to demonstrate how you can achieve something that looks like multithreading when working with SWING.
To rephrase your question: You want to run an indefinite task like let say we're playing a song, and then onclick of a button to pause the song, on click again should continue the song?, if so, I think below tiny program might work for you.
public class Test{
static JLabel label;
static int i = 0;
static JButton action;
static boolean x = false; //setting our x to false initialy
public static void main(String[] args) {
JFrame f=new JFrame();//creating instance of JFrame
label = new JLabel("0 Sec"); //initialized with a text
label.setBounds(130,200,100, 40);//x axis, y axis, width, height
action=new JButton("Play");//initialized with a text
action.setBounds(130,100,100, 40);//x axis, y axis, width, height
f.add(action);//adding button in JFrame
f.add(label);
action.addActionListener(new ActionListener(){
#Override
public void actionPerformed(ActionEvent e){
if(x){
x = false; //Update x here
action.setText("Play");
action.revalidate();
}else{
x = true; //Update x here also
action.setText("Pause");
action.revalidate();
if(x){ //Using x here to determind whether we should start our child thread or not.
(new Thread(new Child())).start();
}
}
}
});
f.setSize(500, 700);//500 width and 700 height
f.setLayout(null);//using no layout managers
f.setVisible(true);//making the frame visible
}
}
class Child implements Runnable{
#Override
public void run() {
while (x) {
//You can put your long task here.
i++;
label.setText(i+" Secs");
label.revalidate();
try {
sleep(1000); //Sleeping time for our baby thread ..lol
} catch (InterruptedException ex) {
Logger.getLogger("No Foo");
}
}
}
}

JLabel's Set Location inside for not moving smoothly

i'm using a label for a little sprite for some testing that i'm doing, but i want to move the sprite 10 pixels per keypress. Now, i can do that, but now i'm trying to make it move the 10 pixels smoothly, so i tried the next code:
for(int i = 0; i < 100; i++){
x++;
container.setLocation(x, y);
System.out.println(x);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Now the problem is that, the sprite only moves when the for cycle ends, but the console shows the X value changing for each iteration. Any thoughts/help?
Thanks!
I suggest you to take a look at how to animate a JComponent using Swing Timer class, instead of for loop. You can find various tutorials about how to use Swing Timer. Here, to briefly explain, you are blocking EDT(Event Dispatch Thread) which operates the graphical side of the Java. Whenever you want to make a constant and smooth flow in your animations, make sure that you never block the EDT.
EDIT: Here is the demonstration of the usage of Swing Timer Class:
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.Timer;
public class AnimationTrial extends JFrame {
private final int DELAY = 10;
private Timer timer;
private int x, y;
private JLabel label;
public static void main(String[] args) {
EventQueue.invokeLater( new Runnable () {
#Override
public void run() {
new AnimationTrial();
}
});
}
public AnimationTrial()
{
setSize(500, 500);
x = 50;
y = 50;
label = new JLabel("They see me movin' they hatin'!");
timer = new Timer( DELAY, new ActionListener()
{
#Override
public void actionPerformed(ActionEvent arg0) {
x++;
label.setLocation(x, y);
}
});
timer.start();
getContentPane().add(label);
pack();
setVisible (true);
}
}
If you dont create new Thread, the user interface runs on the same thread as its method.
Therefore your for-cycle is fired after some action and thread cant do anything else until it ends.
Solution : Create your own class, pass the JLabel or the whole form as parameter in constructor, implement threading and run it as new thread.
I'd suggest you give a look to the Timing Framework, if you want to do something close to an animation in Swing. It could help you, depending on your general need.
If you want other sprites to move in sync with your sprite you can create a TimerTask and use scheduleAtFixedRate(). Your TimerTask would then be responsible for moving all sprites and redrawing everything that was part of the moving like the JPanel in the background and the sprites.
To make your code snippet work you would have to add redrawing of the Background and the sprite after setting the location but I would advise against that approach as it can easily lead to badly designed code where you create one God Class that does everything.
The TimerTask approach should also be more precise if the calculations need a bit time as it tries to have the same time between 2 calls where the approach with the sleeping thread can easily lead to different delays if the calculations are finished earlier or later.

Java gui countdown

I need to make a GUI where a worker enters a station (a spot on the panel) and stays there for a set amount of seconds, shown in a countdown about the workers head (so, once the workers moves to the spot, the station's label shows 3s -> 2s -> 1s and then the worker leaves, and the label reverts back to "OPEN"). I'm having trouble with making this happen, as I'm not too good with the Timer(s?) that Java has. I tried with something like this:
Timer timer = new Timer(1000, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e)
{
//change label text/color, decrement countdown
panel.repaint();
Thread.sleep(1000);
}
});
But I can't reach the number of seconds to count down from from inside the timer, and I'm not sure how to pass that value to the timer. If someone can help me out, I'd really appreciate it.
Get rid of the Thread.sleep(). That's what the 1000 in Timer(1000, new ActionListener() does. It sets an interval for each timer event. Every time a timer event is fired, the actionPerformed is called. So you need to determine what needs to happen every "tick", and put that code in the actionPerformed. Maybe something like
Timer timer = new Timer(1000, new ActionListener() {
private int count = 5;
#Override
public void actionPerformed(ActionEvent e) {
if (count <= 0) {
label.setText("OPEN");
((Timer)e.getSource()).stop();
count = 5;
} else {
label.setText(Integer.toString(count);
count--;
}
}
});
You need to decide when to call timer.start().
For general information, see How to Use Swing Timers
Problem #1: You are calling Thread.sleep() from within the Swing GUI thread. That causes the thread to stop taking input and freeze. Delete that line. It does you no good! While you are at it, delete the repaint call as well.
Now that that's said and done, instead of creating an anonymous instance of ActionListener, you can create an actual class that implements ActionListener and provides a constructor. That constructor can have as an argument the number of seconds you want to start counting down. You can declare that class inside the method you are using, or you can declare it inside the class.
Here's a skeletal example:
public class OuterClass {
JLabel secondsLabel = ...;
Timer myTimer;
private void setupTimer(int numSecondsToCountDown) {
secondsLabel.setText(Integer.toString(numSecondsToCountDown));
myTimer = new Timer(1000, new CountdownListener(numSecondsToCountDown));
myTimer.start();
}
// ...
class CountdownListener implements ActionListener {
private int secondsCount;
public CountdownListener(int startingSeconds) { secondsCount = startingSeconds; }
public void actionPerformed(ActionEvent evt) {
secondsLabel.setText(Integer.toString(secondsCount);
secondsCount--;
if (secondsCount <= 0) { // stop the countdown
myTimer.stop();
}
}
}
}

JLabel changing values through setText but not updating on screen

Well, I have been working on a piece of code for almost 2 days now and not able to resolve the issue.
DESIRED BEHAVIOUR
The following code is supposed to display 10 strings one by one (the next replacing the previous one) with a gap of aprox. 200 ms.
q1
q2
q3
...and so on upto q10
This display sequence starts when the user presses ENTER key.
REFLECTED BEHAVIOUR
The screen waits for aprox. 2 sec after pressing and then shows q10.
Some more info
The label stringText changes value during execution (which I found by writing to console) but the same is not updated on screen (JFrame).
The label changes values through click event on a button, everything else remaining same (as much as possible).
The timer is through a while loop - this may not be as per most people's liking, but lets forget it for the time being.
The method displayQuestion(int number) has a few unnecessary lines. I put them all because I was not sure what would work. Actually, nothing worked!
THE CODE
package sush4;
import java.util.Date;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class Sush4 {
// Timer control variables
static long holdTimeWord = 200L;
static long elapsedTime = 0L;
// Counter for Strings displayed
static int i = 0;
// Strings in use
static String[] questionStack = {"q1", "q2", "q3", "q4", "q5", "q6", "q7", "q8", "q9", "q10"};
// UI: String display variables
static JLabel stringText;
static JFrame mainWindow;
// Key binding action object
private static Action userKeyCommand;
/// Display the question
private static void displayQuestion(int number) {
mainWindow.remove(stringText);
stringText.setText(questionStack[number]);
mainWindow.add(stringText);
mainWindow.setVisible(true);
mainWindow.revalidate();
mainWindow.repaint();
}
private static void q120(){
//// Initiate the text
for(i = 0; i < questionStack.length; i++) {
displayQuestion(i);
//// And wait for Word hold time
long startTime = System.currentTimeMillis();
elapsedTime = 0L;
// Now wait for event to happen
while ( (elapsedTime < holdTimeWord) ) {
elapsedTime = (new Date()).getTime() - startTime;
}
}
}
Sush4() {
//// Create the Window
mainWindow = new JFrame("Sush");
mainWindow.setSize(700, 500);
mainWindow.setLayout(new FlowLayout());
//// And add key bindings for user events
userKeyCommand = new UserKeyCommand();
JRootPane rootPane = mainWindow.getRootPane();
rootPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("ENTER"), "doEnterAction");
rootPane.getActionMap().put("doEnterAction", userKeyCommand);
// Terminate the program when the user closes the application.
mainWindow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
mainWindow.setResizable(false);
//// Add the text label
stringText = new JLabel("Random Text");
mainWindow.add(stringText);
//// Finally, display the frame.
mainWindow.setVisible(true);
}
static class UserKeyCommand extends AbstractAction {
public void actionPerformed( ActionEvent tf ) {
q120();
}
}
public static void main(String[] args) {
// Create the frame on the event dispatching thread.
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new Sush4();
}
});
}
}
The timer is through a while loop - this may not be as per most people's liking, but lets forget it for the time being.
Actually we can't forget about this while loop because this is what is causing troubles. See, q120() method is called when you click a button:
static class UserKeyCommand extends AbstractAction {
#Override // don't forget #Override annotation
public void actionPerformed( ActionEvent tf ) {
q120();
}
}
It means this code is executed in the context of the Event Dispatch Thread (EDT). This is a single and special thread where Swing components must be created/updated and event handling (i.e.: action events) must be performed. If we have a loop in this thread waiting for some condition to continue we'll block the EDT and GUI won't be able to repaint itself until the thread is unlocked.
For repetitive tasks (such as the one in your question) consider use a Swing Timer. For heavy tasks with interim results consider use a SwingWorker instead.

Timer start/stop parameters

I've made leaps and bounds in skill and progress since joining this community. You all are a huge help. I'm having trouble with giving a timer that I've implemented certain parameters for when it starts and stops.
I either get errors saying "the local variable timer may not have been initialized" or I get no errors, but nothing happens. Maybe I have the timer in the wrong place?
If I put timer.start(); in the constructor too everything works fine, but then the timer has to start when the program is initialized. I would really like to have the timer not start until a certain parameter is met. Say, for instance, until the int p1Laps=1; but if I place timer.start(); into an if-statement in the constructor (i.e. if(p1Laps>=1) { timer.start(); } the timer doesn't ever start.
I've tried placing timer.start(); in various places and have either gotten no response or generated an error about the lack of local variable timer.
A second, somewhat related problem I have is the inability to put any parameters in place to call on timer.stop(); without getting the aforementioned "local variable timer may not have been initialized" error. I've left timer.stop(); where I think it needs to be in the code, but it receives that error.
So in short, I want to be able to tell the timer to start when a parameter is met, namely when a player has completed a lap. And I want to be able to tell the timer to stop when it reaches a value.
Thanks in advance for the great advice I'm sure I'll receive. Note: this is not the whole code, just relevant information.
import javax.swing.*;
import javax.swing.event.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.awt.geom.*;
public class RacerDoom extends JFrame {
int counter = 0;
int p1Laps = 0;
public RacerDoom() {
//create JFrame
super("Racer Doom Squared");
setSize(WIDTH,HEIGHT);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setVisible(true);
//set up Timer
final Timer timer=new Timer(1000, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if(counter>=10) {
timer.stop(); //error here reads "local variable timer may
//not have been initialized"
}
else{
counter++;
}
System.out.println(counter);
}
});
//inner class threads
Move1 m1 = new Move1();
m1.start();
//start timer
if(p1Laps>=1) {
timer.start(); //error here is that timer will not start when
//p1Laps>=1
}
}
private class Move1 extends Thread implements KeyListener {
public void run() {
addKeyListener(this);
while(true) {
try {
repaint();
//collisions
if(p1.intersects(finishtop)&&p1Direction==UP&&p1cross!=true){
p1cross=true;
p1Laps++;
p1Boost++;
counter=0;
System.out.println(p1Laps);
}
if(p1.intersects(finishtop)==false) {
p1cross=false;
}
public static void main (String [] args) {
new RacerDoom();
}
}
As you want to start and stop the timer at different places in the code you should make it member variable. This will fix the problem where you are trying to stop the timer inside the action listener.
The variable p1Laps will not change in the constructor (after you have initialized it to 0) so you need to start the timer where you change the value of plLaps. I am not sure if it is safe to call timer.start() from another thread (Move1). So it may be safer to start timer with SwingUtilities.invokeLater().
Quick fix:
Rather than
timer.stop();
Do
((Timer)e.getSource()).stop();
The ActionEvent's getSource method will return a reference to the object that calls the actioPerformed method (the Timer), so this should work.
There may be other issues with your code including your background thread without a Thread.sleep(...), your use of KeyListeners rather than Key Binding, your adding a KeyListener in a background thread,...

Categories

Resources