javaintellij-ideagui-designer

IntelliJ's GridLayoutManager doesn't shrink its children


Here's a simple demo. The main class displays a frame with a GridLayoutManager-managed panel. The panel contains a stack of JLabels with long text strings

I expect the labels to shrink as I shrink the window, like so

However, they don't

import javax.swing.*;

public class FormDemoMain {
    public static void main(String[] args) {
        JFrame frame = new JFrame("Form Demo");
        JPanel mainPanel = new LabelContainer().mainPanel;
        frame.setContentPane(mainPanel);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }
}
import org.apache.commons.lang3.StringUtils;

import javax.swing.*;

public class LabelContainer {
    JPanel mainPanel;
    private JLabel labelOne;
    private JLabel labelTwo;
    private JLabel labelThree;

    LabelContainer() {
        // you may replace StringUtils.repeat("str", 20) with "str".repeat(20) if your JDK is 11+
        String longText = StringUtils.repeat("long ", 20) + "text";
        labelOne.setText("Label One: " + longText);
        labelTwo.setText("Label Two: " + longText);
        labelThree.setText("Label Three: " + longText);
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="stretchableLabel.form.LabelContainer">
  <grid id="27dc6" binding="mainPanel" layout-manager="GridLayoutManager" row-count="3" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="10" vgap="10">
    <margin top="0" left="0" bottom="0" right="0"/>
    <constraints>
      <xy x="20" y="20" width="500" height="400"/>
    </constraints>
    <properties>
      <background color="-7563106"/>
    </properties>
    <border type="none"/>
    <children>
      <component id="cadde" class="javax.swing.JLabel" binding="labelOne">
        <constraints>
          <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
        </constraints>
        <properties>
          <background color="-11841453"/>
          <foreground color="-1"/>
          <opaque value="true"/>
          <text value="Label"/>
        </properties>
      </component>
      <component id="36e79" class="javax.swing.JLabel" binding="labelTwo">
        <constraints>
          <grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
        </constraints>
        <properties>
          <background color="-11841453"/>
          <foreground color="-1"/>
          <opaque value="true"/>
          <text value="Label"/>
        </properties>
      </component>
      <component id="744ac" class="javax.swing.JLabel" binding="labelThree">
        <constraints>
          <grid row="2" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
        </constraints>
        <properties>
          <autoscrolls value="false"/>
          <background color="-11841453"/>
          <foreground color="-1"/>
          <opaque value="true"/>
          <text value="Label"/>
        </properties>
      </component>
    </children>
  </grid>
</form>

enter image description here

The problem is not reproduced with the OOB GridLayout. Note the ellipses

import org.apache.commons.lang3.StringUtils;

import javax.swing.*;
import java.awt.*;

public class GridLayoutDemo {
    public static void main(String[] args) {
        JFrame frame = new JFrame("Grid Layout Demo");
        JPanel labelPanel = new JPanel();
        LayoutManager layout = new GridLayout(0,1,10,10);
        labelPanel.setLayout(layout);
        labelPanel.setBackground(Color.LIGHT_GRAY);
        String shortText = "Short label text";
        JLabel labelOne = new JLabel(shortText);
        labelOne.setOpaque(true);
        labelOne.setBackground(Color.DARK_GRAY);
        labelOne.setForeground(Color.WHITE);
        JLabel labelTwo = new JLabel(shortText);
        labelTwo.setOpaque(true);
        labelTwo.setBackground(Color.DARK_GRAY);
        labelTwo.setForeground(Color.WHITE);
        labelPanel.add(labelOne);
        labelPanel.add(labelTwo);
        frame.add(labelPanel);
        JButton makeShortButton = new JButton("Make short");
        makeShortButton.addActionListener(e -> {
            labelOne.setText("Label One: " + shortText);
            labelTwo.setText("Label Two: " + shortText);
            frame.pack();
        });
        JButton makeLongButton = new JButton("Make long");
        String longText = "Long" + StringUtils.repeat(" long", 30) + " label text";
        makeLongButton.addActionListener(e -> {
            labelOne.setText("Label One: " + longText);
            labelTwo.setText("Label Two: " + longText);
            frame.pack();
        });
        JPanel buttonPanel = new JPanel();
        buttonPanel.add(makeShortButton);
        buttonPanel.add(makeLongButton);
        frame.add(buttonPanel, BorderLayout.SOUTH);
        frame.pack();
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }
}

enter image description here

The GUI Designer interface doesn't allow a regular GridLayout, only JetBrains' own com.intellij.uiDesigner.core.GridLayoutManager

enter image description here

What complicates things further, I can't debug it. I can't even open the source file of that layout manager in the IDE

One possible way out is GridBagLayout which is supported by GUI Designer. However, the layout manager either proved to be too complex to get it right or doesn't support it at all. Here's my attempt (without GUI forms)

import org.apache.commons.lang3.StringUtils;

import javax.swing.*;
import java.awt.*;

public class GridBagLayoutManagerDemo {
    public static void main(String[] args) {
        JFrame frame = new JFrame("GridBagLayout Demo");
        JLabel labelOne = new JLabel("first" + StringUtils.repeat(" long label", 20));
        labelOne.setOpaque(true);
        labelOne.setBackground(Color.DARK_GRAY);
        labelOne.setForeground(Color.WHITE);
        JLabel labelTwo = new JLabel("second" + StringUtils.repeat(" long label", 20));
        labelTwo.setOpaque(true);
        labelTwo.setBackground(Color.DARK_GRAY);
        labelTwo.setForeground(Color.WHITE);
        JPanel labelPanel = new JPanel();
        labelPanel.setBackground(Color.LIGHT_GRAY);
        GridBagLayout layout = new GridBagLayout();

        labelPanel.setLayout(layout);
        GridBagConstraints constraints = new GridBagConstraints();
        constraints.fill = GridBagConstraints.HORIZONTAL;
        constraints.gridy = 0;
        labelPanel.add(labelOne, constraints);
        constraints.gridy = 1;
        labelPanel.add(labelTwo, constraints);
        frame.add(labelPanel, BorderLayout.CENTER);
        frame.pack();
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }
}

It starts to shrink at first...

enter image description here

...but then abandons that idea as I shrink the window further

enter image description here

I know the drawbacks of GUI Designer only too well. But we're so coupled with it, we can't ever get uncoupled. The ability to see the approximate visual rendering of your components right in the source code is so appealing

How can I make a stack of labels shrink as the containing window is shrinked with GUI Designer forms?


Solution

  • Referring to the standard GridBagLayout, its documentation doesn’t say anything about shrinking. But we get on the right track when we consider the scenario of having less than the preferred or minimum size as “distributing negative ‘extra space’”:

    GridBagConstraints.weightx

    Specifies how to distribute extra horizontal space.

    The grid bag layout manager calculates the weight of a column to be the maximum weightx of all the components in a column. If the resulting layout is smaller horizontally than the area it needs to fill, the extra space is distributed to each column in proportion to its weight. A column that has a weight of zero receives no extra space.

    So, the solution looks like

    public class GridBagLayoutManagerDemo {
        public static void main(String[] args) {
            JFrame frame = new JFrame("GridBagLayout Demo");
            JLabel labelOne = new JLabel("first" + " long label".repeat(20));
            labelOne.setOpaque(true);
            labelOne.setBackground(Color.DARK_GRAY);
            labelOne.setForeground(Color.WHITE);
            labelOne.setFont(new Font(Font.DIALOG, 0, 12)); // https://stackoverflow.com/a/78725796/2711488
            JLabel labelTwo = new JLabel("second" + " long label".repeat(20));
            labelTwo.setOpaque(true);
            labelTwo.setBackground(Color.DARK_GRAY);
            labelTwo.setForeground(Color.WHITE);
            labelTwo.setFont(new Font(Font.DIALOG, 0, 12)); // https://stackoverflow.com/a/78725796/2711488
            JPanel labelPanel = new JPanel();
            labelPanel.setBackground(Color.LIGHT_GRAY);
            GridBagLayout layout = new GridBagLayout();
    
            labelPanel.setLayout(layout);
            GridBagConstraints constraints = new GridBagConstraints();
            constraints.fill = GridBagConstraints.HORIZONTAL;
            constraints.gridy = 0;
            constraints.weightx = 1;
            labelPanel.add(labelOne, constraints);
            constraints.gridy = 1;
            labelPanel.add(labelTwo, constraints);
            frame.add(labelPanel, BorderLayout.CENTER);
            frame.pack();
            frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
            frame.setLocationRelativeTo(null);
            frame.setVisible(true);
        }
    }
    

    Note that in case of having more space than needed, this solution only expands the virtual cells the components live in. To let the components take the extra space of the larger cell, the fill property must be set appropriately.