Почему мой циклический таймер графического интерфейса не отображается?

Я пытаюсь создать таймер с графическим интерфейсом без использования javax.swing.Timer(странная задача), но у меня не получается заставить его работать. Предполагается, что поток приостанавливается на 1 секунду, добавляется 1 к seconds и повторяется (бесконечно). Когда я запускаю свою программу, значок появляется, но окно не появляется. Я предполагаю, что моя ошибка находится в строке Thread.sleep(1000); или в этой области, но я не уверен, почему это не работает. Является ли Thread.sleep(millis) несовместимым с Swing-приложениями? Должен ли я использовать многопоточность? Вот моя программа:

import java.awt.*;
import javax.swing.*;

public class GUITimer extends JFrame {
    private static final long serialVersionUID = 1L;
    private int seconds = 0;

    public GUITimer() {
        initGUI();
        pack();
        setVisible(true);
        setResizable(false);
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }

    private void initGUI(){
        JLabel title = new JLabel("Timer");
        Font titleFont = new Font(Font.SERIF, Font.BOLD, 32);
        title.setFont(titleFont);
        title.setHorizontalAlignment(JLabel.CENTER);
        title.setBackground(Color.BLACK);
        title.setForeground(Color.WHITE);
        title.setOpaque(true);
        add(title, BorderLayout.NORTH);
        JLabel timeDisplay = new JLabel(Integer.toString(seconds));//this label shows seconds
        add(timeDisplay, BorderLayout.CENTER);
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        seconds++;
        initGUI();
    }

    public static void main(String[] args) {
        try {
            String className = UIManager.getCrossPlatformLookAndFeelClassName();
            UIManager.setLookAndFeel(className);
        }
        catch (Exception e) {}

        EventQueue.invokeLater(new Runnable() {
            public void run() {
                new GUITimer();
            }
        });
    }
}

EDIT:
Я заметил, что когда я печатаю seconds в своем методе initGUI() для консоли, он правильно печатает их с шагом в одну секунду. Итак, когда это выглядит так:

private void initGUI() {
    System.out.println(seconds);
    //...

он печатает значение seconds через каждую секунду (как должно быть JLabel). Это показывает, что мой цикл работает нормально, и мой Thread.sleep(1000) тоже в порядке. Моя единственная проблема сейчас в том, что кадр не отображается.


person Community    schedule 22.05.2017    source источник
comment
Вы должны начать с изучения Concurrency In Swing, чтобы выяснить причину и Я бы порекомендовал взглянуть на Как использовать Swing Timer как возможное решение   -  person MadProgrammer    schedule 22.05.2017
comment
@MadProgrammer Я не хотел использовать таймер свинга для этого проекта, я практикую рекурсию.   -  person    schedule 22.05.2017
comment
Что ж, тогда вам нужно перестать блокировать EDT, что и дает вам Swing Timer. Вместо этого рассмотрите SwingWorker. Осторожно, Thread.sleep гарантирует только минимальную задержку; Swing НЕ является потокобезопасным, поэтому вам нужно убедиться, что вы обновляете пользовательский интерфейс только из контекста EDT, и вы рискуете StackOverFlowException   -  person MadProgrammer    schedule 22.05.2017
comment
I am practicing using recursion. - это не практическое использование рекурсии. Определенно нет необходимости постоянно создавать компоненты и добавлять их во фрейм.   -  person camickr    schedule 22.05.2017
comment
@camickr хорошая мысль, я должен сделать отдельный метод, который добавляет секунды и обновляет метку.   -  person    schedule 22.05.2017
comment
Рекурсия — это не просто вызов одного и того же метода из одного и того же метода, это просто цикл. Рекурсия — это когда вы на самом деле выполняете некоторую обработку, например, перечисляете файлы в каталоге. Итак, вы передаете параметр методу getFiles(). Затем метод перечисляет файлы. Когда if находит каталог, он снова вызывает getFiles(...) с новым каталогом. Это повторяется до тех пор, пока не будут перечислены все файлы и метод не завершится.   -  person camickr    schedule 22.05.2017
comment
@camickr Хорошо, спасибо за разъяснение. Я думал, что эта программа использует рекурсию, но вы правы. Я отредактирую свой пост, чтобы исправить это.   -  person    schedule 23.05.2017


Ответы (2)


Ваше главное окно не появляется, потому что вы вызвали бесконечную рекурсию внутри конструктора. GUITimer не будет создан, и этот основной поток будет заблокирован.

Для этой цели вам нужно использовать многопоточность. Основной поток для времени рисования, приращение второго потока и установка значения для метки

Например:

import javax.swing.*;
import java.awt.*;

public class GUITimer extends JFrame
{
    private static final long serialVersionUID = 1L;
    private int seconds = 0;
    private Thread timerThread;
    private JLabel timeDisplay;

    public GUITimer()
    {
        initGUI();
        pack();
        setVisible(true);
        setResizable(false);
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }

    private void initGUI()
    {
        JLabel title = new JLabel("Timer");
        Font titleFont = new Font(Font.SERIF, Font.BOLD, 32);
        title.setFont(titleFont);
        title.setHorizontalAlignment(JLabel.CENTER);
        title.setBackground(Color.BLACK);
        title.setForeground(Color.WHITE);
        title.setOpaque(true);
        add(title, BorderLayout.NORTH);
        timeDisplay = new JLabel(Integer.toString(seconds));//this label shows seconds
        add(timeDisplay, BorderLayout.CENTER);
    }

    public void start()
    {
        seconds = 0;
        timerThread = new Thread(new Runnable()
        {
            @Override
            public void run()
            {
                while(true)
                {
                    timeDisplay.setText(Integer.toString(seconds++));
                    try
                    {
                        Thread.sleep(1000L);
                    }
                    catch(InterruptedException e) {}
                }
            }
        });
        timerThread.start();
    }

    public void stop()
    {
        timerThread.interrupt();
    }

    public static void main(String[] args)
    {
        try
        {
            GUITimer timer = new GUITimer();
            timer.start();
        }
        catch(Exception e)
        {
            e.printStackTrace();
        }
    }
}
person Виталий Махнев    schedule 22.05.2017
comment
@theProgrammer101, вы можете посмотреть выше - person Виталий Махнев; 22.05.2017
comment
Поскольку Swing НЕ является потокобезопасным, вам не следует обновлять пользовательский интерфейс из вашего Thread. Более безопасной альтернативой может быть использование SwingWorker - О, и ваш метод stop на самом деле ничего не сделает. - person MadProgrammer; 22.05.2017

Основная проблема заключается в том, что вы блокируете пользовательский интерфейс, постоянно вызывая initGUI, что в конечном итоге приведет к сбою с StackOverFlowException, поскольку вызовы метода никогда не заканчиваются.

Предпочтительно было бы использовать Swing Timer, но, поскольку вы заявили, что не хотите этого делать, лучшим решением было бы использовать SwingWorker, причина этого - Swing НЕ является потокобезопасным, а SwingWorker обеспечивает удобный механизм, позволяющий нам безопасно обновлять пользовательский интерфейс.

Поскольку и Swing Timer, и Thead.sleep гарантируют только минимальную задержку, они не являются надежным средством измерения течения времени, вместо этого было бы лучше использовать Java 8 Date/Time API.

import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;

public class Test {

    public static void main(String[] args) {
        new Test();
    }

    public Test() {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame();
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        private JLabel label = new JLabel("00:00:00");
        private TimeWorker timeWorker;

        public TestPane() {
            setLayout(new GridBagLayout());
            GridBagConstraints gbc = new GridBagConstraints();
            gbc.gridwidth = GridBagConstraints.REMAINDER;
            add(label, gbc);

            JButton button = new JButton("Start");
            add(button, gbc);

            button.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    if (timeWorker == null) {
                        timeWorker = new TimeWorker(label);
                        timeWorker.execute();
                        button.setText("Stop");
                    } else {
                        timeWorker.cancel(true);
                        timeWorker = null;
                        button.setText("Start");
                    }
                }
            });
        }
    }

    public class TimeWorker extends SwingWorker<Void, Duration> {

        private JLabel label;

        public TimeWorker(JLabel label) {
            this.label = label;
        }

        @Override
        protected Void doInBackground() throws Exception {
            LocalDateTime startTime = LocalDateTime.now();
            Duration totalDuration = Duration.ZERO;
            while (!isCancelled()) {
                LocalDateTime now = LocalDateTime.now();
                Duration tickDuration = Duration.between(startTime, now);
                publish(tickDuration);
                Thread.sleep(500);
            }

            return null;
        }

        @Override
        protected void process(List<Duration> chunks) {
            Duration duration = chunks.get(chunks.size() - 1);
            String text = format(duration);
            label.setText(text);
        }

        public String format(Duration duration) {
            long hours = duration.toHours();
            duration = duration.minusHours(hours);
            long minutes = duration.toMinutes();
            duration = duration.minusMinutes(minutes);
            long millis = duration.toMillis();
            long seconds = (long)(millis / 1000.0);

            return String.format("%02d:%02d:%02d", hours, minutes, seconds);
        }
    }
}
person MadProgrammer    schedule 22.05.2017