Reputation: 199
I need two horizontally positioned components in a container, both of them should fill all of their container's vertical space.
Do I now have to use the heavy-weight GridBagLayout
, because of the fill requirement? (and if I want it to flow to boot, bad luck, I'm out of options)
Java 8, Swing.
Upvotes: 0
Views: 72
Reputation: 3366
As an alternative to GridBagLayout
for this scenario, you can use a BoxLayout
like so:
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class BoxLayoutMain {
/**
* Specifies minimum and maximum sizes for each child {@code Component} of the
* {@linkplain BoxLayout#getTarget() target} of the given {@code BoxLayout}, such that aspect
* ratios of their preferred size are maintained throughout parent resizing. Assumes all
* children of the target are already added to it. Assumes the layout is horizontal.
* @param boxLayout
*/
private static void assignHorizontalBoxLayoutChildWeights(final BoxLayout boxLayout) {
final int maxValue = Short.MAX_VALUE;
final Container parent = boxLayout.getTarget();
final int totalWidth = parent.getPreferredSize().width;
final Dimension commonMinimumSize = new Dimension();
for (final Component child: parent.getComponents()) {
child.setMinimumSize(commonMinimumSize);
final Dimension prefSize = child.getPreferredSize();
child.setMaximumSize(new Dimension((int) Math.round(maxValue * (prefSize.width / (double) totalWidth)), maxValue));
}
}
public static void main(final String[] args) {
SwingUtilities.invokeLater(() -> {
final JLabel component1 = new JLabel("Component 1", JLabel.CENTER);
component1.setBorder(BorderFactory.createLineBorder(Color.RED, 5));
final JLabel component2 = new JLabel("Component 2 (which is a little longer)", JLabel.CENTER);
component2.setBorder(BorderFactory.createLineBorder(Color.GREEN, 5));
final JPanel parent = new JPanel();
parent.setBorder(BorderFactory.createLineBorder(Color.BLUE, 5));
final BoxLayout layout = new BoxLayout(parent, BoxLayout.X_AXIS);
parent.setLayout(layout);
parent.add(component1);
parent.add(component2);
assignHorizontalBoxLayoutChildWeights(layout);
final JFrame frame = new JFrame("Box for no gridbag");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(parent);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
}
This produces the following result:
When the parent Container
is resized the children maintain their width aspect ratios, while always filling their whole height.
Nevertheless, sadly, a minor gap can be observed between green and blue borders on the right side of the parent when it is resized. This is shown in the brown circle in the following image:
This may not be a problem though depending on the situation (eg in case you are not using any borders for the children in the above code).
SpringLayout
also comes to mind for this scenario:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Spring;
import javax.swing.SpringLayout;
import javax.swing.SwingUtilities;
public class SpringLayoutMainWithoutFixedSizes {
private static Component createComponent(final String label,
final Color borderColor) {
final JPanel panel = new JPanel(new BorderLayout());
panel.setBorder(BorderFactory.createLineBorder(borderColor, 5));
if (label != null) {
final JLabel c = new JLabel(label, JLabel.CENTER);
panel.add(c, BorderLayout.CENTER);
}
return panel;
}
public static void main(final String[] args) {
SwingUtilities.invokeLater(() -> {
final Component component1 = createComponent("Component 1", Color.RED),
component2 = createComponent("Component 2 (which is a little longer)", Color.GREEN);
final SpringLayout layout = new SpringLayout();
final JPanel parent = new JPanel(layout);
parent.setBorder(BorderFactory.createLineBorder(Color.BLUE, 5));
parent.add(component1);
parent.add(component2);
final SpringLayout.Constraints constraints1 = layout.getConstraints(component1),
constraints2 = layout.getConstraints(component2),
constraintsParent = layout.getConstraints(parent);
//Component 2 x will be equal to component 1 width:
constraints2.setX(constraints1.getWidth());
//The width of the container will be equal to the summary of the width of the two children:
constraintsParent.setWidth(Spring.sum(constraints1.getWidth(), constraints2.getWidth()));
//The height of the container will be equal to the maximum height between the two children:
constraintsParent.setHeight(Spring.max(constraints1.getHeight(), constraints2.getHeight()));
final JFrame frame = new JFrame("Spring for no gridbag");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(parent);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
}
Which is not explicitly setting minimum/maximum/preferred sizes per child.
Notice however here that the width ratio of the preferred sizes of the children is not preserved intact as we resize the window. This is because of the values of minimum and maximum sizes per child, which are also taken into account. This is true for both SpringLayout
and BoxLayout
. For example, because the children have equal maximum sizes, their width ratio tends to 1 as we enlarge the window. In other words they tend to occupy equal space horizontally for sizes greater than the preferred ones.
I also wrap the JLabel
children into a JPanel
each, because JLabel
s by default have minimum and maximum sizes equal to their preferred size, thus the width ratio change could not be observed by JLabel
s without manually setting their minimum and maximum sizes.
Follows a BoxLayout
approach equivalent to the posted SpringLayout
one:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class BoxLayoutMainWithoutFixedSizes {
private static Component createComponent(final String label,
final Color borderColor) {
final JPanel panel = new JPanel(new BorderLayout());
panel.setBorder(BorderFactory.createLineBorder(borderColor, 5));
if (label != null) {
final JLabel c = new JLabel(label, JLabel.CENTER);
panel.add(c, BorderLayout.CENTER);
}
return panel;
}
public static void main(final String[] args) {
SwingUtilities.invokeLater(() -> {
final Component component1 = createComponent("Component 1", Color.RED),
component2 = createComponent("Component 2 (which is a little longer)", Color.GREEN);
final JPanel parent = new JPanel();
parent.setBorder(BorderFactory.createLineBorder(Color.BLUE, 5));
final BoxLayout layout = new BoxLayout(parent, BoxLayout.X_AXIS);
parent.setLayout(layout);
parent.add(component1);
parent.add(component2);
final JFrame frame = new JFrame("Box for no gridbag");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(parent);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
}
The above (last) code does not explicitly set minimum nor maximum sizes per child, but on the other hand the width ratio is not preserved for the same reason as for the SpringLayout
code (ie exactly because we are not settings sizes, which are taken into account by these two LayoutManager
s).
Note, I now understand that you want an equivalent solution to GridBagLayout
without explicitly setting minimum/maximum/preferred sizes, but I leave the above ideas as close alternatives.
In case there is a need for the user to be able to determine manually the width of each Component
then you could use a JSplitPane
instead.
Upvotes: 1