I need to run through a for loop where the text of a JLabel is set with the counter variable i and after that a Timer method is called. I now want the loop to wait until the method finishes.
Code:
public void actionPerformed(ActionEvent arg0) {
for (int i = 1; i < 6; i++) {
labelNr.setText(Integer.toString(i));
startTimer();
}
}
Because what it does now is that it instantly sets the text of the JLabel to 5, because it runs through the loop, but starting the timer just in the first loop.
I want the text of the JLabel to be 1 for the first loop, then call the Timer method, then 2 for the second loop, again Timer, 3 for the third loop and so on.
It basically just needs to wait until the startTimer() method has finished its execution. I know that synchronized is the possible solution, but I do not know how to implement it properly...
Thanks in advance.
OK, since I want a quick and dirty answer, it was voted down. So how about this:
public void actionPerformed(ActionEvent arg0) {
changeLabelAndExecuteTask(1)
}
public void changeLabelAndExecuteTask(int i){
labelNr.setText(Integer.toString(i));
startTimer(i);
}
public void startTimer(final int i){
//start a timer here
new Timer().schedule(new TimerTask(){
#Override
void run(){
methodTimerCall(i)
}}, 0);
}
public void methodTimerCall(int i){
//execute things here
if(++i < 6){
changeLabelAndExecuteTask(i);
}
}
forget the under part
You can add a static boolean and reset it value by the timer:
bool waitForTimer = false;
public void actionPerformed(ActionEvent arg0) {
for (int i = 1; i < 6; i++) {
waitForTimer = true;
labelNr.setText(Integer.toString(i));
startTimer();
while(waitForTimer){}
}
}
public void startTimer(){
//start a timer here
}
public void methodTimerCall(){
//execute
waitForTimer = false;
}
An effective approach could be to put the code inside the for loop in a separate method, and have the 'startTime()' method call this method at the end of its execution.This way you guarantee that it runs exactly when the method is finished, rather than at an arbitrary time (I.e. with Thread.sleep();)
Related
I need to display output to a JTextArea one character at a time, with a slight delay between each character. My attempt is as follows:
private static void printInput(final String input)
{
Timer timer = new Timer(60,new ActionListener(){
public void actionPerformed(ActionEvent e)
{
for(int i = 0; i<input.length(); i++)
{
messageArea.append(Character.toString(input.charAt(i)));
}
}
});
}
There are similar questions however I could not find one with an example I could use to figure out my problem
A Timer is a pseudo loop, that it is, it triggers an cycle after a predefined delay, each cycle is an iteration of the loop and each iteration you need to update the UI and update the iterator value.
Now, because I don't like working in the static context, the first thing I suggest is you wrap up the basic concept into a separate class. This way you can easily encapsulate the state
public class TypeWriter {
private Timer timer;
private int characterIndex = 0;
private String input;
private JTextArea textArea;
public TypeWriter(JTextArea textArea, String input) {
this.textArea = textArea;
this.input = input;
timer = new Timer(60, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (characterIndex < input.length()) {
textArea.append(Character.toString(input.charAt(characterIndex)));
characterIndex++;
} else {
stop();
}
}
});
}
public void start() {
textArea.setText(null);
characterIndex = 0;
timer.start();
}
public void stop() {
timer.stop();
}
}
So, this is pretty basic, all this does is, using a Timer, check to see if there are any more characters that need to be printed, if there is, it takes the next character and appends it to the text area and updates the iterator value. If we're at the end of the text, it will stop the Timer (ie, the exit condition)
I am having trouble delaying the method assign_backgrounds() within a for loop. I am trying to create a Simon says game, but instead of delaying and showing the next button that "Simon" presses, it shows all the buttons at once. Any help here would be greatly appreciated. Thanks.
boolean simonsTurn = true;
int x = 4;
int s;
int delay = 1000;
int array_values[] = new int[]{1,2,3,4};
public void simonSays() {
// running = true;
if (simonsTurn == true) {
go();
for (int i = 0; i < x; i++) {
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
public void run() {
go();
}
}, 1000);
}
}
}
public void go(){
s = random_int_between(0,3);
assign_backgrounds(array_values[s]);
}
public void assign_backgrounds( int array_values ){
Handler handler = new Handler();
if( array_values == 1){
button1_.invalidate();
button1_.setBackgroundResource(R.drawable.goatclicked);
button1_.refreshDrawableState();
handler.postDelayed(new Runnable(){
public void run(){
button1_.invalidate();
button1_.setBackgroundResource(R.drawable.goat);
button1_.refreshDrawableState();}}, 1000);
}
else if( array_values == 2){
button2_.invalidate();
button2_.setBackgroundResource(R.drawable.pigclicked);
button2_.refreshDrawableState();
handler.postDelayed(new Runnable(){
public void run(){
button2_.invalidate();
button2_.setBackgroundResource(R.drawable.pig);
button2_.refreshDrawableState();}}, 1000);
}
else if( array_values == 3){
button3_.invalidate();
button3_.setBackgroundResource(R.drawable.chickenclicked);
button3_.refreshDrawableState();
handler.postDelayed(new Runnable() {
public void run() {
button3_.invalidate();
button3_.setBackgroundResource(R.drawable.chicken);
button3_.refreshDrawableState();}}, 1000);
}
if( array_values == 4) {
button4_.invalidate();
button4_.setBackgroundResource(R.drawable.cowclicked);
button4_.refreshDrawableState();
handler.postDelayed(new Runnable(){
public void run(){
button4_.invalidate();
button4_.setBackgroundResource(R.drawable.cow);
button4_.refreshDrawableState();}}, 1000);
}
}
It's because you are creating handlers very fast and then they are all starting at the same time. You should look into how Handler's work and about Asyncronous/Background tasks.
Now back to your problem, you are calling the a loop and it is creating handlers all in a row and they are being created very fast (nanoseconds). They will then all launch 1 second from that creation time because of your postDelayed() call. This is why everything is popping up at the same time! All of these delay posts are being executed at almost the same time on concurrent background threads.
Instead of a for(int i,...) loop you want to have a global int i, just add it to the top of the file.
At the end of any of Simon's turn you'll want, inside of the if, else if statement inside assign_background (at the end of the Runnables, then you'll want to call go().
This might cause problems because you are trying to access the main thread from all these background threads. so you might have to call the function runOnMainUIThread() as a quick hack when you call the go function.
All in all, you are going to have some problems until you understand Handlers, Background Processes, and Threads. Definitely great knowledge to learn about and Android has solid documentation on it just FYI.
I am having an issue with a piece of my code in Java, it seems to be creating an endless loop.
public void progress(){
x = 3;
timer = new Timer(800, new ActionListener() {
public void actionPerformed(ActionEvent evt){
System.out.println(x);
x--;
if(x < 1){
UI();
timer.stop();
}
}
});
timer.start();
}
The method UI asks for input via SavitchIn, and it doesn't seem to run the line. I print before I ask for input in the UI method, and the print works just fine. When I remove this timer from my code, and keep the UI method the same, it works fine. Prints and then takes input. I've added a timer.stop() in UI method as well, and I am positive the timer is stopped, however after running the program I am forced to Reset the Virtual Machine the next time around otherwise it wont run. Any help is appreciated!
Your UI() method likely should be called on a background thread as it is likely tying up the Swing event thread (this we have to guess since you don't show it). So create a SwingWorker or a background thread and do this call in there. For more on this, please read Concurrency in Swing.
e.g.,
public void progress() {
x = 3;
timer = new Timer(800, new ActionListener() {
public void actionPerformed(ActionEvent evt) {
System.out.println(x);
x--;
if (x < 1) {
new Thread(new Runnable() {
public void run() {
UI();
}
}).start();
timer.stop();
}
}
});
timer.start();
}
First of all, apologies for how long winded this is.
I'm trying to make a simple roulette game that allows a user to add players, place bets for these players, and spin the roulette wheel, which is represented as a simple JLabel that updates it's text with each number it passes.
However, I've run into a bug that I'm having a lot of trouble with: the JLabel only updates the text for the last element in my loop.
Basically, my solution works like this:
When a user presses a button labelled "Spin" (given that users have been added to the game), I call a method from a class called SpinWheelService, which is an Observable singleton which in turn calls the notifyObservers() method:
public void actionPerformed(ActionEvent e) {
String cmd = e.getActionCommand();
String description = null;
if (ADD_PLAYER.equals(cmd)) {
addDialog();
} else if (PLACE_BET.equals(cmd)) {
betDialog();
} else if (SPIN.equals(cmd)) {
SpinWheelService.sws.setSpinWheelService();
} else if (DISPLAY.equals(cmd)) {
System.out.println("Display selected!");
}
}
Here is my SpinWheelService class:
package model;
import java.util.*;
public class SpinWheelService extends Observable {
public static SpinWheelService sws = new SpinWheelService();
public void setSpinWheelService() {
setChanged();
notifyObservers();
}
}
The only listener registered for SpinWheelService is this class, where GameEngine is my game engine that handles internal game logic, WheelCallbackImpl is a class that updates the View:
class SpinWheelObserver implements Observer {
GameEngine gameEngine;
ArrayList<SimplePlayer> players;
WheelCallbackImpl wheelCall;
int n;
public SpinWheelObserver(GameEngine engine, WheelCallbackImpl wheel, ArrayList<SimplePlayer> playerList) {
players = playerList;
gameEngine = engine;
wheelCall = wheel;
}
public void update(Observable sender, Object arg) {
// check if any players are present
if (players.size() == 0) {
System.out.println("Empty player array!");
return;
}
do {
gameEngine.spin(40, 1, 300, 30, wheelCall);
n = wheelCall.playback();
} while (n== 0);
}
}
The main point of note here is my gameEngine.spin() method, which is this:
public class GameEngineImpl implements GameEngine {
private List<Player> playerList = new ArrayList<Player>();
// method handles the slowing down of the roulette wheel, printing numbers at an incremental delay
public void delay(int millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
System.out.println("Sleep method failed.");
}
}
public void spin(int wheelSize, int initialDelay, int finalDelay,
int delayIncrement, WheelCallback callback) {
Random rand = new Random();
int curNo = rand.nextInt(wheelSize) + 1;
int finalNo = 0;
assert (curNo >= 1);
// while loop handles how long the wheel will spin for
while (initialDelay <= finalDelay) {
delay(initialDelay);
initialDelay += delayIncrement;
// handles rotating nature of the wheel, ensures that if it reaches wheel size, reverts to 1
if (curNo > wheelSize) {
curNo = 1;
callback.nextNumber(curNo, this);
curNo++;
}
assert (curNo <= wheelSize);
callback.nextNumber(curNo, this);
curNo++;
finalNo = curNo - 1;
}
calculateResult(finalNo);
callback.result(finalNo, this);
}
The method callback.nextNumber(curNo, this):
public void nextNumber(int nextNumber, GameEngine engine) {
String strNo = Integer.toString(nextNumber);
assert (nextNumber >= 1);
System.out.println(nextNumber);
wcWheel.setCounter(strNo);
}
Where in, wcWheel is my singleton instance of my View, which contains the method setCounter():
public void setCounter(String value) {
label.setText(value);
}
Sorry for how convoluted my explanation is, but basically what it boils down to is that setCounter() is definitely being called, but seems to only call the setText() method on the final number. So what I'm left with is an empty label that doesn't present the number until the entire roulette has finished spinning.
I've determined that setCounter() runs on the event dispatch thread, and I suspect this is a concurrency issue but I have no idea how to correct it.
I've tried to include all relevant code, but if I'm missing anything, please mention it and I'll post it up as well.
I'm at my wits end here, so if anyone would be kind of enough to help, that would be so great.
Thank you!
Your while loop along Thread.sleep() will block and repainting or changing of the UI until the loop is finished.
Instead you'll want to implement a javax.swing.Timer for the delay, and keep a counter for the number of ticks, to stop it. You can see more at How to Use Swing Timers
The basic construct is
Timer ( int delayInMillis, ActionListener listener )
where delayInMillis is the millisecond delay between firing of an ActionEvent. This event is listened for by the listener. So every time the event is fired, the actionPerfomed of the listener is called. So you might do something like this:
Timer timer = new Timer(delay, new ActionListener()(
#Override
public void actionPerformed(ActionEvent e) {
if (count == 0) {
((Timer)e.getSource()).stop();
} else {
//make a change to your label
count--;
}
}
));
You can call timer.start() to start the timer. Every delay milliseconds, the label will change to what you need it to, until some arbitrary count reaches 0, then timer stops. You can then set the count variable to whatever you need to, if you want to to be random, say depending on how hard the wheel is spun :D
I think you didn't post all the relevant code that is required to know exactly the problem.
But most likely the problem is due to you run your loop and JLabel.setText() in the EDT (Event Dispatching Thread).
Note that updating the UI components (e.g. the text of a JLabel) also happens in the EDT, so while your loop runs in the EDT, the text will not be updated, only after your loop ended and you return from your event listener. Then since you modified the text of the JLabel it will be refreshed / repainted and you will see the last value you set to it.
Example to demonstrate this. In the following example a loop in the event listener loops from 0 to 9 and sets the text of the label, but you will only see the final 9 be set:
JPanel p = new JPanel();
final JLabel l = new JLabel("-1");
p.add(l);
JButton b = new JButton("Loop");
p.add(b);
b.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
for ( int i = 0; i < 10; i++ ) {
l.setText( "" + i );
try { Thread.sleep( 200 ); } catch ( InterruptedException e1 ) {}
}
}
} );
A proposed solution: Use javax.swing.Timer to do the loop's work. Swing's timer calls its listeners in the EDT so it's safe to update swing components in it, and once the listener returns, a component UI update can happen immediately:
JPanel p = new JPanel();
final JLabel l = new JLabel("-1");
p.add(l);
JButton b = new JButton("Loop");
p.add(b);
b.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
new Timer(200, new ActionListener() {
int i = 0;
#Override
public void actionPerformed(ActionEvent e2) {
l.setText("" + i);
if ( ++i == 10 )
((Timer)e2.getSource()).stop();
}
}).start();
}
} );
In this solution you will see the label's text counting from 0 up to 9 nicely.
It's appears to me that your entire game must block in the action handler until the while loop has finished? So the text of the label will be getting updated but only the last update will be visible once the AWT thread is running again.
private class MultipleGensListener implements ActionListener
{
public void actionPerformed(ActionEvent e)
{
for(int i = 0; i < 25; i++)
{
game.runSimulationOneGen();
changeGrid();
}
}
}
//this is the loop. The changeGrid method displays a game grid on a GUI but
// only the 25th iteration is visible on screen. I would like each one to be
// visible for about a half a second before the loop continues.
// I have seen some questions answered on here that are very close to what I'm asking,
// but I just don't really understand how to apply it to my program..
// thanks for any help.
If the code performed by the simulation is quick and does not consume too much CPU and time, then consider using a Swing Timer to do your looping and delay. Otherwise, you'll need to use a background thread such as can be done with a SwingWorker object.
For e.g. if using both Swing Timer and SwingWorker:
private class MultipleGensListener implements ActionListener {
protected static final int MAX_INDEX = 25;
public void actionPerformed(ActionEvent e) {
int timerDelay = 500; // ms delay
new Timer(timerDelay, new ActionListener() {
int index = 0;
public void actionPerformed(ActionEvent e) {
if (index < MAX_INDEX) { // loop only MAX_INDEX times
index++;
// create the SwingWorker and execute it
new SwingWorker<Void, Void>() {
#Override
protected Void doInBackground() throws Exception {
game.runSimulationOneGen(); // this is done in background thread.
return null;
}
#Override
protected void done() {
changeGrid(); // this is called on EDT after background thread done.
}
}.execute(); // execute the SwingWorker
} else {
((Timer) e.getSource()).stop(); // stop the timer
}
}
}).start(); // start the Swing timer
}
}
NEVER BLOCK THE GUI EVENT THREAD
you can use a timer for that and have it only fire 25 times
final Timer t = new Timer(500,null);
t.addActionListener(new ActionListener(){
int i=0;
public void actionPerformed(ActionEvent e){
game.runSimulationOneGen();//run 1 iteration per tick
changeGrid();
if(i>25){t.stop();}
i++;
}
});
t.setRepeats(true);
t.start();
btw the reason only the last iteration is shown is that gui updates (redraws) are done in a separate event, but to let another event trigger you need to return from the listener method which you didn't
the Timer I showed is a more elaborate iteration which lets other events run in between iterations allowing the gui to show the changes
check my post that shows both methods java.swing.Timer#setDelay(int)
and
correct usage of Thread.sleep(int)
java wait cursor display problem