-1

I need a one-time pause in this program for what I'm trying to do. I display some text in a Java Swing JFrame, repaint shows it, I wait 1.5 sec, then I change the text.

Basically, I started with this:

statusLabel.setText(s);    
appFrame.repaint();
Thread.sleep(1500);
statusLabel.setText(y);
appFrame.repaint();

But this wasn't working. Thread.sleep() would invoke before repaint had finished, meaning s would never be shown. I read a lot of places that you're not supposed to use Thread.sleep() in swing applications because it pauses all threads, even the threads trying to repaint, and that to pause something triggered by actionPerformed() you need to use a Java Swing Timer.

Which is all well and fine, except I can't find a single place that offers a decent explanation on how they work. Since, as far as I can tell, timers are specifically used for repeating events on a timer. I just want a 1.5 second delay between 2 repaints.

I tried doing this...

statusLabel.setText(s);    
appFrame.repaint();

Timer timer = new Timer(1500, new ActionListener()
{
    public void actionPerformed(ActionEvent ae)
    {

    }
});

timer.setInitialDelay(1500);
timer.setRepeats(false);
timer.start();

statusLabel.setText(y);
appFrame.repaint();

...adding a timer with a 1.5 sec initial delay, no repeating, and no body to its actionPerformed event, so that it literally does nothing but wait that 1.5 sec, but it didn't work.

Shaeldon
  • 873
  • 4
  • 18
  • 28
CSDragon
  • 129
  • 1
  • 10
  • possible duplicate of [Using SwingWorker and Timer to display time on a label?](http://stackoverflow.com/questions/9855037/using-swingworker-and-timer-to-display-time-on-a-label) – GSerg Jan 29 '15 at 08:18

2 Answers2

3

As coded in your example, it looks like the timer would "work", it just doesn't do anything because the actionPerformed method is empty. You might be thinking that timer.start() blocks and waits for the timer to trigger, but it fact it returns immediately. The way timers work is that the timer's actionPerformed method will be invoked from the UI thread when it is supposed to be. Placing code inside the actionPerformed method of a timer is a good way to update the UI state periodically.

Atsby
  • 2,277
  • 12
  • 14
  • Ok, so if it doesn't block and wait for the timer to trigger, how do I do exactly that then? – CSDragon Jan 29 '15 at 08:38
  • 1
    Generally you shouldn't. You can make it do that using synchronization, e.g., by having an atomic variable that you update from inside the `actionPerformed` method. But then it will have the same behavior as `Thread.sleep`. These are not good solutions. The proper way to do things is to refactor your code to actually use the timer callback to do whatever it is that needs to be done rather waiting for the timeout from a different thread. – Atsby Jan 29 '15 at 08:42
  • 1
    Well that's...confusing. – CSDragon Jan 29 '15 at 08:53
  • Also, `Thread.sleep` doesn't "pause all threads" in Swing, it works as normal. repaint() requests repainting, but the painting doesn't actually happen until the UI thread gets around to arranging it, including magical stuff like actually talking to the computer's graphics system. If you're pausing the UI thread using `Thread.sleep` in the UI thread, then painting will not work properly. The correct way to implement UIs in Swing is to just not ever block the UI thread. If you need blocking or sleeping for some reason, make a separate thread for that task, do not do it in the UI thread. – Atsby Jan 29 '15 at 08:58
1

Have you tried placing statusLabel.setText(y); inside the actionPerformed method of your ActionListener?

statusLabel.setText(s);    

Timer timer = new Timer(1500, new ActionListener()
{
    public void actionPerformed(ActionEvent ae)
    {
        statusLabel.setText(y);
    }
});

timer.setRepeats(false);
timer.start();

If that's still not working, then consider providing a runnable example which demonstrates your problem. This will result in less confusion and better responses

Updated

What you "seem" to be wanting to do, is set up a series of events which get trigger at different times...Rather then using separate Timers, you should be using a single Timer like a loop, each time it ticks, you check it's state and make some decisions about what should be done, for example...

import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class Flashy {

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

    public Flashy() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public static class TestPane extends JPanel {

        private JLabel flash;
        private JButton makeFlash;

        protected static final Color[] FLASH_COLORS = new Color[]{Color.BLUE, Color.RED, Color.GREEN, Color.YELLOW};
        protected static final int[] FLASH_DELAY = new int[]{1000, 2000, 3000, 4000};
        private int flashPoint;

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

            flash = new JLabel("Flash");
            flash.setOpaque(true);
            makeFlash = new JButton("Make Flash");

            add(flash, gbc);
            add(makeFlash, gbc);

            makeFlash.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    flashPoint = -1;
                    Timer timer = new Timer(0, new ActionListener() {
                        @Override
                        public void actionPerformed(ActionEvent e) {
                            Timer timer = ((Timer)e.getSource());
                            flashPoint++;
                            if (flashPoint < FLASH_COLORS.length) {
                                flash.setBackground(FLASH_COLORS[flashPoint]);
                                System.out.println(FLASH_DELAY[flashPoint]);
                                timer.setDelay(FLASH_DELAY[flashPoint]);
                            } else {
                                flash.setBackground(null);
                                timer.stop();
                                makeFlash.setEnabled(true);
                            }
                        }
                    });
                    timer.setInitialDelay(0);
                    timer.start();                  
                    makeFlash.setEnabled(false);
                }
            });

        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(200, 200);
        }

    }

}

Now, if you wanted to do something really fancy, you could devise a series of key frames over a given period of time.

This means that you could change the duration of the animation, without needing to change any other piece of code, for example...

import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.text.NumberFormat;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class Flashy {

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

    public Flashy() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public static class TestPane extends JPanel {

        private JLabel flash;
        private JButton makeFlash;

        protected static final Color[] FLASH_COLORS = new Color[]{Color.BLUE, Color.RED, Color.GREEN, Color.YELLOW};
        protected static final double[] FLASH_DELAY = new double[]{0, 0.2, 0.4, 0.6};

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

            flash = new JLabel("Flash");
            flash.setOpaque(true);
            makeFlash = new JButton("Make Flash");

            add(flash, gbc);
            add(makeFlash, gbc);

            makeFlash.addActionListener(new ActionListener() {
                private int playTime = 10000;
                private long startTime;
                private int currentFrame = -1;

                @Override
                public void actionPerformed(ActionEvent e) {
                    startTime = System.currentTimeMillis();

                    Timer timer = new Timer(50, new ActionListener() {
                        @Override
                        public void actionPerformed(ActionEvent e) {
                            Timer timer = ((Timer) e.getSource());
                            long now = System.currentTimeMillis();
                            long duration = now - startTime;

                            double progress = (double) duration / (double) playTime;
                            int keyFrame = 0;
                            for (keyFrame = 0; keyFrame < FLASH_DELAY.length; keyFrame++) {

                                double current = FLASH_DELAY[keyFrame];
                                double next = 1d;
                                if (keyFrame + 1 < FLASH_DELAY.length) {
                                    next = FLASH_DELAY[keyFrame + 1];
                                }

                                if (progress >= current && progress < next) {
                                    break;
                                }

                            }

                            if (keyFrame < FLASH_COLORS.length) {

                                flash.setBackground(FLASH_COLORS[keyFrame]);

                            }

                            if (duration >= playTime) {
                                timer.stop();
                                makeFlash.setEnabled(true);
                                flash.setBackground(null);
                            }

                        }
                    });
                    timer.setInitialDelay(0);
                    timer.start();
                    makeFlash.setEnabled(false);
                }
            });

        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(200, 200);
        }

    }

}

A much more advanced concept, which is demonstrated in this answer

Community
  • 1
  • 1
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • While this works for this specific example, it doesn't work for others I'm trying to do. For instance, another one sets the text 3 times, with a 1 sec delay between the 1st and 2nd, and a .5 second delay after that between the 2nd and 3rd. From my experimenting and from what Atsby said, it'll queue up the timers at virtually the same time, and the .5 sec will go off first and the 1 sec delay will go off 2nd. And while I could just put 1.5 sec on the 2nd one so that it goes off on time, I'm not trying to learn how to hard-code, I'm trying to learn how to make a thread sleep properly in swing. – CSDragon Jan 29 '15 at 08:51
  • *"For instance, another one sets the text 3 times, with a 1 sec delay between the 1st and 2nd"* - Use a counter within the `Timer` to track where you are up to. *"rom my experimenting and from what Atsby said, it'll queue up the timers at virtually the same time"* - Then use a single `Timer` and code the logic within to provide the changes you need based on the number of times the `Timer` has been triggered – MadProgrammer Jan 29 '15 at 08:57
  • 1
    *"I'm trying to learn how to make a thread sleep properly in swing"* - The simple answer to that is, don't – MadProgrammer Jan 29 '15 at 08:57