Home > OS >  How to make components evenly share JPanel container vertical area with GridBagLayout layout on resi
How to make components evenly share JPanel container vertical area with GridBagLayout layout on resi

Time:12-05

The problem:

JPanel with GridBagLayout contains two JScrollPane components, the top one contains JTextArea, the bottom one contains JTable. I expect this set up to make the components fluidly fill the container JPanel and evenly share the vertical area of it on resizing. What actually happens is that the top JScrollPane component shrinks vertically on resizing giving more vertical area to the bottom JScrollPane component to invade; this happens at some sizes, I don not know if they are random.

Important notes:

I already applied GridBagConstraints#weighty = 0.5; to both components, which is supposed to do the job.

I noticed that the problem is because of the presence of the JTextArea, in that if I switch locations; make the JScrollPane/JTextArea on the bottom side and JScrollPane/JTable on the top side; the bottom component JScrollPane/JTextArea shrinks vertically giving more vertical area to the top component JScrollPane/JTable to invade.

I know this exact case can be solved simply using GridLayout(2,1) instead of GridBagLayout (or may be other solutions), however I am fan of GridBagLayout and I want to use most of it. Also if I want to add three components instead of two and I want to make them share the vertical area by percentage for example (%, %, 50%) I can use GridBagConstraints#weighty = (0.25, 0.25, 0.5) which is easily applicable with GridBagLayout.

So, in this case how to make components evenly share the vertical area of JPanel container with GridBagLayout on resizing?. Or, in other words; how to enforce components to respect GridBagConstraints#weighty = 0.5; setting on resizing which is applied to both?

Code in SSCCE format:

import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextArea;
import javax.swing.table.DefaultTableModel;

public class GridBagFluidFill {

    private static JPanel createFormPanel() {

        JPanel containerPanel = new JPanel(new GridBagLayout());
        containerPanel.setPreferredSize(new Dimension(350, 200));
        JScrollPane scrollableTextArea;
        JScrollPane scrollableTable;

        JTextArea textArea = new JTextArea();
        scrollableTextArea = new JScrollPane(textArea);

        DefaultTableModel model = new DefaultTableModel(new String[]{"Column1", "Column2", "Column3"}, 0);
        JTable table = new JTable(model);
        scrollableTable = new JScrollPane(table);

        GridBagConstraints c = new GridBagConstraints();
        c.gridy = 0;
        c.weightx = 1.0;
        // I expect this to reserve 50% of the height all time.
        c.weighty = 0.5;
        c.fill = GridBagConstraints.BOTH;
        c.anchor = GridBagConstraints.PAGE_START;
        containerPanel.add(scrollableTextArea, c);

        c = new GridBagConstraints();
        c.gridy = 1;
        c.weightx = 1.0;
        // I expect this to reserve 50% of the height all time.
        c.weighty = 0.5;
        c.fill = GridBagConstraints.BOTH;
        c.anchor = GridBagConstraints.PAGE_END;
        containerPanel.add(scrollableTable, c);

        return containerPanel;
    }

    public static void main(String[] args) {
        javax.swing.SwingUtilities.invokeLater(() -> {
            JPanel formPanel = createFormPanel();
            JFrame frame = new JFrame("GridBagFluidFill");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.getContentPane().add(formPanel);
            frame.pack();
            frame.setVisible(true);
        });
    }
}

CodePudding user response:

GridBagLayout is complex, and while its documentation does describe all of its functionality, some of it is easy to misinterpret.

I already applied GridBagConstraints#weighty = 0.5; to both components, which is supposed to do the job.

That is not what weightx and weighty do.

Equal weightx and weighty values do not coerce components into having the same width or height. The weight values determine the distribution of extra space when a container is larger than the preferred sizes of each component.

Here is a program which demonstrates this:

import java.awt.EventQueue;
import java.awt.GridBagLayout;
import java.awt.GridBagConstraints;

import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JPanel;
import javax.swing.JFrame;

public class GridBagEqualWeightsDemo {
    static void showWindow() {
        // Source: https://www.gutenberg.org/files/98/98-h/98-h.htm
        String text =
            "It was the best of times, it was the worst of times, it was"
              " the age of wisdom, it was the age of foolishness, it was the"
              " epoch of belief, it was the epoch of incredulity, it was the"
              " season of Light, it was the season of Darkness, it was the"
              " spring of hope, it was the winter of despair, we had everything"
              " before us, we had nothing before us, we were all going direct"
              " to Heaven, we were all going direct the other way—in short,"
              " the period was so far like the present period, that some of its"
              " noisiest authorities insisted on its being received, for good"
              " or for evil, in the superlative degree of comparison only.";

        JTextArea topArea = new JTextArea(text, 4, 20);
        topArea.setLineWrap(true);
        topArea.setWrapStyleWord(true);

        JTextArea bottomArea = new JTextArea(text, 8, 20);
        bottomArea.setLineWrap(true);
        bottomArea.setWrapStyleWord(true);

        JScrollPane top = new JScrollPane(topArea);
        JScrollPane bottom = new JScrollPane(bottomArea);

        top.setMinimumSize(top.getPreferredSize());
        bottom.setMinimumSize(bottom.getPreferredSize());

        JPanel panel = new JPanel(new GridBagLayout());

        GridBagConstraints gbc = new GridBagConstraints();
        gbc.fill = GridBagConstraints.BOTH;
        gbc.gridwidth = GridBagConstraints.REMAINDER;
        gbc.weightx = 1;

        gbc.weighty = 0.5;
        panel.add(top, gbc);
        panel.add(bottom, gbc);

        JFrame frame = new JFrame("GridBagConstraints Equal Weights");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(panel);
        frame.pack();
        frame.setLocationByPlatform(true);
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(() -> showWindow());
    }
}

As you can see, the two components start out with different preferred heights, so GridBagLayout will never give them the same height. As you make the window taller, the components’ respective heights look more similar due to the smaller relative difference, but they are never the same.

GridBagLayout only uses the weighty to determine how to distribute the extra space—that is, the container height which exceeds each component’s (different) preferred height. If the panel is 50 pixels higher than the preferred height, as computed based on each child component’s preferred size, then equal weighty values means each component will be laid out with 25 pixels added to its respective preferred height.

If you want to force two components which may have different preferred sizes to always have the same width and/or height, GridBagLayout is the wrong tool for the job. As you have mentioned, GridLayout is well suited to this. SpringLayout can also do this, though its usage is complex.

You are free (and encouraged!) to use multiple panels inside each other with different layouts.

CodePudding user response:

I already applied GridBagConstraints#weighty = 0.5; to both components, which is supposed to do the job.

The weighty constraint is used to tell the GridBagLayout how to allocate "extra" (and apparently less) space.

That is, each component is first allocated its preferred space. Then if there is extra (or apparently less) space available, that constraint is used.

If you add debug code to display the preferred sizes of each scroll pane you will get:

java.awt.Dimension[width=223,height=19] // text area
java.awt.Dimension[width=453,height=403] // table

So the issue is with this statement:

containerPanel.setPreferredSize(new Dimension(350, 200));

You are forcing the components to be smaller than their preferred size. So it appears the GridBagLayout is allocating 50% to each.

I was wondering why JTextArea shrinks without a good reason

As the height of the frame grows each component is allocated 50% until the point at which the frame is large enough to display each component at its preferred size. This is why the text area shrinks and the table jumps.

As you increase the height from there extra space is allocated at 50/50 as expected.

If you uncomment the setPreferredSize() statement you will see the natural layout and the reason for the jumping becomes clear.

I want to make them share the vertical area by percentage for example (%, %, 50%)

If you want allocations like that then check out Relative Layout which was designed for this purpose.

  • Related