visualizing JPanel change within a loop

2020-05-06 14:25发布

问题:

I am new to Java swing programming. I want to make a frame which will appear red and blue in turn one after another. So, I took 2 child JPanel, 1 for red and other for blue, and a for-loop. On each iteration I remove one panel from parent panel and add another. But, when I run the program it only shows the last state of the frame.

Can anyone explain why? And what's the intended approach to make a program work like that? My code:

public class Test2 extends JFrame {

public Test2() {

    JPanel Red = new JPanel(new BorderLayout());
    JPanel Blue = new JPanel(new BorderLayout());

    //...initialize Red and Blue
    Red.setBackground(Color.red);
    Blue.setBackground(Color.blue);
    Red.setPreferredSize(new Dimension(200,200));
    Blue.setPreferredSize(new Dimension(200,200));


    JPanel panel = new JPanel(new BorderLayout());
    panel.setPreferredSize(new Dimension(200,200));

    add(panel);

    pack();

    setTitle("Border Example");
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    setLocationRelativeTo(null);

    int M = 1000000; //note that, I made a long iteration to not finish the program fast and visualize the effect
    for(int i=0;i<M;i++)
    {
        if(i%(M/10)==0) System.out.println(i); //to detect whether the program is running

        if(i%2==0)
        {
            panel.removeAll();
            panel.repaint();
            panel.revalidate();
            panel.add(Red,BorderLayout.CENTER);
        }
        else
        {
            panel.removeAll();
            panel.repaint();
            panel.revalidate();
            panel.add(Blue,BorderLayout.CENTER);
        }
    }
}

public static void main(String[] args) {
    SwingUtilities.invokeLater(new Runnable() {
        public void run() {
            Test2 ex = new Test2();
            ex.setVisible(true);
        }
    });
}}

回答1:

Don't use a loop. Swing will only repaint the frame once the entire loop has finished executing.

Instead you need to use a Swing Timer. When the Timer fires you invoke your logic. Read the section from the Swing tutorial on How to Use Swing Timers.

Here is a simple example of a Timer that simply displays the time every second: Update a Label with a Swing Timer

Also, don't remove/add panels. Instead you can use a Card Layout and sway the visible panel. Again read the tutorial on How to Use CardLayout.



回答2:

Basically you don't need to use a while (or any other) loop, Swing only paints once it has finished that loop then repaint the GUI.

As stated before by @camickr on his answer, you could try a Swing Timer; here's an example that does exactly what you want.

From your comment on another answer:

Could you please explain why "repaint" does not work in a loop? And why is the Timer working without a "repaint"?

Swing is smart enough to know it doesn't needs to repaint in a loop, instead it will repaint once it the loop finishes, if you read the tutorial on Swing Custom Paint on the step 3 it says:

"Swing is smart enough to take that information and repaint those sections of the screen all in one single paint operation. In other words, Swing will not repaint the component twice in a row, even if that is what the code appears to be doing."

And Timer will repaint it, because it's not running on the EDT but in it's own Thread



回答3:

I would suggest to take in one step at a time.
First make it run without changing panels / colors.
Now it doesn't because this

public final void Test2() {

is a method (which is never used) and not a constructor. Change to a constructor declaration like :

public  Test2() {

to make the program do something. Then you can go to the next step.

Also use Java naming conventions (like blue instead of Blue).