Jbutton flash between two background colors

纵饮孤独 提交于 2019-12-24 14:09:37

问题


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);          
       }
   }

回答1:


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...

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




回答2:


Found a workaround! 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(i==1?(corr?Color.green:Color.red):Color.yellow);  
                    // 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");
              }          
   }

Ok its messy, the message dialog appears briefly but due to the location of the game frame its hidden. If you move main frame it appears briefly etc. etc. But it seems I am on to something here. Something that happens when the JOptionPane is created makes the buttons repaint. If anyone got an idea on how to recreate what happens when the JOptionPane is created without having to create it , it would be great!



来源:https://stackoverflow.com/questions/34623827/jbutton-flash-between-two-background-colors

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!