Can't understand Java Swing Timers. How do I m

2019-09-20 03:51发布

问题:

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.

回答1:

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.



回答2:

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