This is a follow-up to this question. Any viable answer will also answer that one.
What layout may be used with as little modification as possible to replicate the aligning nature of a FlowLayout, but never linebreak and also be available in a from-top-to-bottom flavour?
The obvious candidate, BoxLayout, does not work nicely with JPanels. Consider the following two examples:
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
class App
{
public static void main(String[] args)
{
JFrame window = new JFrame();
Box box = new Box(BoxLayout.Y_AXIS);
for(int i = 0; i < 5; ++i)
{
JLabel label = new JLabel("XX");
box.add(label);
}
box.add(Box.createVerticalGlue());
window.add(box);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setVisible(true);
}
}
This will properly display a vertical line of labels, beginning at the top and stretching as far towards the bottom as the labels take space. Good.
Modifying this, however, just a tiny bit:
public static void main(String[] args)
{
JFrame window = new JFrame();
Box box = new Box(BoxLayout.Y_AXIS);
for(int i = 0; i < 5; ++i)
{
JLabel label = new JLabel("XX");
JPanel panel = new JPanel();
panel.add(label);
box.add(panel);
}
box.add(Box.createVerticalGlue());
window.add(box);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setVisible(true);
}
This will stretch all components of the Box to the same height, placing the labels far away from each other. Bad.
Overriding the JPanel's getPreferredSize and getMaximumSize methods (with getMinimumSize) has no effect and would be a bad way to fix it, because it relied on the components rather than the container and its layout.
Addendum:
Here is an already pretty successful attempt using GroupLayout. Unfortunately it did not seem to occur to the designer that among DEFAULT_SIZE and PREFERRED_SIZE a choice MINIMUM_SIZE would have been a good idea.
Furthermore if it is possible to invert the sequence of GroupLayout.SequentialGroup, the API is no help to figure out how. I for one certainly have no clue how to even extend that class.
import java.awt.Component;
import java.awt.ComponentOrientation;
import java.awt.Container;
import javax.swing.GroupLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
class LineLayout extends GroupLayout
{
public LineLayout(Container owner, int axis)
{
super(owner);
this.direction = axis;
this.direction |= owner.getComponentOrientation() != ComponentOrientation.LEFT_TO_RIGHT
? LineLayout.RIGHT_TO_LEFT : LineLayout.LEFT_TO_RIGHT;
this.setupGroups();
}
public LineLayout(Container owner, int axis, int orientation)
{
super(owner);
this.direction = axis;
this.direction |= orientation;
this.setupGroups();
}
@Override // to replicate FlowLayout functionality : this method is called from owner.add
public void addLayoutComponent(Component component, Object constraints)
{
if(constraints == null)
{
// REALLY surprised that this works, considering that overriding the JPanel's
// getMaximumSize method with getPreferredSize had no effect
this.horizontal.addComponent(component, GroupLayout.DEFAULT_SIZE,
GroupLayout.DEFAULT_SIZE,
GroupLayout.PREFERRED_SIZE);
this.vertical.addComponent (component, GroupLayout.DEFAULT_SIZE,
GroupLayout.DEFAULT_SIZE,
GroupLayout.PREFERRED_SIZE);
}
// TODO: else
}
protected void setupGroups()
{
super.setAutoCreateGaps(false); // does nothing
if((this.direction & LineLayout.AXIS) == LineLayout.Y_AXIS)
{
this.horizontal = super.createParallelGroup();
this.vertical = (this.direction & LineLayout.ORIENTATION) == LineLayout.RIGHT_TO_LEFT
? this.createSequentialInvertedGroup() : super.createSequentialGroup();
}
else
{
this.horizontal = (this.direction & LineLayout.ORIENTATION) == LineLayout.RIGHT_TO_LEFT
? this.createSequentialInvertedGroup() : super.createSequentialGroup();
this.vertical = super.createParallelGroup();
}
super.setHorizontalGroup(this.horizontal);
super.setVerticalGroup (this.vertical);
}
// How!?
// protected LineLayout.SequentialInvertedGroup createSequentialInvertedGroup() { return new LineLayout.SequentialInvertedGroup(); }
protected GroupLayout.SequentialGroup createSequentialInvertedGroup() { return super.createSequentialGroup(); } // placeholder
protected int direction;
protected GroupLayout.Group horizontal;
protected GroupLayout.Group vertical;
// not sure how reliable the constant field values of BoxLayout are, whether it's smart to assume them unchanging over the ages
public static final int AXIS = 0b1;
public static final int X_AXIS = 0b0; // = BoxLayout.X_AXIS;
public static final int Y_AXIS = 0b1; // = BoxLayout.Y_AXIS;
public static final int ORIENTATION = 0b10;
public static final int LEFT_TO_RIGHT = 0b00; // also top to bottom
public static final int RIGHT_TO_LEFT = 0b10; // also bottom to top
// No idea how; only has "add" methods; cannot actually do anything with the added components!?
//protected static class SequentialInvertedGroup extends GroupLayout.SequentialGroup
//{}
}
class Applikation
{
public static void main(String[] args)
{
JFrame window = new JFrame();
JPanel box = new JPanel();
box.setLayout(new LineLayout(box, LineLayout.Y_AXIS));
for(int i = 0; i < 5; ++i)
{
JLabel label = new JLabel("XX");
JPanel panel = new JPanel();
panel.add(label);
box.add(panel);
}
window.add(box);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setVisible(true);
}
}
If you try this out, you will note that there are still notable border spaces between the "XX" labels, taking up about 2/3 of an extra label per gap. While already much better than in the BoxLayout example, I do not think there is a good way to improve this spacing further.