Zdeněk Krast
Zdeněk Krast

Reputation: 27

Thread sleep in GUI Action Listener JAVA

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

Answers (3)

George Z.
George Z.

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

Adam Ramos
Adam Ramos

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

MTilsted
MTilsted

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

Related Questions