removeAll not removing at next validate?

Can someone explain why the following doesn't work as I expect?

Pressing the button 'should' result in the display only containing the (empty) JScrollPane, ie the input field and button should disappear. However they stay until the component is resized...

public static void main(String[] args)
{
    JFrame frame = new JFrame("test");
    frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    final JPanel panel = new JPanel();

    Container cp = frame.getContentPane();
    cp.setLayout(new FlowLayout());
    cp.add(new JScrollPane(panel));

    Component textField = new JTextField("i am input");
    JButton button = new JButton(new AbstractAction("i am pressy")
    {
        @Override
        public void actionPerformed(ActionEvent e)
        {
            // this is already on the EDT
            panel.removeAll();
            panel.revalidate();
        }
    });

    panel.setLayout(new FlowLayout());
    panel.add(textField);
    panel.add(button);

    frame.pack();
    frame.setVisible(true);
}

Thanks for your help. p.


When updating a visible GUI the code should be:

panel.revalidate();
panel.repaint(); // sometimes needed, this appears to be one of them

The revalidate() method marks components as needing to be laid out, but until something triggers repaint() you won't see any change. Resizing the parent window is one such trigger; switching applications is another. In this previous version, note how setSize() on the panel obviates the need for repaint(). Similarly, this example changes the layout in resetGame().

The article Painting in AWT and Swing goes into more detail.

import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.WindowConstants;

/** @see https://stackoverflow.com/questions/5812002 */
public class RevalidateTest {

    private static JPanel panel = new JPanel(); // default FlowLayout
    private static JTextField text = new JTextField("Text field");
    private static JButton clear = new JButton(new AbstractAction("Clear") {

        @Override
        public void actionPerformed(ActionEvent e) {
            panel.removeAll();
            panel.add(reset);
            panel.revalidate();
            panel.repaint();
        }
    });
    private static JButton reset = new JButton(new AbstractAction("Reset") {

        @Override
        public void actionPerformed(ActionEvent e) {
            panel.removeAll();
            panel.add(text);
            panel.add(clear);
            panel.revalidate();
            panel.repaint();
        }
    });

    static void createAndShowGUI() {
        JFrame frame = new JFrame("Test");
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        panel.add(text);
        panel.add(clear);
        frame.add(panel); // default BorderLayout center
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                createAndShowGUI();
            }
        });
    }
}

You can execute panel.repaint() as specified in the comment by @Jeremy, however, the UI will still change when you resize the window. The reason being that the removal of the elements from the JPanel will cause the panel to resize. A repaint operation will not cause the panel to resize until the JFrame rechecks its layout (as happens on a window resize).

To make sure that the layout is correctly layed out on a change, you can call frame.validate(). This operation will cause the JFrame to revalidate itself and all child components, which is the same operation that is taking place during a window resize event. To execute this method in your code you would need to change JFrame frame to final, i.e.,

final JFrame frame = new JFrame("test");