Search code examples
javaswingjframejpanelpreferredsize

getPreferredSize() only called twice after pack()


Why does getPreferredSize() get called only twice when pack() is called on a JFrame, like in the following example:

public class PackTest {
    static JFrame f = new JFrame();
    @SuppressWarnings("serial")
    public static void main(String[] args) {
        f.add(new JPanel() {
            int i = 0;
            @Override
            public Dimension getPreferredSize() {
                System.out.println("getPreferredSize() called");
                if(i++ >= 2)
                    return new Dimension(200, 200); // This is never returned
                else
                    return new Dimension(100, 100);
            }
        });
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.pack();
        f.setVisible(true);
        for(int i = 0; i < 10; i++) {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch(InterruptedException e) {}
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    System.out.println("Calling pack()");
                    f.pack();
                }
            });
        }
    }
}

It seems that getPreferredSize() only gets called as long as it keeps on returning something different (i.e. if it returns a different dimension every time, it will get called 10 times in the example). Why does Swing do this, and how do I get pack() to resize the JFrame properly?


Solution

  • Try revalidating your JPanel to get it to call it's preferredSize:

    import java.awt.*;
    import java.awt.event.*;
    import javax.swing.*;
    
    public class PackTest {
    
       public static void main(String[] args) {
          SwingUtilities.invokeLater(new Runnable() {
             public void run() {
                createAndShowGui();
             }
          });
       }
    
       public static void createAndShowGui() {
          final JFrame f = new JFrame();
          final JPanel panel = new JPanel() {
             int i = 0;
    
             @Override
             public Dimension getPreferredSize() {
                System.out.println("getPreferredSize() called");
                if (i++ >= 2)
                   return new Dimension(200, 200); // This is never returned
                else
                   return new Dimension(100, 100);
             }
          };
          f.add(panel);
          f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
          f.pack();
          f.setVisible(true);
    
          int delay = 1000;
          new Timer(delay, new ActionListener() {
    
             @Override
             public void actionPerformed(ActionEvent arg0) {
                System.out.println("Calling pack()");
                panel.revalidate();
                f.pack();
             }
          }).start();
       }
    }
    

    As an aside, you'll want to make sure that with your real code you take pains to respect Swing threading rules.