Dimos Fotiadis Dimos Fotiadis - 4 years ago 180
Java Question

Java jbutton flash between two backround colors

My problem: Efficient way for the program to delay in my scenario.

My Scenario:
I am making a quiz game for a short project, long story short. A question is answered when the user presses one of four Jbuttons having an answer on them. Then my quizengine class calls the paintbutton method showed here. After that it proceeds to call some other methods responsible for making the next question appear. Now what I want is to make the button change between two colors with decreasing time intervals.

What have I tried so far:
First I placed a JoptionPane between the paintbutton method and the method that changes the interface to the next question just to see if the button would change color. It did successfully. Of course that wasn't my intention, I wanted just a time interval.
Then I tried using Thread.Sleep. Although the program would wait before changing to the next question , the color change was not visible.
Finally I tried an implementation of the Timer thingy (probably not correctly) which although it changed the color the program went ahead and proceeded to the next question.

My want to be code

/* Paints the button pressed red or green
* Green if the anwser is correct red if
* its false
*
* @param int bnr the number of the button
* @param boolean corr true if correct false if false :D
*/
public static void paintbutton(int bnr,boolean corr) {
for (int i=10;i>1;i--){
b[bnr-1].setBackground(null);
//wait for i*100 milliseconds
b[bnr-1].setBackground(corr?Color.green:Color.red);
}
}


Found a workaround!EDIT:
Ok I don't know on how many levels my solution is wrong but it seems to do the trick. I noticed that if a JOptionPane.showMessageDialog is used the button is being painted normally. Now the next problem was the user not having to press the ok button on the dialog ... so I used a robot! Here is the code:

public static void paintbutton(int bnr,boolean corr) {
long time;
try {
Robot robot = new Robot();
for (int i=5;i>1;i--){
b[bnr-1].setBackground(null);
// Simulate a key press
robot.keyPress(KeyEvent.VK_SPACE);
robot.keyRelease(KeyEvent.VK_SPACE);
JOptionPane.showMessageDialog(null,"hi");
time = System.currentTimeMillis();

do {
}while(time+i*100>System.currentTimeMillis());
b[bnr-1].setBackground(corr?Color.green:Color.red);
// Simulate a key press
robot.keyPress(KeyEvent.VK_SPACE);
robot.keyRelease(KeyEvent.VK_SPACE);
JOptionPane.showMessageDialog(null,"hi");
time = System.currentTimeMillis();
do {
}while(time+i*100>System.currentTimeMillis());
}
} catch (AWTException e) {
System.err.println("error");
}
}

Answer Source

Let's start with some basics. Swing is single threaded, meaning you must never block or execute long running code within the context of the Event Dispatching Thread, this will make the UI freeze and the user upset

Swing is NOT thread safe, this means that you should never create or update the UI from outside of the context of the EDT.

This leads you into a problem. You want to, after a some small delay, update the UI.

Lucky for you, there are at least two possibilities, a Swing Timer or a SwingWorker.

A Swing Timer is relatively simple, but doesn't really provide a means to generate a variable delay between updates. A SwingWorker is more complex, but gives you the control to basically do what you want.

Both of these (can) "wait" outside of the EDT, but both provide means by which you can push updates to the EDT safely.

For example...

Blinky

import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingWorker;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class BlinkyTheButton {

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

    public BlinkyTheButton() {
        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 class TestPane extends JPanel {

        private JButton blinky;

        public TestPane() {
            blinky = new JButton("Blinky");
            blinky.setOpaque(true);
            blinky.addActionListener(new ActionListener() {
                private BlinkWorker worker;
                @Override
                public void actionPerformed(ActionEvent e) {
                    if (worker == null || worker.getState() == SwingWorker.StateValue.DONE) {
                        worker = new BlinkWorker(blinky, Color.RED);
                        worker.addPropertyChangeListener(new PropertyChangeListener() {
                            @Override
                            public void propertyChange(PropertyChangeEvent evt) {
                                SwingWorker worker = (SwingWorker) evt.getSource();
                                if ("state".equals(evt.getPropertyName())) {
                                    if (worker.getState() == SwingWorker.StateValue.DONE) {
                                        // this is where you would then call the method to
                                        // update the state for a new question
                                    }
                                }
                            }
                        });
                        worker.execute();
                    }
                }
            });
            setLayout(new GridBagLayout());
            add(blinky);
        }

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

        public class BlinkWorker extends SwingWorker<Void, Color> {

            private JButton btn;
            private Color color;
            private Color normal;

            public BlinkWorker(JButton btn, Color color) {
                this.btn = btn;
                normal = btn.getBackground();
                this.color = color;
            }

            @Override
            protected Void doInBackground() throws Exception {
                for (int index = 10; index > 1; index--) {
                    publish(color);
                    Thread.sleep(index * 100);
                    publish(normal);
                    Thread.sleep(index * 100);
                }
                return null;
            }

            @Override
            protected void process(List<Color> chunks) {
                Color color =  chunks.get(chunks.size() - 1);
                btn.setBackground(color);
            }

        }

    }

}

Have a look at Concurrency in Swing, How to use Swing Timers and Worker Threads and SwingWorker for more details

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download