Reputation: 27
I have a problem with Thread sleep. I'm coding easy alarm clock, which will set up 10 min then it will count down in label(lab1). When I press button, it freezes and thats all. Where is problem? I cant work with threads as master so far thats the reason why it is linearly. And can i have a question? How do I execute shell command? (Sorry, but it wants to make this longer. But it interest me too);
public class Budik extends JFrame{
private JLabel lab1,lab2;
private JButton butt1,butt2,butt3;
private JTextField t1,t2;
private JSpinner spinner;
private JCheckBox cx1,cx2;
private JList list;
private JPanel p1,p2,p3,p4,p5;
private JPasswordField pass1,pass2;
public static void main(String[] args)
{
Budik okno = new Budik();
okno.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
okno.setVisible(true);
okno.pack();
okno.setLocationRelativeTo(null);
okno.setResizable(false);
}
public Budik() {
super("Countdown");
setLayout(new BorderLayout());
/////////////////////////////////////////////////////////////////
// p1
GridLayout el = new GridLayout(2,1);
p1 = new JPanel(el);
add(p1,BorderLayout.NORTH);
lab1 = new JLabel("Welcome",SwingConstants.CENTER); //centr labelu
lab2 = new JLabel("Created by WajFaj",SwingConstants.CENTER); //centr labelu
lab1.setFont(new Font("Serif", Font.PLAIN, 36)); //velikost fontu
p1.add(lab1);
p1.add(lab2);
//p2
p2 = new JPanel();
add(p2,BorderLayout.WEST);
butt1 = new JButton("START");
p2.add(butt1);
//p3
p3 = new JPanel();
add(p3,BorderLayout.CENTER);
butt2 = new JButton("STOP");
p3.add(butt2);
//p4
p4 = new JPanel();
add(p4,BorderLayout.EAST);
butt3 = new JButton("RESET");
p4.add(butt3);
//p5
p5 = new JPanel();
add(p5,BorderLayout.SOUTH);
butt1.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent evt){
for(int i=600; i>0;i-- ){
try{
Thread.sleep(1000);
String cc = ""+i;
lab1.setText(cc);
}
catch(InterruptedException e){
System.out.println("Chyba!");
}
}
}
});
Upvotes: 1
Views: 1147
Reputation: 6808
I am going to drop here of how i would do it (furthermore how it should be). Comments inside the code.
package test;
import java.awt.BorderLayout;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.concurrent.TimeUnit;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JPasswordField;
import javax.swing.JSpinner;
import javax.swing.JTextField;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class Budik extends JFrame {
private static final int SLEEP_MINUTES = 10;
private long endTime;
private JLabel lab1, lab2;
private JButton butt1, butt2, butt3;
private JTextField t1, t2;
private JSpinner spinner;
private JCheckBox cx1, cx2;
private JList list;
private JPanel p1, p2, p3, p4, p5;
private JPasswordField pass1, pass2;
private Timer timer;
public static void main(String[] args) {
//All swing applications must run on EDT.
//Take a look https://docs.oracle.com/javase/tutorial/uiswing/concurrency/initial.html
SwingUtilities.invokeLater(()->{
Budik okno = new Budik();
okno.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
okno.pack();
okno.setLocationRelativeTo(null);
okno.setResizable(false);
okno.setVisible(true); //First pack, then setVisible(), recommended.
});
}
public Budik() {
super("Countdown");
setLayout(new BorderLayout());
/////////////////////////////////////////////////////////////////
// p1
GridLayout el = new GridLayout(2, 1);
p1 = new JPanel(el);
add(p1, BorderLayout.NORTH);
lab1 = new JLabel("Welcome", SwingConstants.CENTER); // centr labelu
lab2 = new JLabel("Created by WajFaj", SwingConstants.CENTER); // centr labelu
lab1.setFont(new Font("Serif", Font.PLAIN, 36)); // velikost fontu
p1.add(lab1);
p1.add(lab2);
// p2
p2 = new JPanel();
add(p2, BorderLayout.WEST);
butt1 = new JButton("START");
p2.add(butt1);
// p3
p3 = new JPanel();
add(p3, BorderLayout.CENTER);
butt2 = new JButton("STOP");
p3.add(butt2);
// p4
p4 = new JPanel();
add(p4, BorderLayout.EAST);
butt3 = new JButton("RESET");
p4.add(butt3);
// p5
p5 = new JPanel();
add(p5, BorderLayout.SOUTH);
butt1.addActionListener(e->{
endTime = System.currentTimeMillis()+TimeUnit.MINUTES.toMillis(SLEEP_MINUTES); //define the end time
timer.start(); //Start the timer (in stop button, just add timer.stop())
});
//Initialize timer.
timer = new Timer(1000, e->{ //Loop every 1000ms a.k.a 1second
long millisBeforeEnd = endTime - System.currentTimeMillis();
if (millisBeforeEnd < 0) {
timer.stop();
lab1.setText("Welcome"); //Restore the text to your label, or write whatever you want.
return;
}
long secsBeforeEnd = TimeUnit.MILLISECONDS.toSeconds(millisBeforeEnd); //Convert the millis to seconds.
lab1.setText(secsBeforeEnd+"");
//If you want some kind of pretty print, uncomment the code below.
/*
int hours = (int) TimeUnit.MILLISECONDS.toHours(millisBeforeEnd);
int mins = (int) (TimeUnit.MILLISECONDS.toMinutes(millisBeforeEnd) - hours * 60);
int secs = (int) (TimeUnit.MILLISECONDS.toSeconds(millisBeforeEnd) - mins * 60 - hours * 60 * 60);
lab1.setText(String.format("%02d:%02d:%02d", hours, mins, secs));
*/
});
timer.setInitialDelay(0); //Don't loose the first second.
}
}
Upvotes: 2
Reputation: 124
Part of the issue is that you're calling Thread.sleep()
That is putting the Main thread to sleep so you're entire application stops executing.
for(int i=600; i>0;
try{
Thread.sleep(1000);
String cc = ""+i;
lab1.setText(cc);
}
For your application sounds like you need to make a separate thread to run that will then wake up and actually run the alarm.
Maybe try something like this:
Thread t = new Thread(() -> {
for(int i=600; i>0;i-- ){
try{
Thread.sleep(1000);
String cc = ""+i;
lab1.setText(cc);
}
catch(InterruptedException e){
System.out.println("Chyba!");
}
}
});
t.start();
The call to Thread.sleep()
should put the sub thread to sleep and not effect the rest of your application.
Please note I'm using arrow notation (->
) for the thread. You don't have to. Instead you can simply create a runnable:
Runnable alarmRunnable = new Runnable() {
@Override
public void run() {
for(int i=600; i>0;i-- ){
try{
Thread.sleep(1000);
String cc = ""+i;
lab1.setText(cc);
}
catch(InterruptedException e){
System.out.println("Chyba!");
}
}
}
};
Thread t = new Thread(alarmRunnable);
t.start();
The arrow notation (otherwise known as lambda) I think is cleaner though.
Upvotes: 0
Reputation: 5543
You really don't want your program to "wait" until the timer goes off, because then the program will be unable to do anything else, including updating the graphics interface as you found out.
The best solution is to create a javax.swing.Timer object, and then set timer to notify your system, when the needed time have passed. See https://docs.oracle.com/javase/tutorial/uiswing/misc/timer.html for details about creating a timer.
Upvotes: 1