0

I'm trying to make a chronometer but when I click on "Iniciar (start)", the program freezes. This is the part that give me problems:

ActionListener escucha = new ActionListener() {
        public void actionPerformed(ActionEvent e) {
            iniciar.setEnabled(false);
            pausar.setEnabled(true);
            reiniciar.setEnabled(true);
            rb1.setEnabled(false);
            rb2.setEnabled(false);

            try {
                while (true) {
                    milisegundos++;
                    if (milisegundos > 999) {
                        milisegundos = 0;
                        segundos++;
                        if (segundos > 59) {
                            segundos = 0;
                            minutos++;
                            if (minutos > 59) {
                                minutos = 0;
                                horas++;
                            }
                        }
                    }

                    if (milisegundos < 10) {
                        MS = "00"+milisegundos;
                    } else if (milisegundos < 100) {
                        MS = "0"+milisegundos;
                    } else {
                        MS = Integer.toString(milisegundos);
                    }

                    if (segundos < 10) {
                        S = "0"+segundos;
                    } else {
                        S = Integer.toString(segundos);
                    }

                    if (minutos < 10) {
                        M = "0"+minutos;
                    } else {
                        M = Integer.toString(minutos);
                    }

                    if (horas < 10) {
                        H = "0"+horas;
                    } else {
                        H = Integer.toString(horas);
                    }

                    cadena = H+":"+M+":"+":"+S+":"+MS;
                    tiempo.setText(cadena);
                    panel.repaint();

                }   
            } catch (Exception w) {
                w.printStackTrace();
            }
        }
    };

    iniciar.setText("Iniciar");
    iniciar.addActionListener(escucha);

I have done another similar algorithm but it is a very basic digital clock, with the same method of a while and a Thread.sleep() and it works:

    import java.util.Date;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import java.awt.Font;

public class asdfasdflkj {

public static void main(String[] args) {
    try {
        JFrame frame = new JFrame();
        JPanel panel = new JPanel();
        JLabel lblHoli = new JLabel("Holi");
        lblHoli.setFont(new Font("Arial", Font.PLAIN, 43));


        frame.setTitle("Hola Gráfica");
        frame.setSize(400, 200);
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(panel);

        panel.setLayout(null);
        panel.add(lblHoli);

        lblHoli.setBounds(10, 11, 364, 106);

        int hora, minuto, segundo;

        while (true) {
            Date dt = new Date();
            hora = dt.getHours();
            minuto = dt.getMinutes();
            segundo = dt.getSeconds();

            lblHoli.setText(hora+":"+minuto+":"+segundo);
            Thread.sleep(1000);

        }
    } catch (Exception e) {
        e.printStackTrace();
    }


}
}

Little edit: posting all program code (It's incomplete):

    import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JTextField;
import java.awt.Toolkit;
import javax.swing.SwingConstants;
import java.awt.Font;

public class principal {

public static int horas = 0, minutos = 0, segundos = 0, milisegundos = 0;
public static String cadena = "00:00:00:00", MS, S, M, H;
public static boolean condición = true, alredySelected = false;

public static void main(String[] args) {

    JFrame frame = new JFrame();
    JPanel panel = new JPanel();
    JPanel panel_botones = new JPanel();
    JPanel panel_opciones = new JPanel();
    JRadioButton rb1 = new JRadioButton();
    JRadioButton rb2 = new JRadioButton();
    ButtonGroup bg = new ButtonGroup();
    JButton iniciar = new JButton();
    JButton pausar = new JButton();
    JButton reiniciar = new JButton();
    Dimension minimumSize = new Dimension(400, 200);
    JTextField tiempo = new JTextField(cadena);
    JCheckBox siempreArriba = new JCheckBox();

    tiempo.setFont(new Font("Arial", Font.BOLD, 45));
    tiempo.setHorizontalAlignment(SwingConstants.CENTER);
    tiempo.setEditable(false);

    pausar.setEnabled(false);
    reiniciar.setEnabled(false);

    frame.setTitle("Cronómetro");
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setSize(minimumSize);
    frame.setMinimumSize(minimumSize);
    frame.setLocationRelativeTo(null);
    frame.setVisible(true);
    frame.setIconImage(Toolkit.getDefaultToolkit().getImage(principal.class.getResource("/icons/icono.png")));

    panel.setLayout(new BorderLayout());
    panel.add(panel_botones, BorderLayout.PAGE_END);
    panel.add(panel_opciones, BorderLayout.PAGE_START);
    panel.add(tiempo, BorderLayout.CENTER);
    panel.add(siempreArriba, BorderLayout.EAST);

    panel_botones.setLayout(new FlowLayout());
    panel_botones.add(iniciar);
    panel_botones.add(pausar);
    panel_botones.add(reiniciar);

    siempreArriba.setText("Siempre arriba");
    siempreArriba.addItemListener(new ItemListener(){
        public void itemStateChanged(ItemEvent e) {
            if (siempreArriba.isSelected()) {
                frame.setAlwaysOnTop(true);
            }else{
                frame.setAlwaysOnTop(false);
            }
        }
    });

    ActionListener escucha = new ActionListener() {
        public void actionPerformed(ActionEvent e) {
            iniciar.setEnabled(false);
            pausar.setEnabled(true);
            reiniciar.setEnabled(true);
            rb1.setEnabled(false);
            rb2.setEnabled(false);

            try {
                while (true) {
                    milisegundos++;
                    if (milisegundos > 999) {
                        milisegundos = 0;
                        segundos++;
                        if (segundos > 59) {
                            segundos = 0;
                            minutos++;
                            if (minutos > 59) {
                                minutos = 0;
                                horas++;
                            }
                        }
                    }

                    if (milisegundos < 10) {
                        MS = "00"+milisegundos;
                    } else if (milisegundos < 100) {
                        MS = "0"+milisegundos;
                    } else {
                        MS = Integer.toString(milisegundos);
                    }

                    if (segundos < 10) {
                        S = "0"+segundos;
                    } else {
                        S = Integer.toString(segundos);
                    }

                    if (minutos < 10) {
                        M = "0"+minutos;
                    } else {
                        M = Integer.toString(minutos);
                    }

                    if (horas < 10) {
                        H = "0"+horas;
                    } else {
                        H = Integer.toString(horas);
                    }

                    cadena = H+":"+M+":"+":"+S+":"+MS;
                    tiempo.setText(cadena);
                    panel.repaint();

                }   
            } catch (Exception w) {
                w.printStackTrace();
            }
        }
    };

    iniciar.setText("Iniciar");
    iniciar.addActionListener(escucha);

    pausar.setText("Pausar");
    pausar.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
            condición = false;
            pausar.setEnabled(false);
            iniciar.setEnabled(true);
            iniciar.setText("Reanudar");
        }
    });

    reiniciar.setText("Reiniciar");
    reiniciar.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {

        }
    });

    rb1.setText("Cronómetro");
    rb1.setSelected(true);
    rb1.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
           alredySelected = false;
        }
    });
    rb2.setText("Cuenta regresiva");
    rb2.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
            try {
                if (rb2.isSelected() && alredySelected==false) {
                    alredySelected = true;
                    String temp = JOptionPane.showInputDialog(frame, "Ingrese el número en segundos:");
                    while (temp.equals("")) {
                        JOptionPane.showMessageDialog(frame, "¡Debe ingresar un valor!", "Error", JOptionPane.ERROR_MESSAGE);
                        temp = JOptionPane.showInputDialog(frame, "Ingrese el número en segundos:");
                    }
                    horas = Integer.parseInt(temp);
                }
            } catch (java.lang.NumberFormatException x) {
                JOptionPane.showMessageDialog(frame, "¡Solo debe ingresar números!", "Error", JOptionPane.ERROR_MESSAGE);
                rb1.setSelected(true);
                alredySelected = false;
            } catch (java.lang.NullPointerException x) {
                rb1.setSelected(true);
                alredySelected = false;
            }
        }
    });
    bg.add(rb1);
    bg.add(rb2);
    panel_opciones.setLayout(new FlowLayout());
    panel_opciones.add(rb1);
    panel_opciones.add(rb2);

}

}
  • try updating the field in an [`invokeLater`](http://stackoverflow.com/questions/6567870/what-does-swingutilities-invokelater-do) call – Salem Jan 11 '17 at 07:09
  • @1blustone Because that will only cause the update to occur later on the EDT, which is generally blocked anyway – MadProgrammer Jan 11 '17 at 07:10
  • 2
    The cause ... [The Event Dispatch Thread](https://docs.oracle.com/javase/tutorial/uiswing/concurrency/dispatch.html); [one possible solution](https://docs.oracle.com/javase/tutorial/uiswing/misc/timer.html); [another possible solution](https://docs.oracle.com/javase/tutorial/uiswing/concurrency/worker.html) – MadProgrammer Jan 11 '17 at 07:11
  • 1
    Basically, you're block the Event Dispatching Thread, which is responsible for update the UI (and other things) with your `while (true)` loop (particularly in the `ActionListener`). Depending on your needs you should use either a `javax.swing.Timer` or `SwingWorker` ... as linked in my previous comment – MadProgrammer Jan 11 '17 at 07:13
  • And, [an example](http://stackoverflow.com/questions/33487186/swing-timer-stopwatch-in-java/33488613#33488613) and a [stop watch example](http://stackoverflow.com/questions/21011914/creating-a-gui-based-chronometer-or-stopwatch/21012092#21012092) – MadProgrammer Jan 11 '17 at 07:20
  • @MadProgrammer I don't understand all, but why in the clock the code works if it uses the same while (true) and Thread.sleep(1000); but without the ActionListener? – Lorenzo Nuñez Jan 11 '17 at 07:30
  • The `while` loop in `main` is running in the "main" thread, the `while` loop in your `ActionListener` is running your in the "event dispatching thread" (welcome to the wonderful world of concurrency and threading). The loops are violating the single threaded nature of Swing and are basically doing the two worst things you can do, 1. trying to update the UI from outside the context of the EDT and 2. Blocking the EDT – MadProgrammer Jan 11 '17 at 07:35

2 Answers2

3

Basically, you're violating the single threaded nature of Swing.

  1. Never update the UI from any thread other then the Event Dispatching Thread
  2. Never block the Event Dispatching Thread

Doing the second will prevent the EDT from updating the UI (and responding to events and other important things)

Take a look at The Event Dispatching Thread for more details

In your case, a Swing Timer would be more then sufficient. Have a look at Swing Timer for more details

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JTextField;
import java.awt.Toolkit;
import javax.swing.SwingConstants;
import java.awt.Font;
import javax.swing.SwingUtilities;
import javax.swing.Timer;

public class Principal {

    public int horas = 0, minutos = 0, segundos = 0, milisegundos = 0;
    public String cadena = "00:00:00:00", MS, S, M, H;
    public boolean condición = true, alredySelected = false;

    private Timer timer;

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                new Principal();
            }
        });
    }

    public Principal() {
        JFrame frame = new JFrame();
        JPanel panel = new JPanel();
        JPanel panel_botones = new JPanel();
        JPanel panel_opciones = new JPanel();
        JRadioButton rb1 = new JRadioButton();
        JRadioButton rb2 = new JRadioButton();
        ButtonGroup bg = new ButtonGroup();
        JButton iniciar = new JButton();
        JButton pausar = new JButton();
        JButton reiniciar = new JButton();
        Dimension minimumSize = new Dimension(400, 200);
        JTextField tiempo = new JTextField(cadena);
        JCheckBox siempreArriba = new JCheckBox();

        tiempo.setFont(new Font("Arial", Font.BOLD, 45));
        tiempo.setHorizontalAlignment(SwingConstants.CENTER);
        tiempo.setEditable(false);
        tiempo.setColumns(11);

        pausar.setEnabled(false);
        reiniciar.setEnabled(false);

        frame.setTitle("Cronómetro");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
//        frame.setIconImage(Toolkit.getDefaultToolkit().getImage(Principal.class.getResource("/icons/icono.png")));

        panel.setLayout(new BorderLayout());
        panel.add(panel_botones, BorderLayout.PAGE_END);
        panel.add(panel_opciones, BorderLayout.PAGE_START);
        panel.add(tiempo, BorderLayout.CENTER);
        panel.add(siempreArriba, BorderLayout.EAST);

        panel_botones.setLayout(new FlowLayout());
        panel_botones.add(iniciar);
        panel_botones.add(pausar);
        panel_botones.add(reiniciar);

        siempreArriba.setText("Siempre arriba");
        siempreArriba.addItemListener(new ItemListener() {
            public void itemStateChanged(ItemEvent e) {
                if (siempreArriba.isSelected()) {
                    frame.setAlwaysOnTop(true);
                } else {
                    frame.setAlwaysOnTop(false);
                }
            }
        });

        timer = new Timer(1, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                milisegundos++;
                if (milisegundos > 999) {
                    milisegundos = 0;
                    segundos++;
                    if (segundos > 59) {
                        segundos = 0;
                        minutos++;
                        if (minutos > 59) {
                            minutos = 0;
                            horas++;
                        }
                    }
                }

                if (milisegundos < 10) {
                    MS = "00" + milisegundos;
                } else if (milisegundos < 100) {
                    MS = "0" + milisegundos;
                } else {
                    MS = Integer.toString(milisegundos);
                }

                if (segundos < 10) {
                    S = "0" + segundos;
                } else {
                    S = Integer.toString(segundos);
                }

                if (minutos < 10) {
                    M = "0" + minutos;
                } else {
                    M = Integer.toString(minutos);
                }

                if (horas < 10) {
                    H = "0" + horas;
                } else {
                    H = Integer.toString(horas);
                }

                cadena = H + ":" + M + ":" + S + ":" + MS;
                tiempo.setText(cadena);
                panel.repaint();

            }
        });

        ActionListener escucha = new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                milisegundos = 0;
                iniciar.setEnabled(false);
                pausar.setEnabled(true);
                reiniciar.setEnabled(true);
                rb1.setEnabled(false);
                rb2.setEnabled(false);

                timer.start();

//                try {
//                    while (true) {
//                        milisegundos++;
//                        if (milisegundos > 999) {
//                            milisegundos = 0;
//                            segundos++;
//                            if (segundos > 59) {
//                                segundos = 0;
//                                minutos++;
//                                if (minutos > 59) {
//                                    minutos = 0;
//                                    horas++;
//                                }
//                            }
//                        }
//
//                        if (milisegundos < 10) {
//                            MS = "00" + milisegundos;
//                        } else if (milisegundos < 100) {
//                            MS = "0" + milisegundos;
//                        } else {
//                            MS = Integer.toString(milisegundos);
//                        }
//
//                        if (segundos < 10) {
//                            S = "0" + segundos;
//                        } else {
//                            S = Integer.toString(segundos);
//                        }
//
//                        if (minutos < 10) {
//                            M = "0" + minutos;
//                        } else {
//                            M = Integer.toString(minutos);
//                        }
//
//                        if (horas < 10) {
//                            H = "0" + horas;
//                        } else {
//                            H = Integer.toString(horas);
//                        }
//
//                        cadena = H + ":" + M + ":" + ":" + S + ":" + MS;
//                        tiempo.setText(cadena);
//                        panel.repaint();
//
//                    }
//                } catch (Exception w) {
//                    w.printStackTrace();
//                }
            }
        };

        iniciar.setText("Iniciar");
        iniciar.addActionListener(escucha);

        pausar.setText("Pausar");
        pausar.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                condición = false;
                pausar.setEnabled(false);
                iniciar.setEnabled(true);
                iniciar.setText("Reanudar");
                timer.stop();
            }
        });

        reiniciar.setText("Reiniciar");
        reiniciar.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                timer.start();
            }
        });

        rb1.setText("Cronómetro");
        rb1.setSelected(true);
        rb1.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                alredySelected = false;
            }
        });
        rb2.setText("Cuenta regresiva");
        rb2.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                try {
                    if (rb2.isSelected() && alredySelected == false) {
                        alredySelected = true;
                        String temp = JOptionPane.showInputDialog(frame, "Ingrese el número en segundos:");
                        while (temp.equals("")) {
                            JOptionPane.showMessageDialog(frame, "¡Debe ingresar un valor!", "Error", JOptionPane.ERROR_MESSAGE);
                            temp = JOptionPane.showInputDialog(frame, "Ingrese el número en segundos:");
                        }
                        horas = Integer.parseInt(temp);
                    }
                } catch (java.lang.NumberFormatException x) {
                    JOptionPane.showMessageDialog(frame, "¡Solo debe ingresar números!", "Error", JOptionPane.ERROR_MESSAGE);
                    rb1.setSelected(true);
                    alredySelected = false;
                } catch (java.lang.NullPointerException x) {
                    rb1.setSelected(true);
                    alredySelected = false;
                }
            }
        });
        bg.add(rb1);
        bg.add(rb2);
        panel_opciones.setLayout(new FlowLayout());
        panel_opciones.add(rb1);
        panel_opciones.add(rb2);    

        frame.add(panel);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

}

Personally, I don't like incrementing a counter in a Timer like this, as the Timer is inaccurate, a better solution would be to keep a starting point in time and calculate the difference between when the Timer ticks and this point, but, that's me

MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • Thanks very much! That solved my problem ^^ I also removed milliseconds to improve precision, so the format of the string is "00:00:00 istead of 00:00:00:000" – Lorenzo Nuñez Jan 11 '17 at 17:02
0

First, you while loop is exhausting your CPU, the variable millisecond is increasing, and decreasing when reach 999, nonstop, CPU never get a break to rest, and everytime the value millisecond changes, you ask panel to repaints, it is impossible mission for your CPU to do that.

Second, the millisecond won't work the way you expect it to, unless your CPU take 1 millisecond for every clock cycle, not even a antique computer is that slow.

You should replace your while loop with Timer, (Google Java Timer example), so your code get run once every millisecond, which your CPU can handle one handed. :)

Kevin Wang
  • 182
  • 1
  • 9