0

I'm pretty new to Swing. I have an application that dynamically generates a UI based on an external input. At the top of the panel, there is a JLabel with some heading text, and then a button (which tells the app to reload the dynamically generated stuff), and then below that is a JScrollPane which contains all of the dynamically generated stuff. I want the label and the button to take only as much vertical space as they need, and then the scroll pane to fill all remaining available space.

I'm using GridBagLayout, and setting gbc.fill = BOTH before I add the scroll pane.

I've put together an SSCCE, and when it first loads, the window looks exactly like what I want. But if I resize the window smaller, my expectation is that the scrollpane would continue to consume all available space and that scrollbars would appear. But what happens instead is that as soon as I shrink the window by even a single pixel, the scrollpane becomes tiny, with its contents barely visible.

I found How to get a JScrollPane to resize with its parent JPanel, but that doesn't work when the JScrollPane isn't an only-child, so to speak.

What am I doing wrong?

SSCCE:

public class SwingTest {
    public static void main(String[] args) {
        JPanel bigPanel = new JPanel();
        bigPanel.setLayout(new GridLayout(10, 10));
        for (int i = 0; i < 100; i++) {
            bigPanel.add(new JCheckBox("Item " + i));
        }

        GridBagConstraints gbc = new GridBagConstraints();
        gbc.gridx = 0;

        JFrame mainWindow = new JFrame("Swing Test");
        mainWindow.setLayout(new GridBagLayout());
        mainWindow.add(new JLabel("Heading"), gbc);
        mainWindow.add(new JButton("Button"), gbc);
        gbc.fill = GridBagConstraints.BOTH;
        mainWindow.add(new JScrollPane(bigPanel), gbc);

        mainWindow.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        mainWindow.setLocationByPlatform(true);
        mainWindow.pack();
        mainWindow.setVisible(true);
    }
}
Community
  • 1
  • 1
JakeRobb
  • 1,711
  • 1
  • 17
  • 32

1 Answers1

0

So, I figured this out while writing my SSCCE. (Funny how that happens!)

Based on the related question I linked, I tried using BorderLayout:

public class SwingTest {
    public static void main(String[] args) {
        JFrame mainWindow = new JFrame("Swing Test");

        mainWindow.setLayout(new BorderLayout());
        mainWindow.add(new JLabel("Heading"));
        mainWindow.add(new JButton("Button"));
        JPanel bigPanel = new JPanel();
        bigPanel.setLayout(new GridLayout(10, 10));
        for (int i = 0; i < 100; i++) {
            bigPanel.add(new JCheckBox("Item " + i));
        }
        mainWindow.add(new JScrollPane(bigPanel));

        mainWindow.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        mainWindow.setLocationByPlatform(true);
        mainWindow.pack();
        mainWindow.setVisible(true);
    }
}

But that didn't work, because I think I had a fundamental misunderstanding of what BorderLayout does. By not specifying a constraint to my calls to add(...), I was adding all three components to the "CENTER" pane. BorderLayout's purpose is to house a single component that scales with the container size, and you can put other stuff around the edges (i.e., the borders) that will stay fixed size. Only one component can be in each position, so by adding all three to CENTER, I was left only with the last one I added. I needed to wrap my heading and button in a JPanel and put that at the top edge:

public class SwingTest {
    public static void main(String[] args) {
        JPanel top = new JPanel();
        top.setLayout(new BoxLayout(top, BoxLayout.PAGE_AXIS));
        top.add(new JLabel("Heading"));
        top.add(new JButton("Button"));

        JPanel bigPanel = new JPanel(new GridLayout(10, 10));
        for (int i = 0; i < 100; i++) {
            bigPanel.add(new JCheckBox("Item " + i));
        }

        JFrame mainWindow = new JFrame("Swing Test");
        mainWindow.setLayout(new BorderLayout());
        mainWindow.add(top, PAGE_START);
        mainWindow.add(new JScrollPane(bigPanel), CENTER);

        mainWindow.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        mainWindow.setLocationByPlatform(true);
        mainWindow.pack();
        mainWindow.setVisible(true);
    }
}

The components behaved correctly after that, but when the window grew large enough that no scrolling was needed, the grid of checkboxes expanded to fit the space, which I didn't want. I just wanted it to pin to the upper left. Here's what fixed it:

public class SwingTest {
    public static void main(String[] args) {
        JPanel top = new JPanel();
        top.setLayout(new BoxLayout(top, BoxLayout.PAGE_AXIS));
        top.add(new JLabel("Heading"));
        top.add(new JButton("Button"));

        GridBagConstraints gbc = new GridBagConstraints();
        gbc.fill = NONE;
        JPanel bigPanel = new JPanel(new GridBagLayout());
        for (int i = 0; i < 10; i++) {
            gbc.gridx = RELATIVE;
            gbc.gridy = i;
            gbc.anchor = NORTHWEST;
            for (int j = 0; j < 10; j++) {
                bigPanel.add(new JCheckBox("Item " + (10 * i + j)), gbc);
            }
        }

        // add a panel to the right side to prevent the grid from growing horizontally
        gbc.fill = BOTH;
        gbc.weightx = 1;
        bigPanel.add(new JPanel(), gbc);

        // add another panel to the bottom to prevent the grid from growing vertically
        gbc.gridy = RELATIVE;
        gbc.gridx = 0;
        gbc.weightx = 0;
        gbc.weighty = 1;
        bigPanel.add(new JPanel(), gbc);

        JFrame mainWindow = new JFrame("Swing Test");
        mainWindow.setLayout(new BorderLayout());
        mainWindow.add(top, PAGE_START);
        mainWindow.add(new JScrollPane(bigPanel), CENTER);

        mainWindow.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        mainWindow.setLocationByPlatform(true);
        mainWindow.pack();
        mainWindow.setVisible(true);
    }
}

Takeaways:

  • Always put a JScrollPane by itself in the CENTER of a BorderLayout.
  • If you don't want your grid-like content to expand its "cell padding" dynamically when you resize, use GridBagLayout, not GridLayout.
JakeRobb
  • 1,711
  • 1
  • 17
  • 32
  • 4
    *"So, I figured this out while writing my SSCCE. (Funny how that happens!)"* While it may be funny, it is also quite common. That's one of the (many) advantages of making a short example. It reduces big, seemingly complex problems down to the core issues, and that in turn makes both problems and solutions easier to see. Glad you got it sorted. :) – Andrew Thompson Aug 18 '16 at 14:24
  • 3
    The default layout for a JFrame content pane is a BorderLayout, so there is no need for the statement `mainWindow.setLayout(new BorderLayout());` – FredK Aug 18 '16 at 15:23
  • @AndrewThompson "Funny how that happens!" was meant to point out that very thing. I debated about simply not posting, but it took me quite a while to figure it out, so I decided not to let me work and my SSCCE go to waste. :) – JakeRobb Aug 18 '16 at 16:54
  • @FredK, thanks. I just assumed it defaulted to FlowLayout like everything else. :) – JakeRobb Aug 18 '16 at 18:52