Search code examples
javaswinglayout-managerspringlayout

JPanel's with SpringLayout preferredSize does not calculate the right value


I fail to see how to make a JPanel with SpringLayout properly calculate its preferred size, as currently it is always (0, 0). I tried calling panel.setPreferredSize(layout.preferredLayoutSize(panel));, however it doesn`t change anything. I also tried putting constraints (commented out in the code below) on panel's south and east sides comparatively to components that are the most south / east, and while it starts giving me right preferred size, the GUI gets absolutely messed up when resizing JFrame (e.g. some components shift out of view, overlap each other etc.).

This is the class that is giving me issues:

public class TileView extends JPanel {

    private TilesController controller;

    private JRadioButton paint, select;
    private JPanel prototype_panel, selection_panel;

    public TileView (TilesController tiles_controller) {
        controller = tiles_controller;
        SpringLayout layout = new SpringLayout();
        setLayout(layout);

        prototype_panel = new TileBrushPanel(controller);
        selection_panel = new TileSelectionPanel(controller);
        paint = new JRadioButton("draw");
        paint.addActionListener(l -> controller.setPaintTileMode());
        select = new JRadioButton ("select");
        select.addActionListener(l -> controller.setSelectMode());

        ButtonGroup bGroup = new ButtonGroup();
        bGroup.add(paint);
        bGroup.add(select);

        layout.putConstraint(NORTH, paint, 4, NORTH, this);
        layout.putConstraint(WEST, paint, 4, WEST, this);
        add (paint);

        layout.putConstraint(NORTH, select, 0, NORTH, paint);
        layout.putConstraint(WEST, select, 4, EAST, paint);
        add (select);

        layout.putConstraint(NORTH, prototype_panel, 4, NORTH, paint);
        layout.putConstraint(WEST, prototype_panel, 4, WEST, this);
        add (prototype_panel);

        layout.putConstraint(NORTH, selection_panel, 0, NORTH, prototype_panel);
        layout.putConstraint(WEST, selection_panel, 4, EAST, prototype_panel);
        add (selection_panel);

        //layout.putConstraint(SOUTH, this, 4, SOUTH, prototype_panel);
        //layout.putConstraint(EAST, this, 0, EAST, selection_panel);
        setPreferredSize(layout.preferredLayoutSize(this)); 

    }

}

Solution

  • SpringLayout is working just fine. You had it right the first time; you should uncomment those last two putConstraint calls, and remove the setPreferredSize call.

    The reason you are not getting the results you expect is probably due to this line:

    layout.putConstraint(NORTH, prototype_panel, 4, NORTH, paint);
    

    which you probably meant to write as:

    layout.putConstraint(NORTH, prototype_panel, 4, SOUTH, paint);
    

    By aligning the north edge of prototype_panel with the north edge of paint, while aligning both of their west edges to the west edge of the container, you made them overlap.

    SpringLayout is difficult to use. In my opinion, you would be better off using a different layout, or a combination of layouts. There’s nothing wrong with nesting many panels inside each other.

    In your case, I believe you could have accomplished the same layout using Box instances:

    Box radioButtons = Box.createHorizontalBox();
    radioButtons.add(paint);
    radioButtons.add(select);
    
    Box panels = Box.createHorizontalBox();
    panels.add(prototype_panel);
    panels.add(selection_panel);
    
    Box windowContents = Box.createVerticalBox();
    windowContents.add(radioButtons);
    windowContents.add(panels);
    
    setLayout(new BorderLayout());
    add(windowContents);
    

    It’s not much shorter, but it’s considerably easier to read and understand.

    As for why you’re getting a width and height of zero, I can only guess that you’re printing out the preferred size before validation has taken place. Even using your SpringLayout code, I never got a width and height of zero, once I added the TileView panel to a JFrame and called pack() on it.